Binder refactoring, Monster refactoring

This commit is contained in:
Michael Schimmel
2025-11-02 19:38:52 +01:00
parent 8f29212cba
commit ea39a57b77
22 changed files with 3061 additions and 2638 deletions
+54 -51
View File
@@ -12,8 +12,7 @@ uses
Myc.Ast.Visitor,
Myc.Ast.Scope,
Myc.Ast.Types,
Myc.Ast,
Myc.Ast.Binding; // Required for TAstBinder.Bind
Myc.Ast;
type
IAstMacroExpander = interface(IAstVisitor)
@@ -31,11 +30,12 @@ type
FMacroScope: IExecutionScope;
function TransformAndSpliceNodes(const ANodes: TArray<IAstNode>): TArray<IAstNode>;
protected
function VisitUnquote(const Node: IUnquoteNode): TDataValue; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue; override;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue; override;
// Override the new IAstNode-returning methods
function VisitUnquote(const Node: IUnquoteNode): IAstNode; override;
function VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode; override;
function VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode; override;
public
constructor Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc);
class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode;
@@ -50,11 +50,10 @@ type
FCurrentDescriptor: IScopeDescriptor;
FEvaluatorFactory: TEvaluatorFactory;
protected
function Accept(const Node: IAstNode): TDataValue; override;
// Finds macro definitions
function VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue; override;
function VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode; override;
// Finds and expands macro calls
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
function VisitFunctionCall(const Node: IFunctionCallNode): IAstNode; override;
public
constructor Create(const AInitialScope: IExecutionScope; const AEvaluatorFactory: TEvaluatorFactory);
function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
@@ -71,6 +70,7 @@ implementation
uses
System.Generics.Defaults,
Myc.Ast.Binding, // Required for TAstBinder.Bind
Myc.Ast.Evaluator, // Required for TEvaluatorVisitor.Execute
Myc.Data.Keyword;
@@ -89,14 +89,19 @@ class function TExpansionVisitor.Expand(
const AEvaluate: TEvaluateProc
): IAstNode;
begin
var expander := TExpansionVisitor.Create(MacroScope, AEvaluate) as IAstTransformer;
Result := expander.Execute(RootNode);
var expander := TExpansionVisitor.Create(MacroScope, AEvaluate);
try
Result := expander.Accept(RootNode); // Use IAstNode-returning Accept
finally
expander.Free;
end;
end;
function TExpansionVisitor.TransformAndSpliceNodes(const ANodes: TArray<IAstNode>): TArray<IAstNode>;
var
newList: TList<IAstNode>;
nodeToSplice: IAstNode;
transformedNode: IAstNode;
begin
newList := TList<IAstNode>.Create;
try
@@ -126,9 +131,10 @@ begin
end
else
begin
var transformedValue := node.Accept(self);
if not transformedValue.IsVoid then
newList.Add(transformedValue.AsIntf<IAstNode>);
// Use the IAstNode-returning Accept helper
transformedNode := Self.Accept(node);
if Assigned(transformedNode) then
newList.Add(transformedNode);
end;
end;
Result := newList.ToArray;
@@ -137,7 +143,7 @@ begin
end;
end;
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): TDataValue;
function TExpansionVisitor.VisitUnquote(const Node: IUnquoteNode): IAstNode;
var
value: TDataValue;
expr: IAstNode;
@@ -155,7 +161,7 @@ begin
var argValue := FMacroScope.Values[symbol.Address];
if argValue.Kind = vkInterface then
begin
Result := argValue;
Result := argValue.AsIntf<IAstNode>; // Return the node directly
exit;
end;
end;
@@ -167,44 +173,44 @@ begin
// If the result is an AST node (e.g. from a helper function), return it directly.
if value.Kind = vkInterface then
begin
Result := value;
Result := value.AsIntf<IAstNode>;
exit;
end;
// If the result is a scalar, text, or void, wrap it in a TConstantNode.
if value.Kind in [vkScalar, vkText, vkVoid] then
begin
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value));
Result := TAst.Constant(value);
end
else
raise Exception.CreateFmt('Cannot unquote complex runtime value of type %s at compile time.', [value.Kind.ToString]);
end;
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): TDataValue;
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
begin
// This should be handled by the caller (VisitFunctionCall / VisitBlockExpression)
raise Exception.Create('Unquote-splicing (`~@`) can only appear inside a list form (e.g., a function call or a `do` block).');
end;
function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
function TExpansionVisitor.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
var
newArgs: TArray<IAstNode>;
transformedCallee: IAstNode;
begin
transformedCallee := Self.Accept(Node.Callee).AsIntf<IAstNode>;
transformedCallee := Self.Accept(Node.Callee); // Calls IAstNode helper
newArgs := TransformAndSpliceNodes(Node.Arguments);
Result := TDataValue.FromIntf<IFunctionCallNode>(TAst.FunctionCall(transformedCallee, newArgs));
Result := TAst.FunctionCall(transformedCallee, newArgs);
end;
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
var
newExprs: TArray<IAstNode>;
begin
newExprs := TransformAndSpliceNodes(Node.Expressions);
Result := TDataValue.FromIntf<IBlockExpressionNode>(TAst.Block(newExprs));
Result := TAst.Block(newExprs);
end;
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
begin
// Record literals do not support splicing.
// We just use the default TAstTransformer implementation which visits child values.
@@ -238,40 +244,30 @@ end;
function TMacroExpander.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
begin
// This executes the transformation (Accept)
var transformedValue := Accept(RootNode);
Result := Accept(RootNode); // Calls the IAstNode-returning helper
if transformedValue.IsVoid then
Result := TAst.Block([])
else
Result := transformedValue.AsIntf<IAstNode>;
if not Assigned(Result) then
Result := TAst.Block([]); // e.g. if root was (defmacro ...)
Descriptor := FCurrentDescriptor;
end;
function TMacroExpander.Accept(const Node: IAstNode): TDataValue;
begin
// Standard transformer Accept
if (not Assigned(Node)) or Done then
exit;
Result := Node.Accept(Self);
end;
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): TDataValue;
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
begin
// Register the macro in the current descriptor
FCurrentDescriptor.DefineMacro(Node.Name.Name, Node);
// Remove the (defmacro ...) node from the AST; it's compile-time only.
Result := TDataValue.Void;
Result := nil;
end;
function TMacroExpander.VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
function TMacroExpander.VisitFunctionCall(const Node: IFunctionCallNode): IAstNode;
var
calleeIdentifier: TIdentifierNode;
macroDef: IMacroDefinitionNode;
i: Integer;
begin
// Check if this is an identifier
if not (Node.Callee is TIdentifierNode) then
if Node.Callee.Kind <> akIdentifier then
exit(inherited VisitFunctionCall(Node));
calleeIdentifier := Node.Callee as TIdentifierNode;
@@ -292,7 +288,7 @@ begin
// 2. Populate scope: Map parameter names to the *un-evaluated* AST nodes
for i := 0 to High(params) do
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i]));
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i])); // Use FromIntf
// 3. Create the evaluation callback (TEvaluateProc)
var evaluatorProc: TEvaluateProc;
@@ -300,26 +296,33 @@ begin
function(const ANodeToEvaluate: IAstNode): TDataValue
var
subDescriptor: IScopeDescriptor;
evalScope: IExecutionScope;
evaluator: IEvaluatorVisitor;
boundSubAst: IAstNode;
begin
// This is the compile-time evaluation (Binder + Evaluator)
// It runs in the *current* context (FInitialScope + FCurrentDescriptor)
// not the expansionScope.
var tempInitScope := TScope.CreateScope(FInitialScope.Parent, FCurrentDescriptor, nil);
var boundSubAst := TAstBinder.Bind(tempInitScope, ANodeToEvaluate, subDescriptor);
var evalScope := subDescriptor.CreateScope(tempInitScope);
var evaluator := FEvaluatorFactory(evalScope);
// Bind (mutates ANodeToEvaluate) and get its descriptor
boundSubAst := TAstBinder.Bind(tempInitScope, ANodeToEvaluate, subDescriptor);
// Create eval scope from the new descriptor
evalScope := subDescriptor.CreateScope(tempInitScope);
evaluator := FEvaluatorFactory(evalScope);
Result := evaluator.Execute(boundSubAst);
end;
// 4. Expand the macro body using the TExpansionVisitor
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.Expression, evaluatorProc);
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsUnquoteSplicing.Expression, evaluatorProc);
// 5. Wrap the result in a MacroExpansionNode (for debugging/tracing)
var macroNode := TMacroExpansionNode.Create(Node, expandedBody);
var macroNode := TAst.MacroExpansionNode(Node, expandedBody);
// 6. IMPORTANT: Re-run the expander on the *new* AST fragment
// to handle macros that expand into other macros.
Result := Self.Accept(macroNode);
Result := Self.Accept(macroNode); // Calls the IAstNode helper, returns IAstNode
end;
end.