diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ec84db4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.dll
+command_examples.txt
+screen.txt
+.vscode/
+bin/
+obj/
+Resources/
diff --git a/Engine.csproj b/Engine.csproj
new file mode 100644
index 0000000..b9f88db
--- /dev/null
+++ b/Engine.csproj
@@ -0,0 +1,16 @@
+
+
+
+ WinExe
+ net7.0-windows
+ enable
+ true
+ true
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Engine.csproj.user b/Engine.csproj.user
new file mode 100644
index 0000000..0295687
--- /dev/null
+++ b/Engine.csproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Engine.sln b/Engine.sln
new file mode 100644
index 0000000..3586548
--- /dev/null
+++ b/Engine.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Engine", "Engine.csproj", "{C33BE1C6-5FB0-44EC-935F-9E307963A8DF}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C33BE1C6-5FB0-44EC-935F-9E307963A8DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C33BE1C6-5FB0-44EC-935F-9E307963A8DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C33BE1C6-5FB0-44EC-935F-9E307963A8DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C33BE1C6-5FB0-44EC-935F-9E307963A8DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FA1AF6AE-3E75-40AC-8795-FD3959110D5B}
+ EndGlobalSection
+EndGlobal
diff --git a/Game.cs b/Game.cs
new file mode 100644
index 0000000..b1f63b8
--- /dev/null
+++ b/Game.cs
@@ -0,0 +1,51 @@
+using KumiScript.Renderer;
+using SDL2;
+
+public class EngineWindow
+{
+ internal ushort width {get; private set;}
+ internal ushort height {get; private set;}
+ internal string title {get; private set;}
+ public EngineWindow(ushort horizontalResolution, ushort verticalResolution, string windowTitle)
+ {
+ width = horizontalResolution;
+ height = verticalResolution;
+ title = windowTitle;
+
+ if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO) < 0)
+ throw new Exception("SDL2 Video init failed!");
+
+ if (SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG) < 0)
+ throw new Exception("SDL2 Image init failed!");
+ }
+
+ public void show()
+ {
+ SDLWindow window = new SDLWindow(width, height, title);
+ SDLRenderer renderer = new SDLRenderer(window);
+ bool mainLoop = true;
+ SDL.SDL_Event e;
+
+ Scene s = new Scene(renderer);
+ SceneElement background = new SceneElement(new Background("Resources/Backgrounds/test.png", renderer), 0, 0);
+ SceneElement seiba = new SceneElement(new Sprite("Resources/Sprites/test.png", renderer), 0, 0);
+ s.AddElement(background);
+ s.AddElement(seiba);
+
+ while (mainLoop)
+ {
+ while (SDL.SDL_PollEvent(out e) > 0)
+ {
+ switch (e.type)
+ {
+ case SDL.SDL_EventType.SDL_QUIT:
+ mainLoop = false;
+ break;
+ }
+ renderer.Clear();
+ s.DrawScene();
+ renderer.SwapBuffers();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..96ab12c
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,14 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript {
+
+ class KumiScript {
+ static void Main (string[] args) {
+ Console.WriteLine(System.IO.Directory.GetCurrentDirectory());
+ EngineWindow engineWindow = new EngineWindow(800, 600, "KumiScript Unicode");
+ engineWindow.show();
+ /*ReadEvalPrintLoop repl = new ReadEvalPrintLoop(Console.OpenStandardInput(), Console.OpenStandardOutput());
+ repl.Loop();*/
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/Environment.cs b/interpreter/Environment.cs
new file mode 100644
index 0000000..82622e0
--- /dev/null
+++ b/interpreter/Environment.cs
@@ -0,0 +1,83 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace KumiScript.Interpreter
+{
+ public class Environment
+ {
+ readonly Environment? _outer;
+ readonly Dictionary _bindings;
+ public Environment()
+ {
+ _bindings = new Dictionary(SymbolComparer.GetInstance());
+ }
+ public Environment(Environment outer)
+ {
+ _bindings = new Dictionary(SymbolComparer.GetInstance());
+ _outer = outer;
+ }
+
+ public Expression Lookup(Symbol symbol)
+ {
+ Expression? result;
+ _bindings.TryGetValue(symbol, out result);
+
+ if (result is not null)
+ return result;
+
+ if (_outer is null)
+ throw new InterpreterUnboundSymbolException();
+
+ return _outer.Lookup(symbol);
+ }
+
+ public void AddSymbol(Symbol symbol, Expression value)
+ {
+ _bindings.Add(symbol, value);
+ return;
+ }
+
+ public void RedefineSymbol(Symbol symbol, Expression value)
+ {
+ if (_bindings.ContainsKey(symbol))
+ {
+ _bindings.Remove(symbol);
+ _bindings.Add(symbol, value);
+ return;
+ }
+
+ if (_outer is null)
+ throw new InterpreterUnboundSymbolException(symbol.ToString());
+
+ _outer.RedefineSymbol(symbol, value);
+ }
+ }
+
+ internal class SymbolComparer : IEqualityComparer
+ {
+ private static SymbolComparer? _instance;
+ private SymbolComparer()
+ {
+ }
+
+ public static SymbolComparer GetInstance()
+ {
+ if (_instance is null)
+ _instance = new SymbolComparer();
+
+ return _instance;
+ }
+
+ public bool Equals(Symbol? x, Symbol? y)
+ {
+ if (x is not Symbol || y is not Symbol)
+ return false;
+
+ return x.Equals(y);
+ }
+
+ public int GetHashCode([DisallowNull] Symbol obj)
+ {
+ return obj.ToString().GetHashCode();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/InterpreterException.cs b/interpreter/InterpreterException.cs
new file mode 100644
index 0000000..77edfe2
--- /dev/null
+++ b/interpreter/InterpreterException.cs
@@ -0,0 +1,101 @@
+using System.Runtime.Serialization;
+
+namespace KumiScript.Interpreter
+{
+ [Serializable]
+ internal class InterpreterInvalidApplicationException : Exception //trying to invoke procedure with wrong args
+ {
+ public InterpreterInvalidApplicationException()
+ {
+ }
+
+ public InterpreterInvalidApplicationException(string? message) : base(message)
+ {
+ }
+
+ public InterpreterInvalidApplicationException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected InterpreterInvalidApplicationException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+ internal class InterpreterInvalidInvocationException : Exception //trying to invoke non-procedure as procedure
+ {
+ public InterpreterInvalidInvocationException()
+ {
+ }
+
+ public InterpreterInvalidInvocationException(string? message) : base(message)
+ {
+ }
+
+ public InterpreterInvalidInvocationException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected InterpreterInvalidInvocationException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+ internal class InterpreterUnboundSymbolException : Exception //try to lookup unbound symbol
+ {
+ public InterpreterUnboundSymbolException()
+ {
+ }
+
+ public InterpreterUnboundSymbolException(string? message) : base(message)
+ {
+ }
+
+ public InterpreterUnboundSymbolException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected InterpreterUnboundSymbolException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+ internal class InterpreterInvalidDefinitionException : Exception //broken definition of procedure
+ {
+ public InterpreterInvalidDefinitionException()
+ {
+ }
+
+ public InterpreterInvalidDefinitionException(string? message) : base(message)
+ {
+ }
+
+ public InterpreterInvalidDefinitionException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected InterpreterInvalidDefinitionException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+
+ internal class InterpreterTypingException : Exception //wrong type of arg
+ {
+ public InterpreterTypingException()
+ {
+ }
+
+ public InterpreterTypingException(string? message) : base(message)
+ {
+ }
+
+ public InterpreterTypingException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected InterpreterTypingException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/LispPrimitives.cs b/interpreter/LispPrimitives.cs
new file mode 100644
index 0000000..26d454c
--- /dev/null
+++ b/interpreter/LispPrimitives.cs
@@ -0,0 +1,267 @@
+namespace KumiScript.Interpreter
+{
+ public class LispPrimitives
+ {
+ public static Expression Lambda(ListExpression args, Environment env)
+ {
+ Expression car = args.Car();
+ ListExpression rest = args.Cdr();
+
+ ListExpression? head = car as ListExpression;
+ if (head is null)
+ throw new InterpreterInvalidDefinitionException();
+
+ List headl = head.GetMembers();
+ List arg_names = new List();
+ for (int i = 0; i < headl.Count; i++)
+ {
+ SymbolExpression? argi = headl[i] as SymbolExpression;
+ if (argi is null)
+ throw new InterpreterInvalidDefinitionException();
+
+ arg_names.Add(argi.GetSymbol());
+ }
+
+ Expression body = rest.Car();
+ return new ProcedureExpression(new LispProcedure(arg_names, body, env));
+ }
+
+ public static NumberExpression Plus(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ decimal acc = 0;
+ for (int i = 0; i < argl.Count; i++)
+ {
+ NumberExpression? argin = argl[i] as NumberExpression;
+ if (argin is null)
+ throw new InterpreterTypingException();
+
+ acc += argin.GetValueAsFloat();
+ }
+
+ return NumberFactory.NormalizeFloat(acc);
+ }
+
+ public static NumberExpression Minus(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count == 0)
+ throw new InterpreterInvalidApplicationException();
+
+ NumberExpression? argn0 = argl[0] as NumberExpression;
+ if (argn0 is null)
+ throw new InterpreterTypingException();
+
+ decimal acc = argn0.GetValueAsFloat();
+ if (argl.Count == 1)
+ return NumberFactory.NormalizeFloat(1 - acc);
+
+ for (int i = 1; i < argl.Count; i++)
+ {
+ NumberExpression? argin = argl[i] as NumberExpression;
+ if (argin is null)
+ throw new InterpreterTypingException();
+
+ acc -= argin.GetValueAsFloat();
+ }
+ return NumberFactory.NormalizeFloat(acc);
+ }
+
+ public static NumberExpression Times(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count < 2)
+ throw new InterpreterInvalidInvocationException();
+
+ NumberExpression? arg0 = argl[0] as NumberExpression;
+ if (arg0 is null)
+ throw new InterpreterTypingException();
+
+ decimal f = arg0.GetValueAsFloat();
+ for (int i = 1; i < argl.Count; i++)
+ {
+ NumberExpression? argn = argl[i] as NumberExpression;
+ if (argn is null)
+ throw new InterpreterTypingException();
+
+ f *= argn.GetValueAsFloat();
+ }
+ return NumberFactory.NormalizeFloat(f);
+ }
+
+ public static NumberExpression DividedBy(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ NumberExpression? arg0 = argl[0] as NumberExpression;
+ if (arg0 is null)
+ throw new InterpreterTypingException();
+
+ NumberExpression? arg1 = argl[1] as NumberExpression;
+ if (arg1 is null)
+ throw new InterpreterTypingException();
+
+ return NumberFactory.NormalizeFloat(arg0.GetValueAsFloat() / arg1.GetValueAsFloat());
+ }
+
+ public static NumberExpression Modulus(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ NumberExpression? arg0 = argl[0] as NumberExpression;
+ if (arg0 is null)
+ throw new InterpreterTypingException();
+
+ NumberExpression? arg1 = argl[1] as NumberExpression;
+ if (arg1 is null)
+ throw new InterpreterTypingException();
+
+ return NumberFactory.NormalizeFloat(arg0.GetValueAsFloat() % arg1.GetValueAsFloat());
+ }
+
+
+ public static Expression Quote(ListExpression args, Environment env)
+ {
+ List argl = args.GetMembers();
+ if (argl.Count != 1)
+ throw new InterpreterInvalidApplicationException();
+
+ return argl[0];
+ }
+
+ public static Expression Define(ListExpression args, Environment env)
+ {
+ List argl = args.GetMembers();
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ SymbolExpression? binding = argl[0] as SymbolExpression;
+ if (binding is null)
+ throw new InterpreterTypingException();
+
+ Symbol name = binding.GetSymbol();
+ Expression definition = argl[1].Eval(env);
+ env.AddSymbol(name, definition);
+
+ return definition;
+ }
+
+ public static Expression Car(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 1)
+ throw new InterpreterInvalidApplicationException();
+
+ ListExpression? list = argl[0] as ListExpression;
+ if (list is null)
+ throw new InterpreterTypingException();
+
+ return list.Car();
+ }
+
+ public static ListExpression Cdr(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 1)
+ throw new InterpreterInvalidApplicationException();
+
+ ListExpression? list = argl[0] as ListExpression;
+ if (list is null)
+ throw new InterpreterTypingException();
+
+ return list.Cdr();
+ }
+
+ public static ProperListExpression Cons(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ Expression car = argl[0];
+ ListExpression? cdr = argl[1] as ListExpression;
+ if (cdr is null)
+ throw new InterpreterTypingException();
+
+ return cdr.Cons(car);
+ }
+
+ public static Expression Quit(ListExpression args, Environment env)
+ {
+ System.Environment.Exit(0);
+ return new SymbolExpression(new Symbol("bye"));
+ }
+
+ public static Expression Cond(ListExpression args, Environment env)
+ {
+ List argl = args.GetMembers();
+ for (int i = 0; i < argl.Count; i ++)
+ {
+ ProperListExpression? conditional = argl[i] as ProperListExpression;
+ if (conditional is null)
+ throw new InterpreterTypingException();
+
+ Expression antecedent = conditional.Car();
+ Expression consequent = conditional.Cdr().Car();
+ if (!(antecedent.Eval(env) is NilExpression))
+ return consequent.Eval(env);
+ }
+ return NilExpression.GetInstance();
+ }
+
+ public static Expression Eq(ListExpression args, Environment env)
+ {
+ List argl = args.EvalMembers(env);
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ if (argl[0].Equals(argl[1]))
+ return TrueExpression.GetInstance();
+
+ return NilExpression.GetInstance();
+ }
+
+ public static Expression Set(ListExpression args, Environment env)
+ {
+ List argl = args.GetMembers();
+ if (argl.Count != 2)
+ throw new InterpreterInvalidApplicationException();
+
+ SymbolExpression? binding = argl[0] as SymbolExpression;
+ if (binding is null)
+ throw new InterpreterTypingException();
+
+ Symbol name = binding.GetSymbol();
+ Expression definition = argl[1].Eval(env);
+ env.RedefineSymbol(name, definition);
+
+ return definition;
+ }
+
+ public static Environment RegisterPrimitives()
+ {
+ Environment result = new Environment();
+ result.AddSymbol(new Symbol("lambda"), new ProcedureExpression(new PrimitiveProcedure(Lambda)));
+ result.AddSymbol(new Symbol("quote"), new ProcedureExpression(new PrimitiveProcedure(Quote)));
+ result.AddSymbol(new Symbol("define"), new ProcedureExpression(new PrimitiveProcedure(Define)));
+ result.AddSymbol(new Symbol("car"), new ProcedureExpression(new PrimitiveProcedure(Car)));
+ result.AddSymbol(new Symbol("cdr"), new ProcedureExpression(new PrimitiveProcedure(Cdr)));
+ result.AddSymbol(new Symbol("cons"), new ProcedureExpression(new PrimitiveProcedure(Cons)));
+ result.AddSymbol(new Symbol("quit"), new ProcedureExpression(new PrimitiveProcedure(Quit)));
+ result.AddSymbol(new Symbol("cond"), new ProcedureExpression(new PrimitiveProcedure(Cond)));
+ result.AddSymbol(new Symbol("+"), new ProcedureExpression(new PrimitiveProcedure(Plus)));
+ result.AddSymbol(new Symbol("-"), new ProcedureExpression(new PrimitiveProcedure(Minus)));
+ result.AddSymbol(new Symbol("*"), new ProcedureExpression(new PrimitiveProcedure(Times)));
+ result.AddSymbol(new Symbol("/"), new ProcedureExpression(new PrimitiveProcedure(DividedBy)));
+ result.AddSymbol(new Symbol("%"), new ProcedureExpression(new PrimitiveProcedure(Modulus)));
+ result.AddSymbol(new Symbol("eq"), new ProcedureExpression(new PrimitiveProcedure(Eq)));
+ result.AddSymbol(new Symbol("set!"), new ProcedureExpression(new PrimitiveProcedure(Set)));
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/LispProcedure.cs b/interpreter/LispProcedure.cs
new file mode 100644
index 0000000..6c29359
--- /dev/null
+++ b/interpreter/LispProcedure.cs
@@ -0,0 +1,46 @@
+using System.DirectoryServices;
+
+namespace KumiScript.Interpreter
+{
+ public class LispProcedure : Procedure
+ {
+ readonly List _parameters;
+ readonly Expression _body;
+ readonly Environment _closureEnv;
+ public LispProcedure(List parameters, Expression body, Environment closureEnv)
+ {
+ _parameters = parameters;
+ _body = body;
+ _closureEnv = closureEnv;
+ }
+
+ public override Expression ApplyWithArgs(ListExpression args, Environment env)
+ {
+ List evaluatedArgs = args.EvalMembers(env);
+ if (_parameters.Count != evaluatedArgs.Count)
+ throw new InterpreterInvalidApplicationException();
+
+ Environment inner = MapArgs(evaluatedArgs, _closureEnv);
+ return _body.Eval(inner);
+ }
+
+ private Environment MapArgs(List args, Environment env)
+ {
+ Environment inner = new Environment(env);
+ for (int i = 0; i < _parameters.Count; i++)
+ inner.AddSymbol(_parameters[i], args[i]);
+
+ return inner;
+ }
+
+ public override bool IsPrimitive()
+ {
+ return false;
+ }
+
+ public override string ToString()
+ {
+ return string.Concat("λ(", string.Join(' ', _parameters), ")");
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/PrimitiveProcedure.cs b/interpreter/PrimitiveProcedure.cs
new file mode 100644
index 0000000..925c49b
--- /dev/null
+++ b/interpreter/PrimitiveProcedure.cs
@@ -0,0 +1,27 @@
+namespace KumiScript.Interpreter
+{
+ public class PrimitiveProcedure : Procedure
+ {
+ public delegate Expression PrimitiveDelegate(ListExpression args, Environment env);
+ readonly PrimitiveDelegate _function;
+ public PrimitiveProcedure(PrimitiveDelegate function)
+ {
+ _function = function;
+ }
+
+ public override Expression ApplyWithArgs(ListExpression args, Environment env)
+ {
+ return _function(args, env);
+ }
+
+ public override bool IsPrimitive()
+ {
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return _function.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/Procedure.cs b/interpreter/Procedure.cs
new file mode 100644
index 0000000..5febe60
--- /dev/null
+++ b/interpreter/Procedure.cs
@@ -0,0 +1,12 @@
+namespace KumiScript.Interpreter
+{
+ public abstract class Procedure
+ {
+ public Procedure()
+ {
+ }
+
+ public abstract Expression ApplyWithArgs(ListExpression args, Environment env);
+ public abstract bool IsPrimitive();
+ }
+}
\ No newline at end of file
diff --git a/interpreter/ReadEvalPrintLoop.cs b/interpreter/ReadEvalPrintLoop.cs
new file mode 100644
index 0000000..c5d8f4d
--- /dev/null
+++ b/interpreter/ReadEvalPrintLoop.cs
@@ -0,0 +1,46 @@
+using KumiScript.Reader;
+
+namespace KumiScript.Interpreter
+{
+ public class ReadEvalPrintLoop
+ {
+ Stream _stdin;
+ Stream _stdout;
+ public ReadEvalPrintLoop(Stream stdin, Stream stdout)
+ {
+ _stdin = stdin;
+ _stdout = stdout;
+ }
+
+ public void Loop()
+ {
+ Lexer lexer = new Lexer(_stdin);
+ Parser parser = new Parser(lexer);
+ StreamWriter streamWriter = new StreamWriter(_stdout);
+ Environment top = LispPrimitives.RegisterPrimitives();
+
+ while (true) //this thing is ugly but it's just a little test
+ {
+ Expression expr = parser.NextTopLevelExpression();
+ try
+ {
+ Expression result = expr.Eval(top);
+ streamWriter.Write(string.Format("{0}\n", result.ToString()));
+ streamWriter.Flush();
+ } catch (Exception ex) {
+ if (ex is InterpreterInvalidApplicationException
+ or InterpreterInvalidDefinitionException
+ or InterpreterInvalidInvocationException
+ or InterpreterTypingException
+ or InterpreterUnboundSymbolException)
+ {
+ streamWriter.WriteLine(ex);
+ streamWriter.Flush();
+ continue;
+ }
+ throw;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/Symbol.cs b/interpreter/Symbol.cs
new file mode 100644
index 0000000..e8b137d
--- /dev/null
+++ b/interpreter/Symbol.cs
@@ -0,0 +1,21 @@
+namespace KumiScript.Interpreter
+{
+ public class Symbol
+ {
+ readonly string _name;
+ public Symbol(string name)
+ {
+ _name = name;
+ }
+
+ public bool Equals(Symbol s)
+ {
+ return s.ToString() == _name;
+ }
+
+ public override string ToString()
+ {
+ return _name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/Expression.cs b/interpreter/expression/Expression.cs
new file mode 100644
index 0000000..209fa91
--- /dev/null
+++ b/interpreter/expression/Expression.cs
@@ -0,0 +1,12 @@
+namespace KumiScript.Interpreter
+{
+ public abstract class Expression
+ {
+ public Expression()
+ {
+ }
+
+ public abstract Expression Eval(Environment env);
+ public abstract bool Equals(Expression expr);
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/FloatExpression.cs b/interpreter/expression/FloatExpression.cs
new file mode 100644
index 0000000..8c37a24
--- /dev/null
+++ b/interpreter/expression/FloatExpression.cs
@@ -0,0 +1,41 @@
+using System.Numerics;
+
+namespace KumiScript.Interpreter
+{
+ public class FloatExpression : NumberExpression
+ {
+ readonly decimal _value;
+ public FloatExpression(decimal f)
+ {
+ _value = f;
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ NumberExpression? nexpr = expr as NumberExpression;
+ if (nexpr is null)
+ return false;
+
+ return nexpr.GetValueAsFloat() == _value;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+ public override decimal GetValueAsFloat()
+ {
+ return _value;
+ }
+
+ public override int GetValueAsInt()
+ {
+ return (int) _value;
+ }
+
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/IntegerExpression.cs b/interpreter/expression/IntegerExpression.cs
new file mode 100644
index 0000000..39ffc3e
--- /dev/null
+++ b/interpreter/expression/IntegerExpression.cs
@@ -0,0 +1,42 @@
+using System.Numerics;
+
+namespace KumiScript.Interpreter
+{
+ public class IntegerExpression : NumberExpression
+ {
+ readonly int _value;
+ public IntegerExpression(int n)
+ {
+ _value = n;
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ NumberExpression? nexpr = expr as NumberExpression;
+ if (nexpr is null)
+ return false;
+
+ return nexpr.GetValueAsFloat() == _value;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+
+ public override decimal GetValueAsFloat()
+ {
+ return _value;
+ }
+
+ public override int GetValueAsInt()
+ {
+ return _value;
+ }
+
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/ListExpression.cs b/interpreter/expression/ListExpression.cs
new file mode 100644
index 0000000..6ce8db6
--- /dev/null
+++ b/interpreter/expression/ListExpression.cs
@@ -0,0 +1,15 @@
+namespace KumiScript.Interpreter
+{
+ public abstract class ListExpression : Expression
+ {
+ public ListExpression()
+ {
+ }
+
+ public abstract Expression Car();
+ public abstract ListExpression Cdr();
+ public abstract ProperListExpression Cons(Expression expr);
+ public abstract List EvalMembers(Environment env);
+ public abstract List GetMembers();
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/ListFactory.cs b/interpreter/expression/ListFactory.cs
new file mode 100644
index 0000000..eec0ac1
--- /dev/null
+++ b/interpreter/expression/ListFactory.cs
@@ -0,0 +1,13 @@
+namespace KumiScript.Interpreter
+{
+ public class ListFactory
+ {
+ public static ListExpression MakeList(List expressions)
+ {
+ if (expressions.Any())
+ return new ProperListExpression(expressions);
+
+ return NilExpression.GetInstance();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/NilExpression.cs b/interpreter/expression/NilExpression.cs
new file mode 100644
index 0000000..1c784f1
--- /dev/null
+++ b/interpreter/expression/NilExpression.cs
@@ -0,0 +1,58 @@
+namespace KumiScript.Interpreter
+{
+ public class NilExpression : ListExpression
+ {
+ private static NilExpression? _instance;
+ private NilExpression()
+ {
+ }
+
+ public static NilExpression GetInstance()
+ {
+ if (_instance is null)
+ _instance = new NilExpression();
+
+ return _instance;
+ }
+
+ public override Expression Car()
+ {
+ return this;
+ }
+
+ public override ListExpression Cdr()
+ {
+ return this;
+ }
+
+ public override ProperListExpression Cons(Expression expr)
+ {
+ return new ProperListExpression(new List {expr});
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ return expr is NilExpression;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+
+ public override List EvalMembers(Environment env)
+ {
+ return new List();
+ }
+
+ public override List GetMembers()
+ {
+ return new List();
+ }
+
+ public override string ToString()
+ {
+ return "null";
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/NumberExpression.cs b/interpreter/expression/NumberExpression.cs
new file mode 100644
index 0000000..60ead07
--- /dev/null
+++ b/interpreter/expression/NumberExpression.cs
@@ -0,0 +1,12 @@
+namespace KumiScript.Interpreter
+{
+ public abstract class NumberExpression : Expression
+ {
+ public NumberExpression()
+ {
+ }
+
+ public abstract decimal GetValueAsFloat();
+ public abstract int GetValueAsInt();
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/NumberFactory.cs b/interpreter/expression/NumberFactory.cs
new file mode 100644
index 0000000..0cd5576
--- /dev/null
+++ b/interpreter/expression/NumberFactory.cs
@@ -0,0 +1,15 @@
+namespace KumiScript.Interpreter
+{
+ public class NumberFactory
+ {
+ public static NumberExpression NormalizeFloat(decimal f)
+ {
+ //TODO: Handle overflows
+ int i = (int) f;
+ if (i == f)
+ return new IntegerExpression(i);
+
+ return new FloatExpression(f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/ProcedureExpression.cs b/interpreter/expression/ProcedureExpression.cs
new file mode 100644
index 0000000..9caf636
--- /dev/null
+++ b/interpreter/expression/ProcedureExpression.cs
@@ -0,0 +1,51 @@
+namespace KumiScript.Interpreter
+{
+ public class ProcedureExpression : Expression
+ {
+ readonly Procedure _proc;
+ readonly bool _branching;
+ public ProcedureExpression(Procedure p)
+ {
+ _proc = p;
+ _branching = false;
+ }
+
+ public ProcedureExpression(Procedure p, bool branching)
+ {
+ _proc = p;
+ _branching = branching;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+
+ public Expression Apply(ListExpression args, Environment env)
+ {
+ return _proc.ApplyWithArgs(args, env);
+ }
+
+ public override string ToString()
+ {
+ if (_proc.IsPrimitive())
+ return string.Format("#Primitive<{0}>", _proc.ToString());
+
+ return string.Format("#Procedure<{0}>", _proc.ToString());
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ ProcedureExpression? pe = expr as ProcedureExpression;
+ if (pe is null)
+ return false;
+
+ return _proc.Equals(pe._proc);
+ }
+
+ public Procedure GetProcedure()
+ {
+ return _proc;
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/ProperListExpression.cs b/interpreter/expression/ProperListExpression.cs
new file mode 100644
index 0000000..3236e95
--- /dev/null
+++ b/interpreter/expression/ProperListExpression.cs
@@ -0,0 +1,76 @@
+namespace KumiScript.Interpreter
+{
+ public class ProperListExpression : ListExpression
+ {
+ readonly List _values;
+ public ProperListExpression(List expressions)
+ {
+ _values = expressions;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ ProcedureExpression? rator = Car().Eval(env) as ProcedureExpression;
+ if (rator is null)
+ throw new InterpreterInvalidInvocationException();
+
+ ListExpression rand = Cdr();
+ return rator.Apply(rand, env);
+ }
+
+ public override List EvalMembers(Environment env)
+ {
+ List result = new List();
+ for (int i = 0; i < _values.Count; i++)
+ result.Add(_values[i].Eval(env));
+
+ return result;
+ }
+
+ public override List GetMembers()
+ {
+ return _values;
+ }
+
+ public override string ToString()
+ {
+ return String.Concat("(",
+ String.Join(' ', (object[]) _values.ToArray()),
+ ")");
+ }
+
+ public override Expression Car()
+ {
+ if (_values.Count == 0)
+ return this;
+
+ return _values.First();
+ }
+
+ public override ListExpression Cdr()
+ {
+ if (_values.Count == 0)
+ return this;
+
+ List rest = new List(_values);
+ rest.RemoveAt(0);
+ return ListFactory.MakeList(rest);
+ }
+
+ public override ProperListExpression Cons(Expression expr)
+ {
+ List consd = new List(_values);
+ consd.Insert(0, expr);
+ return new ProperListExpression(consd);
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ ProperListExpression? lexpr = expr as ProperListExpression;
+ if (lexpr is null)
+ return false;
+
+ return lexpr.GetMembers().SequenceEqual(_values);
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/StringExpression.cs b/interpreter/expression/StringExpression.cs
new file mode 100644
index 0000000..5fec1e2
--- /dev/null
+++ b/interpreter/expression/StringExpression.cs
@@ -0,0 +1,30 @@
+namespace KumiScript.Interpreter
+{
+ public class StringExpression : Expression
+ {
+ string _value;
+ public StringExpression(string str)
+ {
+ _value = str;
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ StringExpression? se = expr as StringExpression;
+ if (se is null)
+ return false;
+
+ return _value == se._value;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+
+ public override string ToString()
+ {
+ return _value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/SymbolExpression.cs b/interpreter/expression/SymbolExpression.cs
new file mode 100644
index 0000000..feee1b6
--- /dev/null
+++ b/interpreter/expression/SymbolExpression.cs
@@ -0,0 +1,35 @@
+namespace KumiScript.Interpreter
+{
+ public class SymbolExpression : Expression
+ {
+ readonly Symbol _value;
+ public SymbolExpression(Symbol s)
+ {
+ _value = s;
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ SymbolExpression? sy = expr as SymbolExpression;
+ if (sy is null)
+ return false;
+
+ return _value.Equals(sy._value);
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return env.Lookup(_value);
+ }
+
+ public Symbol GetSymbol()
+ {
+ return _value;
+ }
+
+ public override string ToString()
+ {
+ return _value.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/interpreter/expression/TrueExpression.cs b/interpreter/expression/TrueExpression.cs
new file mode 100644
index 0000000..7f9f027
--- /dev/null
+++ b/interpreter/expression/TrueExpression.cs
@@ -0,0 +1,33 @@
+namespace KumiScript.Interpreter
+{
+ public class TrueExpression : Expression
+ {
+ private static TrueExpression? _instance;
+ private TrueExpression()
+ {
+ }
+
+ public static TrueExpression GetInstance()
+ {
+ if (_instance is null)
+ _instance = new TrueExpression();
+
+ return _instance;
+ }
+
+ public override bool Equals(Expression expr)
+ {
+ return expr is TrueExpression;
+ }
+
+ public override Expression Eval(Environment env)
+ {
+ return this;
+ }
+
+ public override string ToString()
+ {
+ return "t";
+ }
+ }
+}
\ No newline at end of file
diff --git a/loader/KSMetaParser.cs b/loader/KSMetaParser.cs
new file mode 100644
index 0000000..37ce4b2
--- /dev/null
+++ b/loader/KSMetaParser.cs
@@ -0,0 +1,43 @@
+using System.Text.RegularExpressions;
+
+namespace KumiScript.Loader
+{
+ public class KSMetaParser
+ {
+ readonly string _path;
+ readonly StreamReader _streamReader;
+ readonly Dictionary _properties;
+ public KSMetaParser(string path)
+ {
+ _path = path;
+ _streamReader = new StreamReader(File.OpenRead(path));
+ _properties = new Dictionary(8);
+ ParseFile();
+ }
+
+ private void ParseFile()
+ {
+ while (!_streamReader.EndOfStream)
+ {
+ string? line = _streamReader.ReadLine();
+ if (line is null)
+ continue;
+
+ line = Regex.Replace(line, "\\s+", string.Empty);
+ string[] kvp = line.Split(":", 2);
+ int v;
+ if (!int.TryParse(kvp[1], out v))
+ throw new Exception("Bad file!");
+
+ _properties.Add(kvp[0], v);
+ }
+ }
+
+ public int GetAttribute(string name)
+ {
+ int v = 0;
+ _properties.TryGetValue(name, out v);
+ return v;
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/AtomToken.cs b/parser/AtomToken.cs
new file mode 100644
index 0000000..a09639b
--- /dev/null
+++ b/parser/AtomToken.cs
@@ -0,0 +1,38 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class AtomToken : Token
+ {
+ readonly string _value;
+ public AtomToken(string value)
+ {
+ _value = value;
+ }
+
+ public override Expression Accept(ITokenVisitor tokenVisitor)
+ {
+ return tokenVisitor.VisitAtom(this);
+ }
+
+ public virtual Expression ToExpression()
+ {
+ decimal f;
+ if (decimal.TryParse(_value, out f))
+ return NumberFactory.NormalizeFloat(f);
+
+ if (_value == "null")
+ return NilExpression.GetInstance();
+
+ if (_value == "t")
+ return TrueExpression.GetInstance();
+
+ return new SymbolExpression(new Symbol(_value));
+ }
+
+ public override string GetValue()
+ {
+ return _value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/EndOfFileToken.cs b/parser/EndOfFileToken.cs
new file mode 100644
index 0000000..5536bbc
--- /dev/null
+++ b/parser/EndOfFileToken.cs
@@ -0,0 +1,21 @@
+using KumiScript.Reader;
+
+namespace KumiScript.Interpreter
+{
+ public class EndOfFileToken : Token
+ {
+ public EndOfFileToken()
+ {
+ }
+
+ public override Expression Accept(ITokenVisitor tokenVisitor)
+ {
+ return tokenVisitor.VisitEoF(this);
+ }
+
+ public override string GetValue()
+ {
+ return "EOF";
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/Lexer.cs b/parser/Lexer.cs
new file mode 100644
index 0000000..ee838df
--- /dev/null
+++ b/parser/Lexer.cs
@@ -0,0 +1,151 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class Lexer
+ {
+ Stack _tokenStack;
+ readonly StreamReader _streamReader;
+
+ public Lexer(Stream stream)
+ {
+ _tokenStack = new Stack(16);
+ _streamReader = new StreamReader(stream);
+ }
+
+ public Token NextToken()
+ {
+ if (_tokenStack.IsEmpty())
+ return LexFromStream();
+
+ return _tokenStack.Pop();
+ }
+
+ public Token PeekToken()
+ {
+ Token t = NextToken();
+ PushToken(t);
+ return t;
+ }
+
+ public void PushToken(Token token)
+ {
+ _tokenStack.Push(token);
+ return;
+ }
+
+ private Token LexFromStream()
+ {
+ int c = GetChar();
+
+ while (char.IsWhiteSpace((char) c))
+ c = GetChar();
+
+ if (c == ';')
+ {
+ while ((c = GetChar()) != '\n');
+ c = GetChar();
+ }
+
+ if (c == -1)
+ return new EndOfFileToken();
+ if (c == '(')
+ return new ParenthesisToken(true);
+ if (c == ')')
+ return new ParenthesisToken(false);
+ if (c == '"')
+ return LexString();
+ if (isspec(c))
+ return new SpecialToken((char) c);
+
+ return LexGeneric(c);
+ }
+
+ private Token LexString()
+ {
+ List chars = new List();
+ int c = GetChar();
+
+ while (c != '"')
+ {
+ if (c == '\\')
+ c = GetChar();
+
+ chars.Add((char) c);
+ c = GetChar();
+ if (c == -1)
+ throw new Exception("Unexpected EOF!");
+ }
+
+ return new StringToken(new string(chars.ToArray()));
+ }
+
+ private Token LexGeneric(int c)
+ {
+ List chars = new List();
+ chars.Add((char) c); //NOTE: The language server is WRONG! List((char) c) BREAKS! **** you m$
+
+ int p = PeekChar();
+ while (!char.IsWhiteSpace((char) p) && !isspecparen(p) && p != -1)
+ {
+ c = GetChar();
+ chars.Add((char) c);
+ p = PeekChar();
+ }
+
+ return new AtomToken(new string(chars.ToArray()));
+ }
+
+ private static bool isspec(int c)
+ {
+ switch (c)
+ {
+ case '#':
+ return true;
+ case '\'':
+ return true;
+ case '\\':
+ return true;
+ case '`':
+ return true;
+ case ',':
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool isspecparen(int c)
+ {
+ switch (c)
+ {
+ case '(':
+ return true;
+ case ')':
+ return true;
+ case '#':
+ return true;
+ case '\'':
+ return true;
+ case '\\':
+ return true;
+ case '`':
+ return true;
+ case ',':
+ return true;
+ }
+
+ return false;
+ }
+
+ private int GetChar()
+ {
+ return _streamReader.Read();
+ }
+
+ private int PeekChar()
+ {
+ return _streamReader.Peek();
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/ParenthesisToken.cs b/parser/ParenthesisToken.cs
new file mode 100644
index 0000000..ddb1f3c
--- /dev/null
+++ b/parser/ParenthesisToken.cs
@@ -0,0 +1,25 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class ParenthesisToken : Token
+ {
+ public readonly bool _leftParen;
+ public ParenthesisToken(bool lp)
+ {
+ _leftParen = lp;
+ }
+
+ public override Expression Accept(ITokenVisitor tokenVisitor)
+ {
+ return tokenVisitor.VisitParen(this);
+ }
+
+ public override string GetValue()
+ {
+ if (_leftParen)
+ return "(";
+ return ")";
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/Parser.cs b/parser/Parser.cs
new file mode 100644
index 0000000..0a69cc0
--- /dev/null
+++ b/parser/Parser.cs
@@ -0,0 +1,25 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class Parser
+ {
+ Lexer _lexer;
+ public Parser(Lexer lexer)
+ {
+ _lexer = lexer;
+ }
+
+ public Expression NextTopLevelExpression()
+ {
+ Token t = _lexer.NextToken();
+ return t.Accept(new ParserTopLevelVisitor(this));
+ }
+
+ public Expression NextExpressionCC(ITokenVisitor visitor)
+ {
+ Token t = _lexer.NextToken();
+ return t.Accept(visitor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/ParserExceptions.cs b/parser/ParserExceptions.cs
new file mode 100644
index 0000000..cbef5c9
--- /dev/null
+++ b/parser/ParserExceptions.cs
@@ -0,0 +1,62 @@
+using System.Runtime.Serialization;
+
+namespace KumiScript.Reader
+{
+ [Serializable]
+ internal class ParserEndOfFileException : Exception
+ {
+ public ParserEndOfFileException()
+ {
+ }
+
+ public ParserEndOfFileException(string? message) : base(message)
+ {
+ }
+
+ public ParserEndOfFileException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected ParserEndOfFileException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+ internal class ParserUnexpectedTokenException : Exception
+ {
+ public ParserUnexpectedTokenException()
+ {
+ }
+
+ public ParserUnexpectedTokenException(string? message) : base(message)
+ {
+ }
+
+ public ParserUnexpectedTokenException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected ParserUnexpectedTokenException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+
+ internal class ParserUnexpectedEndOfFileException : Exception
+ {
+ public ParserUnexpectedEndOfFileException()
+ {
+ }
+
+ public ParserUnexpectedEndOfFileException(string? message) : base(message)
+ {
+ }
+
+ public ParserUnexpectedEndOfFileException(string? message, Exception? innerException) : base(message, innerException)
+ {
+ }
+
+ protected ParserUnexpectedEndOfFileException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/ParserListVisitor.cs b/parser/ParserListVisitor.cs
new file mode 100644
index 0000000..e79a606
--- /dev/null
+++ b/parser/ParserListVisitor.cs
@@ -0,0 +1,53 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class ParserListVisitor : ITokenVisitor
+ {
+ Parser _parser;
+ public ParserListVisitor(Parser parser)
+ {
+ _parser = parser;
+ }
+
+ public Expression VisitAtom(AtomToken atom)
+ {
+ return atom.ToExpression();
+ }
+
+ public Expression VisitString(StringToken str)
+ {
+ return str.ToExpression();
+ }
+
+ public Expression VisitEoF(EndOfFileToken eof)
+ {
+ throw new ParserUnexpectedEndOfFileException("Expected ')' before end of file.");
+ }
+
+ public Expression VisitParen(ParenthesisToken paren)
+ {
+ if (!paren._leftParen)
+ return null; //TODO: some other way of throwing it back
+
+ List list = new List();
+ Expression item = _parser.NextExpressionCC(this);
+
+ while (item is not null)
+ {
+ list.Add(item);
+ item = _parser.NextExpressionCC(this);
+ }
+
+ return ListFactory.MakeList(list);
+ }
+
+ public Expression VisitSpecial(SpecialToken spec)
+ {
+ if (spec._value == '\'')
+ return _parser.NextExpressionCC(new ParserQuoteVisitor(_parser));
+
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/ParserQuoteVisitor.cs b/parser/ParserQuoteVisitor.cs
new file mode 100644
index 0000000..1a35b99
--- /dev/null
+++ b/parser/ParserQuoteVisitor.cs
@@ -0,0 +1,65 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class ParserQuoteVisitor : ITokenVisitor
+ {
+ Parser _parser;
+ public ParserQuoteVisitor(Parser parser)
+ {
+ _parser = parser;
+ }
+
+ public Expression VisitAtom(AtomToken atom)
+ {
+ List quotedAtom = new List
+ {
+ new SymbolExpression(new Symbol("quote")),
+ atom.ToExpression()
+ };
+ return new ProperListExpression(quotedAtom);
+ }
+
+ public Expression VisitEoF(EndOfFileToken eof)
+ {
+ throw new ParserUnexpectedEndOfFileException("Expected , , got EOF.");
+ }
+
+ public Expression VisitParen(ParenthesisToken paren)
+ {
+ if (!paren._leftParen)
+ throw new ParserUnexpectedTokenException("Unexpected Token ')'! Wanted , '('.");
+
+ List list = new List();
+ ParserListVisitor listVisitor = new ParserListVisitor(_parser);
+ Expression item = _parser.NextExpressionCC(listVisitor);
+ while (item is not null)
+ {
+ list.Add(item);
+ item = _parser.NextExpressionCC(listVisitor);
+ }
+ Expression listExpression = ListFactory.MakeList(list);
+ List quotedList = new List
+ {
+ new SymbolExpression(new Symbol("quote")),
+ listExpression
+ };
+ return new ProperListExpression(quotedList);
+ }
+
+ public Expression VisitSpecial(SpecialToken spec)
+ {
+ throw new ParserUnexpectedTokenException("Expected , , got SPECIAL.");
+ }
+
+ public Expression VisitString(StringToken str)
+ {
+ List quotedString = new List
+ {
+ new SymbolExpression(new Symbol("quote")),
+ str.ToExpression()
+ };
+ return new ProperListExpression(quotedString);
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/ParserTopLevelVisitor.cs b/parser/ParserTopLevelVisitor.cs
new file mode 100644
index 0000000..eb1734e
--- /dev/null
+++ b/parser/ParserTopLevelVisitor.cs
@@ -0,0 +1,54 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class ParserTopLevelVisitor : ITokenVisitor
+ {
+ Parser _parser;
+ public ParserTopLevelVisitor(Parser parser)
+ {
+ _parser = parser;
+ }
+
+ public Expression VisitAtom(AtomToken atom)
+ {
+ return atom.ToExpression();
+ }
+
+ public Expression VisitString(StringToken str)
+ {
+ return str.ToExpression();
+ }
+
+ public Expression VisitEoF(EndOfFileToken eof)
+ {
+ throw new ParserEndOfFileException();
+ }
+
+ public Expression VisitParen(ParenthesisToken paren)
+ {
+ if (!paren._leftParen)
+ throw new ParserUnexpectedTokenException("Unexpected Token ')'! Wanted ATOM, '(', or EOF.");
+
+ List list = new List();
+ ParserListVisitor listVisitor = new ParserListVisitor(_parser);
+ Expression item = _parser.NextExpressionCC(listVisitor);
+
+ while (item is not null)
+ {
+ list.Add(item);
+ item = _parser.NextExpressionCC(listVisitor);
+ }
+
+ return ListFactory.MakeList(list);
+ }
+
+ public Expression VisitSpecial(SpecialToken spec)
+ {
+ if (spec._value == '\'')
+ return _parser.NextExpressionCC(new ParserQuoteVisitor(_parser));
+
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/SpecialToken.cs b/parser/SpecialToken.cs
new file mode 100644
index 0000000..a13d3d8
--- /dev/null
+++ b/parser/SpecialToken.cs
@@ -0,0 +1,23 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class SpecialToken : Token
+ {
+ public readonly char _value;
+ public SpecialToken(char value)
+ {
+ _value = value;
+ }
+
+ public override Expression Accept(ITokenVisitor tokenVisitor)
+ {
+ return tokenVisitor.VisitSpecial(this);
+ }
+
+ public override string GetValue()
+ {
+ return _value.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/Stack.cs b/parser/Stack.cs
new file mode 100644
index 0000000..89c5682
--- /dev/null
+++ b/parser/Stack.cs
@@ -0,0 +1,39 @@
+namespace KumiScript.Reader
+{
+
+ public class Stack
+ {
+ List _values;
+
+ public Stack(int size)
+ {
+ _values = new List(size);
+ }
+
+ public T Pop()
+ {
+ if (!_values.Any())
+ throw new Exception("Stack empty!");
+
+ T last = _values.Last();
+ _values.Remove(last);
+ return last;
+ }
+
+ public bool Push(T item)
+ {
+ _values.Add(item);
+ return true;
+ }
+
+ public bool IsEmpty()
+ {
+ return !_values.Any();
+ }
+
+ public List ToList()
+ {
+ return _values;
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/StringToken.cs b/parser/StringToken.cs
new file mode 100644
index 0000000..94dc73a
--- /dev/null
+++ b/parser/StringToken.cs
@@ -0,0 +1,23 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public class StringToken : AtomToken
+ {
+ readonly string _value;
+ public StringToken(string value) : base (value)
+ {
+ _value = value;
+ }
+
+ public override Expression ToExpression()
+ {
+ return new StringExpression(_value);
+ }
+
+ public override string GetValue()
+ {
+ return _value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/parser/Token.cs b/parser/Token.cs
new file mode 100644
index 0000000..5673fe5
--- /dev/null
+++ b/parser/Token.cs
@@ -0,0 +1,10 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public abstract class Token
+ {
+ public abstract String GetValue();
+ public abstract Expression Accept(ITokenVisitor tokenVisitor);
+ }
+}
\ No newline at end of file
diff --git a/parser/TokenVisitor.cs b/parser/TokenVisitor.cs
new file mode 100644
index 0000000..75db59a
--- /dev/null
+++ b/parser/TokenVisitor.cs
@@ -0,0 +1,13 @@
+using KumiScript.Interpreter;
+
+namespace KumiScript.Reader
+{
+ public interface ITokenVisitor
+ {
+ Expression VisitEoF(EndOfFileToken eof);
+ Expression VisitAtom(AtomToken atom);
+ Expression VisitString(StringToken str);
+ Expression VisitParen(ParenthesisToken paren);
+ Expression VisitSpecial(SpecialToken spec);
+ }
+}
\ No newline at end of file
diff --git a/renderer/Background.cs b/renderer/Background.cs
new file mode 100644
index 0000000..32557ab
--- /dev/null
+++ b/renderer/Background.cs
@@ -0,0 +1,44 @@
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class Background : IDrawable
+ {
+ SDLRenderer _renderer;
+ Image _image;
+ int _height;
+ int _width;
+
+ public Background(string path, SDLRenderer renderer)
+ {
+ _renderer = renderer;
+ _image = new Image(path, renderer);
+ }
+
+ public void Draw(int x, int y)
+ {
+ SDL.SDL_RenderCopy(_renderer.id, _image.GetTexture().id, 0, 0);
+ }
+
+ public int GetBitmapHeight()
+ {
+ return _height;
+ }
+
+ public int GetBitmapWidth()
+ {
+ return _width;
+ }
+
+ public int GetXOffset()
+ {
+ return 0;
+ }
+
+ public int GetYOffset()
+ {
+ return 0;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/renderer/IDrawable.cs b/renderer/IDrawable.cs
new file mode 100644
index 0000000..637760e
--- /dev/null
+++ b/renderer/IDrawable.cs
@@ -0,0 +1,11 @@
+namespace KumiScript.Renderer
+{
+ public interface IDrawable
+ {
+ void Draw(int x, int y);
+ int GetBitmapWidth();
+ int GetBitmapHeight();
+ int GetXOffset();
+ int GetYOffset();
+ }
+}
\ No newline at end of file
diff --git a/renderer/Image.cs b/renderer/Image.cs
new file mode 100644
index 0000000..aa15c79
--- /dev/null
+++ b/renderer/Image.cs
@@ -0,0 +1,21 @@
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class Image
+ {
+ SDLRenderer _renderer;
+ SDLTexture _texture;
+
+ public Image(string path, SDLRenderer renderer)
+ {
+ _renderer = renderer;
+ _texture = new SDLTexture(path, renderer);
+ }
+
+ public SDLTexture GetTexture()
+ {
+ return _texture;
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/Scene.cs b/renderer/Scene.cs
new file mode 100644
index 0000000..d854bdf
--- /dev/null
+++ b/renderer/Scene.cs
@@ -0,0 +1,32 @@
+namespace KumiScript.Renderer
+{
+ public class Scene
+ {
+ SDLRenderer _renderer;
+ List _elements;
+ public Scene(SDLRenderer renderer)
+ {
+ _renderer = renderer;
+ _elements = new List(16);
+ }
+
+ public void DrawScene()
+ {
+ foreach (SceneElement e in _elements)
+ {
+ e.Draw(0, 0);
+ }
+ }
+
+ public void AddElement(SceneElement e)
+ {
+ _elements.Add(e);
+ return;
+ }
+
+ public bool RemoveElement(SceneElement e)
+ {
+ return _elements.Remove(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/SceneElement.cs b/renderer/SceneElement.cs
new file mode 100644
index 0000000..2ec08a1
--- /dev/null
+++ b/renderer/SceneElement.cs
@@ -0,0 +1,21 @@
+namespace KumiScript.Renderer
+{
+ public class SceneElement
+ {
+ IDrawable _elem;
+ int _x;
+ int _y;
+ public SceneElement(IDrawable elem, int xPos, int yPos)
+ {
+ _elem = elem;
+ _x = xPos;
+ _y = yPos;
+ }
+
+ public void Draw(int sceneX, int sceneY)
+ {
+ _elem.Draw(_x + sceneX, _y + sceneY);
+ return;
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/Sprite.cs b/renderer/Sprite.cs
new file mode 100644
index 0000000..ca50819
--- /dev/null
+++ b/renderer/Sprite.cs
@@ -0,0 +1,56 @@
+using System.Text.Json.Serialization;
+using KumiScript.Loader;
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class Sprite : IDrawable
+ {
+ readonly SDLRenderer _renderer;
+ readonly Image _image;
+ SDL.SDL_Rect _drawCoords;
+ readonly int _height;
+ readonly int _width;
+ readonly int _xOffset;
+ readonly int _yOffset;
+ public Sprite(string path, SDLRenderer renderer)
+ {
+ _renderer = renderer;
+ _image = new Image(path, renderer);
+ KSMetaParser parser = new KSMetaParser(string.Concat(path, ".ksmeta"));
+ _height = parser.GetAttribute("height");
+ _width = parser.GetAttribute("width");
+ _xOffset = parser.GetAttribute("xOffset");
+ _yOffset = parser.GetAttribute("yOffset");
+ _drawCoords.w = _width;
+ _drawCoords.h = _height;
+ }
+
+ public void Draw(int x, int y)
+ {
+ _drawCoords.x = x + _xOffset;
+ _drawCoords.y = y + _yOffset;
+ SDL.SDL_RenderCopy(_renderer.id, _image.GetTexture().id, 0, ref _drawCoords);
+ }
+
+ public int GetBitmapHeight()
+ {
+ return _height;
+ }
+
+ public int GetBitmapWidth()
+ {
+ return _width;
+ }
+
+ public int GetXOffset()
+ {
+ return _xOffset;
+ }
+
+ public int GetYOffset()
+ {
+ return _yOffset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/sdl/SDLRenderer.cs b/renderer/sdl/SDLRenderer.cs
new file mode 100644
index 0000000..6fc15b2
--- /dev/null
+++ b/renderer/sdl/SDLRenderer.cs
@@ -0,0 +1,25 @@
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class SDLRenderer
+ {
+ internal readonly nint id;
+ internal SDLRenderer(SDLWindow window)
+ {
+ id = SDL.SDL_CreateRenderer(window.Id, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED);
+ if (id == 0)
+ throw new Exception("Failed to create renderer!");
+ }
+
+ public void Clear()
+ {
+ SDL.SDL_RenderClear(id);
+ }
+
+ public void SwapBuffers()
+ {
+ SDL.SDL_RenderPresent(id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/sdl/SDLTexture.cs b/renderer/sdl/SDLTexture.cs
new file mode 100644
index 0000000..c086c90
--- /dev/null
+++ b/renderer/sdl/SDLTexture.cs
@@ -0,0 +1,26 @@
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class SDLTexture : IDisposable
+ {
+ internal readonly nint id;
+ public SDLTexture(string path, SDLRenderer renderer)
+ {
+ nint surface = SDL_image.IMG_Load(path);
+ if (surface == 0)
+ throw new Exception(SDL_image.IMG_GetError());
+
+ nint texture = SDL.SDL_CreateTextureFromSurface(renderer.id, surface);
+ if (texture == 0)
+ throw new Exception(SDL.SDL_GetError());
+
+ SDL.SDL_FreeSurface(surface);
+ id = texture;
+ }
+ public void Dispose()
+ {
+ SDL.SDL_DestroyTexture(id);
+ }
+ }
+}
\ No newline at end of file
diff --git a/renderer/sdl/SDLWindow.cs b/renderer/sdl/SDLWindow.cs
new file mode 100644
index 0000000..a1f6bd2
--- /dev/null
+++ b/renderer/sdl/SDLWindow.cs
@@ -0,0 +1,39 @@
+using SDL2;
+
+namespace KumiScript.Renderer
+{
+ public class SDLWindow
+ {
+ internal readonly nint Id;
+ ushort _width;
+ ushort _height;
+ string _title;
+ SDLRenderer? _renderer;
+ public SDLWindow(ushort horizontalResolution, ushort verticalResolution, string windowTitle)
+ {
+ _width = horizontalResolution;
+ _height = verticalResolution;
+ _title = windowTitle;
+
+ nint window = SDL.SDL_CreateWindow(windowTitle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED,
+ horizontalResolution, verticalResolution, 0);
+ if (window == 0)
+ throw new Exception("Failed to create window!");
+
+ Id = window;
+ }
+
+ public int UpdateSurface()
+ {
+ return SDL.SDL_UpdateWindowSurface(Id);
+ }
+
+ public SDLRenderer GetRenderer()
+ {
+ if (_renderer is null)
+ _renderer = new SDLRenderer(this);
+
+ return _renderer;
+ }
+ }
+}
\ No newline at end of file