From 81dd69bf49ed7f939399b5e95498a81fd34a1d57 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Sat, 20 Sep 2025 12:06:51 +0200 Subject: [PATCH] RECUR keyword added --- ASTPlayground/ASTPlayground.dpr | 6 +- ASTPlayground/ASTPlayground.dproj | 6 +- ASTPlayground/MainForm.pas | 149 +++++++++-------------- ASTPlayground/Myc.Fmx.AstEditor.Text.pas | 22 ++++ ASTPlayground/Myc.Fmx.AstEditor.pas | 41 +++++++ Src/AST/Myc.Ast.Binding.pas | 46 ++++++- Src/AST/Myc.Ast.Dumper.pas | 16 +++ Src/AST/Myc.Ast.Evaluator.pas | 31 ++++- Src/AST/Myc.Ast.JSON.pas | 44 +++++++ Src/AST/Myc.Ast.Nodes.pas | 12 ++ Src/AST/Myc.Ast.Printer.pas | 16 +++ Src/AST/Myc.Ast.Traverser.pas | 13 ++ Src/AST/Myc.Ast.pas | 48 ++++++++ 13 files changed, 352 insertions(+), 98 deletions(-) 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);