From 7a06ad4650b4e450c9d871ebe5e6a8dcc20cd77a Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Fri, 24 Jan 2025 12:48:59 +0800 Subject: [PATCH] WIP: Add builtin operators and fix parsing --- Src/Parser/ParseResults.cs | 4 ++-- Src/Runtime/Builtin.cs | 42 ++++++++++++++++++++++++++++++++++ Src/Runtime/EvaluationNodes.cs | 28 +++++++++++++++++++++++ Src/Runtime/Interpreter.cs | 37 +++++++++++++++++++++++++----- Src/Runtime/Values.cs | 39 +++++++++++++++++++++++++++---- Src/Utils/Extensions.cs | 9 ++++++++ Tests/Mario/calc.mario | 28 +++++++++++++++++++++++ 7 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 Src/Runtime/Builtin.cs create mode 100644 Tests/Mario/calc.mario diff --git a/Src/Parser/ParseResults.cs b/Src/Parser/ParseResults.cs index f981616..1649293 100644 --- a/Src/Parser/ParseResults.cs +++ b/Src/Parser/ParseResults.cs @@ -12,7 +12,7 @@ public abstract class ResultList: IEvaluatable { protected List> list = new List>(); public void Append(IEvaluatable res) { - list.Append(res); + list.Add(res); } public abstract TRes Evaluate(TEnv env); @@ -39,7 +39,7 @@ public abstract class StringLiteral : Literal { } public abstract class Symbol: IEvaluatable { - private string name; + protected string name; public abstract TRes Evaluate(TEnv env); diff --git a/Src/Runtime/Builtin.cs b/Src/Runtime/Builtin.cs new file mode 100644 index 0000000..b000035 --- /dev/null +++ b/Src/Runtime/Builtin.cs @@ -0,0 +1,42 @@ +using Marionette.Parser; + +namespace Marionette.Runtime { + public class BinaryOperator : IEvaluatable { + private string name; + + private Value Add(Value lhs, Value rhs) { + if (lhs is LiteralValue i1 && rhs is LiteralValue i2) { + return new LiteralValue(i1.Value + i2.Value); + } + else if (lhs is LiteralValue i && rhs is LiteralValue d) { + return new LiteralValue(i.Value + d.Value); + } + else if (lhs is LiteralValue d2 && rhs is LiteralValue i3) { + return new LiteralValue(i3.Value + d2.Value); + } + else if (lhs is LiteralValue d3 && rhs is LiteralValue d4) { + return new LiteralValue(d3.Value + d4.Value); + } + else if (lhs is LiteralValue s1 && rhs is LiteralValue s2) { + return new LiteralValue(s1.Value + s2.Value); + } + + throw new Exception(string.Format("cannot add {0} and {1}.", lhs.Show(), rhs.Show())); + } + + public Value Evaluate(Environment env) { + var lhs = new EvalSymbol("lhs").Evaluate(env); + var rhs = new EvalSymbol("rhs").Evaluate(env); + if (name == "+") { + return Add(lhs, rhs); + } + // TODO: other + + throw new Exception(string.Format("{0} cannot be used as a builtin symbol here.", name)); + } + + public BinaryOperator(string name) { + this.name = name; + } + } +} // namespace Marionette.Runtime diff --git a/Src/Runtime/EvaluationNodes.cs b/Src/Runtime/EvaluationNodes.cs index d732363..881d4df 100644 --- a/Src/Runtime/EvaluationNodes.cs +++ b/Src/Runtime/EvaluationNodes.cs @@ -1,4 +1,5 @@ using Marionette.Parser; +using Marionette.Utils; namespace Marionette.Runtime { public class EvalIntLit: IntLiteral { @@ -41,6 +42,33 @@ public EvalStringLit(string s) { } } + public class EvalList: ResultList { + public EvalList() {} + + public override Value Evaluate(Environment env){ + if (list.IsEmpty()) { + throw new Exception("Empty invocation."); + } + + var fun = list[0].Evaluate(env); // TODO: + if (fun is Closure closure) { + list.RemoveAt(0); + return closure.Evaluate(list.Map(x => x.Evaluate(env)).ToArray()); + } + else { + throw new Exception(string.Format("{0} is not a function.", fun.Show())); + } + } + } + + public class EvalSymbol: Symbol { + public override Value Evaluate(Environment env){ + return env.GetOrElse(name, n => throw new Exception(string.Format("name not found: {0}", n))); + } + + public EvalSymbol(string name) : base(name) {} + } + public class EvalEOF: EOF { public override Value Evaluate(Environment env) { return new UnitValue(); diff --git a/Src/Runtime/Interpreter.cs b/Src/Runtime/Interpreter.cs index 8025c6a..0055c4d 100644 --- a/Src/Runtime/Interpreter.cs +++ b/Src/Runtime/Interpreter.cs @@ -5,20 +5,45 @@ namespace Marionette.Runtime { public class Environment { private Dictionary env = new Dictionary(); + private Environment? parent = null; + private Environment() { + env.Add("+", new Closure(["lhs", "rhs"], globalEnvironment, new BinaryOperator("+"))); + } + + public Environment(Environment parent) { + this.parent = parent; + } + + private static Environment globalEnvironment = new Environment(); + + public static Environment GlobalEnvironment() { + return globalEnvironment; } - public static Environment CreateEmpty() { - return new Environment(); + public void Add(string name, Value value) { + env[name] = value; + } + + public Value GetOrElse(string name, Func fallback) { + if (env.ContainsKey(name)) { + return env[name]; + } + else if (parent is Environment p) { + return parent.GetOrElse(name, fallback); + } + else { + return fallback(name); + } } } public class Interpreter { - private Environment env = Environment.CreateEmpty(); + private Environment env = Environment.GlobalEnvironment(); private ResultList AllocateList() { - throw new NotImplementedException(); + return new EvalList(); } private Literal AllocateLiteral(string text) { @@ -51,7 +76,7 @@ private Literal AllocateLiteral(string text) { else if (text.StartsWith("0b")) { return new EvalIntLit(flag * Convert.ToInt32(text[2..], 2)); } - else if (text.StartsWith("0")) { + else if (text.StartsWith("0") && text.Length > 1) { return new EvalIntLit(flag * Convert.ToInt32(text[1..], 8)); } else { @@ -63,7 +88,7 @@ private Literal AllocateLiteral(string text) { } private Symbol AllocateSymbol(string name) { - throw new NotImplementedException(); + return new EvalSymbol(name); } private EOF AllocateEOF() { diff --git a/Src/Runtime/Values.cs b/Src/Runtime/Values.cs index 0b3f8c8..23ea25c 100644 --- a/Src/Runtime/Values.cs +++ b/Src/Runtime/Values.cs @@ -1,3 +1,5 @@ +using Marionette.Parser; + namespace Marionette.Runtime { public abstract class Value { public abstract string Show(); @@ -19,13 +21,42 @@ public override string Show() { } } - public class UnitValue : Value { - public override string Show() { - return ""; + public class Closure : Value { + private string[] bindings; + private Environment environment; + private IEvaluatable body; + + public override string Show() { + return "[Function]"; + } + + public Closure(string[] bindings, Environment environment, IEvaluatable body) { + this.bindings = bindings; + this.environment = environment; + this.body = body; + } + + public Value Evaluate(Value[] values) { + if (bindings.Length != values.Length) { + throw new Exception(string.Format("Expect {0} argument{1}, got {2}", bindings.Length, (bindings.Length > 1) ? "s" : "", values.Length)); } + + var nestCxt = new Environment(environment); + foreach (var p in bindings.Zip(values)) { + nestCxt.Add(p.First, p.Second); + } + + return body.Evaluate(nestCxt); } + } + + public class UnitValue : Value { + public override string Show() { + return ""; + } + } - public class Result { + public class Result { private bool succeeded = false; private List diagnosis = new List(); private Value value = new UnitValue(); diff --git a/Src/Utils/Extensions.cs b/Src/Utils/Extensions.cs index 665f27b..6a7b153 100644 --- a/Src/Utils/Extensions.cs +++ b/Src/Utils/Extensions.cs @@ -61,6 +61,15 @@ public static class ListExtensions { public static bool IsEmpty(this List value) { return value == null || value.Count == 0; } + + public static List Map(this List value, Func func) { + var res = new List(); + foreach (var item in value) { + res.Add(func(item)); + } + + return res; + } } public static class ArrayExtensions { diff --git a/Tests/Mario/calc.mario b/Tests/Mario/calc.mario new file mode 100644 index 0000000..e355390 --- /dev/null +++ b/Tests/Mario/calc.mario @@ -0,0 +1,28 @@ +(+ 1 1) +;;|| --- Result --- || +;;|| Res: 2 + + +(+ 3.14 6.28) +;;|| --- Result --- || +;;|| Res: 9.42 + + +(+ 0b1100 0b0011) +;;|| --- Result --- || +;;|| Res: 15 + + +(+ 1 1.14) +;;|| --- Result --- || +;;|| Res: 2.1399999999999997 + + +(+ 5.14 0) +;;|| --- Result --- || +;;|| Res: 5.14 + + +(+ "hello " "world") +;;|| --- Result --- || +;;|| Res: hello world