Files
MycLib/Src/AST/Myc.Ast.Nodes.pas
T
Michael Schimmel 0b7a60e338 AST Identities
2025-11-25 18:45:47 +01:00

599 lines
20 KiB
ObjectPascal

unit Myc.Ast.Nodes;
interface
uses
System.SysUtils,
System.Generics.Collections,
Myc.Data.Scalar,
Myc.Data.Value,
Myc.Data.Keyword,
Myc.Ast.Scope,
Myc.Ast.Types;
type
// --- Forward Declarations ---
IAstNode = interface;
// --- Identity & Provenance ---
/// Represents the immutable identity or provenance of an AST node.
/// This allows tracking where a node came from (Parser, Generator, Macro, etc.)
/// without coupling the AST to specific source formats.
IAstIdentity = interface
['{5C496755-721D-4793-9526-7E2E63357719}']
function ToString: string;
end;
/// Specific identity for nodes originating from source code text.
ISourceLocation = interface(IAstIdentity)
['{98226687-F323-49B0-8D38-61803B225973}']
function GetLine: Integer;
function GetCol: Integer;
property Line: Integer read GetLine;
property Col: Integer read GetCol;
end;
// --- Error Handling Infrastructure ---
TCompilerErrorLevel = (elHint, elWarning, elError);
TCompilerError = record
Level: TCompilerErrorLevel;
Message: string;
Node: IAstNode; // Context node where the error occurred
constructor Create(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil);
function ToString: string;
end;
ICompilerLog = interface
['{8E5F2C10-4B3A-49F1-9C2D-7A8B5E6F4D3C}']
procedure Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil);
procedure AddError(const AMessage: string; const ANode: IAstNode = nil);
procedure AddWarning(const AMessage: string; const ANode: IAstNode = nil);
function HasErrors: Boolean;
function GetEntryCount: Integer;
function GetEntries: TArray<TCompilerError>;
property Entries: TArray<TCompilerError> read GetEntries;
end;
// Standard implementation of the log
TCompilerLog = class(TInterfacedObject, ICompilerLog)
private
FEntries: TList<TCompilerError>;
public
constructor Create;
destructor Destroy; override;
procedure Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode = nil);
procedure AddError(const AMessage: string; const ANode: IAstNode = nil);
procedure AddWarning(const AMessage: string; const ANode: IAstNode = nil);
function HasErrors: Boolean;
function GetEntryCount: Integer;
function GetEntries: TArray<TCompilerError>;
end;
// Base class for all Compiler related exceptions.
EAstException = class(Exception);
// The concrete exception thrown at the END of a compilation pass if errors occurred.
ECompilationFailed = class(EAstException)
private
FErrors: TArray<TCompilerError>;
public
constructor Create(const AErrors: TArray<TCompilerError>);
function ToString: string; override;
property Errors: TArray<TCompilerError> read FErrors;
end;
// --- AST Interfaces ---
IAstVisitor = interface;
IFunctionDefinition = interface;
IIdentifierNode = interface;
IConstantNode = interface;
IKeywordNode = interface;
IIfExpressionNode = interface;
ITernaryExpressionNode = interface;
ILambdaExpressionNode = interface;
IFunctionCallNode = interface;
IMacroExpansionNode = interface;
IBlockExpressionNode = interface;
IVariableDeclarationNode = interface;
IAssignmentNode = interface;
IMacroDefinitionNode = interface;
IQuasiquoteNode = interface;
IUnquoteNode = interface;
IUnquoteSplicingNode = interface;
IIndexerNode = interface;
IMemberAccessNode = interface;
IRecordLiteralNode = interface;
ICreateSeriesNode = interface;
IAddSeriesItemNode = interface;
ISeriesLengthNode = interface;
IRecurNode = interface;
INopNode = interface;
IAstTypedNode = interface;
// Defines the concrete kinds of AST nodes
TAstNodeKind = (
akConstant,
akIdentifier,
akKeyword,
akIfExpression,
akTernaryExpression,
akLambdaExpression,
akFunctionCall,
akMacroExpansion,
akBlockExpression,
akVariableDeclaration,
akAssignment,
akMacroDefinition,
akQuasiquote,
akUnquote,
akUnquoteSplicing,
akIndexer,
akMemberAccess,
akRecordLiteral,
akCreateSeries,
akAddSeriesItem,
akSeriesLength,
akRecur,
akNop
);
TAstNodeKindHelper = record helper for TAstNodeKind
public
function ToString: string;
end;
// --- Concrete Type Definitions ---
TRecordFieldLiteral = record
Key: IKeywordNode;
Value: IAstNode;
constructor Create(const AKey: IKeywordNode; const AValue: IAstNode);
end;
IAstVisitor = interface
function VisitConstant(const Node: IConstantNode): TDataValue;
function VisitIdentifier(const Node: IIdentifierNode): TDataValue;
function VisitKeyword(const Node: IKeywordNode): TDataValue;
function VisitIfExpression(const Node: IIfExpressionNode): TDataValue;
function VisitTernaryExpression(const Node: ITernaryExpressionNode): TDataValue;
function VisitLambdaExpression(const Node: ILambdaExpressionNode): TDataValue;
function VisitFunctionCall(const Node: IFunctionCallNode): TDataValue;
function VisitMacroExpansionNode(const Node: IMacroExpansionNode): TDataValue;
function VisitBlockExpression(const Node: IBlockExpressionNode): TDataValue;
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 VisitRecordLiteral(const Node: IRecordLiteralNode): TDataValue;
function VisitCreateSeries(const Node: ICreateSeriesNode): TDataValue;
function VisitAddSeriesItem(const Node: IAddSeriesItemNode): TDataValue;
function VisitSeriesLength(const Node: ISeriesLengthNode): TDataValue;
function VisitRecurNode(const Node: IRecurNode): TDataValue;
function VisitNop(const Node: INopNode): TDataValue;
end;
IAstNode = interface
{$region 'private'}
function GetKind: TAstNodeKind;
function GetIsTyped: Boolean;
function GetIdentity: IAstIdentity;
{$endregion}
function Accept(const Visitor: IAstVisitor): TDataValue;
function AsConstant: IConstantNode;
function AsIdentifier: IIdentifierNode;
function AsKeyword: IKeywordNode;
function AsIfExpression: IIfExpressionNode;
function AsTernaryExpression: ITernaryExpressionNode;
function AsLambdaExpression: ILambdaExpressionNode;
function AsFunctionCall: IFunctionCallNode;
function AsMacroExpansion: IMacroExpansionNode;
function AsBlockExpression: IBlockExpressionNode;
function AsVariableDeclaration: IVariableDeclarationNode;
function AsAssignment: IAssignmentNode;
function AsMacroDefinition: IMacroDefinitionNode;
function AsQuasiquote: IQuasiquoteNode;
function AsUnquote: IUnquoteNode;
function AsUnquoteSplicing: IUnquoteSplicingNode;
function AsIndexer: IIndexerNode;
function AsMemberAccess: IMemberAccessNode;
function AsRecordLiteral: IRecordLiteralNode;
function AsCreateSeries: ICreateSeriesNode;
function AsAddSeriesItem: IAddSeriesItemNode;
function AsSeriesLength: ISeriesLengthNode;
function AsRecur: IRecurNode;
function AsNop: INopNode;
function AsTypedNode: IAstTypedNode;
property IsTyped: Boolean read GetIsTyped;
property Kind: TAstNodeKind read GetKind;
property Identity: IAstIdentity read GetIdentity;
end;
IAstTypedNode = interface(IAstNode)
{$region 'private'}
function GetStaticType: IStaticType;
{$endregion}
property StaticType: IStaticType read GetStaticType;
end;
IFunctionDefinition = interface(IAstTypedNode)
{$region 'private'}
function GetBody: IAstNode;
function GetParameters: TArray<IIdentifierNode>;
function GetIsPure: Boolean;
{$endregion}
property Body: IAstNode read GetBody;
property Parameters: TArray<IIdentifierNode> read GetParameters;
property IsPure: Boolean read GetIsPure;
end;
INopNode = interface(IAstTypedNode)
end;
IConstantNode = interface(IAstTypedNode)
{$region 'private'}
function GetValue: TDataValue;
{$endregion}
property Value: TDataValue read GetValue;
end;
IIdentifierNode = interface(IAstTypedNode)
{$region 'private'}
function GetAddress: TResolvedAddress;
function GetName: string;
{$endregion}
property Address: TResolvedAddress read GetAddress;
property Name: string read GetName;
end;
IKeywordNode = interface(IAstTypedNode)
{$region 'private'}
function GetValue: IKeyword;
{$endregion}
property Value: IKeyword read GetValue;
end;
IIfExpressionNode = interface(IAstTypedNode)
{$region 'private'}
function GetCondition: IAstNode;
function GetThenBranch: IAstNode;
function GetElseBranch: IAstNode;
{$endregion}
property Condition: IAstNode read GetCondition;
property ThenBranch: IAstNode read GetThenBranch;
property ElseBranch: IAstNode read GetElseBranch;
end;
ITernaryExpressionNode = interface(IAstTypedNode)
{$region 'private'}
function GetCondition: IAstNode;
function GetThenBranch: IAstNode;
function GetElseBranch: IAstNode;
{$endregion}
property Condition: IAstNode read GetCondition;
property ThenBranch: IAstNode read GetThenBranch;
property ElseBranch: IAstNode read GetElseBranch;
end;
ILambdaExpressionNode = interface(IFunctionDefinition)
{$region 'private'}
function GetParameters: TArray<IIdentifierNode>;
function GetBody: IAstNode;
function GetLayout: IScopeLayout;
function GetDescriptor: IScopeDescriptor;
function GetUpvalues: TArray<TResolvedAddress>;
function GetHasNestedLambdas: Boolean;
{$endregion}
property Parameters: TArray<IIdentifierNode> read GetParameters;
property Body: IAstNode read GetBody;
property Layout: IScopeLayout read GetLayout;
property Descriptor: IScopeDescriptor read GetDescriptor;
property Upvalues: TArray<TResolvedAddress> read GetUpvalues;
property HasNestedLambdas: Boolean read GetHasNestedLambdas;
end;
IFunctionCallNode = interface(IAstTypedNode)
{$region 'private'}
function GetCallee: IAstNode;
function GetArguments: TArray<IAstNode>;
function GetIsTailCall: Boolean;
function GetStaticTarget: TDataValue.TFunc;
function GetIsTargetPure: Boolean;
{$endregion}
property Callee: IAstNode read GetCallee;
property Arguments: TArray<IAstNode> read GetArguments;
property IsTailCall: Boolean read GetIsTailCall;
property StaticTarget: TDataValue.TFunc read GetStaticTarget;
property IsTargetPure: Boolean read GetIsTargetPure;
end;
IMacroExpansionNode = interface(IAstTypedNode)
{$region 'private'}
function GetCallNode: IFunctionCallNode;
function GetExpandedBody: IAstNode;
{$endregion}
property CallNode: IFunctionCallNode read GetCallNode;
property ExpandedBody: IAstNode read GetExpandedBody;
end;
IRecurNode = interface(IAstTypedNode)
{$region 'private'}
function GetArguments: TArray<IAstNode>;
{$endregion}
property Arguments: TArray<IAstNode> read GetArguments;
end;
IBlockExpressionNode = interface(IAstTypedNode)
{$region 'private'}
function GetExpressions: TArray<IAstNode>;
{$endregion}
property Expressions: TArray<IAstNode> read GetExpressions;
end;
IVariableDeclarationNode = interface(IAstTypedNode)
{$region 'private'}
function GetTarget: IAstNode;
function GetInitializer: IAstNode;
function GetIsBoxed: Boolean;
{$endregion}
property Target: IAstNode read GetTarget;
property Initializer: IAstNode read GetInitializer;
property IsBoxed: Boolean read GetIsBoxed;
end;
IAssignmentNode = interface(IAstTypedNode)
{$region 'private'}
function GetTarget: IAstNode;
function GetValue: IAstNode;
{$endregion}
property Target: IAstNode read GetTarget;
property Value: IAstNode read GetValue;
end;
IMacroDefinitionNode = interface(IAstTypedNode)
{$region 'private'}
function GetName: IIdentifierNode;
function GetParameters: TArray<IIdentifierNode>;
function GetBody: IAstNode;
{$endregion}
property Name: IIdentifierNode read GetName;
property Parameters: TArray<IIdentifierNode> read GetParameters;
property Body: IAstNode read GetBody;
end;
IQuasiquoteNode = interface(IAstNode)
{$region 'private'}
function GetExpression: IAstNode;
{$endregion}
property Expression: IAstNode read GetExpression;
end;
IUnquoteNode = interface(IAstNode)
{$region 'private'}
function GetExpression: IAstNode;
{$endregion}
property Expression: IAstNode read GetExpression;
end;
IUnquoteSplicingNode = interface(IAstNode)
{$region 'private'}
function GetExpression: IQuasiquoteNode;
{$endregion}
property Expression: IQuasiquoteNode read GetExpression;
end;
IIndexerNode = interface(IAstTypedNode)
{$region 'private'}
function GetBase: IAstNode;
function GetIndex: IAstNode;
{$endregion}
property Base: IAstNode read GetBase;
property Index: IAstNode read GetIndex;
end;
IMemberAccessNode = interface(IAstTypedNode)
{$region 'private'}
function GetBase: IAstNode;
function GetMember: IKeywordNode;
{$endregion}
property Base: IAstNode read GetBase;
property Member: IKeywordNode read GetMember;
end;
IRecordLiteralNode = interface(IAstTypedNode)
{$region 'private'}
function GetFields: TArray<TRecordFieldLiteral>;
function GetGenericDefinition: IGenericRecordDefinition;
function GetScalarDefinition: IScalarRecordDefinition;
{$endregion}
property Fields: TArray<TRecordFieldLiteral> read GetFields;
property GenericDefinition: IGenericRecordDefinition read GetGenericDefinition;
property ScalarDefinition: IScalarRecordDefinition read GetScalarDefinition;
end;
ICreateSeriesNode = interface(IAstTypedNode)
{$region 'private'}
function GetDefinition: String;
{$endregion}
property Definition: String read GetDefinition;
end;
IAddSeriesItemNode = interface(IAstTypedNode)
{$region 'private'}
function GetSeries: IIdentifierNode;
function GetValue: IAstNode;
function GetLookback: IAstNode;
{$endregion}
property Series: IIdentifierNode read GetSeries;
property Value: IAstNode read GetValue;
property Lookback: IAstNode read GetLookback;
end;
ISeriesLengthNode = interface(IAstTypedNode)
{$region 'private'}
function GetSeries: IIdentifierNode;
{$endregion}
property Series: IIdentifierNode read GetSeries;
end;
IEvaluatorVisitor = interface(IAstVisitor)
function Execute(const RootNode: IAstNode): TDataValue;
end;
TEvaluatorFactory = reference to function(const Scope: IExecutionScope): IEvaluatorVisitor;
implementation
uses
System.Classes,
System.Rtti,
System.StrUtils;
{ TAstNodeKindHelper }
function TAstNodeKindHelper.ToString: string;
begin
Result := TRttiEnumerationType.GetName<TAstNodeKind>(Self);
Assert(StartsText('ak', Result));
Result := Result.Substring(2);
end;
{ TRecordFieldLiteral }
constructor TRecordFieldLiteral.Create(const AKey: IKeywordNode; const AValue: IAstNode);
begin
Key := AKey;
Value := AValue;
end;
{ TCompilerError }
constructor TCompilerError.Create(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode);
begin
Level := ALevel;
Message := AMessage;
Node := ANode;
end;
function TCompilerError.ToString: string;
var
location: string;
loc: ISourceLocation;
begin
// 1. Try to extract location from Node Identity
location := '';
if Assigned(Node) and Assigned(Node.Identity) then
begin
if Supports(Node.Identity, ISourceLocation, loc) then
location := Format('[Line %d, Col %d] ', [loc.Line, loc.Col])
else
location := Format('[%s] ', [Node.Identity.ToString]);
end;
// 2. Prefix with Level
case Level of
elHint: Result := '[Hint] ';
elWarning: Result := '[Warning] ';
elError: Result := '[Error] ';
end;
// 3. Assemble
Result := Result + location + Message;
end;
{ TCompilerLog }
constructor TCompilerLog.Create;
begin
inherited Create;
FEntries := TList<TCompilerError>.Create;
end;
destructor TCompilerLog.Destroy;
begin
FEntries.Free;
inherited;
end;
procedure TCompilerLog.Add(ALevel: TCompilerErrorLevel; const AMessage: string; const ANode: IAstNode);
begin
FEntries.Add(TCompilerError.Create(ALevel, AMessage, ANode));
end;
procedure TCompilerLog.AddError(const AMessage: string; const ANode: IAstNode);
begin
Add(elError, AMessage, ANode);
end;
procedure TCompilerLog.AddWarning(const AMessage: string; const ANode: IAstNode);
begin
Add(elWarning, AMessage, ANode);
end;
function TCompilerLog.HasErrors: Boolean;
var
entry: TCompilerError;
begin
Result := False;
for entry in FEntries do
if entry.Level = elError then
Exit(True);
end;
function TCompilerLog.GetEntryCount: Integer;
begin
Result := FEntries.Count;
end;
function TCompilerLog.GetEntries: TArray<TCompilerError>;
begin
Result := FEntries.ToArray;
end;
{ ECompilationFailed }
constructor ECompilationFailed.Create(const AErrors: TArray<TCompilerError>);
var
msg: string;
begin
if Length(AErrors) > 0 then
msg := Format('Compilation failed with %d error(s). First: %s', [Length(AErrors), AErrors[0].Message])
else
msg := 'Compilation failed with unspecified errors.';
inherited Create(msg);
FErrors := AErrors;
end;
function ECompilationFailed.ToString: string;
var
sb: TStringBuilder;
err: TCompilerError;
begin
sb := TStringBuilder.Create;
try
sb.AppendLine(Message);
for err in FErrors do
begin
sb.Append(' - ');
sb.AppendLine(err.ToString);
end;
Result := sb.ToString;
finally
sb.Free;
end;
end;
end.