From bb0ecda6bee8633250c8e10c46739cc91d54d6e0 Mon Sep 17 00:00:00 2001 From: Michael Schimmel Date: Fri, 3 Oct 2025 14:17:34 +0200 Subject: [PATCH] added macro quoting nodes --- Src/AST/Myc.Ast.Nodes.pas | 30 ++++++++++ Src/AST/Myc.Ast.Script.pas | 109 ++++++++++++++++++++++++++++++------ Src/AST/Myc.Ast.Visitor.pas | 52 ++++++++++++++++- Src/AST/Myc.Ast.pas | 101 ++++++++++++++++++++++++++++++++- 4 files changed, 273 insertions(+), 19 deletions(-) diff --git a/Src/AST/Myc.Ast.Nodes.pas b/Src/AST/Myc.Ast.Nodes.pas index 11c2326..1a56ec2 100644 --- a/Src/AST/Myc.Ast.Nodes.pas +++ b/Src/AST/Myc.Ast.Nodes.pas @@ -24,6 +24,9 @@ type IVariableDeclarationNode = interface; IAssignmentNode = interface; IMacroDefinitionNode = interface; + IQuasiquoteNode = interface; + IUnquoteNode = interface; + IUnquoteSplicingNode = interface; IIndexerNode = interface; IMemberAccessNode = interface; ICreateSeriesNode = interface; @@ -59,6 +62,9 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; function VisitAssignment(const Node: IAssignmentNode): TDataValue; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; + function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; + function VisitUnquote(const Node: IUnquoteNode): TDataValue; + function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; function VisitIndexer(const Node: IIndexerNode): TDataValue; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; @@ -190,6 +196,30 @@ type property Body: IAstNode read GetBody; end; + // Represents a quasiquoted expression, e.g., `(a b) + IQuasiquoteNode = interface(IAstNode) + {$region 'private'} + function GetExpression: IAstNode; + {$endregion} + property Expression: IAstNode read GetExpression; + end; + + // Represents an unquoted expression, e.g., ~x + IUnquoteNode = interface(IAstNode) + {$region 'private'} + function GetExpression: IAstNode; + {$endregion} + property Expression: IAstNode read GetExpression; + end; + + // Represents an unquote-splicing expression, e.g., ~@y + IUnquoteSplicingNode = interface(IAstNode) + {$region 'private'} + function GetExpression: IAstNode; + {$endregion} + property Expression: IAstNode read GetExpression; + end; + IIndexerNode = interface(IAstNode) {$region 'private'} function GetBase: IAstNode; diff --git a/Src/AST/Myc.Ast.Script.pas b/Src/AST/Myc.Ast.Script.pas index 0165a0b..557062c 100644 --- a/Src/AST/Myc.Ast.Script.pas +++ b/Src/AST/Myc.Ast.Script.pas @@ -34,6 +34,9 @@ type tkRightParen, // ) tkLeftBracket, // [ tkRightBracket, // ] + tkQuote, // ' + tkBacktick, // ` + tkTilde, // ~ tkIdentifier, tkNumber, tkString, @@ -109,6 +112,9 @@ type function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue; function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; + function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; + function VisitUnquote(const Node: IUnquoteNode): TDataValue; + function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; @@ -148,8 +154,8 @@ var startPos: Integer; begin startPos := FCurrentPos; - // Corrected W1050: Use CharInSet for robust unicode support. - while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']']))) do + // Reader macro characters are now also delimiters for identifiers. + while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']', '''', '`', '~']))) do Advance; Result := Copy(FSource, startPos, FCurrentPos - startPos); end; @@ -228,6 +234,21 @@ begin Result.Kind := tkRightBracket; Advance; end; + '''': + begin + Result.Kind := tkQuote; + Advance; + end; + '`': + begin + Result.Kind := tkBacktick; + Advance; + end; + '~': + begin + Result.Kind := tkTilde; + Advance; + end; '"': begin Result.Kind := tkString; @@ -382,7 +403,6 @@ begin end else if SameText(head.Token.Text, 'fn') then begin - // Corrected handling for lambda expressions if Length(tailNodes) <> 2 then raise Exception.Create('Syntax Error: ''fn'' requires a parameter list and a body.'); if elements[1].Node <> nil then @@ -438,19 +458,43 @@ function TParser.ParseExpression: TExpr; var i64: Int64; dbl: Double; + expr: TExpr; begin - // TODO: Implement reader macros for quasiquoting here. - // The current lexer will tokenize `, ~, and ~@ as identifiers. - // Check for them here and wrap the subsequent expression accordingly. - // Example: - // if FCurrentToken.Text = '`' then - // begin - // NextToken; - // var exprToQuote := ParseExpression; - // Result.Node := TAst.Quasiquote(exprToQuote.Node); - // exit; - // end; - // (This requires IQuasiquoteNode and TAst.Quasiquote to be defined first). + // Handle reader macros. + case FCurrentToken.Kind of + tkBacktick: + begin + NextToken; + expr := ParseExpression; + Result.Node := TAst.Quasiquote(expr.Node); + exit; + end; + tkTilde: + begin + NextToken; + // Check for unquote-splicing ('~@') + if (FCurrentToken.Kind = tkIdentifier) and (FCurrentToken.Text = '@') then + begin + NextToken; + expr := ParseExpression; + Result.Node := TAst.UnquoteSplicing(expr.Node); + end + else // It's a regular unquote ('~') + begin + expr := ParseExpression; + Result.Node := TAst.Unquote(expr.Node); + end; + exit; + end; + tkQuote: + begin + NextToken; + expr := ParseExpression; + // A regular quote '(...) can be desugared into (quote (...)) + Result.Node := TAst.FunctionCall(TAst.Identifier('quote'), [expr.Node]); + exit; + end; + end; Result.Token := FCurrentToken; case FCurrentToken.Kind of @@ -639,7 +683,6 @@ var param: IIdentifierNode; sb: TStringBuilder; begin - // Added visitor for pretty printing macro definitions. sb := TStringBuilder.Create; try for param in Node.Parameters do @@ -661,10 +704,41 @@ begin Result := TDataValue.Void; end; +function TPrettyPrintVisitor.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; +begin + Append('`'); + Node.Expression.Accept(Self); + Result := TDataValue.Void; +end; + +function TPrettyPrintVisitor.VisitUnquote(const Node: IUnquoteNode): TDataValue; +begin + Append('~'); + Node.Expression.Accept(Self); + Result := TDataValue.Void; +end; + +function TPrettyPrintVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; +begin + // To handle '~@', we need to check if the expression is an identifier '@'. + // However, the parser logic now handles this, so we just print '~@'. + Append('~@'); + Node.Expression.Accept(Self); + Result := TDataValue.Void; +end; + function TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; var arg: IAstNode; begin + // Special case for (quote ...) to print it as '... + if (Node.Callee is TIdentifierNode) and (TIdentifierNode(Node.Callee).Name = 'quote') and (Length(Node.Arguments) = 1) then + begin + Append(''''); + Node.Arguments[0].Accept(Self); + exit(TDataValue.Void); + end; + Append('('); Node.Callee.Accept(Self); @@ -828,6 +902,9 @@ begin tkRightParen: Result := ')'; tkLeftBracket: Result := '['; tkRightBracket: Result := ']'; + tkQuote: Result := ''''; + tkBacktick: Result := '`'; + tkTilde: Result := '~'; tkIdentifier: Result := ''; tkNumber: Result := ''; tkString: Result := ''; diff --git a/Src/AST/Myc.Ast.Visitor.pas b/Src/AST/Myc.Ast.Visitor.pas index 47ee9b6..bbc2cda 100644 --- a/Src/AST/Myc.Ast.Visitor.pas +++ b/Src/AST/Myc.Ast.Visitor.pas @@ -25,6 +25,9 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; virtual; abstract; function VisitAssignment(const Node: IAssignmentNode): TDataValue; virtual; abstract; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; virtual; abstract; + function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; virtual; abstract; + function VisitUnquote(const Node: IUnquoteNode): TDataValue; virtual; abstract; + function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; virtual; abstract; function VisitIndexer(const Node: IIndexerNode): TDataValue; virtual; abstract; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; virtual; abstract; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; virtual; abstract; @@ -59,6 +62,9 @@ type function VisitVariableDeclaration(const Node: IVariableDeclarationNode): TDataValue; override; final; function VisitAssignment(const Node: IAssignmentNode): TDataValue; override; final; function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override; final; + function VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; override; final; + function VisitUnquote(const Node: IUnquoteNode): TDataValue; override; final; + function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override; final; function VisitIndexer(const Node: IIndexerNode): TDataValue; override; final; function VisitMemberAccess(const Node: IMemberAccessNode): TDataValue; override; final; function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue; override; final; @@ -79,6 +85,9 @@ type function TransformVariableDeclaration(const Node: IVariableDeclarationNode): IVariableDeclarationNode; virtual; function TransformAssignment(const Node: IAssignmentNode): IAssignmentNode; virtual; function TransformMacroDefinition(const Node: IMacroDefinitionNode): IMacroDefinitionNode; virtual; + function TransformQuasiquote(const Node: IQuasiquoteNode): IQuasiquoteNode; virtual; + function TransformUnquote(const Node: IUnquoteNode): IUnquoteNode; virtual; + function TransformUnquoteSplicing(const Node: IUnquoteSplicingNode): IUnquoteSplicingNode; virtual; function TransformIndexer(const Node: IIndexerNode): IIndexerNode; virtual; function TransformMemberAccess(const Node: IMemberAccessNode): IMemberAccessNode; virtual; function TransformCreateSeries(const Node: ICreateSeriesNode): ICreateSeriesNode; virtual; @@ -249,6 +258,33 @@ begin Result := TAst.MacroDef(name, parameters, body); end; +function TAstTransformer.TransformQuasiquote(const Node: IQuasiquoteNode): IQuasiquoteNode; +begin + var expr := Accept(Node.Expression).AsIntf; + if expr = Node.Expression then + Result := Node + else + Result := TAst.Quasiquote(expr); +end; + +function TAstTransformer.TransformUnquote(const Node: IUnquoteNode): IUnquoteNode; +begin + var expr := Accept(Node.Expression).AsIntf; + if expr = Node.Expression then + Result := Node + else + Result := TAst.Unquote(expr); +end; + +function TAstTransformer.TransformUnquoteSplicing(const Node: IUnquoteSplicingNode): IUnquoteSplicingNode; +begin + var expr := Accept(Node.Expression).AsIntf; + if expr = Node.Expression then + Result := Node + else + Result := TAst.UnquoteSplicing(expr); +end; + function TAstTransformer.TransformIndexer(const Node: IIndexerNode): IIndexerNode; begin var base := Accept(Node.Base).AsIntf; @@ -377,10 +413,24 @@ end; function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; begin - // Added visit method for macro definitions. Result := TDataValue.FromIntf(TransformMacroDefinition(Node)); end; +function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue; +begin + Result := TDataValue.FromIntf(TransformQuasiquote(Node)); +end; + +function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): TDataValue; +begin + Result := TDataValue.FromIntf(TransformUnquote(Node)); +end; + +function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; +begin + Result := TDataValue.FromIntf(TransformUnquoteSplicing(Node)); +end; + function TAstTransformer.VisitIndexer(const Node: IIndexerNode): TDataValue; begin Result := TDataValue.FromIntf(TransformIndexer(Node)); diff --git a/Src/AST/Myc.Ast.pas b/Src/AST/Myc.Ast.pas index 62b7d67..9189cb0 100644 --- a/Src/AST/Myc.Ast.pas +++ b/Src/AST/Myc.Ast.pas @@ -41,6 +41,9 @@ type const AParameters: TArray; const ABody: IAstNode ): IMacroDefinitionNode; static; + class function Quasiquote(const AExpression: IAstNode): IQuasiquoteNode; static; + class function Unquote(const AExpression: IAstNode): IUnquoteNode; static; + class function UnquoteSplicing(const AExpression: IAstNode): IUnquoteSplicingNode; 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; @@ -159,6 +162,33 @@ type function Accept(const Visitor: IAstVisitor): TDataValue; override; end; + TQuasiquoteNode = class(TAstNode, IQuasiquoteNode) + private + FExpression: IAstNode; + function GetExpression: IAstNode; + public + constructor Create(const AExpression: IAstNode); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + end; + + TUnquoteNode = class(TAstNode, IUnquoteNode) + private + FExpression: IAstNode; + function GetExpression: IAstNode; + public + constructor Create(const AExpression: IAstNode); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + end; + + TUnquoteSplicingNode = class(TAstNode, IUnquoteSplicingNode) + private + FExpression: IAstNode; + function GetExpression: IAstNode; + public + constructor Create(const AExpression: IAstNode); + function Accept(const Visitor: IAstVisitor): TDataValue; override; + end; + TFunctionCallNode = class(TAstNode, IFunctionCallNode) private FCallee: IAstNode; @@ -450,7 +480,6 @@ end; constructor TMacroDefinitionNode.Create(const AName: IIdentifierNode; const AParameters: TArray; const ABody: IAstNode); begin - // Added concrete class for macro definitions. inherited Create; FName := AName; FParameters := AParameters; @@ -477,6 +506,60 @@ begin Result := FParameters; end; +{ TQuasiquoteNode } + +constructor TQuasiquoteNode.Create(const AExpression: IAstNode); +begin + inherited Create; + FExpression := AExpression; +end; + +function TQuasiquoteNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitQuasiquote(Self); +end; + +function TQuasiquoteNode.GetExpression: IAstNode; +begin + Result := FExpression; +end; + +{ TUnquoteNode } + +constructor TUnquoteNode.Create(const AExpression: IAstNode); +begin + inherited Create; + FExpression := AExpression; +end; + +function TUnquoteNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitUnquote(Self); +end; + +function TUnquoteNode.GetExpression: IAstNode; +begin + Result := FExpression; +end; + +{ TUnquoteSplicingNode } + +constructor TUnquoteSplicingNode.Create(const AExpression: IAstNode); +begin + inherited Create; + FExpression := AExpression; +end; + +function TUnquoteSplicingNode.Accept(const Visitor: IAstVisitor): TDataValue; +begin + Result := Visitor.VisitUnquoteSplicing(Self); +end; + +function TUnquoteSplicingNode.GetExpression: IAstNode; +begin + Result := FExpression; +end; + { TFunctionCallNode } constructor TFunctionCallNode.Create(const ACallee: IAstNode; const AArguments: TArray); @@ -811,7 +894,6 @@ class function TAst.MacroDef( const ABody: IAstNode ): IMacroDefinitionNode; begin - // Added factory for macro definitions. Result := TMacroDefinitionNode.Create(AName, AParameters, ABody); end; @@ -820,6 +902,11 @@ begin Result := TMemberAccessNode.Create(ABase, AMember); end; +class function TAst.Quasiquote(const AExpression: IAstNode): IQuasiquoteNode; +begin + Result := TQuasiquoteNode.Create(AExpression); +end; + class function TAst.Recur(const AArguments: array of IAstNode): IRecurNode; var args: TArray; @@ -841,6 +928,16 @@ begin Result := TTernaryExpressionNode.Create(ACondition, AThenBranch, AElseBranch); end; +class function TAst.Unquote(const AExpression: IAstNode): IUnquoteNode; +begin + Result := TUnquoteNode.Create(AExpression); +end; + +class function TAst.UnquoteSplicing(const AExpression: IAstNode): IUnquoteSplicingNode; +begin + Result := TUnquoteSplicingNode.Create(AExpression); +end; + class function TAst.UnaryExpr(const AOperator: TScalar.TUnaryOp; const ARight: IAstNode): IUnaryExpressionNode; begin Result := TUnaryExpressionNode.Create(AOperator, ARight);