AST Identities
This commit is contained in:
File diff suppressed because one or more lines are too long
+100
-153
@@ -20,44 +20,32 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
// This BNF should always be kept up to date and valid. It is used to comunicate with LLMs.
|
// This BNF should always be kept up to date and valid.
|
||||||
// (Nop isn't part of the language syntax.)
|
|
||||||
BNF: String =
|
BNF: String =
|
||||||
'''
|
'''
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Main Productions (Start Symbols) ---- *)
|
(* ---- Main Productions (Start Symbols) ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
|
||||||
(* A program is a single expression, which can be followed by comments *)
|
|
||||||
program ::= expression
|
program ::= expression
|
||||||
|
|
||||||
(* An expression is either an atom, a list, a record, or a macro character *)
|
|
||||||
expression ::= atom | list | record_literal | reader_macro
|
expression ::= atom | list | record_literal | reader_macro
|
||||||
|
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Atoms (Basic Values) ---- *)
|
(* ---- Atoms (Basic Values) ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
|
||||||
atom ::= number | string | identifier | keyword
|
atom ::= number | string | identifier | keyword
|
||||||
|
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Reader-Macros (Syntactic Sugar) ---- *)
|
(* ---- Reader-Macros (Syntactic Sugar) ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
reader_macro ::= "'" expression (* (quote ...) *)
|
||||||
reader_macro ::= "'" expression (* (quote ...) *)
|
| "`" expression (* (quasiquote ...) *)
|
||||||
| "`" expression (* (quasiquote ...) *)
|
| "~" expression (* (unquote ...) *)
|
||||||
| "~" expression (* (unquote ...) *)
|
| "~@" expression (* (unquote-splicing ...) *)
|
||||||
| "~@" expression (* (unquote-splicing ...) *)
|
|
||||||
|
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Lists (S-Expressions) ---- *)
|
(* ---- Lists (S-Expressions) ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
|
||||||
(* A list is the primary structure for code. Empty lists () are invalid. *)
|
|
||||||
list ::= "(" list_content ")"
|
list ::= "(" list_content ")"
|
||||||
|
|
||||||
(* The parser distinguishes between special forms (like 'if') and
|
|
||||||
general function calls based on the first element. *)
|
|
||||||
list_content ::= special_form | function_call
|
list_content ::= special_form | function_call
|
||||||
|
|
||||||
special_form ::= "if" expression expression expression?
|
special_form ::= "if" expression expression expression?
|
||||||
@@ -71,35 +59,22 @@ var
|
|||||||
| "get" expression expression
|
| "get" expression expression
|
||||||
| dot_identifier expression
|
| dot_identifier expression
|
||||||
|
|
||||||
(* A function call is any list not recognized as a special form.
|
|
||||||
The first 'expression' is the callee, the rest are the arguments. *)
|
|
||||||
function_call ::= expression expression*
|
function_call ::= expression expression*
|
||||||
|
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Parameter Lists and Records ---- *)
|
(* ---- Parameter Lists and Records ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
|
||||||
(* A parameter list is *only* valid inside 'fn' and 'defmacro' *)
|
|
||||||
parameter_list ::= "[" identifier* "]"
|
parameter_list ::= "[" identifier* "]"
|
||||||
|
|
||||||
(* A record literal consists of 0 or more keyword/value pairs *)
|
|
||||||
record_literal ::= "{" (keyword expression)* "}"
|
record_literal ::= "{" (keyword expression)* "}"
|
||||||
|
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
(* ---- Terminals (Lexer Tokens) ---- *)
|
(* ---- Terminals (Lexer Tokens) ---- *)
|
||||||
(* ---------------------------------------------------------------------- *)
|
(* ---------------------------------------------------------------------- *)
|
||||||
|
|
||||||
number ::= ["-"] digit+ ["." digit+]
|
number ::= ["-"] digit+ ["." digit+]
|
||||||
string ::= '"' ( ? any char except \ or " ? | '\' ( '"' | '\' | 'n' | 'r' | 't' ) )* '"'
|
string ::= '"' ( ? any char except \ or " ? | '\' ( '"' | '\' | 'n' | 'r' | 't' ) )* '"'
|
||||||
keyword ::= ":" identifier
|
keyword ::= ":" identifier
|
||||||
|
|
||||||
(* An identifier for member access, handled specially by the parser *)
|
|
||||||
dot_identifier ::= "." identifier
|
dot_identifier ::= "." identifier
|
||||||
|
|
||||||
(* An identifier is a sequence of characters that are not delimiters.
|
|
||||||
The lexer is very permissive. *)
|
|
||||||
identifier ::= ? any sequence of chars not containing whitespace, '()[]{}'`~:; ?
|
identifier ::= ? any sequence of chars not containing whitespace, '()[]{}'`~:; ?
|
||||||
|
|
||||||
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||||
''';
|
''';
|
||||||
|
|
||||||
@@ -121,10 +96,10 @@ uses
|
|||||||
Myc.Data.Scalar,
|
Myc.Data.Scalar,
|
||||||
Myc.Data.Value,
|
Myc.Data.Value,
|
||||||
Myc.Data.Keyword,
|
Myc.Data.Keyword,
|
||||||
Myc.Ast;
|
Myc.Ast,
|
||||||
|
Myc.Ast.Identities;
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
||||||
// --- Internal Parser Implementation ---
|
// --- Internal Parser Implementation ---
|
||||||
|
|
||||||
TTokenKind = (
|
TTokenKind = (
|
||||||
@@ -155,6 +130,7 @@ type
|
|||||||
Text: string;
|
Text: string;
|
||||||
Line: Integer;
|
Line: Integer;
|
||||||
Col: Integer;
|
Col: Integer;
|
||||||
|
function GetLocation: ISourceLocation;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TLexer = class
|
TLexer = class
|
||||||
@@ -204,7 +180,6 @@ type
|
|||||||
|
|
||||||
// --- Internal Printer Implementation ---
|
// --- Internal Printer Implementation ---
|
||||||
|
|
||||||
// Inherits from the new non-generic TAstVisitor
|
|
||||||
TPrettyPrintVisitor = class(TAstVisitor)
|
TPrettyPrintVisitor = class(TAstVisitor)
|
||||||
private
|
private
|
||||||
FBuilder: TStringBuilder;
|
FBuilder: TStringBuilder;
|
||||||
@@ -217,9 +192,8 @@ type
|
|||||||
constructor Create;
|
constructor Create;
|
||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
function GetResult: string;
|
function GetResult: string;
|
||||||
// Helper
|
|
||||||
function Execute(const RootNode: IAstNode): TDataValue;
|
function Execute(const RootNode: IAstNode): TDataValue;
|
||||||
// Override abstract procedures from TAstVisitor
|
|
||||||
procedure VisitConstant(const Node: IConstantNode); override;
|
procedure VisitConstant(const Node: IConstantNode); override;
|
||||||
procedure VisitIdentifier(const Node: IIdentifierNode); override;
|
procedure VisitIdentifier(const Node: IIdentifierNode); override;
|
||||||
procedure VisitKeyword(const Node: IKeywordNode); override;
|
procedure VisitKeyword(const Node: IKeywordNode); override;
|
||||||
@@ -245,7 +219,41 @@ type
|
|||||||
procedure VisitNop(const Node: INopNode); override;
|
procedure VisitNop(const Node: INopNode); override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ EParserException }
|
// -----------------------------------------------------------------------------
|
||||||
|
// IMPLEMENTATION: Helpers & Basic Types
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function TTokenKindHelper.ToString: String;
|
||||||
|
begin
|
||||||
|
case Self of
|
||||||
|
tkLeftParen: Result := '(';
|
||||||
|
tkRightParen: Result := ')';
|
||||||
|
tkLeftBracket: Result := '[';
|
||||||
|
tkRightBracket: Result := ']';
|
||||||
|
tkLeftBrace: Result := '{';
|
||||||
|
tkRightBrace: Result := '}';
|
||||||
|
tkQuote: Result := '''';
|
||||||
|
tkBacktick: Result := '`';
|
||||||
|
tkTilde: Result := '~';
|
||||||
|
tkAt: Result := '@';
|
||||||
|
tkIdentifier: Result := '<identifier>';
|
||||||
|
tkKeyword: Result := '<keyword>';
|
||||||
|
tkNumber: Result := '<number>';
|
||||||
|
tkString: Result := '<string>';
|
||||||
|
tkEOF: Result := '<EOF>';
|
||||||
|
else
|
||||||
|
Result := '<error>';
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TToken.GetLocation: ISourceLocation;
|
||||||
|
begin
|
||||||
|
Result := TIdentities.Location(Line, Col);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// IMPLEMENTATION: Lexer
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
constructor EParserException.Create(const AMessage: string; ALine, ACol: Integer);
|
constructor EParserException.Create(const AMessage: string; ALine, ACol: Integer);
|
||||||
begin
|
begin
|
||||||
@@ -254,8 +262,6 @@ begin
|
|||||||
FCol := ACol;
|
FCol := ACol;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TLexer }
|
|
||||||
|
|
||||||
constructor TLexer.Create(const ASource: string);
|
constructor TLexer.Create(const ASource: string);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -320,8 +326,6 @@ var
|
|||||||
startPos: Integer;
|
startPos: Integer;
|
||||||
begin
|
begin
|
||||||
startPos := FCurrentPos;
|
startPos := FCurrentPos;
|
||||||
|
|
||||||
// Handle the optional leading minus sign
|
|
||||||
if (Peek = '-') then
|
if (Peek = '-') then
|
||||||
Advance;
|
Advance;
|
||||||
|
|
||||||
@@ -358,28 +362,23 @@ begin
|
|||||||
case c of
|
case c of
|
||||||
'"': builder.Append('"');
|
'"': builder.Append('"');
|
||||||
'\': builder.Append('\');
|
'\': builder.Append('\');
|
||||||
'n': builder.Append(sLineBreak); // Use system's line break
|
'n': builder.Append(sLineBreak);
|
||||||
't': builder.Append(#9);
|
't': builder.Append(#9);
|
||||||
'r': builder.Append(#13);
|
'r': builder.Append(#13);
|
||||||
else
|
else
|
||||||
// Pass through unknown escapes (e.g., "\z" becomes "z")
|
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
end;
|
end;
|
||||||
Advance; // Consume escaped char
|
Advance; // Consume escaped char
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
// Append regular char (including literal newlines)
|
if c = #0 then
|
||||||
if c = #0 then // Check for unexpected EOF
|
|
||||||
break;
|
break;
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
Advance;
|
Advance;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// If we fall out of the loop, we hit EOF without a closing quote.
|
|
||||||
Error('Syntax Error: Unterminated string literal.');
|
Error('Syntax Error: Unterminated string literal.');
|
||||||
|
|
||||||
finally
|
finally
|
||||||
builder.Free;
|
builder.Free;
|
||||||
end;
|
end;
|
||||||
@@ -387,7 +386,6 @@ end;
|
|||||||
|
|
||||||
function TLexer.GetNextToken: TToken;
|
function TLexer.GetNextToken: TToken;
|
||||||
begin
|
begin
|
||||||
// Skip whitespace and comments
|
|
||||||
while FCurrentPos <= Length(FSource) do
|
while FCurrentPos <= Length(FSource) do
|
||||||
begin
|
begin
|
||||||
if FSource[FCurrentPos].IsWhiteSpace then
|
if FSource[FCurrentPos].IsWhiteSpace then
|
||||||
@@ -398,17 +396,13 @@ begin
|
|||||||
|
|
||||||
if FSource[FCurrentPos] = ';' then
|
if FSource[FCurrentPos] = ';' then
|
||||||
begin
|
begin
|
||||||
// Comment found, skip to the end of the line
|
|
||||||
while (FCurrentPos <= Length(FSource)) and (not CharInSet(FSource[FCurrentPos], [#10, #13])) do
|
while (FCurrentPos <= Length(FSource)) and (not CharInSet(FSource[FCurrentPos], [#10, #13])) do
|
||||||
Advance;
|
Advance;
|
||||||
continue;
|
continue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// No whitespace or comment, so break out and process the token
|
|
||||||
break;
|
break;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Capture location for the start of the token
|
|
||||||
Result.Line := CurrentLine;
|
Result.Line := CurrentLine;
|
||||||
Result.Col := CurrentCol;
|
Result.Col := CurrentCol;
|
||||||
|
|
||||||
@@ -477,7 +471,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
':':
|
':':
|
||||||
begin
|
begin
|
||||||
Advance; // Skip :
|
Advance;
|
||||||
Result.Kind := tkKeyword;
|
Result.Kind := tkKeyword;
|
||||||
Result.Text := ReadIdentifier;
|
Result.Text := ReadIdentifier;
|
||||||
if Result.Text.IsEmpty then
|
if Result.Text.IsEmpty then
|
||||||
@@ -497,13 +491,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TParser }
|
// -----------------------------------------------------------------------------
|
||||||
|
// IMPLEMENTATION: Parser
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
constructor TParser.Create(const ASource: string);
|
constructor TParser.Create(const ASource: string);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
FLexer := TLexer.Create(ASource);
|
FLexer := TLexer.Create(ASource);
|
||||||
NextToken; // Load the first token
|
NextToken;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TParser.Destroy;
|
destructor TParser.Destroy;
|
||||||
@@ -547,7 +543,9 @@ begin
|
|||||||
Error('Syntax Error: Unexpected end of file.');
|
Error('Syntax Error: Unexpected end of file.');
|
||||||
if FCurrentToken.Kind <> tkIdentifier then
|
if FCurrentToken.Kind <> tkIdentifier then
|
||||||
Error('Syntax Error: Expected identifier in parameter list.');
|
Error('Syntax Error: Expected identifier in parameter list.');
|
||||||
params.Add(TAst.Identifier(FCurrentToken.Text));
|
|
||||||
|
// Pass Location to Factory
|
||||||
|
params.Add(TAst.Identifier(FCurrentToken.Text, FCurrentToken.GetLocation));
|
||||||
NextToken;
|
NextToken;
|
||||||
end;
|
end;
|
||||||
Result := params.ToArray;
|
Result := params.ToArray;
|
||||||
@@ -562,7 +560,9 @@ var
|
|||||||
fields: TList<TRecordFieldLiteral>;
|
fields: TList<TRecordFieldLiteral>;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
fieldValue: IAstNode;
|
fieldValue: IAstNode;
|
||||||
|
keyToken: TToken;
|
||||||
begin
|
begin
|
||||||
|
var startLoc := FCurrentToken.GetLocation;
|
||||||
Consume(tkLeftBrace);
|
Consume(tkLeftBrace);
|
||||||
fields := TList<TRecordFieldLiteral>.Create;
|
fields := TList<TRecordFieldLiteral>.Create;
|
||||||
try
|
try
|
||||||
@@ -571,21 +571,21 @@ begin
|
|||||||
if FCurrentToken.Kind = tkEOF then
|
if FCurrentToken.Kind = tkEOF then
|
||||||
Error('Syntax Error: Unexpected end of file in record literal.');
|
Error('Syntax Error: Unexpected end of file in record literal.');
|
||||||
|
|
||||||
// Key must be a keyword
|
|
||||||
if FCurrentToken.Kind <> tkKeyword then
|
if FCurrentToken.Kind <> tkKeyword then
|
||||||
Error('Syntax Error: Expected keyword (e.g., :key) as field name in record literal.');
|
Error('Syntax Error: Expected keyword (e.g., :key) as field name in record literal.');
|
||||||
|
|
||||||
|
keyToken := FCurrentToken;
|
||||||
fieldName := FCurrentToken.Text;
|
fieldName := FCurrentToken.Text;
|
||||||
NextToken;
|
NextToken;
|
||||||
|
|
||||||
// Value can be any expression
|
|
||||||
if FCurrentToken.Kind = tkRightBrace then
|
if FCurrentToken.Kind = tkRightBrace then
|
||||||
Error('Syntax Error: Missing value for key ' + fieldName + ' in record literal.');
|
Error('Syntax Error: Missing value for key ' + fieldName + ' in record literal.');
|
||||||
fieldValue := ParseExpression.Node;
|
fieldValue := ParseExpression.Node;
|
||||||
|
|
||||||
fields.Add(TRecordFieldLiteral.Create(TAst.Keyword(fieldName), fieldValue));
|
fields.Add(TRecordFieldLiteral.Create(TAst.Keyword(fieldName, keyToken.GetLocation), fieldValue));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Result := TAst.RecordLiteral(fields.ToArray);
|
Result := TAst.RecordLiteral(fields.ToArray, startLoc);
|
||||||
finally
|
finally
|
||||||
fields.Free;
|
fields.Free;
|
||||||
end;
|
end;
|
||||||
@@ -596,10 +596,11 @@ function TParser.ParseList: IAstNode;
|
|||||||
var
|
var
|
||||||
elements: TList<TExpr>;
|
elements: TList<TExpr>;
|
||||||
head: TExpr;
|
head: TExpr;
|
||||||
tailTokens: TArray<TToken>;
|
|
||||||
tailNodes: TArray<IAstNode>;
|
tailNodes: TArray<IAstNode>;
|
||||||
initializer: IAstNode;
|
initializer: IAstNode;
|
||||||
|
startLoc: ISourceLocation;
|
||||||
begin
|
begin
|
||||||
|
startLoc := FCurrentToken.GetLocation;
|
||||||
Consume(tkLeftParen);
|
Consume(tkLeftParen);
|
||||||
if FCurrentToken.Kind = tkRightParen then
|
if FCurrentToken.Kind = tkRightParen then
|
||||||
Error('Syntax Error: Empty list () is not a valid expression.');
|
Error('Syntax Error: Empty list () is not a valid expression.');
|
||||||
@@ -615,85 +616,70 @@ begin
|
|||||||
|
|
||||||
head := elements[0];
|
head := elements[0];
|
||||||
|
|
||||||
SetLength(tailTokens, elements.Count - 1);
|
|
||||||
SetLength(tailNodes, elements.Count - 1);
|
SetLength(tailNodes, elements.Count - 1);
|
||||||
for var i := 0 to High(tailNodes) do
|
for var i := 0 to High(tailNodes) do
|
||||||
begin
|
|
||||||
tailTokens[i] := elements[i + 1].Token;
|
|
||||||
tailNodes[i] := elements[i + 1].Node;
|
tailNodes[i] := elements[i + 1].Node;
|
||||||
end;
|
|
||||||
|
|
||||||
if head.Token.Kind = tkIdentifier then
|
if head.Token.Kind = tkIdentifier then
|
||||||
begin
|
begin
|
||||||
// Handle special forms
|
|
||||||
if SameText(head.Token.Text, 'if') then
|
if SameText(head.Token.Text, 'if') then
|
||||||
begin
|
begin
|
||||||
if not (Length(tailNodes) in [2, 3]) then
|
if not (Length(tailNodes) in [2, 3]) then
|
||||||
Error('Syntax Error in if statement.');
|
Error('Syntax Error in if statement.');
|
||||||
|
|
||||||
var elseBranch: IAstNode := nil;
|
var elseBranch: IAstNode := nil;
|
||||||
if Length(tailNodes) = 3 then
|
if Length(tailNodes) = 3 then
|
||||||
elseBranch := tailNodes[2];
|
elseBranch := tailNodes[2];
|
||||||
|
Result := TAst.IfExpr(tailNodes[0], tailNodes[1], elseBranch, startLoc);
|
||||||
Result := TAst.IfExpr(tailNodes[0], tailNodes[1], elseBranch);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, '?') then
|
else if SameText(head.Token.Text, '?') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 3 then
|
if Length(tailNodes) <> 3 then
|
||||||
Error('Syntax Error in ternary statement.');
|
Error('Syntax Error in ternary statement.');
|
||||||
|
Result := TAst.TernaryExpr(tailNodes[0], tailNodes[1], tailNodes[2], startLoc);
|
||||||
Result := TAst.TernaryExpr(tailNodes[0], tailNodes[1], tailNodes[2]);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'def') then
|
else if SameText(head.Token.Text, 'def') then
|
||||||
begin
|
begin
|
||||||
// Identifier check removed to support macros (e.g. def ~x 1)
|
|
||||||
if not (Length(tailNodes) in [1, 2]) then
|
if not (Length(tailNodes) in [1, 2]) then
|
||||||
Error('Syntax Error: ''def'' requires a target and an optional initializer.');
|
Error('Syntax Error: ''def'' requires a target and an optional initializer.');
|
||||||
|
|
||||||
initializer := nil;
|
initializer := nil;
|
||||||
if Length(tailNodes) = 2 then
|
if Length(tailNodes) = 2 then
|
||||||
initializer := tailNodes[1];
|
initializer := tailNodes[1];
|
||||||
|
Result := TAst.VarDecl(tailNodes[0], initializer, startLoc);
|
||||||
Result := TAst.VarDecl(tailNodes[0], initializer);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'defmacro') then
|
else if SameText(head.Token.Text, 'defmacro') then
|
||||||
begin
|
begin
|
||||||
if (Length(tailNodes) <> 3) or (tailTokens[0].Kind <> tkIdentifier) then
|
if (Length(tailNodes) <> 3) or (elements[1].Token.Kind <> tkIdentifier) then
|
||||||
Error('Syntax Error: ''defmacro'' requires a name, a parameter list, and a body.');
|
Error('Syntax Error: ''defmacro'' requires a name, a parameter list, and a body.');
|
||||||
|
|
||||||
var macroName := tailNodes[0].AsIdentifier;
|
var macroName := tailNodes[0].AsIdentifier;
|
||||||
var macroParams := elements[2].Params;
|
var macroParams := elements[2].Params;
|
||||||
|
|
||||||
if tailTokens[2].Kind <> tkBacktick then
|
if tailNodes[2].Kind <> akQuasiquote then
|
||||||
Error('Syntax Error: Expected a quasiquote as macro body.');
|
Error('Syntax Error: Expected a quasiquote as macro body.');
|
||||||
|
|
||||||
var macroBody := IQuasiQuoteNode(tailNodes[2]);
|
Result := TAst.MacroDef(macroName, macroParams, tailNodes[2], startLoc);
|
||||||
|
|
||||||
Result := TAst.MacroDef(macroName, macroParams, macroBody);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'assign') then
|
else if SameText(head.Token.Text, 'assign') then
|
||||||
begin
|
begin
|
||||||
// Identifier check removed
|
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''assign'' requires exactly 2 arguments (target and value).');
|
Error('Syntax Error: ''assign'' requires exactly 2 arguments (target and value).');
|
||||||
|
Result := TAst.Assign(tailNodes[0], tailNodes[1], startLoc);
|
||||||
Result := TAst.Assign(tailNodes[0], tailNodes[1]);
|
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'fn') then
|
else if SameText(head.Token.Text, 'fn') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''fn'' requires a parameter list and a body.');
|
Error('Syntax Error: ''fn'' requires a parameter list and a body.');
|
||||||
Result := TAst.LambdaExpr(elements[1].Params, tailNodes[1]);
|
Result := TAst.LambdaExpr(elements[1].Params, tailNodes[1], startLoc);
|
||||||
end
|
end
|
||||||
else if SameText(head.Token.Text, 'do') then
|
else if SameText(head.Token.Text, 'do') then
|
||||||
Result := TAst.Block(tailNodes)
|
Result := TAst.Block(tailNodes, startLoc)
|
||||||
else if SameText(head.Token.Text, 'recur') then
|
else if SameText(head.Token.Text, 'recur') then
|
||||||
Result := TAst.Recur(tailNodes)
|
Result := TAst.Recur(tailNodes, startLoc)
|
||||||
else if SameText(head.Token.Text, 'get') then
|
else if SameText(head.Token.Text, 'get') then
|
||||||
begin
|
begin
|
||||||
if Length(tailNodes) <> 2 then
|
if Length(tailNodes) <> 2 then
|
||||||
Error('Syntax Error: ''get'' requires exactly 2 arguments (base and index).');
|
Error('Syntax Error: ''get'' requires exactly 2 arguments (base and index).');
|
||||||
Result := TAst.Indexer(tailNodes[0], tailNodes[1])
|
Result := TAst.Indexer(tailNodes[0], tailNodes[1], startLoc)
|
||||||
end
|
end
|
||||||
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
|
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
|
||||||
begin
|
begin
|
||||||
@@ -702,13 +688,12 @@ begin
|
|||||||
'Syntax Error: Member access (e.g., ''%s'') requires exactly 1 argument (the base object).',
|
'Syntax Error: Member access (e.g., ''%s'') requires exactly 1 argument (the base object).',
|
||||||
[head.Token.Text]
|
[head.Token.Text]
|
||||||
);
|
);
|
||||||
Result := TAst.MemberAccess(tailNodes[0], TAst.Keyword(head.Token.Text.Substring(1)));
|
Result := TAst.MemberAccess(tailNodes[0], TAst.Keyword(head.Token.Text.Substring(1), head.Token.GetLocation), startLoc);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// If no special form matched, treat it as a generic function call.
|
|
||||||
if Result = nil then
|
if Result = nil then
|
||||||
Result := TAst.FunctionCall(head.Node, tailNodes);
|
Result := TAst.FunctionCall(head.Node, tailNodes, startLoc);
|
||||||
|
|
||||||
finally
|
finally
|
||||||
elements.Free;
|
elements.Free;
|
||||||
@@ -722,15 +707,17 @@ var
|
|||||||
i64: Int64;
|
i64: Int64;
|
||||||
dbl: Double;
|
dbl: Double;
|
||||||
expr: TExpr;
|
expr: TExpr;
|
||||||
|
startLoc: ISourceLocation;
|
||||||
begin
|
begin
|
||||||
// Handle reader macros.
|
|
||||||
Result.Token := FCurrentToken;
|
Result.Token := FCurrentToken;
|
||||||
|
startLoc := FCurrentToken.GetLocation;
|
||||||
|
|
||||||
case FCurrentToken.Kind of
|
case FCurrentToken.Kind of
|
||||||
tkBacktick:
|
tkBacktick:
|
||||||
begin
|
begin
|
||||||
NextToken;
|
NextToken;
|
||||||
expr := ParseExpression;
|
expr := ParseExpression;
|
||||||
Result.Node := TAst.Quasiquote(expr.Node);
|
Result.Node := TAst.Quasiquote(expr.Node, startLoc);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
tkTilde:
|
tkTilde:
|
||||||
@@ -740,12 +727,12 @@ begin
|
|||||||
begin
|
begin
|
||||||
NextToken;
|
NextToken;
|
||||||
expr := ParseExpression;
|
expr := ParseExpression;
|
||||||
Result.Node := TAst.UnquoteSplicing(expr.Node.AsQuasiquote);
|
Result.Node := TAst.UnquoteSplicing(expr.Node.AsQuasiquote, startLoc);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
expr := ParseExpression;
|
expr := ParseExpression;
|
||||||
Result.Node := TAst.Unquote(expr.Node);
|
Result.Node := TAst.Unquote(expr.Node, startLoc);
|
||||||
end;
|
end;
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
@@ -753,7 +740,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
NextToken;
|
NextToken;
|
||||||
expr := ParseExpression;
|
expr := ParseExpression;
|
||||||
Result.Node := TAst.FunctionCall(TAst.Identifier('quote'), [expr.Node]);
|
Result.Node := TAst.FunctionCall(TAst.Identifier('quote', startLoc), [expr.Node], startLoc);
|
||||||
exit;
|
exit;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -763,45 +750,29 @@ begin
|
|||||||
tkNumber:
|
tkNumber:
|
||||||
begin
|
begin
|
||||||
if TryStrToInt64(FCurrentToken.Text, i64) then
|
if TryStrToInt64(FCurrentToken.Text, i64) then
|
||||||
Result.Node :=
|
Result.Node := TAst.Constant(i64, startLoc)
|
||||||
TAst.Constant(i64)
|
|
||||||
// Use invariant format settings for float parsing.
|
|
||||||
else if TryStrToFloat(FCurrentToken.Text, dbl, TFormatSettings.Invariant) then
|
else if TryStrToFloat(FCurrentToken.Text, dbl, TFormatSettings.Invariant) then
|
||||||
Result.Node := TAst.Constant(dbl)
|
Result.Node := TAst.Constant(dbl, startLoc)
|
||||||
else
|
else
|
||||||
ErrorFmt('Syntax Error: Invalid number format "%s"', [FCurrentToken.Text]);
|
ErrorFmt('Syntax Error: Invalid number format "%s"', [FCurrentToken.Text]);
|
||||||
NextToken;
|
NextToken;
|
||||||
end;
|
end;
|
||||||
tkString:
|
tkString:
|
||||||
begin
|
begin
|
||||||
Result.Node := TAst.Constant(FCurrentToken.Text);
|
Result.Node := TAst.Constant(FCurrentToken.Text, startLoc);
|
||||||
NextToken;
|
NextToken;
|
||||||
end;
|
end;
|
||||||
tkKeyword:
|
tkKeyword:
|
||||||
begin
|
begin
|
||||||
Result.Node := TAst.Keyword(FCurrentToken.Text);
|
Result.Node := TAst.Keyword(FCurrentToken.Text, startLoc);
|
||||||
NextToken;
|
NextToken;
|
||||||
end;
|
end;
|
||||||
tkIdentifier:
|
tkIdentifier:
|
||||||
begin
|
begin
|
||||||
// Check for the NOP token ('...')
|
|
||||||
if FCurrentToken.Text = '...' then
|
if FCurrentToken.Text = '...' then
|
||||||
begin
|
Result.Node := TAst.Nop(startLoc)
|
||||||
// The token '...' represents the Nop node, a non-compilable placeholder used exclusively
|
|
||||||
// for visual representations (e.g., UI drag targets).
|
|
||||||
//
|
|
||||||
// We handle it as a special case of tkIdentifier instead of introducing a dedicated tkNop
|
|
||||||
// TokenKind because of its **unofficial and transient status**.
|
|
||||||
//
|
|
||||||
// This allows the Parser to immediately apply the syntactic policy: mapping this
|
|
||||||
// specific identifier string directly to the non-standard TAst.Nop node, ensuring
|
|
||||||
// it bypasses the regular identifier creation and maintains its "placeholder" identity.
|
|
||||||
Result.Node := TAst.Nop;
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
begin
|
Result.Node := TAst.Identifier(FCurrentToken.Text, startLoc);
|
||||||
Result.Node := TAst.Identifier(FCurrentToken.Text);
|
|
||||||
end;
|
|
||||||
NextToken;
|
NextToken;
|
||||||
end;
|
end;
|
||||||
tkLeftParen: Result.Node := ParseList;
|
tkLeftParen: Result.Node := ParseList;
|
||||||
@@ -821,7 +792,10 @@ begin
|
|||||||
Result := expr.Node;
|
Result := expr.Node;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TPrettyPrintVisitor }
|
// -----------------------------------------------------------------------------
|
||||||
|
// IMPLEMENTATION: PrettyPrintVisitor
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
constructor TPrettyPrintVisitor.Create;
|
constructor TPrettyPrintVisitor.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -929,7 +903,7 @@ begin
|
|||||||
for var param in Node.Parameters do
|
for var param in Node.Parameters do
|
||||||
sb.Append(param.Name + ' ');
|
sb.Append(param.Name + ' ');
|
||||||
if sb.Length > 0 then
|
if sb.Length > 0 then
|
||||||
sb.Remove(sb.Length - 1, 1); // remove trailing space
|
sb.Remove(sb.Length - 1, 1);
|
||||||
|
|
||||||
Append('(fn [' + sb.ToString + ']');
|
Append('(fn [' + sb.ToString + ']');
|
||||||
finally
|
finally
|
||||||
@@ -982,8 +956,6 @@ end;
|
|||||||
|
|
||||||
procedure TPrettyPrintVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode);
|
procedure TPrettyPrintVisitor.VisitUnquoteSplicing(const Node: IUnquoteSplicingNode);
|
||||||
begin
|
begin
|
||||||
// To handle '~@', we need to check if the expression is an identifier '@'.
|
|
||||||
// However, the parser logic now handles this, so we just print '~@'.
|
|
||||||
Append('~@');
|
Append('~@');
|
||||||
Node.Expression.Accept(Self);
|
Node.Expression.Accept(Self);
|
||||||
end;
|
end;
|
||||||
@@ -992,7 +964,6 @@ procedure TPrettyPrintVisitor.VisitFunctionCall(const Node: IFunctionCallNode);
|
|||||||
var
|
var
|
||||||
arg: IAstNode;
|
arg: IAstNode;
|
||||||
begin
|
begin
|
||||||
// Special case for (quote ...) to print it as '...
|
|
||||||
if (Node.Callee.Kind = akIdentifier) and (Node.Callee.AsIdentifier.Name = 'quote') and (Length(Node.Arguments) = 1) then
|
if (Node.Callee.Kind = akIdentifier) and (Node.Callee.AsIdentifier.Name = 'quote') and (Length(Node.Arguments) = 1) then
|
||||||
begin
|
begin
|
||||||
Append('''');
|
Append('''');
|
||||||
@@ -1016,8 +987,6 @@ end;
|
|||||||
|
|
||||||
procedure TPrettyPrintVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode);
|
procedure TPrettyPrintVisitor.VisitMacroExpansionNode(const Node: IMacroExpansionNode);
|
||||||
begin
|
begin
|
||||||
// For pretty-printing, show the original macro call, not the expanded body.
|
|
||||||
// Delegate to the regular VisitFunctionCall to print it as such.
|
|
||||||
VisitFunctionCall(Node.CallNode);
|
VisitFunctionCall(Node.CallNode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1137,7 +1106,6 @@ end;
|
|||||||
|
|
||||||
procedure TPrettyPrintVisitor.VisitNop(const Node: INopNode);
|
procedure TPrettyPrintVisitor.VisitNop(const Node: INopNode);
|
||||||
begin
|
begin
|
||||||
// A placeholder node, usually for UI.
|
|
||||||
Append('...');
|
Append('...');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1148,7 +1116,9 @@ begin
|
|||||||
Append(')');
|
Append(')');
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TAstScript }
|
// -----------------------------------------------------------------------------
|
||||||
|
// IMPLEMENTATION: TAstScript Facade
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
class function TAstScript.Parse(const ASource: string): IAstNode;
|
class function TAstScript.Parse(const ASource: string): IAstNode;
|
||||||
var
|
var
|
||||||
@@ -1179,27 +1149,4 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TTokenKindHelper.ToString: String;
|
|
||||||
begin
|
|
||||||
case Self of
|
|
||||||
tkLeftParen: Result := '(';
|
|
||||||
tkRightParen: Result := ')';
|
|
||||||
tkLeftBracket: Result := '[';
|
|
||||||
tkRightBracket: Result := ']';
|
|
||||||
tkLeftBrace: Result := '{';
|
|
||||||
tkRightBrace: Result := '}';
|
|
||||||
tkQuote: Result := '''';
|
|
||||||
tkBacktick: Result := '`';
|
|
||||||
tkTilde: Result := '~';
|
|
||||||
tkAt: Result := '@';
|
|
||||||
tkIdentifier: Result := '<identifier>';
|
|
||||||
tkKeyword: Result := '<keyword>';
|
|
||||||
tkNumber: Result := '<number>';
|
|
||||||
tkString: Result := '<string>';
|
|
||||||
tkEOF: Result := '<EOF>';
|
|
||||||
else
|
|
||||||
Result := '<error>';
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user