Resolved circular depependancy between macro expander and binder
This commit is contained in:
@@ -20,9 +20,14 @@ type
|
|||||||
function Execute(const RootNode: IAstNode): IAstNode;
|
function Execute(const RootNode: IAstNode): IAstNode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TEvaluateProc = reference to function(const Node: IAstNode): TDataValue;
|
// 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).
|
||||||
TExpansionVisitor = class(TAstTransformer)
|
TExpansionVisitor = class(TAstTransformer)
|
||||||
|
type
|
||||||
|
TEvaluateProc = reference to function(const Node: IAstNode): TDataValue;
|
||||||
private
|
private
|
||||||
FEvaluate: TEvaluateProc;
|
FEvaluate: TEvaluateProc;
|
||||||
FMacroScope: IExecutionScope;
|
FMacroScope: IExecutionScope;
|
||||||
@@ -49,11 +54,12 @@ type
|
|||||||
class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode;
|
class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Handles the expansion of macro calls within the AST.
|
||||||
TMacroExpander = class(TAstTransformer, IAstMacroExpander)
|
TMacroExpander = class(TAstTransformer, IAstMacroExpander)
|
||||||
private
|
private
|
||||||
FInitialScope: IExecutionScope;
|
FInitialScope: IExecutionScope;
|
||||||
FCurrentMacroRegistry: IMacroRegistry;
|
FCurrentMacroRegistry: IMacroRegistry;
|
||||||
FEvaluatorFactory: TEvaluatorFactory;
|
FMacroEvaluator: TMacroEvaluatorProc;
|
||||||
|
|
||||||
procedure EnterMacroScope;
|
procedure EnterMacroScope;
|
||||||
procedure ExitMacroScope;
|
procedure ExitMacroScope;
|
||||||
@@ -73,7 +79,7 @@ type
|
|||||||
constructor Create(
|
constructor Create(
|
||||||
const ARootRegistry: IMacroRegistry;
|
const ARootRegistry: IMacroRegistry;
|
||||||
const AInitialScope: IExecutionScope;
|
const AInitialScope: IExecutionScope;
|
||||||
const AEvaluatorFactory: TEvaluatorFactory
|
const AMacroEvaluator: TMacroEvaluatorProc
|
||||||
);
|
);
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
|
|
||||||
@@ -83,10 +89,11 @@ type
|
|||||||
const ARootRegistry: IMacroRegistry;
|
const ARootRegistry: IMacroRegistry;
|
||||||
const AInitialScope: IExecutionScope;
|
const AInitialScope: IExecutionScope;
|
||||||
const RootNode: IAstNode;
|
const RootNode: IAstNode;
|
||||||
const EvaluatorFactory: TEvaluatorFactory
|
const MacroEvaluator: TMacroEvaluatorProc
|
||||||
): IAstNode; static;
|
): IAstNode; static;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Implementation of the macro registry interface defined in Myc.Ast.Environment.
|
||||||
TMacroRegistry = class(TInterfacedObject, IMacroRegistry)
|
TMacroRegistry = class(TInterfacedObject, IMacroRegistry)
|
||||||
private
|
private
|
||||||
FParent: IMacroRegistry;
|
FParent: IMacroRegistry;
|
||||||
@@ -105,8 +112,6 @@ implementation
|
|||||||
uses
|
uses
|
||||||
System.Generics.Defaults,
|
System.Generics.Defaults,
|
||||||
System.SyncObjs,
|
System.SyncObjs,
|
||||||
Myc.Ast.Compiler.Binder,
|
|
||||||
Myc.Ast.Evaluator,
|
|
||||||
Myc.Data.Keyword;
|
Myc.Data.Keyword;
|
||||||
|
|
||||||
{ TExpansionVisitor }
|
{ TExpansionVisitor }
|
||||||
@@ -127,6 +132,8 @@ end;
|
|||||||
|
|
||||||
function TExpansionVisitor.Gensym(const ABaseName: string): string;
|
function TExpansionVisitor.Gensym(const ABaseName: string): string;
|
||||||
begin
|
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
|
if not FRenameMap.TryGetValue(ABaseName, Result) then
|
||||||
begin
|
begin
|
||||||
var count := TInterlocked.Add(FGensymCounter, 1);
|
var count := TInterlocked.Add(FGensymCounter, 1);
|
||||||
@@ -169,6 +176,7 @@ begin
|
|||||||
if (not evaluatedSpliceValue.IsVoid) and (evaluatedSpliceValue.Kind = vkInterface) then
|
if (not evaluatedSpliceValue.IsVoid) and (evaluatedSpliceValue.Kind = vkInterface) then
|
||||||
begin
|
begin
|
||||||
nodeToSplice := evaluatedSpliceValue.AsIntf<IAstNode>;
|
nodeToSplice := evaluatedSpliceValue.AsIntf<IAstNode>;
|
||||||
|
// If the result is a block, flatten it into the current list
|
||||||
if nodeToSplice.Kind = akBlockExpression then
|
if nodeToSplice.Kind = akBlockExpression then
|
||||||
newList.AddRange(nodeToSplice.AsBlockExpression.Expressions)
|
newList.AddRange(nodeToSplice.AsBlockExpression.Expressions)
|
||||||
else
|
else
|
||||||
@@ -176,6 +184,7 @@ begin
|
|||||||
end
|
end
|
||||||
else if (not evaluatedSpliceValue.IsVoid) then
|
else if (not evaluatedSpliceValue.IsVoid) then
|
||||||
begin
|
begin
|
||||||
|
// Treat scalar values as constants in the AST
|
||||||
newList.Add(TAst.Constant(evaluatedSpliceValue));
|
newList.Add(TAst.Constant(evaluatedSpliceValue));
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
@@ -196,6 +205,7 @@ function TExpansionVisitor.VisitIdentifier(const Node: IIdentifierNode): IAstNod
|
|||||||
var
|
var
|
||||||
newName: string;
|
newName: string;
|
||||||
begin
|
begin
|
||||||
|
// Apply hygienic renaming
|
||||||
if FRenameMap.TryGetValue(Node.Name, newName) then
|
if FRenameMap.TryGetValue(Node.Name, newName) then
|
||||||
begin
|
begin
|
||||||
Result := TAst.Identifier(newName, TTypes.Unknown);
|
Result := TAst.Identifier(newName, TTypes.Unknown);
|
||||||
@@ -219,7 +229,8 @@ begin
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
newTarget := Accept(Node.Target); // Recursively expand unquotes in target position!
|
// Recursively expand unquotes in target position (advanced usage)
|
||||||
|
newTarget := Accept(Node.Target);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
|
Result := TAst.VarDecl(newTarget, newInit, TTypes.Unknown);
|
||||||
@@ -232,6 +243,7 @@ var
|
|||||||
newName: string;
|
newName: string;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Parameters in a macro body must also be renamed hygienically
|
||||||
SetLength(newParams, Length(Node.Parameters));
|
SetLength(newParams, Length(Node.Parameters));
|
||||||
for i := 0 to High(Node.Parameters) do
|
for i := 0 to High(Node.Parameters) do
|
||||||
begin
|
begin
|
||||||
@@ -252,6 +264,8 @@ var
|
|||||||
begin
|
begin
|
||||||
expr := Node.Expression;
|
expr := Node.Expression;
|
||||||
|
|
||||||
|
// Optimization: If unquoting a simple identifier that exists in the macro scope,
|
||||||
|
// try to resolve it directly to avoid unnecessary evaluation overhead.
|
||||||
if expr.Kind = akIdentifier then
|
if expr.Kind = akIdentifier then
|
||||||
begin
|
begin
|
||||||
addr := FMacroScope.Resolve(expr.AsIdentifier.Name);
|
addr := FMacroScope.Resolve(expr.AsIdentifier.Name);
|
||||||
@@ -267,14 +281,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Evaluate the expression at compile time
|
||||||
value := FEvaluate(expr);
|
value := FEvaluate(expr);
|
||||||
|
|
||||||
if value.Kind = vkInterface then
|
if value.Kind = vkInterface then
|
||||||
begin
|
begin
|
||||||
|
// It returned an AST node -> Inject it
|
||||||
Result := value.AsIntf<IAstNode>;
|
Result := value.AsIntf<IAstNode>;
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// It returned a value -> Wrap as Constant
|
||||||
if value.Kind in [vkScalar, vkText, vkVoid] then
|
if value.Kind in [vkScalar, vkText, vkVoid] then
|
||||||
Result := TAst.Constant(value)
|
Result := TAst.Constant(value)
|
||||||
else
|
else
|
||||||
@@ -283,6 +300,7 @@ end;
|
|||||||
|
|
||||||
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
function TExpansionVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// ~@ can only be handled within a list context (TransformAndSpliceNodes)
|
||||||
raise Exception.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
|
raise Exception.Create('Unquote-splicing (`~@`) can only appear inside a list form.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -292,6 +310,7 @@ var
|
|||||||
transformedCallee: IAstNode;
|
transformedCallee: IAstNode;
|
||||||
begin
|
begin
|
||||||
transformedCallee := Self.Accept(Node.Callee);
|
transformedCallee := Self.Accept(Node.Callee);
|
||||||
|
// Use special splicing logic for argument lists
|
||||||
newArgs := TransformAndSpliceNodes(Node.Arguments);
|
newArgs := TransformAndSpliceNodes(Node.Arguments);
|
||||||
Result := TAst.FunctionCall(transformedCallee, newArgs);
|
Result := TAst.FunctionCall(transformedCallee, newArgs);
|
||||||
end;
|
end;
|
||||||
@@ -300,12 +319,14 @@ function TExpansionVisitor.VisitBlockExpression(const Node: IBlockExpressionNode
|
|||||||
var
|
var
|
||||||
newExprs: TArray<IAstNode>;
|
newExprs: TArray<IAstNode>;
|
||||||
begin
|
begin
|
||||||
|
// Use special splicing logic for block expressions
|
||||||
newExprs := TransformAndSpliceNodes(Node.Expressions);
|
newExprs := TransformAndSpliceNodes(Node.Expressions);
|
||||||
Result := TAst.Block(newExprs);
|
Result := TAst.Block(newExprs);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
function TExpansionVisitor.VisitRecordLiteral(const Node: IRecordLiteralNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Standard recursion for records
|
||||||
Result := inherited VisitRecordLiteral(Node);
|
Result := inherited VisitRecordLiteral(Node);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -314,12 +335,13 @@ end;
|
|||||||
constructor TMacroExpander.Create(
|
constructor TMacroExpander.Create(
|
||||||
const ARootRegistry: IMacroRegistry;
|
const ARootRegistry: IMacroRegistry;
|
||||||
const AInitialScope: IExecutionScope;
|
const AInitialScope: IExecutionScope;
|
||||||
const AEvaluatorFactory: TEvaluatorFactory
|
const AMacroEvaluator: TMacroEvaluatorProc
|
||||||
);
|
);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FInitialScope := AInitialScope;
|
FInitialScope := AInitialScope;
|
||||||
FEvaluatorFactory := AEvaluatorFactory;
|
FMacroEvaluator := AMacroEvaluator;
|
||||||
|
// Create a local child registry to handle scoped macro definitions (defmacro inside do)
|
||||||
FCurrentMacroRegistry := ARootRegistry.CreateChildRegistry;
|
FCurrentMacroRegistry := ARootRegistry.CreateChildRegistry;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -342,10 +364,10 @@ class function TMacroExpander.ExpandMacros(
|
|||||||
const ARootRegistry: IMacroRegistry;
|
const ARootRegistry: IMacroRegistry;
|
||||||
const AInitialScope: IExecutionScope;
|
const AInitialScope: IExecutionScope;
|
||||||
const RootNode: IAstNode;
|
const RootNode: IAstNode;
|
||||||
const EvaluatorFactory: TEvaluatorFactory
|
const MacroEvaluator: TMacroEvaluatorProc
|
||||||
): IAstNode;
|
): IAstNode;
|
||||||
begin
|
begin
|
||||||
var expander := TMacroExpander.Create(ARootRegistry, AInitialScope, EvaluatorFactory) as IAstMacroExpander;
|
var expander := TMacroExpander.Create(ARootRegistry, AInitialScope, MacroEvaluator) as IAstMacroExpander;
|
||||||
Result := expander.Execute(RootNode);
|
Result := expander.Execute(RootNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -358,6 +380,7 @@ end;
|
|||||||
|
|
||||||
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
function TMacroExpander.VisitBlockExpression(const Node: IBlockExpressionNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Macros defined inside a block should only be visible within that block
|
||||||
EnterMacroScope;
|
EnterMacroScope;
|
||||||
try
|
try
|
||||||
Result := inherited VisitBlockExpression(Node);
|
Result := inherited VisitBlockExpression(Node);
|
||||||
@@ -378,7 +401,11 @@ end;
|
|||||||
|
|
||||||
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
function TMacroExpander.VisitMacroDefinition(const Node: IMacroDefinitionNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Register the macro in the current scope
|
||||||
FCurrentMacroRegistry.Define(Node);
|
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;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -388,6 +415,7 @@ var
|
|||||||
macroDef: IMacroDefinitionNode;
|
macroDef: IMacroDefinitionNode;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Check if it is a macro call
|
||||||
if Node.Callee.Kind <> akIdentifier then
|
if Node.Callee.Kind <> akIdentifier then
|
||||||
exit(inherited VisitFunctionCall(Node));
|
exit(inherited VisitFunctionCall(Node));
|
||||||
|
|
||||||
@@ -396,62 +424,49 @@ begin
|
|||||||
if macroDef = nil then
|
if macroDef = nil then
|
||||||
exit(inherited VisitFunctionCall(Node));
|
exit(inherited VisitFunctionCall(Node));
|
||||||
|
|
||||||
// --- Macro Expansion ---
|
// --- Macro Expansion Logic ---
|
||||||
|
|
||||||
// 1. Create temporary scope for macro arguments (parented to Initial/Global Scope)
|
// 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 expansionScope := TScope.CreateScope(FInitialScope, nil, nil);
|
||||||
|
|
||||||
var params := macroDef.Parameters;
|
var params := macroDef.Parameters;
|
||||||
if Length(Node.Arguments) <> Length(params) then
|
if Length(Node.Arguments) <> Length(params) then
|
||||||
raise Exception.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]);
|
raise Exception.CreateFmt('Macro %s expects %d arguments.', [calleeIdentifier.Name, Length(params)]);
|
||||||
|
|
||||||
// 2. Populate scope
|
// 2. Populate scope with UN-EVALUATED AST nodes (Code as Data)
|
||||||
for i := 0 to High(params) do
|
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]));
|
||||||
|
|
||||||
// 3. Create Evaluator Callback
|
// 3. Create simple callback for TExpansionVisitor
|
||||||
var evaluatorProc: TEvaluateProc;
|
var evaluatorProc: TExpansionVisitor.TEvaluateProc :=
|
||||||
evaluatorProc :=
|
|
||||||
function(const ANodeToEvaluate: IAstNode): TDataValue
|
function(const ANodeToEvaluate: IAstNode): TDataValue
|
||||||
var
|
|
||||||
tmpLayout: IScopeLayout;
|
|
||||||
scratchScope: IExecutionScope;
|
|
||||||
evaluator: IEvaluatorVisitor;
|
|
||||||
boundSubAst: IAstNode;
|
|
||||||
begin
|
begin
|
||||||
// A. BINDING
|
// Delegate complex binding/execution logic to the environment via FMacroEvaluator
|
||||||
// The Binder creates a virtual child scope builder.
|
Result := FMacroEvaluator(expansionScope, ANodeToEvaluate);
|
||||||
// Symbols in expansionScope will have ScopeDepth = 1.
|
|
||||||
// We access the layout via the descriptor (which for dynamic scopes is created on demand/proxy).
|
|
||||||
// Note: expansionScope is dynamic, so its Descriptor.Layout reflects current variables.
|
|
||||||
boundSubAst := TAstBinder.Bind(expansionScope.Descriptor.Layout, ANodeToEvaluate, tmpLayout);
|
|
||||||
|
|
||||||
// B. SCOPE MATCHING (The Fix)
|
|
||||||
// We must execute in a child scope so that ScopeDepth = 1 correctly points to expansionScope.
|
|
||||||
scratchScope := TScope.CreateScope(expansionScope, nil, nil);
|
|
||||||
|
|
||||||
// C. EXECUTION
|
|
||||||
evaluator := FEvaluatorFactory(scratchScope);
|
|
||||||
Result := evaluator.Execute(boundSubAst);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// 4. Expand Body
|
// 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, evaluatorProc);
|
var expandedBody := TExpansionVisitor.Expand(expansionScope, macroDef.Body.AsQuasiquote.Expression, evaluatorProc);
|
||||||
|
|
||||||
// 5. Wrap result
|
// 5. Create a MacroExpansionNode to preserve source mapping
|
||||||
var macroNode := TAst.MacroExpansionNode(Node, expandedBody);
|
var macroNode := TAst.MacroExpansionNode(Node, expandedBody);
|
||||||
|
|
||||||
// 6. Re-expand result
|
// 6. Recursively expand the result (the output of a macro might contain more macro calls)
|
||||||
Result := Self.Accept(macroNode);
|
Result := Self.Accept(macroNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
function TMacroExpander.VisitQuasiquote(const Node: IQuasiquoteNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Quasiquotes in user code are not expanded by the MacroExpander,
|
||||||
|
// they are literals.
|
||||||
Result := Node;
|
Result := Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
function TMacroExpander.VisitUnquote(const Node: IUnquoteNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
|
// Unquotes outside of a macro definition/expansion phase are invalid syntax
|
||||||
raise Exception.Create('Unquote (`~`) can only be used inside a quasiquote.');
|
raise Exception.Create('Unquote (`~`) can only be used inside a quasiquote.');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -560,12 +560,36 @@ end;
|
|||||||
function TEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
function TEnvironment.ExpandMacros(const Node: IAstNode): IAstNode;
|
||||||
begin
|
begin
|
||||||
var cExecutionStrategy := FExecutionStrategy;
|
var cExecutionStrategy := FExecutionStrategy;
|
||||||
|
|
||||||
|
// The Glue: This callback performs the full compilation pipeline for a macro argument expression.
|
||||||
|
// It binds the node to the layout of the temporary macro scope and then executes it.
|
||||||
|
var macroEvaluator: TMacroEvaluatorProc :=
|
||||||
|
function(const Scope: IExecutionScope; const ANode: IAstNode): TDataValue
|
||||||
|
var
|
||||||
|
tmpLayout: IScopeLayout;
|
||||||
|
evaluator: IEvaluatorVisitor;
|
||||||
|
boundSubAst: IAstNode;
|
||||||
|
scratchScope: IExecutionScope;
|
||||||
|
begin
|
||||||
|
// 1. Binding
|
||||||
|
// Note: Scope is dynamic (from TMacroExpander), so Descriptor.Layout describes current variables.
|
||||||
|
boundSubAst := TAstBinder.Bind(Scope.Descriptor.Layout, ANode, tmpLayout);
|
||||||
|
|
||||||
|
// 2. Scope Matching
|
||||||
|
// Create a child scope to ensure correct depth resolution (Binder sees Layout at depth 0 relative to itself)
|
||||||
|
scratchScope := TScope.CreateScope(Scope, nil, nil);
|
||||||
|
|
||||||
|
// 3. Execution
|
||||||
|
evaluator := cExecutionStrategy.CreateVisitor(scratchScope);
|
||||||
|
Result := evaluator.Execute(boundSubAst);
|
||||||
|
end;
|
||||||
|
|
||||||
Result :=
|
Result :=
|
||||||
TMacroExpander.ExpandMacros(
|
TMacroExpander.ExpandMacros(
|
||||||
FMacroRegistry,
|
FMacroRegistry,
|
||||||
FRootScope,
|
FRootScope,
|
||||||
Node,
|
Node,
|
||||||
function(const Scope: IExecutionScope): IEvaluatorVisitor begin Result := cExecutionStrategy.CreateVisitor(Scope); end
|
macroEvaluator // Injected single dependency
|
||||||
);
|
);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user