Ast binding refactoring

This commit is contained in:
Michael Schimmel
2025-10-06 20:07:19 +02:00
parent 0edb9b800b
commit 039a7c4b3e
2 changed files with 192 additions and 44 deletions
+116
View File
@@ -199,5 +199,121 @@
]
}
}
},
"factorial": {
"NodeType": "LambdaExpr",
"Parameters": [
{
"NodeType": "Identifier",
"Name": "n"
}
],
"Body": {
"NodeType": "FunctionCall",
"Callee": {
"NodeType": "LambdaExpr",
"Parameters": [
{
"NodeType": "Identifier",
"Name": "n"
},
{
"NodeType": "Identifier",
"Name": "acc"
}
],
"Body": {
"NodeType": "TernaryExpr",
"Condition": {
"NodeType": "FunctionCall",
"Callee": {
"NodeType": "Identifier",
"Name": "<="
},
"Arguments": [
{
"NodeType": "Identifier",
"Name": "n"
},
{
"NodeType": "Constant",
"Value": {
"Kind": "Scalar",
"Value": {
"Kind": "Ordinal",
"Value": 1
}
}
}
]
},
"ThenBranch": {
"NodeType": "Identifier",
"Name": "acc"
},
"ElseBranch": {
"NodeType": "Recur",
"Arguments": [
{
"NodeType": "FunctionCall",
"Callee": {
"NodeType": "Identifier",
"Name": "-"
},
"Arguments": [
{
"NodeType": "Identifier",
"Name": "n"
},
{
"NodeType": "Constant",
"Value": {
"Kind": "Scalar",
"Value": {
"Kind": "Ordinal",
"Value": 1
}
}
}
]
},
{
"NodeType": "FunctionCall",
"Callee": {
"NodeType": "Identifier",
"Name": "*"
},
"Arguments": [
{
"NodeType": "Identifier",
"Name": "acc"
},
{
"NodeType": "Identifier",
"Name": "n"
}
]
}
]
}
}
},
"Arguments": [
{
"NodeType": "Identifier",
"Name": "n"
},
{
"NodeType": "Constant",
"Value": {
"Kind": "Scalar",
"Value": {
"Kind": "Ordinal",
"Value": 1
}
}
}
]
}
}
}
+76 -44
View File
@@ -21,10 +21,12 @@ type
TAstBinder = class; // Forward declaration
TEvaluateProc = reference to function(const Node: IAstNode): TDataValue;
// This visitor handles the expansion of a single macro body (` `...`).
TExpansionVisitor = class(TAstTransformer)
private
FBinder: TAstBinder;
FEvaluate: TEvaluateProc;
FMacroScope: IExecutionScope;
function TransformAndSpliceNodes(const ANodes: TArray<IAstNode>): TArray<IAstNode>;
protected
@@ -33,7 +35,8 @@ type
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue; override;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue; override;
public
constructor Create(const ABinder: TAstBinder; const AMacroScope: IExecutionScope);
constructor Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc);
class function Expand(const MacroScope: IExecutionScope; const RootNode: IAstNode; const AEvaluate: TEvaluateProc): IAstNode;
end;
TAstBinder = class(TAstTransformer, IAstBinder)
@@ -62,7 +65,6 @@ type
procedure EnterScope;
procedure ExitScope;
function IsValidIdentifier(const Name: string): Boolean;
function EvaluateAtCompileTime(const ANode: IAstNode): TDataValue;
protected
function Accept(const Node: IAstNode): TDataValue; override;
@@ -84,6 +86,13 @@ type
constructor Create(const AInitialScope: IExecutionScope; const AEvaluatorFactory: TEvaluatorFactory);
destructor Destroy; override;
function Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
class function Bind(
const InitialScope: IExecutionScope;
const RootNode: IAstNode;
out Descriptor: IScopeDescriptor;
const EvaluatorFactory: TEvaluatorFactory
): IAstNode; static;
end;
TBoundIdentifierNode = class(TIdentifierNode)
@@ -149,11 +158,21 @@ type
{ TExpansionVisitor }
constructor TExpansionVisitor.Create(const ABinder: TAstBinder; const AMacroScope: IExecutionScope);
constructor TExpansionVisitor.Create(const AMacroScope: IExecutionScope; const AEvaluate: TEvaluateProc);
begin
inherited Create;
FBinder := ABinder;
FMacroScope := AMacroScope;
FEvaluate := AEvaluate;
end;
class function TExpansionVisitor.Expand(
const MacroScope: IExecutionScope;
const RootNode: IAstNode;
const AEvaluate: TEvaluateProc
): IAstNode;
begin
var expander := TExpansionVisitor.Create(MacroScope, AEvaluate) as IAstTransformer;
Result := expander.Execute(RootNode);
end;
function TExpansionVisitor.TransformAndSpliceNodes(const ANodes: TArray<IAstNode>): TArray<IAstNode>;
@@ -216,7 +235,8 @@ begin
end;
end;
value := FBinder.EvaluateAtCompileTime(expr);
// externally evaluate the expression using the injected evaluator
value := FEvaluate(expr);
if value.Kind in [vkScalar, vkText, vkVoid] then
Result := TDataValue.FromIntf<IAstNode>(TAst.Constant(value))
@@ -370,28 +390,22 @@ begin
end;
end;
class function TAstBinder.Bind(
const InitialScope: IExecutionScope;
const RootNode: IAstNode;
out Descriptor: IScopeDescriptor;
const EvaluatorFactory: TEvaluatorFactory
): IAstNode;
begin
var binder := TAstBinder.Create(InitialScope, EvaluatorFactory) as IAstBinder;
Result := binder.Execute(RootNode, Descriptor);
end;
procedure TAstBinder.EnterScope;
begin
FCurrentDescriptor := TScope.CreateDescriptor(FCurrentDescriptor);
end;
function TAstBinder.EvaluateAtCompileTime(const ANode: IAstNode): TDataValue;
var
subBinder: IAstBinder;
subDescriptor: IScopeDescriptor;
boundSubAst: IAstNode;
evalScope: IExecutionScope;
evaluator: IEvaluatorVisitor;
tempInitScope: IExecutionScope;
begin
tempInitScope := TScope.CreateScope(FInitialScope.Parent, FCurrentDescriptor, nil);
subBinder := TAstBinder.Create(tempInitScope, FEvaluatorFactory);
boundSubAst := subBinder.Execute(ANode, subDescriptor);
evalScope := subDescriptor.CreateScope(tempInitScope);
evaluator := FEvaluatorFactory(evalScope);
Result := evaluator.Execute(boundSubAst);
end;
function TAstBinder.Execute(const RootNode: IAstNode; out Descriptor: IScopeDescriptor): IAstNode;
begin
FBoxedDeclarations := TUpvalueAnalyzer.Analyze(RootNode, FCurrentDescriptor.Parent);
@@ -425,15 +439,16 @@ var
unaryOp: TScalar.TUnaryOp;
macroDef: IMacroDefinitionNode;
begin
// --- Optimization: Operator Folding ---
if (Node.Callee is TIdentifierNode) then
begin
calleeIdentifier := Node.Callee as TIdentifierNode;
// --- Optimization: Operator Folding ---
// Try to fold binary operators
if FBinaryOperators.TryGetValue(calleeIdentifier.Name, binaryOp) then
if Length(Node.Arguments) = 2 then
begin
if Length(Node.Arguments) = 2 then
if FBinaryOperators.TryGetValue(calleeIdentifier.Name, binaryOp) then
begin
FNextIsTail := False;
var left := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
@@ -444,27 +459,28 @@ begin
end;
// Try to fold unary operators
if FUnaryOperators.TryGetValue(calleeIdentifier.Name, unaryOp) then
if Length(Node.Arguments) = 1 then
begin
if Length(Node.Arguments) = 1 then
if FUnaryOperators.TryGetValue(calleeIdentifier.Name, unaryOp) then
begin
FNextIsTail := False;
var right := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
Result := TDataValue.FromIntf<IAstNode>(TAst.UnaryExpr(unaryOp, right));
exit;
end;
end;
// Special case for negation '-'
if (calleeIdentifier.Name = '-') and (Length(Node.Arguments) = 1) then
begin
FNextIsTail := False;
var right := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
Result := TDataValue.FromIntf<IAstNode>(TAst.UnaryExpr(TScalar.TUnaryOp.Negate, right));
exit;
// Special case for negation '-'
if (calleeIdentifier.Name = '-') then
begin
FNextIsTail := False;
var right := Accept(Node.Arguments[0]).AsIntf<IAstNode>;
Result := TDataValue.FromIntf<IAstNode>(TAst.UnaryExpr(TScalar.TUnaryOp.Negate, right));
exit;
end;
end;
// --- Macro Expansion ---
macroDef := FCurrentDescriptor.FindMacro(calleeIdentifier.Name);
if macroDef <> nil then
begin
@@ -478,16 +494,32 @@ begin
for var i := 0 to High(params) do
expansionScope.Define(params[i].Name, TDataValue.FromIntf<IAstNode>(Node.Arguments[i]));
if not (macroDef.Body is TQuasiquoteNode) then
raise Exception.CreateFmt('Macro body for "%s" must be a quasiquoted expression.', [calleeIdentifier.Name]);
// expand
var expandedBody :=
TExpansionVisitor.Expand(
expansionScope,
macroDef.Body.Expression,
function(const Node: IAstNode): TDataValue
var
subDescriptor: IScopeDescriptor;
begin
// in place evaluator for expanded expressions
var tempInitScope := TScope.CreateScope(FInitialScope.Parent, FCurrentDescriptor, nil);
var boundSubAst := TAstBinder.Bind(tempInitScope, Node, subDescriptor, FEvaluatorFactory);
var evalScope := subDescriptor.CreateScope(tempInitScope);
var evaluator := FEvaluatorFactory(evalScope);
Result := evaluator.Execute(boundSubAst);
end
);
var quasiquoteBody := macroDef.Body as TQuasiquoteNode;
var expander := TExpansionVisitor.Create(Self, expansionScope);
var expandedBody := expander.Execute(quasiquoteBody.Expression);
// bind expanded body
var boundExpandedBody := Self.Accept(expandedBody).AsIntf<IAstNode>;
var macroNode := TMacroExpansionNode.Create(Node, boundExpandedBody);
Result := TDataValue.FromIntf<IMacroExpansionNode>(macroNode);
exit;
// wrap in new expansion node
var macroNode := TMacroExpansionNode.Create(Node, boundExpandedBody) as IMacroExpansionNode;
// done
exit(TDataValue.FromIntf<IMacroExpansionNode>(macroNode));
end;
end;