Keywords as basic scalar type
Script enhancements
This commit is contained in:
+153
-13
@@ -7,6 +7,90 @@ uses
|
||||
Myc.Ast.Nodes,
|
||||
Myc.Ast.Visitor;
|
||||
|
||||
var
|
||||
// This BNF should always be kept up to date and valid. It is used to comunicate with LLMs.
|
||||
|
||||
BNF: String =
|
||||
'''
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Main Productions (Start Symbols) ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
(* A program is a single expression, which can be followed by comments *)
|
||||
program ::= expression
|
||||
|
||||
(* An expression is either an atom, a list, a record, or a macro character *)
|
||||
expression ::= atom | list | record_literal | reader_macro
|
||||
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Atoms (Basic Values) ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
atom ::= number | string | identifier | keyword
|
||||
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Reader-Macros (Syntactic Sugar) ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
reader_macro ::= "'" expression (* (quote ...) *)
|
||||
| "`" expression (* (quasiquote ...) *)
|
||||
| "~" expression (* (unquote ...) *)
|
||||
| "~@" expression (* (unquote-splicing ...) *)
|
||||
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Lists (S-Expressions) ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
(* A list is the primary structure for code. Empty lists () are invalid. *)
|
||||
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
|
||||
|
||||
special_form ::= "if" expression expression expression?
|
||||
| "?" expression expression expression
|
||||
| "def" identifier expression?
|
||||
| "defmacro" identifier parameter_list expression
|
||||
| "assign" identifier expression
|
||||
| "fn" parameter_list expression
|
||||
| "do" expression*
|
||||
| "recur" expression*
|
||||
| "get" expression 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*
|
||||
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Parameter Lists and Records ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
(* A parameter list is *only* valid inside 'fn' and 'defmacro' *)
|
||||
parameter_list ::= "[" identifier* "]"
|
||||
|
||||
(* A record literal consists of 0 or more keyword/value pairs *)
|
||||
record_literal ::= "{" (keyword expression)* "}"
|
||||
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
(* ---- Terminals (Lexer Tokens) ---- *)
|
||||
(* ---------------------------------------------------------------------- *)
|
||||
|
||||
number ::= ["-"] digit+ ["." digit+]
|
||||
string ::= '"' ( ? any char except \ or " ? | '\' ( '"' | '\' | 'n' | 'r' | 't' ) )* '"'
|
||||
keyword ::= ":" identifier
|
||||
|
||||
(* An identifier for member access, handled specially by the parser *)
|
||||
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, '()[]{}'`~:; ?
|
||||
|
||||
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||
''';
|
||||
|
||||
type
|
||||
// Provides a high-level facade for parsing and printing the AST.
|
||||
TAstScript = record
|
||||
@@ -164,8 +248,8 @@ var
|
||||
startPos: Integer;
|
||||
begin
|
||||
startPos := FCurrentPos;
|
||||
// Reader macro characters are now also delimiters for identifiers.
|
||||
while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']', '{', '}', '''', '`', '~']))) do
|
||||
// Reader macro characters and comments are now also delimiters for identifiers.
|
||||
while (Peek <> #0) and (not (Peek.IsWhiteSpace or CharInSet(Peek, ['(', ')', '[', ']', '{', '}', '''', '`', '~', ';']))) do
|
||||
Advance;
|
||||
Result := Copy(FSource, startPos, FCurrentPos - startPos);
|
||||
end;
|
||||
@@ -175,6 +259,11 @@ var
|
||||
startPos: Integer;
|
||||
begin
|
||||
startPos := FCurrentPos;
|
||||
|
||||
// Handle the optional leading minus sign
|
||||
if (Peek = '-') then
|
||||
Advance;
|
||||
|
||||
while (Peek <> #0) and (Peek.IsDigit or (Peek = '.')) do
|
||||
Advance;
|
||||
Result := Copy(FSource, startPos, FCurrentPos - startPos);
|
||||
@@ -182,15 +271,57 @@ end;
|
||||
|
||||
function TLexer.ReadString: string;
|
||||
var
|
||||
startPos: Integer;
|
||||
builder: TStringBuilder;
|
||||
c: Char;
|
||||
begin
|
||||
Advance; // Skip opening "
|
||||
startPos := FCurrentPos;
|
||||
while (Peek <> #0) and (Peek <> '"') do
|
||||
Advance;
|
||||
Result := Copy(FSource, startPos, FCurrentPos - startPos);
|
||||
if Peek = '"' then
|
||||
Advance; // Skip closing "
|
||||
builder := TStringBuilder.Create;
|
||||
try
|
||||
while (FCurrentPos <= Length(FSource)) do
|
||||
begin
|
||||
c := Peek;
|
||||
if c = '"' then
|
||||
begin
|
||||
Advance; // Consume closing "
|
||||
Result := builder.ToString;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if c = '\' then
|
||||
begin
|
||||
Advance; // Consume '\'
|
||||
if FCurrentPos > Length(FSource) then
|
||||
raise Exception.Create('Syntax Error: String literal ends with an escape character.');
|
||||
|
||||
c := Peek; // Get escaped char
|
||||
case c of
|
||||
'"': builder.Append('"');
|
||||
'\': builder.Append('\');
|
||||
'n': builder.Append(sLineBreak); // Use system's line break
|
||||
't': builder.Append(#9);
|
||||
'r': builder.Append(#13);
|
||||
else
|
||||
// Pass through unknown escapes (e.g., "\z" becomes "z")
|
||||
builder.Append(c);
|
||||
end;
|
||||
Advance; // Consume escaped char
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Append regular char (including literal newlines)
|
||||
if c = #0 then // Check for unexpected EOF
|
||||
break;
|
||||
builder.Append(c);
|
||||
Advance;
|
||||
end;
|
||||
end;
|
||||
|
||||
// If we fall out of the loop, we hit EOF without a closing quote.
|
||||
raise Exception.Create('Syntax Error: Unterminated string literal.');
|
||||
|
||||
finally
|
||||
builder.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TLexer.GetNextToken: TToken;
|
||||
@@ -473,6 +604,8 @@ begin
|
||||
end
|
||||
else if SameText(head.Token.Text, 'assign') then
|
||||
begin
|
||||
if Length(tailNodes) <> 2 then
|
||||
raise Exception.Create('Syntax Error: ''assign'' requires exactly 2 arguments (identifier and value).');
|
||||
if tailTokens[0].Kind <> tkIdentifier then
|
||||
raise Exception.Create('Syntax Error: Expected an identifier for assignment.');
|
||||
Result := TAst.Assign(IIdentifierNode(tailNodes[0]), tailNodes[1]);
|
||||
@@ -488,9 +621,19 @@ begin
|
||||
else if SameText(head.Token.Text, 'recur') then
|
||||
Result := TAst.Recur(tailNodes)
|
||||
else if SameText(head.Token.Text, 'get') then
|
||||
begin
|
||||
if Length(tailNodes) <> 2 then
|
||||
raise Exception.Create('Syntax Error: ''get'' requires exactly 2 arguments (base and index).');
|
||||
Result := TAst.Indexer(tailNodes[0], tailNodes[1])
|
||||
end
|
||||
else if (Length(head.Token.Text) > 1) and (head.Token.Text.StartsWith('.')) then
|
||||
begin
|
||||
if Length(tailNodes) <> 1 then
|
||||
raise Exception.CreateFmt(
|
||||
'Syntax Error: Member access (e.g., ''%s'') requires exactly 1 argument (the base object).',
|
||||
[head.Token.Text]);
|
||||
Result := TAst.MemberAccess(tailNodes[0], TAst.Keyword(head.Token.Text.Substring(1)));
|
||||
end;
|
||||
end;
|
||||
|
||||
// If no special form matched, treat it as a generic function call.
|
||||
@@ -553,7 +696,7 @@ begin
|
||||
Result.Node :=
|
||||
TAst.Constant(i64)
|
||||
// Use invariant format settings for float parsing.
|
||||
else if TryStrToFloat(FCurrentToken.Text, dbl) then
|
||||
else if TryStrToFloat(FCurrentToken.Text, dbl, TFormatSettings.Invariant) then
|
||||
Result.Node := TAst.Constant(dbl)
|
||||
else
|
||||
raise Exception.CreateFmt('Syntax Error: Invalid number format "%s"', [FCurrentToken.Text]);
|
||||
@@ -1009,7 +1152,4 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
initialization
|
||||
FormatSettings.DecimalSeparator := '.';
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user