added macro quoting nodes

This commit is contained in:
Michael Schimmel
2025-10-03 14:17:34 +02:00
parent fd97799b7b
commit bb0ecda6be
4 changed files with 273 additions and 19 deletions
+30
View File
@@ -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;
+93 -16
View File
@@ -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 := '<identifier>';
tkNumber: Result := '<number>';
tkString: Result := '<string>';
+51 -1
View File
@@ -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<IAstNode>;
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<IAstNode>;
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<IAstNode>;
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<IAstNode>;
@@ -377,10 +413,24 @@ end;
function TAstTransformer.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue;
begin
// Added visit method for macro definitions.
Result := TDataValue.FromIntf<IMacroDefinitionNode>(TransformMacroDefinition(Node));
end;
function TAstTransformer.VisitQuasiquote(const Node: IQuasiquoteNode): TDataValue;
begin
Result := TDataValue.FromIntf<IQuasiquoteNode>(TransformQuasiquote(Node));
end;
function TAstTransformer.VisitUnquote(const Node: IUnquoteNode): TDataValue;
begin
Result := TDataValue.FromIntf<IUnquoteNode>(TransformUnquote(Node));
end;
function TAstTransformer.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue;
begin
Result := TDataValue.FromIntf<IUnquoteSplicingNode>(TransformUnquoteSplicing(Node));
end;
function TAstTransformer.VisitIndexer(const Node: IIndexerNode): TDataValue;
begin
Result := TDataValue.FromIntf<IIndexerNode>(TransformIndexer(Node));
+99 -2
View File
@@ -41,6 +41,9 @@ type
const AParameters: TArray<IIdentifierNode>;
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<IAstNode>): 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<IIdentifierNode>; 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<IAstNode>);
@@ -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<IAstNode>;
@@ -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);