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:
Jordan Wages 2025-08-19 03:45:57 -05:00
commit f532d13d8c
2 changed files with 307 additions and 85 deletions

View file

@ -1,49 +1,35 @@
using csmic;
using System;
using System.Text;
using System.Collections.Generic;
using csmic;
COMPILER INTERPRETER
/*
*
* Class Structures
*
*/
* Class structures and helpers
*/
private FunctionValue value = new FunctionValue();
private FunctionValue functionValue = new FunctionValue();
public decimal CalculatedValue
public FunctionValue Result
{
get
{
return this.value;
}
set
{
this.value = value;
}
get { return this.functionValue; }
set { this.functionValue = value; }
}
private InputInterpreter interpreter = null;
public InputInterpreter Interpreter
{
get
{
return this.interpreter;
}
set
{
this.interpreter = value;
}
get { return this.interpreter; }
set { this.interpreter = value; }
}
bool IsFunctionCall()
{
scanner.ResetPeek();
Token next = scanner.Peek();
if (next.kind == _LPAREN && la.kind == _identifier)
return true;
if (next.kind == _LPAREN && la.kind == _identifier) return true;
return false;
}
@ -51,8 +37,7 @@ bool IsCompare()
{
scanner.ResetPeek();
Token next = scanner.Peek();
if (next.kind == _COMPARER)
return true;
if (next.kind == _COMPARER) return true;
return false;
}
@ -60,8 +45,7 @@ bool IsAssignment()
{
scanner.ResetPeek();
Token next = scanner.Peek();
if (next.val == "::" || next.val == ":=" || next.val == "->")
return true;
if (next.val == "::" || next.val == ":=" || next.val == "->") return true;
return false;
}
@ -69,14 +53,12 @@ bool IsArrayCall()
{
scanner.ResetPeek();
Token next = scanner.Peek();
if(next.val == "[")
return true;
if (next.val == "[") return true;
return false;
}
/*
* Parser definitions
*
* Character sets and tokens
*/
CHARACTERS
@ -106,24 +88,264 @@ TOKENS
IGNORE cr + tab
/*
* Parser specification
*
* Grammar
*/
PRODUCTIONS
PRODUCTIONS
INTERPRETER (.
FunctionValue functionValue = new FunctionValue();
INTERPRETER (.
FunctionValue fv = new FunctionValue();
bool success = true;
if(this.interpreter == null)
{
return;
}
if (this.interpreter == null) { return; }
.)
=
IF(IsCompare())
Comparison<out success> (. this.functionValue = (success == true) ? FunctionValue.TRUE : FunctionValue.FALSE; .)
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);
.)
.
Expression<out decimal r>
=
(. decimal r1 = 0; r = 0; .)
Term<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.

View file

@ -8,7 +8,7 @@
</PropertyGroup>
<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>
</Project>