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