qrscript/QRScript/Runner.cs
Tony Bark 6501b72973 Renamed to QR Script
- Split into three projects: Library, Console, and Tests
- Library compiles as QRScript.Interpreter to avoid conflicts
2025-05-07 15:22:16 -04:00

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;
}
}
}
}