From ac9d4b15d1dcc4d2124c3f2d4f3178763e86cd6c Mon Sep 17 00:00:00 2001 From: Ludovic 'Archivist' Lagouardette Date: Wed, 20 Mar 2024 22:23:56 +0100 Subject: [PATCH] Added a primitive scripting system --- Expansion2/RaylibAssetManagerModule.cs | 11 ++ Expansion4/Commands/ICommand.cs | 85 +++++++++++ Expansion4/Commands/IScriptable.cs | 54 +++++++ Expansion4/ScriptComponent.cs | 192 +++++++++++++++++++++++++ 4 files changed, 342 insertions(+) create mode 100644 Expansion4/Commands/ICommand.cs create mode 100644 Expansion4/Commands/IScriptable.cs create mode 100644 Expansion4/ScriptComponent.cs diff --git a/Expansion2/RaylibAssetManagerModule.cs b/Expansion2/RaylibAssetManagerModule.cs index 54c5946..0fec7c1 100644 --- a/Expansion2/RaylibAssetManagerModule.cs +++ b/Expansion2/RaylibAssetManagerModule.cs @@ -8,12 +8,14 @@ namespace Smoll.Ex2 Dictionary images; Dictionary textures; Dictionary sounds; + Dictionary scripts; public RaylibAssetManagerModule(string path = "./assets") { this.path = path; images = new Dictionary(); textures = new Dictionary(); sounds = new Dictionary(); + scripts = new Dictionary(); } public Image GetImage(string name) { @@ -43,5 +45,14 @@ namespace Smoll.Ex2 } return sound; } + + public string GetScript(string name) { + string script; + if(! scripts.TryGetValue(name, out script)) { + script = File.ReadAllText(path+"/"+name); + scripts.Add(name, script); + } + return script; + } } } \ No newline at end of file diff --git a/Expansion4/Commands/ICommand.cs b/Expansion4/Commands/ICommand.cs new file mode 100644 index 0000000..8576689 --- /dev/null +++ b/Expansion4/Commands/ICommand.cs @@ -0,0 +1,85 @@ + +namespace Smoll.Ex4.Commands { + interface ICommand{ + void Perform(List stack); + } + + class PushNumberCommand : ICommand + { + public double self; + public PushNumberCommand(double value) { + self = value; + } + + public void Perform(List stack) + { + stack.Add(new Number(self));; + } + } + + class PushStringCommand : ICommand + { + public string self; + public PushStringCommand(string value) { + self = value; + } + + public void Perform(List stack) + { + stack.Add(new Commands.String(self));; + } + } + + class PushAtomCommand : ICommand + { + public string self; + public PushAtomCommand(string value) { + self = value; + } + + public void Perform(List stack) + { + stack.Add(new Commands.Atom(self));; + } + } + + class AddNumberCommand : ICommand + { + public void Perform(List stack) + { + if(! stack.TakeLast(2).All(x => x is Ex4.Commands.Number)) { + throw new Exception("Bad arguments to add as numbers"); + } + var ret = stack.TakeLast(2).Cast().Select(x => x.value).Sum(); + stack.RemoveAt(stack.Count - 1); + stack.RemoveAt(stack.Count - 1); + stack.Add(new Number(ret)); + } + } + + class PrintCommand : ICommand + { + public void Perform(List stack) + { + var ret = stack.Last(); + stack.RemoveAt(stack.Count - 1); + System.Console.Out.Write(ret.ToString()); + } + } + + class CompositeCommand : ICommand + { + List executable; + public CompositeCommand(List executable) { + this.executable = executable; + } + + public void Perform(List stack) + { + foreach (var item in executable) + { + item.Perform(stack); + } + } + } +} \ No newline at end of file diff --git a/Expansion4/Commands/IScriptable.cs b/Expansion4/Commands/IScriptable.cs new file mode 100644 index 0000000..bc348bd --- /dev/null +++ b/Expansion4/Commands/IScriptable.cs @@ -0,0 +1,54 @@ + +namespace Smoll.Ex4.Commands { + interface IScriptable { + + } + + class Number : IScriptable { + public double value; + + public Number(double value) { + this.value = value; + } + + public override string ToString() + { + return value.ToString(); + } + } + + class Complex : IScriptable { + public Complex value; + + public override string ToString() + { + return value.ToString(); + } + } + + class Atom : IScriptable { + public string value; + + public Atom(string value) { + this.value = value; + } + + public override string ToString() + { + return "[[" + value.ToString() + "]]"; + } + } + + class String : IScriptable { + public string value; + + public String(string value) { + this.value = value; + } + + public override string ToString() + { + return value; + } + } +} \ No newline at end of file diff --git a/Expansion4/ScriptComponent.cs b/Expansion4/ScriptComponent.cs new file mode 100644 index 0000000..59bf901 --- /dev/null +++ b/Expansion4/ScriptComponent.cs @@ -0,0 +1,192 @@ + +using System.Globalization; +using Smoll.Ex2; +using Smoll.Ex4.Commands; + +namespace Smoll.Ex4 { + class ScriptComponent : Component { + string script; + ScriptComponent(string filename) { + script = filename; + } + + static Dictionary? globalCommands; + Dictionary localCommands; + + public override void Draw(Layer.DrawMode drawMode) + { + ICommand command; + if(localCommands.TryGetValue("Draw", out command)) { + List stack = new List(); + command.Perform(stack); + } + } + + public override void OnAttached() + { + localCommands = Compile(owner.layers.First().attachedEngine.GetModule().GetScript(script)); + + ICommand command; + if(localCommands.TryGetValue("OnAttached", out command)) { + List stack = new List(); + command.Perform(stack); + } + } + + public override void Update(float deltaTimeSeconds) + { + ICommand command; + if(localCommands.TryGetValue("Update", out command)) { + List stack = new List(); + stack.Add(new Number(deltaTimeSeconds)); + command.Perform(stack); + } + } + + static void EnsureCommandsInitialized() { + if(globalCommands != null) return; + globalCommands = new Dictionary + { + { "+", new Commands.AddNumberCommand() }, + { "print", new Commands.PrintCommand() } + }; + } + + struct SString { + public bool isLiteral; + public string value; + + public string ToString() { + if(isLiteral) { + return "<" + value + ">"; + } else { + return "["+value+"]"; + } + } + } + + static List Tokenize(string str) { + var l = new List(); + + string current = ""; + bool inString = false; + + TextElementEnumerator graphemeEnum = StringInfo.GetTextElementEnumerator(str.Normalize()); + while (graphemeEnum.MoveNext()) + { + string grapheme = graphemeEnum.GetTextElement(); + if(inString) { + if(grapheme == "\"") { + bool valid = graphemeEnum.MoveNext(); + if(!valid) { + SString s; + s.value = current; + s.isLiteral = true; + l.Add(s); + break; + } + grapheme = graphemeEnum.GetTextElement(); + if(grapheme == "\"") current += "\""; + else if(grapheme.Trim() == string.Empty) { + SString s; + s.value = current; + s.isLiteral = true; + l.Add(s); + current = ""; + inString = false; + } else if(grapheme == "/") { + bool valid2 = graphemeEnum.MoveNext(); + if(!valid2) { + throw new Exception("Found a / for a new line definition that didn't end"); + } + grapheme = graphemeEnum.GetTextElement(); + if(grapheme == "\"") current += "\n"; + } else { + throw new Exception("Found \" of a literal followed by things"); + } + } else { + current += grapheme; + } + } else if(grapheme.Trim() == string.Empty) { + if(current != string.Empty) { + SString s; + s.value = current; + s.isLiteral = false; + l.Add(s); + current = ""; + inString = false; + } + } else { + if(grapheme == "\"" && current == string.Empty) { + inString = true; + } else if(grapheme == "\"" && current != string.Empty) { + throw new Exception("Found \" in atom"); + } else { + current += grapheme; + } + } + } + + if(inString) { + throw new Exception("Found unterminated string"); + } + + if(current != string.Empty) { + SString s; + s.value = current; + s.isLiteral = false; + l.Add(s); + } + + return l; + } + + static public Dictionary Compile(string code) { + EnsureCommandsInitialized(); + Dictionary localCommands = new Dictionary(); + var lines = code.Split("\n", StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var executable = new List(); + var fullLine = Tokenize(line); + if(fullLine.Count < 2) { + throw new Exception("Functions need to start with a name and a --"); + } + var name = fullLine.First().value; + if(fullLine.First().isLiteral) { + throw new Exception("Literals cannot be function names"); + } + if(fullLine[1].isLiteral || fullLine[1].value != "--") { + throw new Exception("Function body needs to start with --"); + } + foreach(var elem in fullLine.Skip(2)) { + if(elem.isLiteral) { + executable.Add(new Commands.PushStringCommand(elem.value)); + continue; + } + { + double attempt; + if(Double.TryParse(elem.value, out attempt)) { + executable.Add(new Commands.PushNumberCommand(attempt)); + continue; + } + } + { + ICommand command; + if(!localCommands.TryGetValue(elem.value, out command)) { + if(!globalCommands.TryGetValue(elem.value, out command)) { + executable.Add(new Commands.PushAtomCommand(elem.value)); + continue; + } + } + executable.Add(command); + } + } + localCommands.Add(name, new CompositeCommand(executable)); + } + + return localCommands; + } + } +} \ No newline at end of file