diff --git a/BeefSpace.toml b/BeefSpace.toml index 3f29d80..8a9237c 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -3,18 +3,3 @@ Projects = {Automate = {Path = "."}} [Workspace] StartupProject = "Automate" - -[Configs.Debug.wasm32] -AllocType = "CRT" -EnableObjectDebugFlags = false -EmitObjectAccessCheck = false - -[Configs.Paranoid.wasm32] -AllocType = "CRT" -EnableObjectDebugFlags = false -EmitObjectAccessCheck = false - -[Configs.Test.wasm32] -AllocType = "CRT" -EnableObjectDebugFlags = false -EmitObjectAccessCheck = false diff --git a/src/AuBytecode.bf b/src/AuBytecode.bf new file mode 100644 index 0000000..6e033b9 --- /dev/null +++ b/src/AuBytecode.bf @@ -0,0 +1,22 @@ +namespace Automate; + +using System; +using System.Collections; + +class AuBytecode +{ + public List Instructions = new .(100) {0} ~ delete _; + + public List Constants = new .() {.() }; + + public Dictionary ConstantMap = new .() ~ delete _; + public Dictionary RegisterMap = new .() ~ DeleteDictionaryAndKeys!(_); + public Dictionary FunctionMap = new .() ~ DeleteDictionaryAndKeys!(_); + + public ~this() + { + for(var i in Constants) + i.Dispose(); + delete Constants; + } +} \ No newline at end of file diff --git a/src/AuCompiler.bf b/src/AuCompiler.bf new file mode 100644 index 0000000..6637e7a --- /dev/null +++ b/src/AuCompiler.bf @@ -0,0 +1,169 @@ +namespace Automate; + +using System; +using System.Collections; + +class AuCompiler +{ + public static Result Compile(StringView pCode, AuInstructions pInstructions, AuBytecode pContext) + { + ///Tokenize everything and then parse the tokens one by one + List Tokens = new .(); + defer delete Tokens; + + for (var line in pCode.Split('\n')) + { + if (line..TrimStart().StartsWith('#')) + continue; + + for (var part in line.Split(' ', '\t')) + { + if (part.StartsWith('"')) + { + int tokenStart = @line.Pos + @part.Pos; + while ( + @part.HasMore + && (!part.EndsWith('"') + || part.EndsWith("\\\"")) + ) + part = @part.GetNext(); + int tokenEnd = @line.Pos + @part.Pos + part.Length; + Tokens.Add(pCode.Substring(tokenStart, tokenEnd - tokenStart)); + } + else + Tokens.Add(part); + } + + for (var i in Tokens.Reversed) + { + if (ProcessToken(i, pInstructions, pContext) case .Err) + return .Err; + Tokens.PopBack(); + } + } + return .Ok; + } + + private static Result ProcessToken(StringView pToken, AuInstructions pInstructions, AuBytecode pContext) + { + if (pToken.IsEmpty) + return .Ok; + + else if (pToken[0].IsNumber || pToken[0] == '-') + if(!pToken.Contains('.') && int.Parse(pToken) case .Ok(let val)) HandleNumberToken(val, pInstructions, pContext); + else if (Double.Parse(pToken) case .Ok(let val)) HandleNumberToken(val, pInstructions, pContext); + else return .Err; + else if (pToken[0] == '<') + { + HandleRegisterToken(.(pToken, 1), pInstructions, pContext); + pContext.Instructions.Add(1); + } + else if (pToken[0] == '>') + { + HandleRegisterToken(.(pToken, 1), pInstructions, pContext); + pContext.Instructions.Add(2); + } + else if (pToken[0] == '$') HandleLabelToken(.(pToken, 1), pInstructions, pContext); + else if (pToken[0] == '"') HandleStringToken(.(pToken, 1), pInstructions, pContext); + else HandleFunctionToken(pToken, pInstructions, pContext); + + return .Ok; + } + + private static void HandleNumberToken(double pNum, AuInstructions pInstructions, AuBytecode pContext) + { + /* + For numeric constants we simply check wether they already exist and add them if they dont + */ + var variant = AuValue.Double(pNum); + var ret = AddConstantOrReturnConstantIndex(variant, pContext); + if (ret > 0) + { + variant.Dispose(); + ret = ret * -1; + } + pContext.Instructions.Add(ret); + } + + private static void HandleStringToken(StringView pString, AuInstructions pInstructions, AuBytecode pContext) + { + /* + String constants are fist correctly escaped, before being added to the list of constants if they are on there + */ + + String s = new .(pString); + if (s.EndsWith('"')) + s.RemoveFromEnd(1); + var escaped = s.Unescape(.. scope .()); + s..Clear().Append(escaped); + + var variant = AuValue.String(s); + var ret = AddConstantOrReturnConstantIndex(variant, pContext); + if (ret > 0) + { + variant.Dispose(); + ret = ret * -1; + } + pContext.Instructions.Add(ret); + } + + private static void HandleLabelToken(StringView pString, AuInstructions pInstructions, AuBytecode pContext) + { + /* + Labels in automate are basically functions + We add a 0 instruction to the list of instructions + If the function already exists we change it to point to the new definition + It it doesnt we just add a new defintion + */ + pContext.Instructions.Add(0); + var res = AddConstantOrReturnConstantIndex(AuValue.Double(pContext.Instructions.Count - 1), pContext); + if (pContext.FunctionMap.ContainsKeyAlt(pString)) + pContext.FunctionMap[scope:: .(pString)] = res > 0 ? -1 * res : res; + else + pContext.FunctionMap.Add(new .(pString), res > 0 ? -1 * res : res); + } + + private static void HandleRegisterToken(StringView pString, AuInstructions pInstructions, AuBytecode pContext) + { + if (!pContext.RegisterMap.ContainsKeyAlt(pString)) + { + var res = AddConstantOrReturnConstantIndex(AuValue.Double(pContext.RegisterMap.Count + 1), pContext); + String reg = new .(pString); + pContext.RegisterMap.Add(reg, res > 0 ? -1 * res : res); + } + pContext.Instructions.Add(pContext.RegisterMap.GetValue(scope .(pString))); + } + + private static void HandleFunctionToken(StringView pString, AuInstructions pInstructions, AuBytecode pContext) + { + if (pInstructions.InstructionMap.GetValue(pString) case .Ok(let val)) + pContext.Instructions.Add(val); + else if (pContext.FunctionMap.GetValue(scope .(pString)) case .Ok(let val)) + { + pContext.Instructions.Add(val); + pContext.Instructions.Add(3); + } + else + { + var variant = AuValue.String(new .(pString)); + var res = AddConstantOrReturnConstantIndex(variant, pContext); + if(res > 0) + variant.Dispose(); + pContext.Instructions.Add(res > 0 ? -1 * res : res); + pContext.Instructions.Add(4); + } + } + + private static int64 AddConstantOrReturnConstantIndex(AuValue pToAdd, AuBytecode pContext) + { + ///The constant already exists + if (pContext.ConstantMap.GetValue(pToAdd) case .Ok(let val)) + return val; + + ///Constant doesnt exist + pContext.Constants.Add(pToAdd); + pContext.ConstantMap.Add(pToAdd, pContext.Constants.Count - 1); + return -1 * pContext.ConstantMap[pToAdd]; + } + +} \ No newline at end of file diff --git a/src/AuContext.bf b/src/AuContext.bf new file mode 100644 index 0000000..9a1b985 --- /dev/null +++ b/src/AuContext.bf @@ -0,0 +1,126 @@ +namespace Automate; + +using System; +using System.Collections; + +/* + Represents a runtime context + which is a snapshot of the current state of the program + It contains the instruction counter aswell as the stack and registers + If you use multiple runtime contexts you can potentially do something like multithreading with this +*/ +class AuContext +{ + public int64 InstructionPointer = 0; + public bool ShouldExit = false; + public bool RuntimeError = false; + public List Stack = new .(100); + public List CallStack = new .(30) ~ delete _; + public Dictionary Registers = new .(); + + public ~this() + { + for(var i in Registers) + i.value.Dispose(); + delete Registers; + for(var i in Stack) + i.Dispose(); + delete Stack; + } + + + + ///Peek the last added object + [Inline] + public AuValue Peek() + { + if(Stack.Count > 0) + return Stack[^1]; + return .(); + } + + ///Peek the nth last added object + [Inline] + public AuValue PeekN(int pOffset) + { + if(Stack.Count > pOffset) + return Stack[^pOffset]; + return .(); + } + + ///Add a new variant to the stack + [Inline] + public void Push(AuValue pToAdd) + { + Stack.Add(pToAdd); + } + + [Inline] + public void Clear() + { + for(var i in Stack) + i.Dispose(); + Stack.Clear(); + } + + ///Pops the highest object on the stack + [Inline] + public AuValue Pop() + { + if(Stack.Count > 0) + return Stack.PopBack(); + return .(); + } + + public Span PopN(int32 n) + { + if(Stack.Count >= n) + return .(Stack.PopBackN(n), n); + return .(); + } + + ///Pops the highest object and automatically disposes it in the scope of it being called + public mixin Pop() + { + var val = Pop(); + defer:mixin val.Dispose(); + val + } + + public void FatalError() + { + ShouldExit = true; + RuntimeError = true; + } + + public int Run(AuInstructions pInstructions, AuBytecode pCContext) + { + while(!this.ShouldExit) + { + var current = pCContext.Instructions[this.InstructionPointer]; + if(current >= 0) + { + pInstructions.Instructions[current].Invoke(pInstructions, pCContext, this); + + } + else //Push something onto the stack from our list of constants + { + var toPush = pCContext.Constants[-1 * current]; + switch(toPush) + { + case .Double(let val): this.Push(.Double(val)); + case .String(let val): this.Push(.String(new .(val))); + case .Variant(let val): this.Push(.Variant(val)); + default: + } + } + + this.InstructionPointer++; + if(!(this.InstructionPointer < pCContext.Instructions.Count)) + return 0; //All instructions are done executing + } + if(RuntimeError) + return -1; + return 0; + } +} \ No newline at end of file diff --git a/src/AuInstructions.bf b/src/AuInstructions.bf new file mode 100644 index 0000000..e3dd722 --- /dev/null +++ b/src/AuInstructions.bf @@ -0,0 +1,64 @@ +namespace Automate; + +using System; +using System.Collections; + +class AuInstructions +{ + public enum InstructionsLoadFlags : uint32 + { + None = 0, //No instructions except stuff the compiler needs + Core = 1, //All of the core instructions + Math = _*2, //Lots of additional math instructions + IO = _*2, //Instructions for working with files and directories + Meta = _*2, //UNSAFE: Instructions that can change instructions and are generally unsafe to use on user input + + All = Core | Math | IO | Meta //All available instructions + } + + + + ///Stores the raw data of all available instructions + public List Instructions = new .(100) { + new (a,b,c) => {}, + new => Automate.Instructions.Buildins.PushRegister, + new => Automate.Instructions.Buildins.PopRegister, + new => Automate.Instructions.Buildins.CallFunction, + new => Automate.Instructions.Buildins.CallFunctionString, + }~ DeleteContainerAndItems!(_); + + ///Maps the name of instructions to their index + public Dictionary InstructionMap = new .(100) ~ delete _; + + + public this(InstructionsLoadFlags pFlags) + { + if(pFlags.HasFlag(.Core)) + Automate.Instructions.Core.AddCoreInstructions(this); + if(pFlags.HasFlag(.Math)) + Automate.Instructions.Math.AddMathInstructions(this); + } + + ///Attempt to add a new instruction + public Result AddInstruction(StringView pName, delegate void(AuInstructions set, AuBytecode context, AuContext data) pInstruction) + { + if(InstructionMap.ContainsKey(pName)) + return .Err; + Instructions.Add(pInstruction); + InstructionMap.Add(pName, Instructions.Count-1); + return .Ok; + } + + ///Remove all instructions + public void ClearInstructions() + { + for(var i in Instructions) + delete i; + InstructionMap.Clear(); + Instructions.Add(new (a,b,c) => {}); + Instructions.Add(new => Automate.Instructions.Buildins.PushRegister); + Instructions.Add(new => Automate.Instructions.Buildins.PopRegister); + Instructions.Add(new => Automate.Instructions.Buildins.CallFunction); + Instructions.Add(new => Automate.Instructions.Buildins.CallFunctionString); + } +} \ No newline at end of file diff --git a/src/AuValue.bf b/src/AuValue.bf new file mode 100644 index 0000000..4ca2683 --- /dev/null +++ b/src/AuValue.bf @@ -0,0 +1,31 @@ +namespace Automate; + +using System; + +enum AuValue : IHashable, IDisposable +{ + case String(String val); + case Double(double val); + case Variant(Variant val); + + public int GetHashCode() + { + switch (this) + { + case .String(var val): return val.GetHashCode(); + case .Double(var val): return val.GetHashCode(); + default: + return 0; + } + } + + public void Dispose() + { + switch(this) + { + case .String(var val): delete val; + case .Variant(var val): val.Dispose(); + default: + } + } +} diff --git a/src/Extensions.bf b/src/Extensions.bf new file mode 100644 index 0000000..6b5d3ad --- /dev/null +++ b/src/Extensions.bf @@ -0,0 +1,18 @@ +namespace System.Collections; + +extension List +{ + [Checked] + public T* PopBackN(int32 n) + { + Runtime.Assert(mSize - n >= 0); + return &mItems[mSize -= n]; + } + + [Unchecked] + public T* PopBackN(int32 n) + { + Runtime.Assert(mSize - n >= 0); + return &mItems[mSize -= n]; + } +} \ No newline at end of file diff --git a/src/Instructions/Buildins.bf b/src/Instructions/Buildins.bf new file mode 100644 index 0000000..6ef0d9d --- /dev/null +++ b/src/Instructions/Buildins.bf @@ -0,0 +1,81 @@ +namespace Automate.Instructions; + +using System; + +class Buildins +{ + + ///Push the current value of a register onto the stack + public static void PushRegister(AuInstructions set, AuBytecode context, AuContext data) + { + var register = data.Pop!(); + switch(register) + { + case .Double(let val): + if(data.Registers.GetValue((.)val) case .Ok(let value)) + { + switch(value) + { + case .Double(let p): + data.Push(.Double(p)); + case .String(let p): + data.Push(.String(new .(p))); + case .Variant(let p): + data.Push(.Variant(p)); + default: + } + } + default: + } + } + + ///Pop the upper value of the stack into a register + public static void PopRegister(AuInstructions set, AuBytecode context, AuContext data) + { + if(data.Pop!() case .Double(let register)) + { + var val = data.Pop!(); + if(data.Registers.ContainsKey((.)register)) + data.Registers[(.)register].Dispose(); + switch(val) + { + case .Double(let v): + data.Registers[(.)register] = .Double(v); + case .String(let v): + data.Registers[(.)register] = .String(new .(v)); + case .Variant(let v): + data.Registers[(.)register] = .Variant(v); + default: + } + } + } + + public static void CallFunction(AuInstructions set, AuBytecode context, AuContext data) + { + data.CallStack.Add(data.InstructionPointer); + var func = data.Pop!(); + switch(func) + { + case .Double(let val): + data.InstructionPointer = (.)val; + default: + data.FatalError(); + } + } + + public static void CallFunctionString(AuInstructions set, AuBytecode context, AuContext data) + { + data.CallStack.Add(data.InstructionPointer); + var func = data.Pop!(); + switch(func) + { + case .String(let val): + if(context.FunctionMap.GetValue(val) case .Ok(let index) && context.Constants[-1 * index] case .Double(let value)) + data.InstructionPointer = (.)value; + else + data.FatalError(); + default: + data.FatalError(); + } + } +} \ No newline at end of file diff --git a/src/Instructions/Core.bf b/src/Instructions/Core.bf new file mode 100644 index 0000000..cb0bf4d --- /dev/null +++ b/src/Instructions/Core.bf @@ -0,0 +1,66 @@ +namespace Automate.Instructions; + +using Automate; + +using System; + +class Core +{ + public static void AddCoreInstructions(AuInstructions pSet) + { + pSet.AddInstruction("echo", new => Echo); + pSet.AddInstruction("return", new => Return); + pSet.AddInstruction("call", new => Call); + pSet.AddInstruction("if", new => If); + pSet.AddInstruction("equals", new => Equals); + } + + private static void Echo(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + var res = pContext.Pop!(); + switch(res) + { + case .String(let val): Console.WriteLine(val); + case .Double(let val): Console.WriteLine(val); + case .Variant(let val): Console.WriteLine(val); + default: + } + } + + private static void Return(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + if(pContext.CallStack.Count >= 1) + pContext.InstructionPointer = pContext.CallStack.PopBack(); + else + pContext.ShouldExit = true; + } + + private static void Equals(AuInstructions pSet, AuBytecode pInstructions, AuContext pContext) + { + switch(pContext.Pop!()) + { + case .Double(let val): if(pContext.Pop!() case .Double(let r) && val == r) pContext.Push(.Double(1)); + case .String(let val): if(pContext.Pop!() case .String(let r) && val == r) pContext.Push(.Double(1)); + case .Variant(let val): if(pContext.Pop!() case .Variant(let r) && val == r) pContext.Push(.Double(1)); + default: + } + + } + + private static void If(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + if(pContext.Pop!() case .Double(let val) && val > 0) + if(pContext.Pop!() case .Double(let jmp)) + pContext.InstructionPointer += (.)jmp; + } + + private static void Call(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + if(pContext.Pop!() case .String(let val) && pCContext.FunctionMap.GetValue(val) case .Ok(let index) && pCContext.Constants[-1 * index] case .Double(let value)) + { + pContext.CallStack.Add(pContext.InstructionPointer); + pContext.InstructionPointer = (.)value; + } + } + +} \ No newline at end of file diff --git a/src/Instructions/InstructionGenerator.bf b/src/Instructions/InstructionGenerator.bf new file mode 100644 index 0000000..1f874fd --- /dev/null +++ b/src/Instructions/InstructionGenerator.bf @@ -0,0 +1,37 @@ +namespace Automate.Instructions; + +using System; + +class InstructionGenerator : Compiler.Generator +{ + public override String Name => "New Instruction"; // This is was the generator will show up as in the "Generator" dropdown + + public override void InitUI() + { + AddEdit("name", "Class Name", ""); + } + + public override void Generate(String outFileName, String outText, ref Flags generateFlags) + { + var name = mParams["name"]; + if (name.EndsWith(".bf", .OrdinalIgnoreCase)) + name.RemoveFromEnd(3); + + outFileName.Append(name); + outText.AppendF($""" + namespace {Namespace}; + + using Automate; + + using System; + + class {scope String(name)..Replace(' ', '_')} + {{ + public static void Instruction_{scope String(name)..Replace(' ', '_')}(AuInstructions set, AuBytecode context, AuContext data) + {{ + + }} + }} + """); + } +} \ No newline at end of file diff --git a/src/Instructions/Math.bf b/src/Instructions/Math.bf new file mode 100644 index 0000000..901803e --- /dev/null +++ b/src/Instructions/Math.bf @@ -0,0 +1,82 @@ +namespace Automate.Instructions; + +using System; + +class Math +{ + public static void AddMathInstructions(AuInstructions pSet) + { + pSet.AddInstruction("add", new => Add); + pSet.AddInstruction("sub", new => Sub); + pSet.AddInstruction("mult", new => Mult); + pSet.AddInstruction("div", new => Div); + pSet.AddInstruction("sqr", new => Sqr); + pSet.AddInstruction("sqrt", new => Sqrt); + pSet.AddInstruction("pow", new => Pow); + } + + private static void Add(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + Span arr = pContext.PopN(2); + if(arr[0] case .Double(let lhs) && arr[1] case .Double(let rhs)) + pContext.Push(.Double(lhs + rhs)); + + for(var i in arr) + i.Dispose(); + } + + private static void Sub(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + Span arr = pContext.PopN(2); + if(arr[0] case .Double(let lhs) && arr[1] case .Double(let rhs)) + pContext.Push(.Double(lhs - rhs)); + + for(var i in arr) + i.Dispose(); + } + + private static void Mult(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + Span arr = pContext.PopN(2); + if(arr[0] case .Double(let lhs) && arr[1] case .Double(let rhs)) + pContext.Push(.Double(lhs * rhs)); + + for(var i in arr) + i.Dispose(); + } + + private static void Div(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + Span arr = pContext.PopN(2); + if(arr[0] case .Double(let lhs) && arr[1] case .Double(let rhs)) + pContext.Push(.Double(lhs / rhs)); + + for(var i in arr) + i.Dispose(); + } + + private static void Sqr(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + var arr = pContext.Pop!(); + if(arr case .Double(let lhs)) + pContext.Push(.Double(lhs * lhs)); + } + + private static void Sqrt(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + var arr = pContext.Pop!(); + if(arr case .Double(let lhs)) + pContext.Push(.Double(System.Math.Sqrt(lhs))); + } + + private static void Pow(AuInstructions pSet, AuBytecode pCContext, AuContext pContext) + { + Span arr = pContext.PopN(2); + + if(arr[0] case .Double(let lhs) && arr[1] case .Double(let rhs)) + pContext.Push(.Double(System.Math.Pow(lhs, rhs))); + + for(var i in arr) + i.Dispose(); + } +} \ No newline at end of file diff --git a/src/Program.bf b/src/Program.bf index e1e1af9..664e556 100644 --- a/src/Program.bf +++ b/src/Program.bf @@ -1,5 +1,24 @@ namespace Automate; +using System; +using System.Diagnostics; +using System.IO; + class Program { + public static void Main(String[] args) + { + String text = new .(""); + defer delete text; + File.ReadAllText("example.au", text); + AuInstructions set = new .(.All); + defer delete set; + var code = AuCompiler.Compile(text, set, .. new .()); + defer delete code; + AuContext context = new .(); + defer delete context; + context.Run(set, code); + //Console.Read(); + } + } \ No newline at end of file