RECUR keyword added

This commit is contained in:
Michael Schimmel
2025-09-20 12:06:51 +02:00
parent e03155179a
commit 81dd69bf49
13 changed files with 352 additions and 98 deletions
+3 -3
View File
@@ -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}
+3 -3
View File
@@ -143,14 +143,14 @@
<DCCReference Include="..\Src\AST\Myc.Ast.ViewModel.pas"/>
<DCCReference Include="Myc.Data.Value.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Debugger.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Node.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Workspace.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Traverser.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Binding.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.Dumper.pas"/>
<DCCReference Include="..\Src\AST\Myc.Ast.RTL.Core.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Node.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Workspace.pas"/>
<DCCReference Include="Myc.Fmx.AstEditor.Text.pas"/>
<DCCReference Include="..\Src\Myc.Utils.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
+59 -90
View File
@@ -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'))
)
+22
View File
@@ -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;
+41
View File
@@ -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;
+45 -1
View File
@@ -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('<self>');
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);
+16
View File
@@ -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;
+30 -1
View File
@@ -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<TDataValue>;
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>(TThunk.Create(calleeValue, argValues));
end;
function TEvaluatorVisitor.VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
var
itemValue, lookbackValue, seriesVar: TDataValue;
+44
View File
@@ -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<TJSONObject>;
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<IAstNode>;
argsArray: TJSONArray;
i: Integer;
begin
argsArray := AObj.GetValue<TJSONArray>('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<IAstNode>;
@@ -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
+12
View File
@@ -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<IAstNode>;
function GetIsTailCall: Boolean;
{$endregion}
property Arguments: TArray<IAstNode> read GetArguments;
property IsTailCall: Boolean read GetIsTailCall;
end;
IBlockExpressionNode = interface(IAstNode)
{$region 'private'}
function GetExpressions: TList<IAstNode>;
+16
View File
@@ -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;
+13
View File
@@ -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;
+48
View File
@@ -36,6 +36,7 @@ type
class function TernaryExpr(const ACondition: IAstNode; const AThenBranch, AElseBranch: IAstNode): ITernaryExpressionNode; static;
class function LambdaExpr(const AParameters: TArray<IIdentifierNode>; const ABody: IAstNode): ILambdaExpressionNode; static;
class function FunctionCall(const ACallee: IAstNode; const AArguments: TArray<IAstNode>): 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<IAstNode>;
FIsTailCall: Boolean;
function GetArguments: TArray<IAstNode>;
function GetIsTailCall: Boolean;
public
constructor Create(const AArguments: TArray<IAstNode>);
function Accept(const Visitor: IAstVisitor): TDataValue; override;
property IsTailCall: Boolean read FIsTailCall write FIsTailCall;
end;
TBlockExpressionNode = class(TAstNode, IBlockExpressionNode)
private
FExpressions: TList<IAstNode>;
@@ -479,6 +492,30 @@ begin
Result := FIsTailCall;
end;
{ TRecurNode }
constructor TRecurNode.Create(const AArguments: TArray<IAstNode>);
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<IAstNode>;
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<IAstNode>;
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);