Most of the rewrite

This commit is contained in:
Booklordofthedings 2024-08-31 20:17:00 +02:00
parent f473f33521
commit fd81f5b33b
12 changed files with 715 additions and 15 deletions

View file

@ -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

22
src/AuBytecode.bf Normal file
View file

@ -0,0 +1,22 @@
namespace Automate;
using System;
using System.Collections;
class AuBytecode
{
public List<int64> Instructions = new .(100) {0} ~ delete _;
public List<AuValue> Constants = new .() {.() };
public Dictionary<AuValue, int64> ConstantMap = new .() ~ delete _;
public Dictionary<String, int64> RegisterMap = new .() ~ DeleteDictionaryAndKeys!(_);
public Dictionary<String, int64> FunctionMap = new .() ~ DeleteDictionaryAndKeys!(_);
public ~this()
{
for(var i in Constants)
i.Dispose();
delete Constants;
}
}

169
src/AuCompiler.bf Normal file
View file

@ -0,0 +1,169 @@
namespace Automate;
using System;
using System.Collections;
class AuCompiler
{
public static Result<void> Compile(StringView pCode, AuInstructions pInstructions, AuBytecode pContext)
{
///Tokenize everything and then parse the tokens one by one
List<StringView> 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<void> 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<StringView>(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<StringView>(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];
}
}

126
src/AuContext.bf Normal file
View file

@ -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<AuValue> Stack = new .(100);
public List<int64> CallStack = new .(30) ~ delete _;
public Dictionary<int64, AuValue> 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<AuValue> 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;
}
}

64
src/AuInstructions.bf Normal file
View file

@ -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<delegate void(AuInstructions set, AuBytecode context, AuContext data)> 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<StringView, int64> 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<void> 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);
}
}

31
src/AuValue.bf Normal file
View file

@ -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:
}
}
}

18
src/Extensions.bf Normal file
View file

@ -0,0 +1,18 @@
namespace System.Collections;
extension List<T>
{
[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];
}
}

View file

@ -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();
}
}
}

66
src/Instructions/Core.bf Normal file
View file

@ -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;
}
}
}

View file

@ -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)
{{
}}
}}
""");
}
}

82
src/Instructions/Math.bf Normal file
View file

@ -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<AuValue> 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<AuValue> 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<AuValue> 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<AuValue> 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<AuValue> 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();
}
}

View file

@ -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();
}
}