diff --git a/ASTPlayground/ASTPlayground.dpr b/ASTPlayground/ASTPlayground.dpr
index 6cecc29..839ece8 100644
--- a/ASTPlayground/ASTPlayground.dpr
+++ b/ASTPlayground/ASTPlayground.dpr
@@ -12,14 +12,14 @@ uses
Myc.Ast.ViewModel in '..\Src\AST\Myc.Ast.ViewModel.pas',
Myc.Data.Value in 'Myc.Data.Value.pas',
Myc.Ast.Debugger in '..\Src\AST\Myc.Ast.Debugger.pas',
- Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas',
- Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas',
- Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
Myc.Ast.Traverser in '..\Src\AST\Myc.Ast.Traverser.pas',
Myc.Ast.Binding in '..\Src\AST\Myc.Ast.Binding.pas',
Myc.Ast.RTL in '..\Src\AST\Myc.Ast.RTL.pas',
Myc.Ast.Dumper in '..\Src\AST\Myc.Ast.Dumper.pas',
Myc.Ast.RTL.Core in '..\Src\AST\Myc.Ast.RTL.Core.pas',
+ Myc.Fmx.AstEditor.Node in 'Myc.Fmx.AstEditor.Node.pas',
+ Myc.Fmx.AstEditor.Workspace in 'Myc.Fmx.AstEditor.Workspace.pas',
+ Myc.Fmx.AstEditor.Text in 'Myc.Fmx.AstEditor.Text.pas',
Myc.Utils in '..\Src\Myc.Utils.pas';
{$R *.res}
diff --git a/ASTPlayground/ASTPlayground.dproj b/ASTPlayground/ASTPlayground.dproj
index 572879e..a8ed000 100644
--- a/ASTPlayground/ASTPlayground.dproj
+++ b/ASTPlayground/ASTPlayground.dproj
@@ -143,14 +143,14 @@
-
-
-
+
+
+
Base
diff --git a/ASTPlayground/MainForm.pas b/ASTPlayground/MainForm.pas
index bc4d1a8..992bc20 100644
--- a/ASTPlayground/MainForm.pas
+++ b/ASTPlayground/MainForm.pas
@@ -21,9 +21,6 @@ uses
FMX.ScrollBox,
FMX.Memo,
FMX.Controls.Presentation,
- Myc.Fmx.AstEditor,
- Myc.Fmx.AstEditor.Node,
- Myc.Fmx.AstEditor.Workspace,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Ast.Nodes,
@@ -36,7 +33,10 @@ uses
Myc.Ast.RTL,
FMX.Layouts,
FMX.Objects,
- Myc.Ast.Debugger;
+ Myc.Ast.Debugger,
+ Myc.Fmx.AstEditor,
+ Myc.Fmx.AstEditor.Node,
+ Myc.Fmx.AstEditor.Workspace;
type
// A test record
@@ -281,31 +281,42 @@ var
sw: TStopwatch;
begin
// Create a setup script to define a memoize-compatible 'fib' function globally.
+ // This is rewritten to be tail-recursive using the 'recur' keyword.
var fibAst :=
TAst.Block(
[
- // 1. var fib; (Declare the name so it can be captured by the lambda. Initializer is nil)
+ // 1. var fib_iter = lambda(n, a, b) { ... }; (The tail-recursive part)
+ TAst.VarDecl(
+ TAst.Identifier('fib_iter'),
+ TAst.LambdaExpr(
+ [TAst.Identifier('n'), TAst.Identifier('a'), TAst.Identifier('b')],
+ TAst.TernaryExpr(
+ TAst.BinaryExpr(TAst.Identifier('n'), boEqual, TAst.Constant(TScalar.FromInt64(0))),
+ TAst.Identifier('a'), // Base case 1
+ TAst.TernaryExpr(
+ TAst.BinaryExpr(TAst.Identifier('n'), boEqual, TAst.Constant(TScalar.FromInt64(1))),
+ TAst.Identifier('b'), // Base case 2
+ TAst.Recur( // Tail-recursive step
+ [
+ TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1))),
+ TAst.Identifier('b'),
+ TAst.BinaryExpr(TAst.Identifier('a'), boAdd, TAst.Identifier('b'))
+ ]
+ )
+ )
+ )
+ )
+ ),
+ // 2. var fib;
TAst.VarDecl(TAst.Identifier('fib')),
- // 2. var fib_impl = lambda(n) { ... fib(n-1) + fib(n-2) ... };
+ // 3. fib = lambda(n) { fib_iter(n, 0, 1) }; (The public-facing function)
TAst.Assign(
TAst.Identifier('fib'),
TAst.LambdaExpr(
[TAst.Identifier('n')],
- TAst.TernaryExpr(
- TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))),
- TAst.Identifier('n'),
- TAst.BinaryExpr(
- // Recursive calls now use the re-bindable name 'fib' instead of 'Self'
- TAst.FunctionCall(
- TAst.Identifier('Self'),
- [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]
- ),
- boAdd,
- TAst.FunctionCall(
- TAst.Identifier('Self'),
- [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(2)))]
- )
- )
+ TAst.FunctionCall(
+ TAst.Identifier('fib_iter'),
+ [TAst.Identifier('n'), TAst.Constant(TScalar.FromInt64(0)), TAst.Constant(TScalar.FromInt64(1))]
)
)
)
@@ -314,10 +325,10 @@ begin
var fibScope := TAstBinder.Bind(fibAst, FGScope).CreateScope(FGScope);
var visitor := CreateVisitor(fibScope);
- Result := visitor.Execute(fibAst);
+ visitor.Execute(fibAst);
Memo1.Lines.Clear;
- Memo1.Lines.Add('--- Recursive fib with AST---');
+ Memo1.Lines.Add('--- Tail-Recursive fib with AST---');
sw := TStopwatch.StartNew;
root := TAst.FunctionCall(TAst.Identifier('fib'), [TAst.Constant(TScalar.FromInt64(30))]);
@@ -330,7 +341,7 @@ begin
Memo1.Lines.Add(Format('Result: fib(30) %s (calculated in %d ms)', [result.ToString, sw.ElapsedMilliseconds]));
Memo1.Lines.Add('');
- Memo1.Lines.Add('--- Memoized recursive fib with AST (using global fib)---');
+ Memo1.Lines.Add('--- Memoized tail-recursive fib with AST (using global fib)---');
sw := TStopwatch.StartNew;
root :=
@@ -375,30 +386,39 @@ var
sw: TStopwatch;
begin
Memo1.Lines.Clear;
- Memo1.Lines.Add('--- Recursive factorial(20) ---');
+ Memo1.Lines.Add('--- Tail-Recursive factorial(20) ---');
sw := TStopwatch.StartNew;
+ // Rewritten to be tail-recursive to use 'recur'
root :=
TAst.Block(
[
+ // Define the tail-recursive helper function
TAst.VarDecl(
- TAst.Identifier('factorial'),
+ TAst.Identifier('fact_iter'),
TAst.LambdaExpr(
- [TAst.Identifier('n')],
+ [TAst.Identifier('n'), TAst.Identifier('acc')],
TAst.TernaryExpr(
- TAst.BinaryExpr(TAst.Identifier('n'), boLess, TAst.Constant(TScalar.FromInt64(2))),
- TAst.Constant(TScalar.FromInt64(1)),
- TAst.BinaryExpr(
- TAst.Identifier('n'),
- boMultiply,
- TAst.FunctionCall(
- TAst.Identifier('Self'),
- [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]
- )
+ TAst.BinaryExpr(TAst.Identifier('n'), boLessOrEqual, TAst.Constant(TScalar.FromInt64(1))),
+ TAst.Identifier('acc'), // Base case: return the accumulator
+ TAst.Recur( // Tail-recursive step
+ [
+ TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1))),
+ TAst.BinaryExpr(TAst.Identifier('acc'), boMultiply, TAst.Identifier('n'))
+ ]
)
)
)
),
+ // Define the public-facing factorial function
+ TAst.VarDecl(
+ TAst.Identifier('factorial'),
+ TAst.LambdaExpr(
+ [TAst.Identifier('n')],
+ TAst.FunctionCall(TAst.Identifier('fact_iter'), [TAst.Identifier('n'), TAst.Constant(TScalar.FromInt64(1))])
+ )
+ ),
+ // Call the main function
TAst.FunctionCall(TAst.Identifier('factorial'), [TAst.Constant(TScalar.FromInt64(20))])
]
);
@@ -566,54 +586,6 @@ begin
var setupAst :=
TAst.Block(
[
- // TAst.VarDecl(
- // TAst.Identifier('CreateSMA'),
- // TAst.LambdaExpr(
- // [TAst.Identifier('len')],
- // TAst.Block(
- // [
- // TAst.VarDecl(TAst.Identifier('sum'), TAst.Constant(TScalar.FromDouble(0.0))),
- // TAst.VarDecl(TAst.Identifier('count'), TAst.Constant(TScalar.FromInt64(0))),
- // TAst.LambdaExpr(
- // [TAst.Identifier('series'), TAst.Identifier('val')],
- // TAst.Block(
- // [
- // TAst.Assign(
- // TAst.Identifier('sum'),
- // TAst.BinaryExpr(TAst.Identifier('sum'), boAdd, TAst.Identifier('val'))
- // ),
- // TAst.Assign(
- // TAst.Identifier('count'),
- // TAst.BinaryExpr(TAst.Identifier('count'), boAdd, TAst.Constant(TScalar.FromInt64(1)))
- // ),
- // TAst.IfExpr(
- // TAst.BinaryExpr(TAst.Identifier('count'), boGreater, TAst.Identifier('len')),
- // TAst.Assign(
- // TAst.Identifier('sum'),
- // TAst.BinaryExpr(
- // TAst.Identifier('sum'),
- // boSubtract,
- // TAst.Indexer(TAst.Identifier('series'), TAst.Identifier('len'))
- // )
- // ),
- // nil
- // ),
- // TAst.BinaryExpr(
- // TAst.Identifier('sum'),
- // boDivide,
- // TAst.TernaryExpr(
- // TAst.BinaryExpr(TAst.Identifier('count'), boLess, TAst.Identifier('len')),
- // TAst.Identifier('count'),
- // TAst.Identifier('len')
- // )
- // )
- // ]
- // )
- // )
- // ]
- // )
- // )
- // ),
TAst.VarDecl(
TAst.Identifier('smaFast'),
TAst.FunctionCall(TAst.Identifier('CreateSMA'), [TAst.Constant(TScalar.FromInt64(smaFastLength))])
@@ -961,14 +933,11 @@ begin
TAst.Identifier('countDown'),
TAst.LambdaExpr(
[TAst.Identifier('n')],
- // if (n > 0) then Self(n-1) else 'done'
+ // if (n > 0) then recur(n-1) else 'done'
TAst.IfExpr(
TAst.BinaryExpr(TAst.Identifier('n'), boGreater, TAst.Constant(TScalar.FromInt64(0))),
- // This is the tail call position.
- TAst.FunctionCall(
- TAst.Identifier('Self'),
- [TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]
- ),
+ // This is the tail call position, now using recur.
+ TAst.Recur([TAst.BinaryExpr(TAst.Identifier('n'), boSubtract, TAst.Constant(TScalar.FromInt64(1)))]),
// Base case of the recursion
TAst.Constant(TScalar.FromString('done'))
)
diff --git a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas
index 2037df3..5341a07 100644
--- a/ASTPlayground/Myc.Fmx.AstEditor.Text.pas
+++ b/ASTPlayground/Myc.Fmx.AstEditor.Text.pas
@@ -30,6 +30,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue;
end;
implementation
@@ -105,6 +106,27 @@ begin
end;
end;
+function TAstToTextVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ i: Integer;
+ sb: TStringBuilder;
+begin
+ sb := TStringBuilder.Create;
+ try
+ sb.Append('recur(');
+ for i := 0 to High(Node.Arguments) do
+ begin
+ sb.Append(Node.Arguments[i].Accept(Self).AsText);
+ if i < High(Node.Arguments) then
+ sb.Append(', ');
+ end;
+ sb.Append(')');
+ Result := sb.ToString;
+ finally
+ sb.Free;
+ end;
+end;
+
function TAstToTextVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
Result := Node.Name;
diff --git a/ASTPlayground/Myc.Fmx.AstEditor.pas b/ASTPlayground/Myc.Fmx.AstEditor.pas
index 73b643e..152e73d 100644
--- a/ASTPlayground/Myc.Fmx.AstEditor.pas
+++ b/ASTPlayground/Myc.Fmx.AstEditor.pas
@@ -118,6 +118,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue;
end;
implementation
@@ -567,6 +568,46 @@ begin
Result := TDataValue.Void;
end;
+function TAstToAuraNodeVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ details: string;
+begin
+ if (FMode = vmControlFlow) and TryGetDescr(Node, details) then
+ begin
+ var recurNode := CreateNodeControl('Recur', details);
+ CreateEntry(recurNode);
+ CreateExit(recurNode, '');
+ FinalizeNodeLayout(recurNode);
+ FLastResult.OutputPin := nil;
+ end
+ else
+ begin
+ VisitOperatorNode(
+ Node.Arguments,
+ function(const InputResults: TAuraNodeResultList): TAuraNodeResult
+ var
+ recurNode: TAuraNode;
+ argPin: array of TControl;
+ i: Integer;
+ begin
+ recurNode := BuildNodeControl('Recur', '');
+ CreateEntry(recurNode);
+ SetLength(argPin, Length(Node.Arguments));
+ for i := 0 to High(argPin) do
+ argPin[i] := CreateInput(recurNode, 'Arg' + i.ToString);
+ CreateExit(recurNode, '');
+ for i := 0 to High(Node.Arguments) do
+ if Assigned(InputResults[i].OutputPin) then
+ FConnections.Add(TPinConnection.Create(InputResults[i].OutputPin, argPin[i]));
+ FinalizeNodeLayout(recurNode);
+ Result.LayoutNode := recurNode;
+ Result.OutputPin := nil;
+ end
+ );
+ end;
+ Result := TDataValue.Void;
+end;
+
function TAstToAuraNodeVisitor.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
var
existingResult: TAuraNodeResult;
diff --git a/Src/AST/Myc.Ast.Binding.pas b/Src/AST/Myc.Ast.Binding.pas
index e845b03..79ee1ed 100644
--- a/Src/AST/Myc.Ast.Binding.pas
+++ b/Src/AST/Myc.Ast.Binding.pas
@@ -32,6 +32,7 @@ type
protected
function Accept(const Node: IAstNode): TDataValue; override;
+ function IsValidIdentifier(const Name: string): Boolean;
public
constructor Create(const AInitialScope: IExecutionScope);
@@ -47,6 +48,7 @@ type
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue; override;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue; override;
function VisitBinaryExpression(const Node: IBinaryExpressionNode): TDataValue; override;
function VisitUnaryExpression(const Node: IUnaryExpressionNode): TDataValue; override;
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
@@ -58,6 +60,7 @@ implementation
uses
System.Generics.Defaults,
+ System.Character,
Myc.Ast;
type
@@ -214,6 +217,17 @@ begin
inherited;
end;
+function TAstBinder.VisitRecurNode(const Node: IRecurNode): TDataValue;
+begin
+ // Check if the current context is a tail position.
+ if not FIsTailStack.Peek then
+ raise Exception.Create('''recur'' can only be used in a tail position.');
+
+ // Arguments to recur are not in a tail position.
+ FNextIsTail := False;
+ inherited;
+end;
+
function TAstBinder.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
var
depth, idx: Integer;
@@ -278,7 +292,9 @@ begin
try
EnterScope;
try
- FCurrentDescriptor.Define('Self');
+ // Reserve slot 0 for the closure itself (for 'recur'),
+ // using a name that cannot be accessed from source code.
+ FCurrentDescriptor.Define('');
for param in Node.Parameters do
FCurrentDescriptor.Define(param.Name);
@@ -341,6 +357,30 @@ begin
inherited;
end;
+function TAstBinder.IsValidIdentifier(const Name: string): Boolean;
+var
+ i: Integer;
+ c: Char;
+begin
+ if Name.IsEmpty then
+ exit(False);
+
+ // First character must be a letter or underscore.
+ c := Name[1];
+ if not (c.IsLetter or (c = '_')) then
+ exit(False);
+
+ // Subsequent characters can be letters, numbers, underscore, or hyphen.
+ for i := 2 to Length(Name) do
+ begin
+ c := Name[i];
+ if not (c.IsLetterOrDigit or (c = '_') or (c = '-')) then
+ exit(False);
+ end;
+
+ Result := True;
+end;
+
function TAstBinder.VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
var
slotIndex: Integer;
@@ -350,6 +390,10 @@ begin
if Assigned(Node.Initializer) then
Accept(Node.Initializer);
+ // Reject identifiers that contain special characters or reserved operator characters.
+ if not IsValidIdentifier(Node.Identifier.Name) then
+ raise Exception.CreateFmt('Invalid identifier name: "%s".', [Node.Identifier.Name]);
+
slotIndex := FCurrentDescriptor.Define(Node.Identifier.Name);
(Node.Identifier as TIdentifierNode).Address := TResolvedAddress.Create(akLocalOrParent, 0, slotIndex);
diff --git a/Src/AST/Myc.Ast.Dumper.pas b/Src/AST/Myc.Ast.Dumper.pas
index 82649e6..a55636b 100644
--- a/Src/AST/Myc.Ast.Dumper.pas
+++ b/Src/AST/Myc.Ast.Dumper.pas
@@ -39,6 +39,7 @@ type
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; override;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; override;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override;
function VisitAssignment(const Node: IAssignmentNode): TDataValue; override;
@@ -230,6 +231,21 @@ begin
Result := TDataValue.Void;
end;
+function TAstDumper.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ arg: IAstNode;
+begin
+ LogFmt('Recur (IsTailCall: %s)', [Node.IsTailCall.ToString(TUseBoolStrs.True)]);
+ Indent;
+ LogFmt('Arguments (%d):', [Length(Node.Arguments)]);
+ Indent;
+ for arg in Node.Arguments do
+ Accept(arg);
+ Unindent;
+ Unindent;
+ Result := TDataValue.Void;
+end;
+
function TAstDumper.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
var
expr: IAstNode;
diff --git a/Src/AST/Myc.Ast.Evaluator.pas b/Src/AST/Myc.Ast.Evaluator.pas
index 9843db0..9d911ad 100644
--- a/Src/AST/Myc.Ast.Evaluator.pas
+++ b/Src/AST/Myc.Ast.Evaluator.pas
@@ -50,6 +50,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; virtual;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; virtual;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue; virtual;
end;
// Registers native Delphi functions into a scope.
@@ -204,7 +205,8 @@ begin
adr.Kind := akLocalOrParent;
adr.ScopeDepth := 0;
- // Capture 'Self' (slot 0) for recursion using the captured variable.
+ // Capture the closure itself in slot 0 for 'recur' to find it.
+ // The name 'Self' is no longer exposed to the user by the binder.
adr.SlotIndex := 0;
lambdaScope[adr] := TDataValue(closure);
@@ -256,6 +258,33 @@ begin
end;
end;
+function TEvaluatorVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ argValues: TArray;
+ calleeAddress: TResolvedAddress;
+ calleeValue: TDataValue;
+ i: Integer;
+begin
+ if not Node.IsTailCall then
+ raise EInvalidOperation.Create('Recur has to be a tail call');
+
+ // Evaluate all arguments for the recursive call.
+ SetLength(argValues, Length(Node.Arguments));
+ for i := 0 to High(Node.Arguments) do
+ argValues[i] := Node.Arguments[i].Accept(Self);
+
+ // The callee is the current function, which is stored by the lambda
+ // expression visitor in slot 0 of the current scope.
+ calleeAddress.Kind := akLocalOrParent;
+ calleeAddress.ScopeDepth := 0;
+ calleeAddress.SlotIndex := 0;
+ calleeValue := FScope[calleeAddress];
+
+ // Recur must be in a tail position, so we always return a thunk.
+ // The binder is responsible for enforcing the tail position rule.
+ Result := TDataValue.FromGeneric(TThunk.Create(calleeValue, argValues));
+end;
+
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
var
itemValue, lookbackValue, seriesVar: TDataValue;
diff --git a/Src/AST/Myc.Ast.JSON.pas b/Src/AST/Myc.Ast.JSON.pas
index d83127e..fcb54de 100644
--- a/Src/AST/Myc.Ast.JSON.pas
+++ b/Src/AST/Myc.Ast.JSON.pas
@@ -41,6 +41,7 @@ type
function JsonToTernaryExprNode(const AObj: TJSONObject): ITernaryExpressionNode;
function JsonToLambdaExprNode(const AObj: TJSONObject): ILambdaExpressionNode;
function JsonToFunctionCallNode(const AObj: TJSONObject): IFunctionCallNode;
+ function JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
function JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
function JsonToVarDeclNode(const AObj: TJSONObject): IVariableDeclarationNode;
function JsonToAssignmentNode(const AObj: TJSONObject): IAssignmentNode;
@@ -59,6 +60,7 @@ type
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue;
function VisitAssignment(const Node: IAssignmentNode): TDataValue;
@@ -321,6 +323,32 @@ begin
Result := TDataValue.Void;
end;
+function TJsonAstConverter.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ obj: TJSONObject;
+ argsArray: TJSONArray;
+ tempArgs: TArray;
+ arg: IAstNode;
+ i: Integer;
+begin
+ for arg in Node.Arguments do
+ arg.Accept(Self);
+
+ SetLength(tempArgs, Length(Node.Arguments));
+ for i := High(tempArgs) downto 0 do
+ tempArgs[i] := FJsonObjectStack.Pop;
+
+ argsArray := TJSONArray.Create;
+ for i := 0 to High(tempArgs) do
+ argsArray.Add(tempArgs[i]);
+
+ obj := TJSONObject.Create;
+ obj.AddPair('NodeType', TJSONString.Create('Recur'));
+ obj.AddPair('Arguments', argsArray);
+ FJsonObjectStack.Push(obj);
+ Result := TDataValue.Void;
+end;
+
function TJsonAstConverter.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
var
obj: TJSONObject;
@@ -604,6 +632,20 @@ begin
Result := TAst.FunctionCall(callee, args);
end;
+function TJsonAstConverter.JsonToRecurNode(const AObj: TJSONObject): IRecurNode;
+var
+ args: TArray;
+ argsArray: TJSONArray;
+ i: Integer;
+begin
+ argsArray := AObj.GetValue('Arguments');
+ SetLength(args, argsArray.Count);
+ for i := 0 to argsArray.Count - 1 do
+ args[i] := JsonToNode(argsArray.Items[i]);
+
+ Result := TAst.Recur(args);
+end;
+
function TJsonAstConverter.JsonToBlockNode(const AObj: TJSONObject): IBlockExpressionNode;
var
expressions: TArray;
@@ -702,6 +744,8 @@ begin
Result := JsonToLambdaExprNode(obj)
else if nodeType = 'FunctionCall' then
Result := JsonToFunctionCallNode(obj)
+ else if nodeType = 'Recur' then
+ Result := JsonToRecurNode(obj)
else if nodeType = 'Block' then
Result := JsonToBlockNode(obj)
else if nodeType = 'VarDecl' then
diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas
index e2e3a75..471020d 100644
--- a/Src/AST/Myc.Ast.Nodes.pas
+++ b/Src/AST/Myc.Ast.Nodes.pas
@@ -28,6 +28,7 @@ type
ICreateSeriesNode = interface;
IAddSeriesItemNode = interface;
ISeriesLengthNode = interface;
+ IRecurNode = interface;
IExecutionScope = interface;
IScopeDescriptor = interface;
@@ -103,6 +104,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue;
end;
IAstNode = interface(IInterface)
@@ -193,6 +195,16 @@ type
property IsTailCall: Boolean read GetIsTailCall;
end;
+ // A node representing a tail-recursive call.
+ IRecurNode = interface(IAstNode)
+ {$region 'private'}
+ function GetArguments: TArray;
+ function GetIsTailCall: Boolean;
+ {$endregion}
+ property Arguments: TArray read GetArguments;
+ property IsTailCall: Boolean read GetIsTailCall;
+ end;
+
IBlockExpressionNode = interface(IAstNode)
{$region 'private'}
function GetExpressions: TList;
diff --git a/Src/AST/Myc.Ast.Printer.pas b/Src/AST/Myc.Ast.Printer.pas
index ca3e61f..041ce36 100644
--- a/Src/AST/Myc.Ast.Printer.pas
+++ b/Src/AST/Myc.Ast.Printer.pas
@@ -40,6 +40,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue;
end;
implementation
@@ -206,6 +207,21 @@ begin
Result := TDataValue.Void;
end;
+function TPrettyPrintVisitor.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ arg: IAstNode;
+begin
+ AppendLine('Recur');
+ Indent;
+ AppendLine('Arguments:');
+ Indent;
+ for arg in Node.Arguments do
+ arg.Accept(Self);
+ Unindent;
+ Unindent;
+ Result := TDataValue.Void;
+end;
+
function TPrettyPrintVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
var
expr: IAstNode;
diff --git a/Src/AST/Myc.Ast.Traverser.pas b/Src/AST/Myc.Ast.Traverser.pas
index bf37e90..2b668cc 100644
--- a/Src/AST/Myc.Ast.Traverser.pas
+++ b/Src/AST/Myc.Ast.Traverser.pas
@@ -35,6 +35,7 @@ type
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; virtual;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue; virtual;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue; virtual;
+ function VisitRecurNode(const Node: IRecurNode): TDataValue; virtual;
end;
// Generic traverser for managing state during AST walks.
@@ -123,6 +124,18 @@ begin
end;
end;
+function TAstTraverser.VisitRecurNode(const Node: IRecurNode): TDataValue;
+var
+ arg: IAstNode;
+begin
+ for arg in Node.Arguments do
+ begin
+ if FDone then
+ break;
+ Accept(arg);
+ end;
+end;
+
function TAstTraverser.VisitIdentifier(const Node: IIdentifierNode): TDataValue;
begin
end;
diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas
index be6fd35..dce8aa4 100644
--- a/Src/AST/Myc.Ast.pas
+++ b/Src/AST/Myc.Ast.pas
@@ -36,6 +36,7 @@ type
class function TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode; static;
class function LambdaExpr(const AParameters: TArray; const ABody: IAstNode): ILambdaExpressionNode; static;
class function FunctionCall(const ACallee: IAstNode; const AArguments: TArray): IFunctionCallNode; static;
+ class function Recur(const AArguments: array of IAstNode): IRecurNode; static;
class function Block(const AExpressions: array of IAstNode): IBlockExpressionNode; static;
class function VarDecl(const AIdentifier: IIdentifierNode; AInitializer: IAstNode = nil): IVariableDeclarationNode; static;
class function Assign(const AIdentifier: IIdentifierNode; const AValue: IAstNode): IAssignmentNode; static;
@@ -164,6 +165,18 @@ type
property IsTailCall: Boolean read FIsTailCall write FIsTailCall;
end;
+ TRecurNode = class(TAstNode, IRecurNode)
+ private
+ FArguments: TArray;
+ FIsTailCall: Boolean;
+ function GetArguments: TArray;
+ function GetIsTailCall: Boolean;
+ public
+ constructor Create(const AArguments: TArray);
+ function Accept(const Visitor: IAstVisitor): TDataValue; override;
+ property IsTailCall: Boolean read FIsTailCall write FIsTailCall;
+ end;
+
TBlockExpressionNode = class(TAstNode, IBlockExpressionNode)
private
FExpressions: TList;
@@ -479,6 +492,30 @@ begin
Result := FIsTailCall;
end;
+{ TRecurNode }
+
+constructor TRecurNode.Create(const AArguments: TArray);
+begin
+ inherited Create;
+ FArguments := AArguments;
+ FIsTailCall := True;
+end;
+
+function TRecurNode.Accept(const Visitor: IAstVisitor): TDataValue;
+begin
+ Result := Visitor.VisitRecurNode(Self);
+end;
+
+function TRecurNode.GetArguments: TArray;
+begin
+ Result := FArguments;
+end;
+
+function TRecurNode.GetIsTailCall: Boolean;
+begin
+ Result := FIsTailCall;
+end;
+
{ TBlockExpressionNode }
constructor TBlockExpressionNode.Create(const AExpressions: array of IAstNode);
@@ -760,6 +797,17 @@ begin
Result := TMemberAccessNode.Create(ABase, AMember);
end;
+class function TAst.Recur(const AArguments: array of IAstNode): IRecurNode;
+var
+ args: TArray;
+ i: Integer;
+begin
+ SetLength(args, Length(AArguments));
+ for i := 0 to High(AArguments) do
+ args[i] := AArguments[i];
+ Result := TRecurNode.Create(args);
+end;
+
class function TAst.SeriesLength(const ASeries: IIdentifierNode): ISeriesLengthNode;
begin
Result := TSeriesLengthNode.Create(ASeries);