Renamed to QR Script

- Split into three projects: Library, Console, and Tests
- Library compiles as QRScript.Interpreter to avoid conflicts
This commit is contained in:
Tony Bark 2025-05-07 15:22:16 -04:00
parent e681e6144c
commit 6501b72973
16 changed files with 82 additions and 20 deletions

14
QRScript/QRScript.csproj Normal file
View file

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>QRScript.Interpreter</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DynamicExpresso.Core" Version="2.19.0" />
</ItemGroup>
</Project>

131
QRScript/Runner.cs Normal file
View file

@ -0,0 +1,131 @@
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;
}
}
}
}

View file

@ -0,0 +1,56 @@
namespace QRScript.Extensions;
using System.Text;
/// <summary>
/// Provides extension methods for common string transformations and validations.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Encodes the current string instance to Base64 using Unicode encoding.
/// </summary>
/// <param name="input">The input string to encode.</param>
/// <returns>A Base64-encoded version of the input string.</returns>
/// <remarks>
/// This uses Unicode (UTF-16) encoding, which is standard in .NET strings.
/// </remarks>
public static string ToBase64(this string input)
{
if (input is null)
throw new ArgumentNullException(nameof(input));
byte[] bytes = Encoding.Unicode.GetBytes(input);
return Convert.ToBase64String(bytes);
}
/// <summary>
/// Joins an array of strings into a single string separated by the system's newline.
/// </summary>
/// <param name="lines">Array of strings to join.</param>
/// <returns>A single string composed of all input lines, separated by newlines.</returns>
public static string JoinLines(this IEnumerable<string> lines)
{
if (lines is null)
throw new ArgumentNullException(nameof(lines));
return string.Join(Environment.NewLine, lines);
}
/// <summary>
/// Determines whether the string exceeds the specified character length limit.
/// </summary>
/// <param name="text">The input string to evaluate.</param>
/// <param name="limit">The maximum number of allowed characters.</param>
/// <returns><c>true</c> if the string's length is greater than or equal to <paramref name="limit"/>; otherwise, <c>false</c>.</returns>
public static bool ExceedsLengthLimit(this string text, int limit)
{
if (text is null)
throw new ArgumentNullException(nameof(text));
if (limit < 0)
throw new ArgumentOutOfRangeException(nameof(limit), "Limit must be non-negative.");
return text.Length >= limit;
}
}