AST Identities
This commit is contained in:
@@ -23,7 +23,6 @@ type
|
||||
end;
|
||||
|
||||
// Callback used by TMacroExpander to request full compilation and execution of macro arguments.
|
||||
// This abstracts away the specific Binder, TypeChecker, and Evaluator implementations.
|
||||
TMacroEvaluatorProc = reference to function(const Scope: IExecutionScope; const Node: IAstNode): TDataValue;
|
||||
|
||||
// Handles the expansion of the macro body template (Quasiquotes/Unquotes).
|
||||
@@ -146,8 +145,6 @@ end;
|
||||
|
||||
function TExpansionVisitor.Gensym(const ABaseName: string): string;
|
||||
begin
|
||||
// Hygienic macros: Generate unique names for identifiers introduced by the macro
|
||||
// that were not captured from the arguments.
|
||||
if not FRenameMap.TryGetValue(ABaseName, Result) then
|
||||
begin
|
||||
var count := TInterlocked.Add(FGensymCounter, 1);
|
||||
@@ -187,7 +184,6 @@ begin
|
||||
var spliceExpr := node.AsUnquoteSplicing.Expression;
|
||||
var evaluatedSpliceValue: TDataValue;
|
||||
|
||||
// GUARDED EVALUATION
|
||||
try
|
||||
evaluatedSpliceValue := FMacroEvaluator(FMacroScope, spliceExpr);
|
||||
except
|
||||
@@ -207,7 +203,8 @@ begin
|
||||
end
|
||||
else if (not evaluatedSpliceValue.IsVoid) then
|
||||
begin
|
||||
newList.Add(TAst.Constant(evaluatedSpliceValue));
|
||||
// Use Splice Node Identity for the new Constant
|
||||
newList.Add(TAst.Constant(evaluatedSpliceValue, node.Identity.Location));
|
||||
end;
|
||||
end
|
||||
else
|
||||
@@ -230,7 +227,8 @@ begin
|
||||
// Apply hygienic renaming
|
||||
if FRenameMap.TryGetValue(Node.Name, newName) then
|
||||
begin
|
||||
Result := TAst.Identifier(newName, TTypes.Unknown);
|
||||
// Create new Identifier with new Name but preserve Location from original Node
|
||||
Result := TAst.Identifier(newName, Node.Identity.Location);
|
||||
exit;
|
||||
end;
|
||||
Result := Node;
|
||||
@@ -243,19 +241,18 @@ var
|
||||
begin
|
||||
newInit := Accept(Node.Initializer);
|
||||
|
||||
// Check if target is identifier before trying to rename
|
||||
if Node.Target.Kind = akIdentifier then
|
||||
begin
|
||||
newName := Gensym(Node.Target.AsIdentifier.Name);
|
||||
newTarget := TAst.Identifier(newName, TTypes.Unknown);
|
||||
// Use location from original target
|
||||
newTarget := TAst.Identifier(newName, Node.Target.Identity.Location);
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Recursively expand unquotes in target position (advanced usage)
|
||||
newTarget := Accept(Node.Target);
|
||||
end;
|
||||
|
||||
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
|
||||
Result := TAst.VarDecl(Node.Identity, newTarget, newInit, TTypes.Unknown, False);
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitLambdaExpression(const Node: ILambdaExpressionNode): IAstNode;
|
||||
@@ -265,17 +262,18 @@ var
|
||||
newName: string;
|
||||
i: Integer;
|
||||
begin
|
||||
// Parameters in a macro body must also be renamed hygienically
|
||||
SetLength(newParams, Length(Node.Parameters));
|
||||
for i := 0 to High(Node.Parameters) do
|
||||
begin
|
||||
newName := Gensym(Node.Parameters[i].Name);
|
||||
newParams[i] := TAst.Identifier(newName, TTypes.Unknown);
|
||||
// Use location from original param
|
||||
newParams[i] := TAst.Identifier(newName, Node.Parameters[i].Identity.Location);
|
||||
end;
|
||||
|
||||
newBody := Accept(Node.Body);
|
||||
|
||||
Result := TAst.LambdaExpr(newParams, newBody, nil, nil, nil, False, Node.IsPure, TTypes.Unknown);
|
||||
// Rebuild structural lambda
|
||||
Result := TAst.LambdaExpr(Node.Identity, newParams, newBody);
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||
@@ -300,8 +298,6 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
// Evaluate the expression at compile time.
|
||||
// This executes user code! It must be guarded.
|
||||
try
|
||||
value := FMacroEvaluator(FMacroScope, expr);
|
||||
except
|
||||
@@ -318,14 +314,14 @@ begin
|
||||
end;
|
||||
|
||||
if value.Kind in [vkScalar, vkText, vkVoid] then
|
||||
Result := TAst.Constant(value)
|
||||
// Create Constant node, inheriting location from the Unquote node
|
||||
Result := TAst.Constant(value, Node.Identity.Location)
|
||||
else
|
||||
raise EMacroException.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]);
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
||||
begin
|
||||
// ~@ can only be handled within a list context (TransformAndSpliceNodes)
|
||||
raise EMacroException.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
|
||||
end;
|
||||
|
||||
@@ -335,23 +331,20 @@ var
|
||||
transformedCallee: IAstNode;
|
||||
begin
|
||||
transformedCallee := Self.Accept(Node.Callee);
|
||||
// Use special splicing logic for argument lists
|
||||
newArgs := TransformAndSpliceNodes(Node.Arguments);
|
||||
Result := TAst.FunctionCall(transformedCallee, newArgs);
|
||||
Result := TAst.FunctionCall(Node.Identity, transformedCallee, newArgs);
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||
var
|
||||
newExprs: TArray<IAstNode>;
|
||||
begin
|
||||
// Use special splicing logic for block expressions
|
||||
newExprs := TransformAndSpliceNodes(Node.Expressions);
|
||||
Result := TAst.Block(newExprs);
|
||||
Result := TAst.Block(Node.Identity, newExprs);
|
||||
end;
|
||||
|
||||
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
||||
begin
|
||||
// Standard recursion for records
|
||||
Result := inherited VisitRecordLiteral(Node);
|
||||
end;
|
||||
|
||||
@@ -366,7 +359,6 @@ begin
|
||||
inherited Create;
|
||||
FInitialScope := AInitialScope;
|
||||
FMacroEvaluator := AMacroEvaluator;
|
||||
// Create a local child registry to handle scoped macro definitions (defmacro inside do)
|
||||
FCurrentMacroRegistry := ARootRegistry.CreateChildRegistry;
|
||||
end;
|
||||
|
||||
@@ -400,12 +392,11 @@ function TMacroExpander.Execute(const RootNode: IAstNode): IAstNode;
|
||||
begin
|
||||
Result := Accept(RootNode);
|
||||
if not Assigned(Result) then
|
||||
Result := TAst.Block([]);
|
||||
Result := TAst.Block([], nil);
|
||||
end;
|
||||
|
||||
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||
begin
|
||||
// Macros defined inside a block should only be visible within that block
|
||||
EnterMacroScope;
|
||||
try
|
||||
Result := inherited VisitBlockExpression(Node);
|
||||
@@ -426,11 +417,7 @@ end;
|
||||
|
||||
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
||||
begin
|
||||
// Register the macro in the current scope
|
||||
FCurrentMacroRegistry.Define(Node);
|
||||
// Remove the definition from the runtime AST (it's compile-time only)
|
||||
// However, for now we return the node so it can be serialized/inspected,
|
||||
// but the Evaluator usually ignores it (returns Void).
|
||||
Result := Node;
|
||||
end;
|
||||
|
||||
@@ -440,7 +427,6 @@ var
|
||||
macroDef: IMacroDefinitionNode;
|
||||
i: Integer;
|
||||
begin
|
||||
// Check if it is a macro call
|
||||
if Node.Callee.Kind <> akIdentifier then
|
||||
exit(inherited VisitFunctionCall(Node));
|
||||
|
||||
@@ -449,41 +435,31 @@ begin
|
||||
if macroDef = nil then
|
||||
exit(inherited VisitFunctionCall(Node));
|
||||
|
||||
// --- Macro Expansion Logic ---
|
||||
|
||||
// 1. Create temporary scope for macro arguments (parented to Initial/Global Scope)
|
||||
// This ensures macro execution is hygienic regarding the environment it runs in.
|
||||
var expansionScope := TScope.CreateScope(FInitialScope, nil, nil);
|
||||
|
||||
var params := macroDef.Parameters;
|
||||
if Length(Node.Arguments) <> Length(params) then
|
||||
raise EMacroException.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]);
|
||||
|
||||
// 2. Populate scope with UN-EVALUATED AST nodes (Code as Data)
|
||||
for i := 0 to High(params) do
|
||||
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i]));
|
||||
|
||||
// 4. Expand the Macro Body
|
||||
// We assume the macro body is a Quasiquote. We expand it using the visitor.
|
||||
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, FMacroEvaluator);
|
||||
|
||||
// 5. Create a MacroExpansionNode to preserve source mapping
|
||||
var macroNode := TAst.MacroExpansionNode(Node, expandedBody);
|
||||
// The MacroExpansionNode wraps the expansion, preserving the original call's identity (Source Location)
|
||||
// for better error reporting.
|
||||
var macroNode := TAst.MacroExpansionNode(Node.Identity, Node, expandedBody);
|
||||
|
||||
// 6. Recursively expand the result (the output of a macro might contain more macro calls)
|
||||
Result := Self.Accept(macroNode);
|
||||
end;
|
||||
|
||||
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
||||
begin
|
||||
// Quasiquotes in user code are not expanded by the MacroExpander,
|
||||
// they are literals.
|
||||
Result := Node;
|
||||
end;
|
||||
|
||||
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||
begin
|
||||
// Unquotes outside of a macro definition/expansion phase are invalid syntax
|
||||
raise EMacroException.Create('Unquote (`~`) can only be used inside a quasiquote.');
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user