diff --git a/BeefProj.toml b/BeefProj.toml index 0d15fdc..87a15ea 100644 --- a/BeefProj.toml +++ b/BeefProj.toml @@ -4,3 +4,11 @@ FileVersion = 1 Name = "Bofa" TargetType = "BeefLib" StartupObject = "Bofa.Program" + +[Platform.Windows] +Description = "A parser for Books Object Format A" +Company = "Booklordofthedings" +Product = "Bofa" +Copyright = "Booklordofthedings" +FileVersion = "1.0.0" +ProductVersion = "1.0.0" diff --git a/BeefSpace.toml b/BeefSpace.toml index 0bb5a2b..9edf396 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -3,3 +3,10 @@ Projects = {Bofa = {Path = "."}} [Workspace] StartupProject = "Bofa" + +[Configs.Test.Win64] +LargeStrings = true + +[Configs.Debug.Win64] +LargeStrings = true +LargeCollections = true diff --git a/README.md b/README.md index 3979052..58d3817 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,147 @@ -# Bofa -Books object format a +# Bofa - Books Object Format A - 1.0.0 +Bofa is an object notation for general purpose use in serialization and deserialization. +The primary focus is on being human readable, while also being extremely easy to parse +This repository implements a Bofa parser in the Beef programming language. + +``` +# A floating point number +n number 125.435 +l line this is a normal line +a Array + t text you can + - put more text in here + t text2 and even + - more in here +``` + +## Index +- How to install +- Changelog +- The Bofa notation +- Parser notes +- Bofa parser API usage +- Potential improvements + +## How to install +- Download the repository, either with git, the download zip functionality or the release tab +- Create or open your existing Beef project +- Right click on the workspace panel +- Select "Add Existing Project" +- Navigate to the BeefProj.toml file inside the Bofa folder +- Right click on the project you want to use Bofa with +- Click "Properties" -> "Dependencies" +- Check the box that says Bofa +- Add the Bofa namespace to the files you want to use Bofa with +- Use Bofa + +## Changelog +- Version 1.0.0 + - Initial Version + - Fast parsing + - Add automated serialization generation + +## The Bofa notation +The notation itself is pretty simple. +Every line contains one Bofa "Entry", but empty lines and comments are also allowed. +A normal Bofa entry has 3 Parts: `{Type} {Name} {Value}` + +The possible values of `{Type}` are: +- l - Line - A string (utf8) without a newline character +- t - Text - A string (utf8) that may contain newline characters +- b - Boolean - A boolean value of either "true" or "false" +- i - Integer - A 32bit signed integer +- ui - UnsignedInteger - A 32bit unsigned integer +- bi - BigInteger - A 64bit signed integer +- bui - BigUnsignedInteger - A 64bit unsigned integer +- n - Number - A 32bit floating point number +- bn - BigNumber - A 64bit floating point number +- c - Custom - Any custom type in a string (utf8) representation +- o - Object - A Key/Value container for other Bofa entries +- a - Array - A container for other Bofa entries, where entries may have identical names + +`{Name}` is simple just a string that does not contain a space or tab character +`{Value}` is a string that represents the choosen datatype and can be parsed into the datatype + +Empty lines are full ignored and comments start with a # as the first non whitespace character in the line. + +An entry of type custom needs additional information to work. +Instead of `{Type} {Name} {Value}` a custom entry uses `{Type} {Typename} {Name} {Value}` instead. +Similarly arrays and objects dont actually need a entry for `{Value}` and just use `{Type} {Name}`. +In order to allow multiline text without too many issues the `-` character is used to indicate that the content of this line should be appended +to the last line if it was of type text. + +Membership of containers like arrays or objects is indicated via a depth value. +In order to indicate depth the start of the line may contain any number of tabs or spaces, the total amount of which indicates the depth of the entry. +The exact object it is a member of is always the last defined object at a given depth. +``` +a Array1 + a Array2 + a Array3 + l Line this line is a member of Array3 which is itself contained inside of Array1 +``` +Objects have a key uniqueness rules, while Arrays dont have this (Entries of an array still need to have a proper name though, even if its only "-"). + +## Parser notes +Bofa should always be parsed loosely and to the best of the parsers ability, but never fail completely. +Instead it can indicate on which line an error occured and leave the user to figure out wether to stop or to continue. +This also enables a parsing pattern where Bofa data can be structured so any parser can read a document from a newer or different parser: +``` +# The following line has a type that was added by a imaginary parser using the Bofa 2.0 Standard +rgb color 255:0:255 +c rgb color 255:0:255 +``` +A parser that supports 2.0 should just parse the first entry and ignore the second, as it contains a dublicate name. +A parser still on an old version will ignore the first one, since it doesnt know a type named rgb but instead parse the second one correctly + +While the insertion at a depth needs to be done in the order of the document, the parsing and validation of the individual line does not. +As such you can theoretically multithread the parsing of each individual line to get some potential speed. + +Different parsers may add different types for their usecases, but single and 2 character typenames should stay reserved for official notation types. + +The entire standard is case sensitive. + +## Bofa parser api usage +This shows of an example of how to use the parser api +```cs +namespace Example; + +using System; +using Bofa; + +class ExampleClass +{ + public static void Main() + { + String bofaString = """ + # A floating point number + n number 125.435 + l line this is a normal line + a Array + t text you can + - put more text in here + t text2 and even + - more in here + """; + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(bofaString, output, errors); + + DeleteDictionaryAndValues!(output); + delete errors; + } +} +``` + +## Potential improvements +- There is still alot of speed left on the table + - Reduce the amount of allocations + - Multithread initial parsing +- New useful types + - Color + - Version + - Encrypted raw data +- More automatic serialization/deserialization in Corlib + - Hashes + - Pointers + - Tuples \ No newline at end of file diff --git a/src/Bofa.bf b/src/Bofa.bf new file mode 100644 index 0000000..1dc3fb3 --- /dev/null +++ b/src/Bofa.bf @@ -0,0 +1,242 @@ +namespace Bofa; + +using System; +using System.Collections; + +class Bofa +{ + + private String _line = null ~ delete _; //The actual line + private Bofa _lastObject = null; //If we are a container we keep track of the last container inside of us + + public StringView Name; + public EBofaType Type; + public StringView Typename; + public BofaValue Value; + + public override void ToString(String strBuffer) + { + int64 depth = 0; + Bofa target = this; + List<(int64, Bofa)> toProcess = new .(); + defer delete toProcess; + + repeat + { + + target.ToString(strBuffer, depth); + + if(target.Type == .Array) + { + depth++; + for(var i in target.Value.Array.Reversed) + toProcess.Add((depth, i)); + } + else if(target.Type == .Object) + { + depth++; + for(var i in target.Value.Object) + toProcess.Add((depth, i.value)); + } + + if(toProcess.IsEmpty) + target = null; + else + { + var t = toProcess.PopBack(); + depth = t.0; + target = t.1; + strBuffer.Append('\n'); + } + } while(target != null); + } + + private void ToString(String strBuffer, int64 pDepth) + { + strBuffer.Append(' ', pDepth); + switch(Type) + { + case .Array: + strBuffer.Append(scope $"a {Name}"); + case .BigInteger: + strBuffer.Append(scope $"bi {Name} {Value.BigInteger}"); + case .BigUnsignedInteger: + strBuffer.Append(scope $"bui {Name} {Value.BigUnsignedInteger}"); + case .BigNumber: + strBuffer.Append(scope $"bn {Name} {Value.BigNumber:R}"); + case .Boolean: + strBuffer.Append(scope $"b {Name} {Value.Boolean ? "true" : "false"}"); + case .Custom: + strBuffer.Append(scope $"c {Typename} {Name} {Value.Custom}"); + case .Integer: + strBuffer.Append(scope $"i {Name} {Value.Integer}"); + case .UnsignedInteger: + strBuffer.Append(scope $"ui {Name} {Value.UnsignedInteger}"); + case .Line: + strBuffer.Append(scope $"l {Name} {Value.Line}"); + case .Number: + strBuffer.Append(scope $"n {Name} {Value.Number:R}"); + case .Object: + strBuffer.Append(scope $"o {Name}"); + case .Text: + strBuffer.Append(scope $"t {Name} "); + for(var i in Value.Text.Split('\n', .None)) + { + strBuffer.Append(i); + if(@i.HasMore) + { + strBuffer.Append('\n'); + strBuffer.Append(' ', pDepth); + strBuffer.Append("- " ); + } + + } + } + } + + ///Deletes this object without filling the stack + public void DeepDeletion() + { + if(this.Type == .Array || this.Type == .Object) + { + List toDelete = new .(); + List toProcess = new .(); + + toDelete.Add(this); + Bofa target = this; + repeat + { + if(target.Type == .Object) + { + for(var i in target.Value.Object) + { + toDelete.Add(i.value); + toProcess.Add(i.value); + } + target.Value.Object.Clear(); + } + else if(target.Type == .Array) + { + for(var i in target.Value.Array) + { + toDelete.Add(i); + toProcess.Add(i); + } + target.Value.Array.Clear(); + } + + if(toProcess.Count > 0 && target != null) + target = toProcess.PopBack(); + else + target = null; + } while(target != null); + + for(var i in toDelete.Reversed) + delete i; + + delete toDelete; + delete toProcess; + } + else + { + if(_line != null) + delete _line; + + if(Type == .Text) + delete Value.Text; + } + } + + + public ~this() + { + if(Type == .Text) + delete Value.Text; + else if(Type == .Object) + DeleteDictionaryAndValues!(Value.Object); + else if(Type == .Array) + DeleteContainerAndItems!(Value.Array); + } + + public ref Bofa this[StringView view] + { + [Checked] + get + { + Runtime.Assert(Type == .Object); + Runtime.Assert(Value.Object.ContainsKey(view)); + return ref Value.Object[view]; + } + + [Unchecked, Inline] + get + { + return ref Value.Object[view]; + } + + [Checked] + set + { + Runtime.Assert(Type == .Object); + Runtime.Assert(!Value.Object.ContainsKey(view)); + Value.Object[view] = value; + } + + [Unchecked, Inline] + set + { + Value.Object[view] = value; + } + } + + public ref Bofa this[int view] + { + [Checked] + get + { + Runtime.Assert(Type == .Array); + Runtime.Assert(Value.Array.Count > view); + return ref Value.Array[view]; + } + + [Unchecked, Inline] + get + { + return ref Value.Array[view]; + } + + [Checked] + set + { + Runtime.Assert(Type == .Array); + Runtime.Assert(Value.Array.Count > view); + Value.Array[view] = value; + } + + [Unchecked, Inline] + set + { + Value.Array[view] = value; + } + } + + ///Get the value if the object is a container + public Result Get(StringView pKey) + { + if(Type != .Object) + return .Err; + if(!Value.Object.ContainsKey(pKey)) + return .Err; + return Value.Object[pKey]; + } + + ///Get the value if the object is a container + public Result Get(int pIndex) + { + if(Type != .Array) + return .Err; + if(!(Value.Array.Count > pIndex)) + return .Err; + return Value.Array[pIndex]; + } +} \ No newline at end of file diff --git a/src/BofaBuilder.bf b/src/BofaBuilder.bf deleted file mode 100644 index 841954e..0000000 --- a/src/BofaBuilder.bf +++ /dev/null @@ -1,233 +0,0 @@ -namespace Bofa.Builder; -using System; -using System.Collections; -typealias bb = BofaBuilder; -class BofaBuilder -{ - /* - Allows users to easily build a bofa file via code - name, typename, typeindicator, value - - This is really fucking slow, because we are allocating so much - In a sensible application you wouldnd call this all that much anyways, so I dont care - */ - public struct b - { - public String Name; //This is always dynamic - public String Type; //Never dynamic - public String TypeName; //Only dynamic on custom types - public String Value; //Dynamic on everything that isnt a array or object - public Span Members; - - public this(String name, String type, String typeName, String value, Span members) - { - Name = name; - Type = type; - TypeName = typeName; - Value = value; - Members = members; - } - - public void Cleanup() - { - delete Name; - if(Type == "c") - delete TypeName; - - if(Type != "a" && Type != "o") - delete Value; - - for(let e in Members) - { - e.Cleanup(); - } - } - - public void ToString(String toAppend, uint32 depth) - { - String toAdd = scope .(scope $"{Type} "); - toAdd.PadLeft(depth+toAdd.Length,' '); - if(Type == "c") - toAdd.Append(scope $"{TypeName} "); - toAdd.Append(scope $"{Name} "); - //Every single - if(Type != "a" && Type != "o") //In this case we can just normally return - { - - if(Type != "t") - toAdd.Append(scope $"{Value}"); - else - { - var en = Value.Split('\n'); - toAdd.Append(en.GetNext()); - - for(var e in en) - { - toAdd.Append('\n'); - String n = scope .(); - n.PadLeft(depth+1, ' '); - n.Append("- "); - n.Append(e); - toAdd.Append(n); - } - } - - - } - toAdd.Append('\n'); - if(Type == "a" || Type == "o") - { - for(var b in Members) - { - b.ToString(toAdd,depth+1); - } - } - toAppend.Append(toAdd); - } - } - - private Span values; - private List valueList = new .(); - public this(params Span input) - { - values = input; - } - - - public ~this() - { - for(var a in values) - a.Cleanup(); - - for(var a in valueList) - a.Cleanup(); - delete valueList; - } - - public override void ToString(String strBuffer) - { - for(var a in values) - { - a.ToString(strBuffer, 0); - } - for(var a in valueList) - { - a.ToString(strBuffer, 0); - } - if(strBuffer[strBuffer.Length-1] == '\n') - strBuffer.RemoveFromEnd(1); //There are probably better ways to do this but I dont care - } - - ///Adds a single entry after the creation - public void Add(b pToAdd) - { - valueList.Add(pToAdd); - } - - public static b Num(StringView name, float number) - { - return .(new .(name), "n", null, new .(number.ToString(.. scope .())),null); - } - - public static b Bool(StringView name, bool bool) - { - return .(new .(name), "b", null, new .(bool.ToString(.. scope .())),null); - } - - public static b Line(StringView name, StringView line) - { - return .(new .(name), "l", null, new .(line),null); - } - - public static b BigNum(StringView name, double bigNum) - { - return .(new .(name), "bn", null, new .(bigNum.ToString(.. scope .())),null); - } - - public static b Int(StringView name, int32 number) - { - return .(new .(name), "i", null, new .(number.ToString(.. scope .())),null); - } - - public static b BigInt(StringView name, int64 number) - { - return .(new .(name), "bi", null, new .(number.ToString(.. scope .())),null); - } - - public static b Text(StringView name, StringView text) - { - return .(new .(name),"t", null, new .(text),null); - } - - public static b Custom(StringView name, StringView type, StringView value) - { - return .(new .(name), "c",new .(type), new .(value), null); - } - - public static b Object(StringView name, params Span input) - { - return .(new .(name), "o", null,null,input); - } - - public static b Array(StringView name, params Span input) - { - return .(new .(name), "a", null,new .(""),input); - } - - - //Array functions, that dont take a name - public static b NumA(float value) - { - return Num("-", value); - } - public static b BoolA(bool value) - { - return Bool("-", value); - } - public static b LineA(StringView value) - { - return Line("-", value); - } - public static b BigNumA(double value) - { - return BigNum("-", value); - } - public static b IntA(int32 value) - { - return Int("-", value); - } - public static b BigIntA(int64 value) - { - return BigInt("-", value); - } - public static b TextA(StringView value) - { - return Text("-", value); - } - public static b CustomA(StringView type, StringView value) - { - return Custom("-", type, value); - } - public static b ObjectA(params Span input) - { - return .("-", "o", null, null, input); - } - public static b ArrayA(params Span input) - { - return .("-", "a", null, null, input); - } - - [Test] - public static void Test() - { - BofaBuilder builder = scope .( - bb.Num("Number",123), - bb.Custom("farbe", "color", "255 0 255"), - bb.Text("Text", "multi \nLine"), - bb.Object( "obj", - bb.Num("SubObject", 123) - ) - ); - Console.WriteLine(builder.ToString(.. scope .())); - } -} \ No newline at end of file diff --git a/src/BofaParser.bf b/src/BofaParser.bf new file mode 100644 index 0000000..64c3f7e --- /dev/null +++ b/src/BofaParser.bf @@ -0,0 +1,337 @@ +namespace Bofa; + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Collections; + +using Bofa.Parser; +using Bofa; + +class BofaParser +{ + public static void Parse(StringView pToParse, Dictionary pResult, List pErrors) + { + StringStream stream = new .(pToParse, .Reference); + defer delete stream; + Parse(stream, pResult, pErrors); + } + + public static void Parse(Stream pToParse, Dictionary pResult, List pErrors) + { + Bofa last = null; + if (pToParse == null || pResult == null) + { //Cannot parse what doesnt exist + pErrors.Add(-1); + return; + } + + StreamReader reader = new .(pToParse); + defer delete reader; + + int64 line = 1; + while (reader.Peek() case .Ok) //This might technically cause an issue when there is data added to the stream while we where busy processing, but if you hook this up to a network stream you deserve this happening to you + { + String l = new String(); + if (reader.ReadLine(l) case .Err) + { //Despite data being here, we cannot read it + delete l; + pErrors.Add(-2); + return; + } + + var entry = ParseLine(l, line); + if (!(entry.Type == .Bofa || entry.Type == .Text)) + delete l; //In these cases we have no useable data + if (entry.Type == .Empty) + { + line++; + continue; + } + if (entry.Type == .Error) + { + if (entry.Data.Bofa != null) + delete entry.Data.Bofa; + pErrors.Add(line); + continue; + } + + //If we are on the lowest level we can just add them here + if (entry.Depth == 0 && entry.Type == .Bofa) + { + if (!pResult.ContainsKey(entry.Data.Bofa.Name)) + { + last = entry.Data.Bofa; + pResult.Add(entry.Data.Bofa.Name, entry.Data.Bofa); + } + else + { + pErrors.Add(entry.Line); //Dublicate name error + delete entry.Data.Bofa; + } + } + else if (entry.Depth == 1 && entry.Type == .Text) + { + if (last.Type == .Text) + last.Value.Text.Append(scope $"\n{entry.Data.Text}"); + else + pErrors.Add(entry.Line); //Bad text error + delete l; + } + else + { + entry.Depth--; + if (last.[Friend]_Insert(&entry) case .Err) + { + pErrors.Add(line); + delete entry.Data.Bofa; + } + else if (entry.Type == .Text) + delete l; + } + line++; + } + return; //Being done normally + } + + [Inline] + private static ParserEntry ParseLine(String pLine, int64 pLineNumber) + { + StringView line = pLine; + ParserEntry toReturn = .(); + toReturn.Line = pLineNumber; + +#region Depth + uint32 depth = 0; + var hasContent = false; //In order to check wether it just ran out, or wether we actually have content + for (var c in line) + { + if (c == ' ' || c == ' ') + { + depth++; + continue; + } + else if (c == '#') + { + toReturn.Type = .Empty; + return toReturn; //Comment + } + hasContent = true; + break; + } + if (!hasContent) + { + toReturn.Type = .Empty; + return toReturn; //Is empty + } + line = .(line, depth); + toReturn.Depth = depth; +#endregion + +#region Type + //Get the type of the line + let type = NextPart(line); + line = type.1; + if (type.0 == "") //This should never be reached + { + Runtime.FatalError("Unreachable code reached"); + } + else if (type.0 == "-") + { + toReturn.Type = .Text; + toReturn.Data.Text = line; + toReturn.Depth = depth + 1; + return toReturn; + } + //We have now assured, that the object we are handling is seemingly a normal one + StringView typeName; + switch (type.0) + { + case "c": + let tnameres = NextPart(line); + if (tnameres.0 == "") + { //Its of type custom but ends after the custom + toReturn.Type = .Error; + return toReturn; + } + line = tnameres.1; + typeName = tnameres.0; + case "n": + typeName = "Number"; + case "b": + typeName = "Boolean"; + case "l": + typeName = "Line"; + case "bn": + typeName = "BigNumber"; + case "i": + typeName = "Integer"; + case "bi": + typeName = "BigInteger"; + case "ui": + typeName = "UnsignedInteger"; + case "bui": + typeName = "BigUnsignedInteger"; + case "t": + typeName = "Text"; + case "a": + typeName = "Array"; + case "o": + typeName = "Object"; + default: + toReturn.Type = .Error; + return toReturn; //Unsupported type error + } +#endregion + +#region Name + //Get the name and return if its a array or object + let nameres = NextPart(line); + line = nameres.1; + if (nameres.0 == "") + { + toReturn.Type = .Error; + return toReturn; + } + + if (type.0 == "o" || type.0 == "a") + { + Bofa bofaRes = new Bofa(); + bofaRes.[Friend]_line = pLine; + bofaRes.Name = nameres.0; + toReturn.Type = .Bofa; + toReturn.Data.Bofa = bofaRes; + bofaRes.Typename = typeName; + if (type.0 == "o") + { + bofaRes.Type = .Object; + bofaRes.Value.Object = new .(); + } + else + { + bofaRes.Type = .Array; + bofaRes.Value.Array = new .(); + } + + return toReturn; + } +#endregion + +#region Value + if (line == "") + { + toReturn.Type = .Error; + return toReturn; + } + Bofa bofaRes = new .(); + bofaRes.Name = nameres.0; + bofaRes.Typename = typeName; + + toReturn.Data.Bofa = bofaRes; + bofaRes.[Friend]_line = pLine; + + + switch (type.0) + { + case "n": + bofaRes.Type = .Number; + var result = float.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Value.Number = result.Value; + case "b": + if (line == "true") + bofaRes.Value.Boolean = true; + else if (line == "false") + bofaRes.Value.Boolean = false; + else + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Type = .Boolean; + case "l": + bofaRes.Value.Line = line; + bofaRes.Type = .Line; + case "bn": + bofaRes.Type = .BigNumber; + var result = double.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Value.BigNumber = result.Value; + case "i": + bofaRes.Type = .Integer; + var result = int32.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Value.Integer = result.Value; + case "bi": + bofaRes.Type = .BigInteger; + var result = int64.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + case "ui": + bofaRes.Type = .UnsignedInteger; + var result = uint32.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Value.UnsignedInteger = result.Value; + case "bui": + bofaRes.Type = .BigUnsignedInteger; + var result = uint64.Parse(line); + if (result case .Err) + { + toReturn.Type = .Error; + return toReturn; + } + bofaRes.Value.BigUnsignedInteger = result.Value; + case "t": + bofaRes.Value.Text = new String(line); + bofaRes.Type = .Text; + case "c": + bofaRes.Value.Custom = line; + bofaRes.Type = .Custom; + default: //Unknown type + toReturn.Type = .Error; + return toReturn; + } +#endregion + //If this ever returns something went wrong + toReturn.Type = .Bofa; + return toReturn; + } + + private static (StringView, StringView) NextPart(StringView pLine) + { + int i = 0; + for (var c in pLine) + { + if (c == ' ') + { + if (@c.GetNext() case .Ok(let val)) + { + i++; + } + break; + } + i++; + } + return (.(pLine, 0, (i < pLine.Length) ? i - 1 : i), .(pLine, i)); + } + +} \ No newline at end of file diff --git a/src/BofaResult.bf b/src/BofaResult.bf deleted file mode 100644 index ca5a8e2..0000000 --- a/src/BofaResult.bf +++ /dev/null @@ -1,88 +0,0 @@ -namespace Bofa; -using System; -using System.Collections; -class BofaResult -{ - /* - BofaResult contains the result of an interaction with the Bofa Parser Library - If you parse a String the parser will return a BofaResult Object - Querying an existing Bofa Result will also return a sub BofaResult - This exist in order to keep the bofa data objects clean and instead manage access via this class - It also contains information about wether any one query was sucessfull - */ - private bool _isResult = false; //If this is true we dont delete any actual object data - private String _data = null; //The backing data, needs to be cleared on destruction if set - private Dictionary _contents = new Dictionary(10); //Storage - private List _result = null; - private Bofa _lastObject = null; //Keeps track of the last object we have added - - - public bool OK { //Keeps track of wether the parsing failed at some point - public get; - private set; - } = true; - - public ~this() - { - - if(_result != null) - delete _result; - - if(_data != null) - { - delete _data; - for(let i in _contents) - { - delete i.value; - } - } //We only delete values on the core object in order to not have error with dumb shit - - delete _contents; - } - - private bool Insert(BofaParser.lineParserResult pResult) - { //This is guaranteed to be a valid pResult, because no one would ever call this from somewhere else with different params - //DONT DO THIS ^ - - if(pResult.Bofa.0 > 0 && (_lastObject == null || (_lastObject.Type != .Object && _lastObject.Type != .Array && _lastObject.Type != .Text))) - return false; - - if(pResult.Bofa.0 > 0) - if(pResult.IsBofa) - return _lastObject.[Friend]Insert(pResult.Bofa.0-1, pResult.Bofa.1); - else - return _lastObject.[Friend]Insert(pResult.Text.0-1, null, pResult.Text.1); - //Okay so we really need to add it in on this layer - - if(!pResult.IsBofa) - { //For text entries on the current layer - if(_lastObject.Type != .Text) - return false; - _lastObject.Value.Text.Append(scope $"\n{pResult.Text.1}"); - return true; - } - - let toAdd = pResult.Bofa.1; - if(_contents.ContainsKey(toAdd.Name)) - return false; //Dublicate name error - _contents.Add(toAdd.Name, toAdd); - _lastObject = toAdd; - return true; - } - - ///Interface functions go here - public Result this[StringView pValue] - { - public get { - if(!_contents.ContainsKey(pValue)) - return .Err; - return _contents[pValue]; - } - } - - public Result GetByName(StringView pValue) - { - return this[pValue]; - } - -} \ No newline at end of file diff --git a/src/BofaValue.bf b/src/BofaValue.bf index ae1e8f8..2399e0e 100644 --- a/src/BofaValue.bf +++ b/src/BofaValue.bf @@ -1,17 +1,21 @@ namespace Bofa; + using System; using System.Collections; + [Union] struct BofaValue { + public StringView Line; + public String Text; public float Number; public double BigNumber; public int32 Integer; public int64 BigInteger; + public uint32 UnsignedInteger; + public uint64 BigUnsignedInteger; public bool Boolean; - public StringView Line; public StringView Custom; - public String Text; - public Dictionary Object = null; - public List Array = null; + public Dictionary Object; + public List Array; } \ No newline at end of file diff --git a/src/Extension.bf b/src/Extension.bf new file mode 100644 index 0000000..606e67d --- /dev/null +++ b/src/Extension.bf @@ -0,0 +1,513 @@ +using Bofa.Serialization; +using Bofa; +using System; +using System.Collections; + + +namespace System +{ + static + { + public static mixin cast(var v) + { + *(T*)(void*)&v + } + } + + extension Int : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Integer; + pTarget.Typename = "Integer"; + pTarget.Value.Integer = (.)Math.Clamp(this, int32.MinValue, int32.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .Integer) + return false; + this = Math.Clamp(pInput.Value.Integer, int.MaxValue, int.MinValue); + return true; + } + } + + extension Int8 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Integer; + pTarget.Typename = "Integer"; + pTarget.Value.Integer = (.)Math.Clamp(this, int8.MinValue, int8.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .Integer) + return false; + this = (.)Math.Clamp(pInput.Value.Integer, int8.MaxValue, int8.MinValue); + return true; + } + } + + extension Int16 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Integer; + pTarget.Typename = "Integer"; + pTarget.Value.Integer = (.)Math.Clamp(this, int16.MinValue, int16.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .Integer) + return false; + this = (.)Math.Clamp(pInput.Value.Integer, int16.MaxValue, int16.MinValue); + return true; + } + } + + extension Int32 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Integer; + pTarget.Typename = "Integer"; + pTarget.Value.Integer = (.)this; + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .Integer) + return false; + this = (.)pInput.Value.Integer; + return true; + } + } + + extension Int64 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .BigInteger; + pTarget.Typename = "BigInteger"; + pTarget.Value.BigInteger = (.)this; + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .BigInteger) + return false; + this = (.)pInput.Value.BigInteger; + return true; + } + } + + extension UInt : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .UnsignedInteger; + pTarget.Typename = "UnsignedInteger"; + pTarget.Value.UnsignedInteger = (.)Math.Clamp(this, uint32.MinValue, uint32.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .UnsignedInteger) + return false; + this = (.)Math.Clamp(pInput.Value.UnsignedInteger, uint.MaxValue, uint.MinValue); + return true; + } + } + + extension UInt8 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .UnsignedInteger; + pTarget.Typename = "UnsignedInteger"; + pTarget.Value.UnsignedInteger = (.)Math.Clamp(this, uint8.MinValue, uint8.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .UnsignedInteger) + return false; + this = (.)Math.Clamp(pInput.Value.UnsignedInteger, uint8.MaxValue, uint8.MinValue); + return true; + } + } + + extension UInt16 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .UnsignedInteger; + pTarget.Typename = "UnsignedInteger"; + pTarget.Value.UnsignedInteger = (.)Math.Clamp(this, uint16.MinValue, uint16.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .UnsignedInteger) + return false; + this = (.)Math.Clamp(pInput.Value.UnsignedInteger, uint16.MaxValue, uint16.MinValue); + return true; + } + } + + extension UInt32 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .UnsignedInteger; + pTarget.Typename = "UnsignedInteger"; + pTarget.Value.UnsignedInteger = (.)Math.Clamp(this, uint32.MinValue, uint32.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .UnsignedInteger) + return false; + this = (.)Math.Clamp(pInput.Value.UnsignedInteger, uint32.MaxValue, uint32.MinValue); + return true; + } + } + + extension UInt64 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .BigUnsignedInteger; + pTarget.Typename = "BigUnsignedInteger"; + pTarget.Value.BigUnsignedInteger = (.)Math.Clamp(this, uint64.MinValue, uint64.MaxValue); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .BigUnsignedInteger) + return false; + this = (.)Math.Clamp(pInput.Value.BigUnsignedInteger, uint64.MaxValue, uint64.MinValue); + return true; + } + } + + extension Char8 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Text; + pTarget.Typename = "Text"; + pTarget.Value.Text = new String()..Append(this); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type == .Text) + this = pInput.Value.Text[0]; + else if(pInput.Type == .Line) + this = pInput.Value.Line[0]; + else + return false; + return true; + } + } + + extension Char16 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Text; + pTarget.Typename = "Text"; + pTarget.Value.Text = new String()..Append(this); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type == .Text) + this = pInput.Value.Text[0]; + else if(pInput.Type == .Line) + this = pInput.Value.Line[0]; + else + return false; + return true; + } + } + + extension Char32 : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Text; + pTarget.Typename = "Text"; + pTarget.Value.Text = new String()..Append(this); + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type == .Text) + this = pInput.Value.Text[0]; + else if(pInput.Type == .Line) + this = pInput.Value.Line[0]; + else + return false; + return true; + } + } + + extension Float : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Number; + pTarget.Typename = "Number"; + pTarget.Value.Number = (.)this; + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .Number) + return false; + this = (.)pInput.Value.Number; + return true; + } + } + + extension Double : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .BigNumber; + pTarget.Typename = "BigNumber"; + pTarget.Value.BigNumber = (.)this; + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) mut + { + if(pInput.Type != .BigNumber) + return false; + this = (.)pInput.Value.BigNumber; + return true; + } + } + + extension String : IBofaParseable + { + public bool Serialize(Bofa.Bofa pTarget) + { + if(this.Contains('\n')) + { + pTarget.Type = .Text; + pTarget.Typename = "Text"; + pTarget.Value.Text = new .(this); + } + else + { + pTarget.Type = .Line; + pTarget.Typename = "Line"; + pTarget.Value.Line = this; + } + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) + { + this.Clear(); + if(pInput.Type == .Line) + this.Append(pInput.Value.Line); + else if(pInput.Type == .Text) + this.Append(pInput.Value.Text); + else + return false; + return true; + } + + } +} + +namespace System.Collections +{ + extension List : IBofaParseable where T : IBofaParseable where T : new where T : delete where T : class + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Array; + pTarget.Typename = "Array"; + pTarget.Value.Array = new .(this.Count); + for(var i in this) + { + Bofa toAdd = new .(); + toAdd.[Friend]_line = new .(@i.Index); + toAdd.Name = toAdd.[Friend]_line; + if(!i.Serialize(toAdd)) + { + delete toAdd; + continue; + } + else + { + pTarget.Value.Array.Add(toAdd); + continue; + } + } + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) + { + if(pInput.Type != .Array) + return false; + for(var i in pInput.Value.Array) + { + var toParse = new T(); + if(!toParse.Deserialize(i)) + delete toParse; + else + this.Add((.)toParse); + + } + return true; + } + } + + extension List : IBofaParseable where T : IBofaParseable where T : struct where T : new + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Array; + pTarget.Typename = "Array"; + pTarget.Value.Array = new .(this.Count); + for(var i in this) + { + Bofa toAdd = new .(); + toAdd.[Friend]_line = new .(@i.Index); + toAdd.Name = toAdd.[Friend]_line; + if(!i.Serialize(toAdd)) + { + delete toAdd; + continue; + } + else + { + pTarget.Value.Array.Add(toAdd); + continue; + } + } + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) + { + if(pInput.Type != .Array) + return false; + for(var i in pInput.Value.Array) + { + var toParse = T(); + if(toParse.Deserialize(i)) + this.Add((.)toParse); + + } + return true; + } + } + + extension Dictionary : IBofaParseable where TKey : String where TValue : IBofaParseable where TValue : new where TValue : delete where TValue : class + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Object; + pTarget.Typename = "Object"; + pTarget.Value.Object = new .(); + for(var i in this) + { + Bofa toAdd = new .(); + toAdd.[Friend]_line = new .(@i.Key); + toAdd.Name = toAdd.[Friend]_line; + if(!i.value.Serialize(toAdd)) + { + delete toAdd; + continue; + } + else + { + pTarget.Value.Array.Add(toAdd); + continue; + } + } + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) + { + if(pInput.Type != .Object) + return false; + for(var i in pInput.Value.Object) + { + var toParse = new TValue(); + if(toParse.Deserialize(i.value)) + this.Add(new .(i.key), toParse); + else + delete toParse; + + } + return true; + } + } + + extension Dictionary : IBofaParseable where TKey : String where TValue : IBofaParseable where TValue : new where TValue : struct + { + public bool Serialize(Bofa.Bofa pTarget) + { + pTarget.Type = .Object; + pTarget.Typename = "Object"; + pTarget.Value.Object = new .(); + for(var i in this) + { + Bofa toAdd = new .(); + toAdd.[Friend]_line = new .(@i.Key); + toAdd.Name = toAdd.[Friend]_line; + if(!i.value.Serialize(toAdd)) + { + delete toAdd; + continue; + } + else + { + pTarget.Value.Array.Add(toAdd); + continue; + } + } + return true; + } + + public bool Deserialize(Bofa.Bofa pInput) + { + if(pInput.Type != .Object) + return false; + for(var i in pInput.Value.Object) + { + var toParse = TValue(); + if(toParse.Deserialize(i.value)) + this.Add(new .(i.key), toParse); + } + return true; + } + } +} \ No newline at end of file diff --git a/src/Parser/BofaParserInsert.bf b/src/Parser/BofaParserInsert.bf new file mode 100644 index 0000000..30dafea --- /dev/null +++ b/src/Parser/BofaParserInsert.bf @@ -0,0 +1,80 @@ +namespace Bofa; + +using Bofa.Parser; + +using System; + +extension Bofa +{ + [Inline] + private Result _Insert(ParserEntry* pToAdd) + { + Bofa target = this; + while(!(pToAdd.Depth == 0 || (pToAdd.Type == .Text && pToAdd.Depth == 1))) + { + if(target == null || !(target.Type == .Object || target.Type == .Array)) + return .Err; + + pToAdd.Depth--; + target = target._lastObject; + } + + if(pToAdd.Type == .Text && target.Type == .Text) + { + target.Value.Text.Append(scope $"\n{pToAdd.Data.Text}"); + return .Ok; + } + else if(target.Type == .Object) + { + if(target.Value.Object.ContainsKey(pToAdd.Data.Bofa.Name)) + return .Err; + target.Value.Object.Add(pToAdd.Data.Bofa.Name, pToAdd.Data.Bofa); + target._lastObject = pToAdd.Data.Bofa; + return .Ok; + + } + else if(target.Type == .Array) + { + target.Value.Array.Add(pToAdd.Data.Bofa); + target._lastObject = pToAdd.Data.Bofa; + return .Ok; + } + return .Err; + + /* + //See if we can insert and do so + if(pToAdd.Depth == 0 || (pToAdd.Type == .Text && pToAdd.Depth == 1)) + { + if(pToAdd.Type == .Text && Type == .Text) + { + Value.Text.Append(scope $"\n{pToAdd.Data.Text}"); + return .Ok; + } + else if(Type == .Object) + { + if(Value.Object.ContainsKey(pToAdd.Data.Bofa.Name)) + return .Err; + Value.Object.Add(pToAdd.Data.Bofa.Name, pToAdd.Data.Bofa); + _lastObject = pToAdd.Data.Bofa; + return .Ok; + + } + else if(Type == .Array) + { + Value.Array.Add(pToAdd.Data.Bofa); + _lastObject = pToAdd.Data.Bofa; + return .Ok; + } + + return .Err; //Cannot insert here + } + + //Can we even go deeper + if(_lastObject == null || !(_lastObject.Type == .Object || _lastObject.Type == .Array)) + return .Err; + + pToAdd.Depth--; + return _lastObject._Insert(pToAdd); + */ + } +} \ No newline at end of file diff --git a/src/Parser/EParserEntryType.bf b/src/Parser/EParserEntryType.bf new file mode 100644 index 0000000..1d89ff5 --- /dev/null +++ b/src/Parser/EParserEntryType.bf @@ -0,0 +1,9 @@ +namespace Bofa.Parser; + +enum EParserEntryType +{ + Bofa, + Text, + Empty, + Error +} \ No newline at end of file diff --git a/src/Parser/ParserEntry.bf b/src/Parser/ParserEntry.bf new file mode 100644 index 0000000..d1b1ecf --- /dev/null +++ b/src/Parser/ParserEntry.bf @@ -0,0 +1,11 @@ +namespace Bofa.Parser; + +using System; + +struct ParserEntry +{ + public int64 Line; + public int64 Depth = 0; + public EParserEntryType Type; + public ParserEntryUnion Data; +} \ No newline at end of file diff --git a/src/Parser/ParserEntryUnion.bf b/src/Parser/ParserEntryUnion.bf new file mode 100644 index 0000000..e9e3b4b --- /dev/null +++ b/src/Parser/ParserEntryUnion.bf @@ -0,0 +1,10 @@ +namespace Bofa.Parser; + +using System; + +[Union] +struct ParserEntryUnion +{ + public Bofa Bofa = null; + public StringView Text; +} \ No newline at end of file diff --git a/src/Serialization/BofaIncludeAttribute.bf b/src/Serialization/BofaIncludeAttribute.bf new file mode 100644 index 0000000..515a068 --- /dev/null +++ b/src/Serialization/BofaIncludeAttribute.bf @@ -0,0 +1,13 @@ +namespace Bofa.Serialization; + +using System; + +struct BofaIncludeAttribute : Attribute +{ + public StringView Name; + + public this(StringView pName) + { + Name = pName; + } +} \ No newline at end of file diff --git a/src/Serialization/BofaSerializeAttribute.bf b/src/Serialization/BofaSerializeAttribute.bf new file mode 100644 index 0000000..c3a17f6 --- /dev/null +++ b/src/Serialization/BofaSerializeAttribute.bf @@ -0,0 +1,112 @@ +namespace Bofa.Serialization; + +using System; +using System.Reflection; + +using Bofa; + +[AttributeUsage(.Struct | .Class)] +struct BofaSerializeAttribute : Attribute, IOnTypeInit +{ + [Comptime] + public void OnTypeInit(Type type, Self* prev) + { + String serializeString = scope .("public bool Serialize(Bofa b)\n{\n"); + String deserializeString = scope .("public bool Deserialize(Bofa b)\n{\n"); + + serializeString.Append(""" + b.Type = .Object; + b.Value.Object = new .(); + b.Typename = "Object"; + Bofa toAdd; + + """); + + deserializeString.Append(""" + if(b.Type != .Object) + return false; + + """); + + + var fields = type.GetFields(); + for(var f in fields) + { + if(!f.IsPublic && !f.HasCustomAttribute()) + continue; + + bool hasIFace = false; + for(var i in f.FieldType.Interfaces) + if(i == typeof(IBofaParseable)) + hasIFace = true; + + //Hardcoded stuff + if(f.FieldType == typeof(int)) + hasIFace = true; + else if(f.FieldType == typeof(int8)) + hasIFace = true; + else if(f.FieldType == typeof(int16)) + hasIFace = true; + else if(f.FieldType == typeof(int32)) + hasIFace = true; + else if(f.FieldType == typeof(int64)) + hasIFace = true; + else if(f.FieldType == typeof(uint)) + hasIFace = true; + else if(f.FieldType == typeof(uint8)) + hasIFace = true; + else if(f.FieldType == typeof(uint16)) + hasIFace = true; + else if(f.FieldType == typeof(uint32)) + hasIFace = true; + else if(f.FieldType == typeof(uint64)) + hasIFace = true; + else if(f.FieldType == typeof(char8)) + hasIFace = true; + else if(f.FieldType == typeof(char16)) + hasIFace = true; + else if(f.FieldType == typeof(char32)) + hasIFace = true; + else if(f.FieldType == typeof(float)) + hasIFace = true; + else if(f.FieldType == typeof(double)) + hasIFace = true; + + if(!hasIFace) + continue; + + + StringView name; + name = f.Name; + if(f.HasCustomAttribute() && f.GetCustomAttribute() case .Ok(let attr)) + name = attr.Name; + + serializeString.Append(scope $""" + toAdd = new .(); + toAdd.Name = "{name}"; + if(!{f.Name}.Serialize(toAdd)) + \{ + delete toAdd; + return false; + \} + else + b.Value.Object.Add("{name}", toAdd); + """); + + deserializeString.Append(scope $""" + if(!b.Value.Object.ContainsKey("{name}")) + return false; + if(!{f.Name}.Deserialize(b.Value.Object["{name}"])) + return false; + """); + + } + + + serializeString.Append("\n\treturn true;\n}\n"); + deserializeString.Append("\n\treturn true;\n}\n"); + Compiler.EmitTypeBody(type, serializeString); + Compiler.EmitTypeBody(type, deserializeString); + Compiler.EmitAddInterface(type, typeof(IBofaParseable)); + } +} \ No newline at end of file diff --git a/src/Serialization/IBofaParseable.bf b/src/Serialization/IBofaParseable.bf new file mode 100644 index 0000000..9cd8314 --- /dev/null +++ b/src/Serialization/IBofaParseable.bf @@ -0,0 +1,11 @@ +namespace Bofa.Serialization; + +using System; + +interface IBofaParseable +{ + ///Serializes the current state of a bofa object, may allocate subobjects to fill + public bool Serialize(Bofa pTarget) mut; + ///Attempt to restore an object from the bofa input state + public bool Deserialize(Bofa pInput) mut; +} \ No newline at end of file diff --git a/src/Testing/Default.bf b/src/Testing/Default.bf new file mode 100644 index 0000000..9b708c7 --- /dev/null +++ b/src/Testing/Default.bf @@ -0,0 +1,136 @@ +namespace Bofa.Testing; + +using System; +using System.IO; +using System.Collections; + +class Default +{ + [Test(Name="Parsing all Types")] + public static void Parsing_1() + { + + String content = """ + l one line of text + b bool true + # saoidsaodsad + t text goes here + - Text addendum + n tone 12.5 + #husdhfiudshfds + bn biggie 65645645645.6 + i int 234345 + + + bi bint 38432847329847329 + o object + b content true + a array + b content true + b content true + """; + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(content, output, errors); + + Test.Assert(output.Count == 9); + Test.Assert(errors.Count == 0); + + DeleteDictionaryAndValues!(output); + delete errors; + } + + [Test(Name="Parsing Number")] + public static void Parsing_2() + { + + String content = """ + n number 1.0 + n number_1 345 + bn bignumber 2132432432.56564 + i integer 21324 + bi biginteger 565765765765765 + """; + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(content, output, errors); + + Test.Assert(output.Count == 5); + Test.Assert(errors.Count == 0); + + DeleteDictionaryAndValues!(output); + delete errors; + } + + [Test(Name="Parsing Multiline Text")] + public static void Parsing_3() + { + + String content = """ + t Multiline text + - several lines + - can be added to a single thing + - this does mean + - that stuff has to be appended + - but thats fine + """; + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(content, output, errors); + + Test.Assert(output.Count == 1); + Test.Assert(errors.Count == 0); + + DeleteDictionaryAndValues!(output); + delete errors; + } + + [Test(Name="Parsing Objects")] + public static void Parsing_4() + { + + String content = """ + o object + n member 1 + b other true + + + o obj + o deeper + o even deeper + n member 1 + """; + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(content, output, errors); + + Test.Assert(output.Count == 2); + Test.Assert(errors.Count == 0); + + DeleteDictionaryAndValues!(output); + delete errors; + } + + [Test(Name="Parsing Dublicate Key")] + public static void Parsing_5() + { + + String content = """ + n number 1 + n number 2 + + o obj + n member 1 + n member 2 + """; + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(content, output, errors); + + Test.Assert(output.Count == 2); + Test.Assert(errors.Count == 2); + + DeleteDictionaryAndValues!(output); + delete errors; + } +} \ No newline at end of file diff --git a/src/Testing/Fuzzing.bf b/src/Testing/Fuzzing.bf new file mode 100644 index 0000000..4374964 --- /dev/null +++ b/src/Testing/Fuzzing.bf @@ -0,0 +1,45 @@ +namespace Bofa.Testing; + +using Bofa; + +using System; +using System.Collections; + +class Fuzzing +{ + [Test(Name = "Fuzzing - 10 * 1000000 random characters")] + public static void Fuzzin_Random() + { + for (int i < 10) + { + String toParse = new .(10 * 1500); + for (int ii < 250000) + toParse.Append(cast!(gRand.NextI32())); + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + } + + [Test(Name = "Fuzzing - 10 * 10000000 random characters")] + public static void Fuzzin_Random_Large() + { + for (int i < 10) + { + String toParse = new .(10 * 1500); + for (int ii < 2500000) + toParse.Append(cast!(gRand.NextI32())); + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + } +} \ No newline at end of file diff --git a/src/Testing/Parsing/T_Bofa.bf b/src/Testing/Parsing/T_Bofa.bf deleted file mode 100644 index fef00e0..0000000 --- a/src/Testing/Parsing/T_Bofa.bf +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bofa.Testing.Parsing; - -class T_Bofa -{ - //TODO: Add some testcases that cover multiple lines and insertions - //Such as texts, objects and arrays -} \ No newline at end of file diff --git a/src/Testing/Parsing/T_BofaError.bf b/src/Testing/Parsing/T_BofaError.bf deleted file mode 100644 index 597759b..0000000 --- a/src/Testing/Parsing/T_BofaError.bf +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bofa.Testing.Parsing; - -class T_BofaError -{ - //TODO; add some testcases that fail the parser -} \ No newline at end of file diff --git a/src/Testing/Parsing/T_SingleLine.bf b/src/Testing/Parsing/T_SingleLine.bf deleted file mode 100644 index 2db3731..0000000 --- a/src/Testing/Parsing/T_SingleLine.bf +++ /dev/null @@ -1,81 +0,0 @@ -namespace Bofa.Testing.Parsing; -using System; -using Bofa; -using Bofa.Builder; -class T_SingleLine -{ - /* - This class checks the validity of any single line bofa object - */ - - [Test] //Any line that should be parsed as empty - public static void EmptyLine() - { - StringView[] lineArr = scope .( - "", - " ", - "\t", - " ", - "#asdasdasdasd", - " # asdasdasdasd", - "#" - ); - - BofaParser p = .(); - for(let e in lineArr) - { - var r = p.[Friend]parseLine(e); - if(!r.IsEmpty) - Runtime.FatalError(); - } - } - - [Test] //Some lines that should be parsed as text - public static void Text() - { - StringView[] lineArr = scope .( - "- text", //Depth 0 - " - text", //Depth 1 - " - TEasdasdas iodfjoidfiosduiofsd", //Depth 2 - " - dadsadasdasdasfjrdfiofjiofdsghjniodfgdf" //Depth 3 - ); - BofaParser p = .(); - for(int i < lineArr.Count) - { - var r = p.[Friend]parseLine(lineArr[i]); - if(!r.Ok || r.IsEmpty) - Runtime.FatalError(); - - if(r.Text.0 != i+1) - Runtime.FatalError(); - } - } - - [Test] //Doing some parsing - public static void Parse() - { - StringView[] lineArr = scope .( - "n float 1223", - "n float 1.232", - "b bool true", - "b bool false", - "l line text goes here", - "bn BigNumber 3242343242354543", - "i integer 12423423", - "bi BigInteger 344234323454365", - "t text sdfsdgdfgdfgdf", - "t text ", - "c color redd 255 0 0", - "a array", - "o object" - ); - BofaParser p = .(); - for(var e in lineArr) - { - var r = p.[Friend]parseLine(e); - if(!r.Ok || r.IsEmpty || !r.IsBofa) - Runtime.FatalError(); - delete r.Bofa.1; - } - } -} \ No newline at end of file diff --git a/src/Testing/Profiling.bf b/src/Testing/Profiling.bf new file mode 100644 index 0000000..bbd7a6c --- /dev/null +++ b/src/Testing/Profiling.bf @@ -0,0 +1,219 @@ +namespace Bofa.Testing; + +using Bofa; + +using System; +using System.Collections; + +class Profiling +{ + [Test(Name = "Profiling: normal 1000 * 15")] + public static void Profiling_Normal() + { + for (int i < 1000) + { + + String toParse = scope .(); + toParse.Append(scope $""" + l one line of text + b bool true + # saoidsaodsad + t text goes here + - Text addendum + n tone 12.5 + husdhfiudshfds + bn biggie 65645645645.6 + i int 234345 + bi bint 38432847329847329 + o object + b content true + a array + b content true + b content true + + """); + + + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + } + } + + [Test(Name = "Profiling: normal 10 * 1500")] + public static void Profiling_Normal_Medium() + { + for (int i < 10) + { + String toParse = new .(10 * 1500); + for (int ii < 100) + { + toParse.Append(scope $""" + l one{ii} line of text + b bool{ii} true + # saoidsaodsad + t text{ii} goes here + - Text addendum + n tone{ii} 12.5 + #husdhfiudshfds + bn biggie{ii} 65645645645.6 + i int{ii} 234345 + + + bi bint{ii} 38432847329847329 + o object{ii} + b content true + a array{ii} + b content true + b content true + + """); + } + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + } + + + + [Test(Name = "Profiling: normal 1 * 300000")] + public static void Profiling_Normal_Large() + { + String toParse = new .(10 * 150000); + for (int ii < 20000) + { + toParse.Append(scope $""" + l one{ii} line of text + b bool{ii} true + # saoidsaodsad + t text{ii} goes here + - Text addendum + n tone{ii} 12.5 + #husdhfiudshfds + bn biggie{ii} 65645645645.6 + i int{ii} 234345 + + + bi bint{ii} 38432847329847329 + o object{ii} + b content true + a array{ii} + b content true + b content true + + """); + } + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + + [Test(Name = "Profiling: Depth testing 100 * 100")] + public static void Profiling_Depth_Testing() + { + for (int i < 100) + { + String toParse = scope .(); + for(int ii < 100) + { + toParse.Append(' ', ii); + toParse.Append(scope $"o obj\n"); + } + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + } + } + [Test(Name = "Profiling: Depth testing 100 * 1000")] + public static void Profiling_Depth_Testing_Medium() + { + for (int i < 100) + { + String toParse = scope .(); + for(int ii < 1000) + { + toParse.Append(' ', ii); + toParse.Append(scope $"o obj\n"); + } + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + } + } + + [Test(Name = "Profiling: Depth testing 10 * 5000")] + public static void Profiling_Depth_Testing_Large() + { + for (int i < 10) + { + String toParse = scope .(); + for(int ii < 5000) + { + toParse.Append(' ', ii); + toParse.Append(scope $"o obj\n"); + } + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + output["obj"].DeepDeletion(); + delete output; + delete errors; + } + } + + [Test(Name = "Profiling: Texts 10 * 1000")] + public static void Profiling_Texts() + { + for (int i < 10) + { + String toParse = new .(10 * 1500); + toParse.Append("t text goes here"); + for (int ii < 1000) + toParse.Append("- Addendum"); + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + } + + [Test(Name = "Profiling: Large Texts 10 * 100000")] + public static void Profiling_Texts_Large() + { + for (int i < 10) + { + String toParse = new .(10 * 1500); + toParse.Append("t text goes here"); + for (int ii < 100000) + toParse.Append("- Addendum"); + + Dictionary output = new .(); + List errors = new .(); + BofaParser.Parse(toParse, output, errors); + DeleteDictionaryAndValues!(output); + delete errors; + delete toParse; + } + } + +} \ No newline at end of file diff --git a/src/Testing/Serialization.bf b/src/Testing/Serialization.bf new file mode 100644 index 0000000..6c21d96 --- /dev/null +++ b/src/Testing/Serialization.bf @@ -0,0 +1,27 @@ +namespace Bofa.Testing; + +using System; +using System.Collections; + +using Bofa; +using Bofa.Serialization; + +[BofaSerialize] +class Serialization +{ + public int anInteger = 1; + public String aString = "Value"; + public List listOfStrings = new .() ~ DeleteContainerAndItems!(_); + + [Test] + public static void Test() + { + Serialization s = scope .(); + s.listOfStrings.Add(new .("dfdsfdsfdsf")); + Bofa b = scope .(); + b.Name = "s"; + s.Serialize(b); + + } + +} \ No newline at end of file diff --git a/src/bofa.bf b/src/bofa.bf deleted file mode 100644 index 62a7191..0000000 --- a/src/bofa.bf +++ /dev/null @@ -1,78 +0,0 @@ -namespace Bofa; -using System; -class Bofa -{ - public eBofaType Type; - public StringView TypeName; - public StringView Name; - public BofaValue Value; - - private Bofa LastObject = null; - - public ~this() - { - - if(Type == .Text) - delete Value.Text; - else if(Type == .Object && Value.Object != null) - DeleteDictionaryAndValues!(Value.Object); - else if(Type == .Array && Value.Array != null) - DeleteContainerAndItems!(Value.Array); - } - - private bool Insert(uint32 depth, Bofa toInsert, StringView toAdd = "") - { - if(depth > 0 && (LastObject == null || (LastObject.Type != .Array && LastObject.Type != .Object && LastObject.Type != .Text))) - return false; - - if(depth > 0) - { - return LastObject.Insert(depth-1, toInsert, toAdd); - } - //Actually insert it now - switch(Type) - { - case .Text: - Value.Text.Append(scope $"\n{toAdd}"); - case .Array: - Value.Array.Add(toInsert); - case .Object: - if(Value.Object.ContainsKey(toInsert.Name)) - return false; //Error - Value.Object.Add(toInsert.Name, toInsert); - default: - return false; - } - LastObject = toInsert; - return true; - } - - - //Finding specific entries easier - public Result this[StringView name] - { - public get { - if(Type != .Object) - return .Err; - if(!Value.Object.ContainsKey(name)) - return .Err; - return .Ok(Value.Object[name]); - } - } - - public Result this[uint32 number] - { - public get { - if(Type != .Array) - return .Err; - if(Value.Array.Count <= number) - return .Err; - return Value.Array[number]; - } - } - - public static mixin GetValueOrDefault(Result result, T dfault) - { - result case .Err ? dfault : result - } -} \ No newline at end of file diff --git a/src/bofaParser.bf b/src/bofaParser.bf deleted file mode 100644 index fa932c8..0000000 --- a/src/bofaParser.bf +++ /dev/null @@ -1,287 +0,0 @@ -namespace Bofa; -using System; -struct BofaParser -{ - public BofaResult Parse(StringView pLine) - { - BofaResult toReturn = new .(); - toReturn.[Friend]_data = new String(pLine); - var enumerator = toReturn.[Friend]_data.Split('\n'); - for(let e in enumerator) - { //Loop through each line - var res = parseLine(e); //Handle the result and do the inserting - if(res.IsEmpty) - continue; - if(!res.Ok) - { - toReturn.[Friend]OK = false; - return toReturn; - } - var result = toReturn.[Friend]Insert(res); - if(result == false) - { - toReturn.[Friend]OK = false; - if(res.IsBofa) - delete res.Bofa.1; - return toReturn; - } - } - return toReturn; - } - - - ///Atttempts to parse a single line into a bofa object and returns its depth - private lineParserResult parseLine(StringView pLine) - { - StringView line = pLine; //So that we can work on it - -#region Depth - //Get the depth of the line - uint32 depth = 0; - var hasContent = false; //In order to check wether it just ran out, or wether we actually have content - for(var c in line) - { - if(c == ' ' || c == ' ') - { - depth++; - continue; - } - else if(c == '#') - return .(); //Comment - hasContent = true; - break; - } - if(!hasContent) - return .(); //Is empty - line = .(line, depth); -#endregion - -#region Type - //Get the type of the line - let type = NextPart(line); - line = type.1; - if(type.0 == "") //Shouldnt be empty - return .(); - else if(type.0 == "-") - return .(type.1, depth+1); - //We have now assured, that the object we are handling is seemingly a normal one - StringView typeName; - switch(type.0) - { - case "c": - let tnameres = NextPart(line); - if(tnameres.0 == "") - return .(false); - line = tnameres.1; - typeName = tnameres.0; - case "n": - typeName = "Number"; - case "b": - typeName = "Boolean"; - case "l": - typeName = "Line"; - case "bn": - typeName = "BigNumber"; - case "i": - typeName = "Integer"; - case "bi": - typeName = "BigInteger"; - case "t": - typeName = "Text"; - case "a": - typeName = "Array"; - case "o": - typeName = "Object"; - default: - return .(false); //Unsupported type error - } -#endregion - -#region Name - //Get the name and return if its a array or object - let nameres = NextPart(line); - line = nameres.1; - if(nameres.0 == "") - return .(false); - - if(type.0 == "o" || type.0 == "a") - { - Bofa toReturn = new Bofa(); - toReturn.Name = nameres.0; - toReturn.TypeName = typeName; - if(type.0 == "o") - { - toReturn.Type = .Object; - toReturn.Value.Object = new .(); - return .(toReturn, depth); - } - toReturn.Type = .Array; - toReturn.Value.Array = new .(); - return .(toReturn, depth); - } -#endregion - -#region Value - if(line == "") - return .(false); - Bofa toReturn = new .(); - toReturn.Name = nameres.0; - toReturn.TypeName = typeName; - switch(type.0) - { - case "n": - toReturn.Type = .Number; - var result = float.Parse(line); - if(result case .Err) - { - delete toReturn; - return .(false); - } - toReturn.Value.Number = result.Value; - case "b": - if(line == "true") - toReturn.Value.Boolean = true; - else if(line == "false") - toReturn.Value.Boolean = false; - else - { - delete toReturn; - return .(false); - } - toReturn.Type = .Boolean; - case "l": - toReturn.Value.Line = line; - toReturn.Type = .Line; - case "bn": - toReturn.Type = .BigNumber; - var result = double.Parse(line); - if(result case .Err) - { - delete toReturn; - return .(false); - } - toReturn.Value.BigNumber = result.Value; - case "i": - toReturn.Type = .Integer; - var result = int32.Parse(line); - if(result case .Err) - { - delete toReturn; - return .(false); - } - toReturn.Value.Integer = result.Value; - case "bi": - toReturn.Type = .BigInteger; - var result = int64.Parse(line); - if(result case .Err) - { - delete toReturn; - return .(false); - } - toReturn.Value.BigInteger = result.Value; - case "t": - toReturn.Value.Text = new .(line); - toReturn.Type = .Text; - case "c": - toReturn.Value.Custom = line; - toReturn.Type = .Custom; - default: - delete toReturn; - return .(false); - } -#endregion - //If this ever returns something went wrong - return .(toReturn,depth); - - } - - - - private static (StringView, StringView) NextPart(StringView pLine) - { - int i = 0; - for(var c in pLine) - { - if(c == ' ') - { - if(@c.GetNext() case .Ok(let val)) - { - i++; - } - break; - } - i++; - } - return (.(pLine, 0, (i < pLine.Length) ? i-1 : i), .(pLine, i)); - } - - public struct lineParserResult - { //I hate that this is necessary to handle parseLine, but its just so much data that needs to be moved - private bool ok = true; - private bool isEmpty = false; - private uint32 depth = 0; - - private bool isBofa = true; //Wether this is a bofa or a string extension - private StringView text = ""; - private Bofa bofa = null; - - public this(bool pIsEmpty = true) - { //Either for errors or incase its an empty line - //the difference is that empty lines should be skipped, while an error would - if(pIsEmpty) - isEmpty = true; - else - ok = false; - } - - public this(StringView pText, uint32 pDepth = 0) - { //For text addendums - depth = pDepth; - isBofa = false; - text = pText; - } - - public this(Bofa pBofa, uint32 pDepth = 0) - { //For normal bofa objects - depth = pDepth; - bofa = pBofa; - } - - public bool Ok - { - public get { - return ok; - } - } - - public bool IsEmpty - { - public get { - return isEmpty; - } - } - - public bool IsBofa - { - public get { - return isBofa; - } - } - - public (uint32, Bofa) Bofa - { - public get { - return (depth, bofa); - } - } - - public (uint32, StringView) Text - { - public get { - return (depth, text); - } - } - - } - -} \ No newline at end of file diff --git a/src/eBofaType.bf b/src/eBofaType.bf index 7bc06e9..d3dab82 100644 --- a/src/eBofaType.bf +++ b/src/eBofaType.bf @@ -1,15 +1,17 @@ namespace Bofa; -enum eBofaType +enum EBofaType { - Number, - Boolean, - Line, - BigNumber, - Integer, - BigInteger, - Text, - Custom, - Array, - Object + Line, //String without \n + Text, //String with \n + Number, //32bit floating point number + BigNumber, //64bit floating point number + Integer, //32bit signed integer + BigInteger, //64bit signed integer + UnsignedInteger, //32bit unsigned integer + BigUnsignedInteger, //64bit unsigned integer + Boolean, //8bit true or false + Object, // Key-Value container + Array, //Numeric container + Custom //String with a type attached to it } \ No newline at end of file