- Split into three projects: Library, Console, and Tests - Library compiles as QRScript.Interpreter to avoid conflicts
131 lines
4.1 KiB
C#
131 lines
4.1 KiB
C#
namespace QRScript;
|
|
|
|
using DynamicExpresso;
|
|
|
|
public static class Runner
|
|
{
|
|
public static void Interpret(string[] lines)
|
|
{
|
|
var env = new Dictionary<string, object>();
|
|
var labels = new Dictionary<string, int>();
|
|
var stack = new Stack<int>();
|
|
var exp = new Interpreter();
|
|
int pc = 0;
|
|
|
|
// First pass: record labels
|
|
for (int i = 0; i < lines.Length; i++)
|
|
{
|
|
string line = lines[i].Trim();
|
|
if (line.StartsWith("@"))
|
|
labels[line[1..]] = i;
|
|
}
|
|
|
|
// Main execution loop
|
|
while (pc < lines.Length)
|
|
{
|
|
string raw = lines[pc].Trim();
|
|
pc++;
|
|
|
|
if (string.IsNullOrWhiteSpace(raw) || raw.StartsWith("#") || raw.StartsWith("@"))
|
|
continue;
|
|
|
|
var parts = raw.Split(' ', 4, StringSplitOptions.RemoveEmptyEntries);
|
|
if (parts.Length == 0) continue;
|
|
|
|
string cmd = parts[0].ToLowerInvariant();
|
|
string arg1 = parts.Length > 1 ? parts[1] : null;
|
|
string arg2 = parts.Length > 2 ? parts[2] : null;
|
|
string arg3 = parts.Length > 3 ? parts[3] : null;
|
|
|
|
object Eval(string expr)
|
|
{
|
|
foreach (var (k, v) in env)
|
|
exp.SetVariable(k, v);
|
|
return exp.Eval(expr);
|
|
}
|
|
|
|
switch (cmd)
|
|
{
|
|
case "say":
|
|
Console.WriteLine(string.Join(' ', parts.Skip(1).Select(p =>
|
|
env.TryGetValue(p, out var val) ? val.ToString() : p)));
|
|
break;
|
|
|
|
case "set":
|
|
env[arg1] = Eval(arg2);
|
|
break;
|
|
|
|
case "add":
|
|
case "sub":
|
|
case "mul":
|
|
case "div":
|
|
case "mod":
|
|
if (!env.TryGetValue(arg1, out var val)) val = 0;
|
|
var op = cmd switch
|
|
{
|
|
"add" => "+",
|
|
"sub" => "-",
|
|
"mul" => "*",
|
|
"div" => "/",
|
|
"mod" => "%",
|
|
_ => throw new InvalidOperationException()
|
|
};
|
|
env[arg1] = Eval($"{val} {op} {arg2}");
|
|
break;
|
|
|
|
case "if":
|
|
// Format: if x > 10 goto label
|
|
if (arg2 == "goto" && arg3 != null)
|
|
{
|
|
var cond = string.Join(' ', parts.Skip(1).Take(2)); // x > 10
|
|
var result = Eval(cond);
|
|
if (result is bool b && b)
|
|
pc = labels[arg3];
|
|
}
|
|
break;
|
|
|
|
case "goto":
|
|
if (arg1 != null && labels.TryGetValue(arg1, out var target))
|
|
pc = target;
|
|
break;
|
|
|
|
case "inp":
|
|
Console.Write($"{arg1}: ");
|
|
env[arg1] = Console.ReadLine();
|
|
break;
|
|
|
|
case "def":
|
|
// No-op in this basic form, handled via label + call/ret
|
|
break;
|
|
|
|
case "call":
|
|
if (arg1 != null && labels.TryGetValue(arg1, out var fnStart))
|
|
{
|
|
stack.Push(pc);
|
|
pc = fnStart + 1;
|
|
if (arg2 != null)
|
|
env["x"] = Eval(arg2);
|
|
}
|
|
break;
|
|
|
|
case "ret":
|
|
if (stack.Count > 0)
|
|
pc = stack.Pop();
|
|
else
|
|
return;
|
|
break;
|
|
|
|
case "cmp":
|
|
Console.WriteLine($"Compare: {Eval($"{arg1} == {arg2}")}");
|
|
break;
|
|
|
|
case "end":
|
|
return;
|
|
|
|
default:
|
|
Console.WriteLine($"Unknown command: {cmd}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|