v2: Implement Interpreter.atg with typed FunctionValue, mixed-arg functions, and expression-binding (:=); fix Coco/R csproj command
This completes the v2 grammar based on master’s capabilities while aligning with the v2 design philosophy (numeric-first, strings as function-args only): - Implement full arithmetic grammar ( +, -, *, /, %, ^, parentheses, unary sign ), with support for number/hex/binary literals. - Add variables and arrays: - Numeric variables via :: (evaluate RHS now and store numeric) - Expression-bound variables via := (capture RHS text; re-parse and evaluate at use-time) - Numeric arrays via -> and array literals + indexing (with bounds/type checks) - Add comparisons (==, <, >, <=, >=) producing FunctionValue TRUE/FALSE. - Add function calls with mixed arguments: numeric expressions and quoted string literals. - In numeric contexts, enforce numeric results; emit clear type errors if a function returns a string. - Root production always produces a FunctionValue through InputInterpreter.ProduceOutput(FunctionValue). Runtime integration (expected APIs on InputInterpreter): - Variable APIs: AssignNumeric, AssignExpression, AssignNumericArray; TryGetNumeric, TryGetExpression, TryGetNumericArray - Function dispatch: ExecuteFunction(name, FunctionArgument[]) - Expression evaluation for :=: EvaluateExpression(expressionText) Coco/R build fix: - Correct the PreBuild command in src/core/core.csproj: - Use -frames (without the stray space) and point to cocor - Use the correct case and path for the grammar: cocor/Interpreter.atg Notes: - Strings are valid only as function arguments and not as standalone values or variables. - Grammar emits concise, actionable error messages for type mismatches, missing variables, and array bounds.
This commit is contained in:
parent
66ec23a420
commit
f532d13d8c
2 changed files with 307 additions and 85 deletions
|
@ -1,129 +1,351 @@
|
||||||
using csmic;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using csmic;
|
||||||
|
|
||||||
COMPILER INTERPRETER
|
COMPILER INTERPRETER
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
* Class structures and helpers
|
||||||
* Class Structures
|
*/
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
private FunctionValue value = new FunctionValue();
|
private FunctionValue functionValue = new FunctionValue();
|
||||||
|
|
||||||
public decimal CalculatedValue
|
public FunctionValue Result
|
||||||
{
|
{
|
||||||
get
|
get { return this.functionValue; }
|
||||||
{
|
set { this.functionValue = value; }
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputInterpreter interpreter = null;
|
private InputInterpreter interpreter = null;
|
||||||
|
|
||||||
public InputInterpreter Interpreter
|
public InputInterpreter Interpreter
|
||||||
{
|
{
|
||||||
get
|
get { return this.interpreter; }
|
||||||
{
|
set { this.interpreter = value; }
|
||||||
return this.interpreter;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
this.interpreter = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsFunctionCall()
|
bool IsFunctionCall()
|
||||||
{
|
{
|
||||||
scanner.ResetPeek();
|
scanner.ResetPeek();
|
||||||
Token next = scanner.Peek();
|
Token next = scanner.Peek();
|
||||||
if (next.kind == _LPAREN && la.kind == _identifier)
|
if (next.kind == _LPAREN && la.kind == _identifier) return true;
|
||||||
return true;
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsCompare()
|
bool IsCompare()
|
||||||
{
|
{
|
||||||
scanner.ResetPeek();
|
scanner.ResetPeek();
|
||||||
Token next = scanner.Peek();
|
Token next = scanner.Peek();
|
||||||
if (next.kind == _COMPARER)
|
if (next.kind == _COMPARER) return true;
|
||||||
return true;
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsAssignment()
|
bool IsAssignment()
|
||||||
{
|
{
|
||||||
scanner.ResetPeek();
|
scanner.ResetPeek();
|
||||||
Token next = scanner.Peek();
|
Token next = scanner.Peek();
|
||||||
if (next.val == "::" || next.val == ":=" || next.val == "->")
|
if (next.val == "::" || next.val == ":=" || next.val == "->") return true;
|
||||||
return true;
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsArrayCall()
|
bool IsArrayCall()
|
||||||
{
|
{
|
||||||
scanner.ResetPeek();
|
scanner.ResetPeek();
|
||||||
Token next = scanner.Peek();
|
Token next = scanner.Peek();
|
||||||
if(next.val == "[")
|
if (next.val == "[") return true;
|
||||||
return true;
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parser definitions
|
* Character sets and tokens
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
CHARACTERS
|
CHARACTERS
|
||||||
|
|
||||||
UpperLetter = 'A'..'Z'.
|
UpperLetter = 'A'..'Z'.
|
||||||
LowerLetter = 'a'..'z'.
|
LowerLetter = 'a'..'z'.
|
||||||
letter = UpperLetter + LowerLetter.
|
letter = UpperLetter + LowerLetter.
|
||||||
digit = "0123456789" .
|
digit = "0123456789" .
|
||||||
cr = '\r' .
|
cr = '\r' .
|
||||||
lf = '\n' .
|
lf = '\n' .
|
||||||
tab = '\t' .
|
tab = '\t' .
|
||||||
PM = "+-" .
|
PM = "+-" .
|
||||||
NoQuote = ANY - '\"' .
|
NoQuote = ANY - '\"' .
|
||||||
|
|
||||||
TOKENS
|
TOKENS
|
||||||
|
|
||||||
identifier = letter { letter | digit}.
|
identifier = letter { letter | digit}.
|
||||||
sign = PM .
|
sign = PM .
|
||||||
binary = ( '0' | '1' ) { '0' | '1' } ('B' | 'b') .
|
binary = ( '0' | '1' ) { '0' | '1' } ('B' | 'b') .
|
||||||
hex = "0x" ( digit | ('A' | 'B' | 'C' | 'D' | 'E' |'F') | ('a' | 'b' | 'c' | 'd' | 'e' |'f') ) { digit | ('A' | 'B' | 'C' | 'D' | 'E' |'F') | ('a' | 'b' | 'c' | 'd' | 'e' |'f') } .
|
hex = "0x" ( digit | ('A' | 'B' | 'C' | 'D' | 'E' |'F') | ('a' | 'b' | 'c' | 'd' | 'e' |'f') ) { digit | ('A' | 'B' | 'C' | 'D' | 'E' |'F') | ('a' | 'b' | 'c' | 'd' | 'e' |'f') } .
|
||||||
number = digit { digit }['.' {digit}] [('E'|'e')['+'|'-'] digit {digit}] .
|
number = digit { digit }['.' {digit}] [('E'|'e')['+'|'-'] digit {digit}] .
|
||||||
string = "\"" { NoQuote } "\"" .
|
string = "\"" { NoQuote } "\"" .
|
||||||
LPAREN = '(' .
|
LPAREN = '(' .
|
||||||
RPAREN = ')' .
|
RPAREN = ')' .
|
||||||
COMPARER = "==" | "<" | ">" | "<=" | ">=" .
|
COMPARER = "==" | "<" | ">" | "<=" | ">=" .
|
||||||
|
|
||||||
IGNORE cr + tab
|
IGNORE cr + tab
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parser specification
|
* Grammar
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
PRODUCTIONS
|
PRODUCTIONS
|
||||||
|
|
||||||
INTERPRETER (.
|
INTERPRETER (.
|
||||||
FunctionValue functionValue = new FunctionValue();
|
FunctionValue fv = new FunctionValue();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
if(this.interpreter == null)
|
if (this.interpreter == null) { return; }
|
||||||
{
|
.)
|
||||||
return;
|
|
||||||
}
|
|
||||||
.)
|
|
||||||
=
|
=
|
||||||
|
IF(IsCompare())
|
||||||
|
Comparison<out success>
|
||||||
|
(. this.functionValue = (success == true) ? FunctionValue.TRUE : FunctionValue.FALSE;
|
||||||
|
this.interpreter.ProduceOutput(this.functionValue);
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
IF(IsAssignment())
|
||||||
|
Assignment<out r>
|
||||||
|
(. this.functionValue = new FunctionValue(ValueType.Numeric, r);
|
||||||
|
this.interpreter.ProduceOutput(this.functionValue);
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
Expression<out r>
|
||||||
|
(. this.functionValue = new FunctionValue(ValueType.Numeric, r);
|
||||||
|
this.interpreter.ProduceOutput(this.functionValue);
|
||||||
|
.)
|
||||||
|
.
|
||||||
|
|
||||||
IF(IsCompare())
|
Expression<out decimal r>
|
||||||
Comparison<out success> (. this.functionValue = (success == true) ? FunctionValue.TRUE : FunctionValue.FALSE; .)
|
=
|
||||||
|
|
(. decimal r1 = 0; r = 0; .)
|
||||||
IF(IsAssignment())
|
Term<out r>
|
||||||
Assignment<out r>
|
{ '+' Term<out r1> (. r += r1; .)
|
||||||
|
| '-' Term<out r1> (. r -= r1; .)
|
||||||
|
}
|
||||||
|
.
|
||||||
|
|
||||||
|
Term<out decimal r>
|
||||||
|
=
|
||||||
|
(. decimal r1 = 0; r = 0; .)
|
||||||
|
Factor<out r>
|
||||||
|
{ '*' Factor<out r1> (. r *= r1; .)
|
||||||
|
| '/' Factor<out r1> (. r /= r1; .)
|
||||||
|
| '%' Term<out r1> (. r %= r1; .)
|
||||||
|
}
|
||||||
|
.
|
||||||
|
|
||||||
|
Factor<out decimal r>
|
||||||
|
=
|
||||||
|
(. decimal r1 = 0; .)
|
||||||
|
Value<out r>
|
||||||
|
{ '^' Expression<out r1>
|
||||||
|
(. r = Convert.ToDecimal(Math.Pow(Convert.ToDouble(r), Convert.ToDouble(r1))); .)
|
||||||
|
}
|
||||||
|
.
|
||||||
|
|
||||||
|
Value<out decimal r> (.
|
||||||
|
r = 0; decimal r1 = 0; int signum = 1;
|
||||||
|
FunctionValue fvr = new FunctionValue();
|
||||||
|
string ident = string.Empty;
|
||||||
|
.)
|
||||||
|
=
|
||||||
|
[ '+' | '-' (. signum = -1; .) ]
|
||||||
|
(
|
||||||
|
IF(IsFunctionCall())
|
||||||
|
Function<out fvr>
|
||||||
|
(.
|
||||||
|
if (fvr.Type == ValueType.Numeric && fvr.Value != null)
|
||||||
|
{
|
||||||
|
try { r = signum * Convert.ToDecimal(fvr.Value); }
|
||||||
|
catch { SemErr("function returned non-numeric"); r = 0; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SemErr("function returned a string; number required");
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
IF(IsArrayCall())
|
||||||
|
ArrayCall<out r> (. r = signum * r; .)
|
||||||
|
|
|
||||||
|
identifier
|
||||||
|
(.
|
||||||
|
ident = t.val;
|
||||||
|
decimal temp = 0;
|
||||||
|
string expr = string.Empty;
|
||||||
|
bool ok = false;
|
||||||
|
// Prefer numeric binding
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// runtime method expected
|
||||||
|
ok = this.interpreter.TryGetNumeric(ident, out temp);
|
||||||
|
}
|
||||||
|
catch { ok = false; }
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
r = signum * temp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check expression binding
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (this.interpreter.TryGetExpression(ident, out expr))
|
||||||
|
{
|
||||||
|
FunctionValue eval = this.interpreter.EvaluateExpression(expr);
|
||||||
|
if (eval.Type == ValueType.Numeric && eval.Value != null)
|
||||||
|
{
|
||||||
|
r = signum * Convert.ToDecimal(eval.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SemErr("expression variable did not evaluate to a number");
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SemErr("variable '" + ident + "' is not numeric");
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { SemErr("error evaluating expression variable"); r = 0; }
|
||||||
|
}
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
number (. r = signum * Convert.ToDecimal(t.val); .)
|
||||||
|
|
|
||||||
|
hex (. string hx = t.val.Remove(0,2);
|
||||||
|
try { r = signum * Convert.ToDecimal(Convert.ToInt64(hx, 16)); }
|
||||||
|
catch { r = 0; }
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
binary (. string bx = t.val.Remove(t.val.Length - 1);
|
||||||
|
try { r = signum * Convert.ToDecimal(Convert.ToInt64(bx, 2)); }
|
||||||
|
catch { r = 0; }
|
||||||
|
.)
|
||||||
|
|
|
||||||
|
'(' Expression<out r> ')'
|
||||||
|
(. r = signum * r; .)
|
||||||
|
)
|
||||||
|
.
|
||||||
|
|
||||||
|
ArrayL<out decimal[] d>
|
||||||
|
=
|
||||||
|
(. List<decimal> list = new List<decimal>(); decimal r = 0; d = new decimal[0]; .)
|
||||||
|
'['
|
||||||
|
Expression<out r> (. list.Add(r); d = list.ToArray(); .)
|
||||||
|
{ ',' Expression<out r> (. list.Add(r); d = list.ToArray(); .) }
|
||||||
|
']'
|
||||||
|
.
|
||||||
|
|
||||||
|
ArrayCall<out decimal r> (. string ident = string.Empty; r = 0; decimal pos = 0; .)
|
||||||
|
=
|
||||||
|
identifier (. ident = t.val; .)
|
||||||
|
'['
|
||||||
|
Expression<out pos>
|
||||||
|
(.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int i = Convert.ToInt32(pos);
|
||||||
|
decimal[] values;
|
||||||
|
if (this.interpreter.TryGetNumericArray(ident, out values))
|
||||||
|
{
|
||||||
|
if (i >= 0 && i < values.Length) { r = values[i]; }
|
||||||
|
else { SemErr("array index out of range"); r = 0; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SemErr("variable '" + ident + "' is not a numeric array");
|
||||||
|
r = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { SemErr("invalid array index"); r = 0; }
|
||||||
|
.)
|
||||||
|
']'
|
||||||
|
.
|
||||||
|
|
||||||
|
Assignment<out decimal r>
|
||||||
|
(. string identifier = string.Empty; string expression = string.Empty; decimal[] d = new decimal[0]; r = 0; .)
|
||||||
|
=
|
||||||
|
identifier (. identifier = t.val; .)
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"::"
|
||||||
|
Expression<out r>
|
||||||
|
(. this.interpreter.AssignNumeric(identifier, r); .)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
(
|
||||||
|
":="
|
||||||
|
AnyExpression<out expression>
|
||||||
|
(. this.interpreter.AssignExpression(identifier, expression); r = 0; .)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
(
|
||||||
|
"->"
|
||||||
|
ArrayL<out d>
|
||||||
|
(. this.interpreter.AssignNumericArray(identifier, d); r = 0; .)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.
|
||||||
|
|
||||||
|
// Function call with mixed arguments (numeric expressions and strings)
|
||||||
|
Function<out FunctionValue r>
|
||||||
|
(. string functionName = string.Empty; FunctionArgument[] args = new FunctionArgument[0]; r = new FunctionValue(); .)
|
||||||
|
=
|
||||||
|
identifier (. functionName = t.val; .)
|
||||||
|
'('
|
||||||
|
ArgList<out args>
|
||||||
|
')'
|
||||||
|
(. r = this.interpreter.ExecuteFunction(functionName, args); .)
|
||||||
|
.
|
||||||
|
|
||||||
|
ArgList<out FunctionArgument[] args>
|
||||||
|
(. List<FunctionArgument> list = new List<FunctionArgument>(); FunctionArgument a = new FunctionArgument { Name = string.Empty, Value = new FunctionValue() }; args = new FunctionArgument[0]; .)
|
||||||
|
=
|
||||||
|
[
|
||||||
|
Arg<out a> (. list.Add(a); args = list.ToArray(); .)
|
||||||
|
{ ',' Arg<out a> (. list.Add(a); args = list.ToArray(); .) }
|
||||||
|
]
|
||||||
|
.
|
||||||
|
|
||||||
|
Arg<out FunctionArgument arg>
|
||||||
|
(. arg = new FunctionArgument { Name = string.Empty, Value = new FunctionValue() }; decimal r = 0; string s = string.Empty; .)
|
||||||
|
=
|
||||||
|
(
|
||||||
|
string (. s = t.val.Substring(1, t.val.Length - 2); arg = new FunctionArgument { Name = string.Empty, Value = new FunctionValue(ValueType.String, s) }; .)
|
||||||
|
|
|
||||||
|
Expression<out r> (. arg = new FunctionArgument { Name = string.Empty, Value = new FunctionValue(ValueType.Numeric, r) }; .)
|
||||||
|
)
|
||||||
|
.
|
||||||
|
|
||||||
|
Comparison<out bool result>
|
||||||
|
(. decimal firstValue = 0; decimal secondValue = 0; string compareType = string.Empty; result = false; .)
|
||||||
|
=
|
||||||
|
Expression<out firstValue>
|
||||||
|
COMPARER (. compareType = t.val; .)
|
||||||
|
Expression<out secondValue>
|
||||||
|
(.
|
||||||
|
switch(compareType)
|
||||||
|
{
|
||||||
|
case "==": result = (firstValue == secondValue); break;
|
||||||
|
case ">": result = (firstValue > secondValue); break;
|
||||||
|
case "<": result = (firstValue < secondValue); break;
|
||||||
|
case ">=": result = (firstValue >= secondValue); break;
|
||||||
|
case "<=": result = (firstValue <= secondValue); break;
|
||||||
|
default: result = false; break;
|
||||||
|
}
|
||||||
|
.)
|
||||||
|
.
|
||||||
|
|
||||||
|
AnyExpression<out string value>
|
||||||
|
(. value = string.Empty; StringBuilder builder = new StringBuilder(); .)
|
||||||
|
=
|
||||||
|
ANY (. builder.Append(t.val); .) { ANY (. builder.Append(t.val); .) } (. value = builder.ToString(); .)
|
||||||
|
.
|
||||||
|
|
||||||
|
END INTERPRETER.
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
<Exec Command="dotnet tool run coco -namespace csmic.Interpreter - frames . $(ProjectDir)\cocor\interpreter.atg" />
|
<Exec Command="dotnet tool run coco -namespace csmic.Interpreter -frames $(ProjectDir)cocor $(ProjectDir)cocor/Interpreter.atg" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue