From b3363d3f5dba7d394bcefad7acaa0ea443e962ea Mon Sep 17 00:00:00 2001 From: Mikhail <68438915+mikhailkfv@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:37:51 -0600 Subject: [PATCH] real initial commit --- .gitignore | 7 + Engine.csproj | 16 ++ Engine.csproj.user | 4 + Engine.sln | 25 ++ Game.cs | 51 ++++ LICENSE.txt | 0 Program.cs | 14 + interpreter/Environment.cs | 83 ++++++ interpreter/InterpreterException.cs | 101 +++++++ interpreter/LispPrimitives.cs | 267 ++++++++++++++++++ interpreter/LispProcedure.cs | 46 +++ interpreter/PrimitiveProcedure.cs | 27 ++ interpreter/Procedure.cs | 12 + interpreter/ReadEvalPrintLoop.cs | 46 +++ interpreter/Symbol.cs | 21 ++ interpreter/expression/Expression.cs | 12 + interpreter/expression/FloatExpression.cs | 41 +++ interpreter/expression/IntegerExpression.cs | 42 +++ interpreter/expression/ListExpression.cs | 15 + interpreter/expression/ListFactory.cs | 13 + interpreter/expression/NilExpression.cs | 58 ++++ interpreter/expression/NumberExpression.cs | 12 + interpreter/expression/NumberFactory.cs | 15 + interpreter/expression/ProcedureExpression.cs | 51 ++++ .../expression/ProperListExpression.cs | 76 +++++ interpreter/expression/StringExpression.cs | 30 ++ interpreter/expression/SymbolExpression.cs | 35 +++ interpreter/expression/TrueExpression.cs | 33 +++ loader/KSMetaParser.cs | 43 +++ parser/AtomToken.cs | 38 +++ parser/EndOfFileToken.cs | 21 ++ parser/Lexer.cs | 151 ++++++++++ parser/ParenthesisToken.cs | 25 ++ parser/Parser.cs | 25 ++ parser/ParserExceptions.cs | 62 ++++ parser/ParserListVisitor.cs | 53 ++++ parser/ParserQuoteVisitor.cs | 65 +++++ parser/ParserTopLevelVisitor.cs | 54 ++++ parser/SpecialToken.cs | 23 ++ parser/Stack.cs | 39 +++ parser/StringToken.cs | 23 ++ parser/Token.cs | 10 + parser/TokenVisitor.cs | 13 + renderer/Background.cs | 44 +++ renderer/IDrawable.cs | 11 + renderer/Image.cs | 21 ++ renderer/Scene.cs | 32 +++ renderer/SceneElement.cs | 21 ++ renderer/Sprite.cs | 56 ++++ renderer/sdl/SDLRenderer.cs | 25 ++ renderer/sdl/SDLTexture.cs | 26 ++ renderer/sdl/SDLWindow.cs | 39 +++ 52 files changed, 2073 insertions(+) create mode 100644 .gitignore create mode 100644 Engine.csproj create mode 100644 Engine.csproj.user create mode 100644 Engine.sln create mode 100644 Game.cs create mode 100644 LICENSE.txt create mode 100644 Program.cs create mode 100644 interpreter/Environment.cs create mode 100644 interpreter/InterpreterException.cs create mode 100644 interpreter/LispPrimitives.cs create mode 100644 interpreter/LispProcedure.cs create mode 100644 interpreter/PrimitiveProcedure.cs create mode 100644 interpreter/Procedure.cs create mode 100644 interpreter/ReadEvalPrintLoop.cs create mode 100644 interpreter/Symbol.cs create mode 100644 interpreter/expression/Expression.cs create mode 100644 interpreter/expression/FloatExpression.cs create mode 100644 interpreter/expression/IntegerExpression.cs create mode 100644 interpreter/expression/ListExpression.cs create mode 100644 interpreter/expression/ListFactory.cs create mode 100644 interpreter/expression/NilExpression.cs create mode 100644 interpreter/expression/NumberExpression.cs create mode 100644 interpreter/expression/NumberFactory.cs create mode 100644 interpreter/expression/ProcedureExpression.cs create mode 100644 interpreter/expression/ProperListExpression.cs create mode 100644 interpreter/expression/StringExpression.cs create mode 100644 interpreter/expression/SymbolExpression.cs create mode 100644 interpreter/expression/TrueExpression.cs create mode 100644 loader/KSMetaParser.cs create mode 100644 parser/AtomToken.cs create mode 100644 parser/EndOfFileToken.cs create mode 100644 parser/Lexer.cs create mode 100644 parser/ParenthesisToken.cs create mode 100644 parser/Parser.cs create mode 100644 parser/ParserExceptions.cs create mode 100644 parser/ParserListVisitor.cs create mode 100644 parser/ParserQuoteVisitor.cs create mode 100644 parser/ParserTopLevelVisitor.cs create mode 100644 parser/SpecialToken.cs create mode 100644 parser/Stack.cs create mode 100644 parser/StringToken.cs create mode 100644 parser/Token.cs create mode 100644 parser/TokenVisitor.cs create mode 100644 renderer/Background.cs create mode 100644 renderer/IDrawable.cs create mode 100644 renderer/Image.cs create mode 100644 renderer/Scene.cs create mode 100644 renderer/SceneElement.cs create mode 100644 renderer/Sprite.cs create mode 100644 renderer/sdl/SDLRenderer.cs create mode 100644 renderer/sdl/SDLTexture.cs create mode 100644 renderer/sdl/SDLWindow.cs 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