Update to version 1.0.0 #3

Merged
Booklordofthedings merged 23 commits from dev into main 2024-08-26 13:33:38 +02:00
26 changed files with 1948 additions and 797 deletions
Showing only changes of commit bafa95f120 - Show all commits

View file

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

View file

@ -3,3 +3,10 @@ Projects = {Bofa = {Path = "."}}
[Workspace]
StartupProject = "Bofa"
[Configs.Test.Win64]
LargeStrings = true
[Configs.Debug.Win64]
LargeStrings = true
LargeCollections = true

149
README.md
View file

@ -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<StringView, Bofa> output = new .();
List<int64> 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

242
src/Bofa.bf Normal file
View file

@ -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<Bofa> toDelete = new .();
List<Bofa> 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<Bofa> 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<Bofa> Get(int pIndex)
{
if(Type != .Array)
return .Err;
if(!(Value.Array.Count > pIndex))
return .Err;
return Value.Array[pIndex];
}
}

View file

@ -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<b> Members;
public this(String name, String type, String typeName, String value, Span<b> 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<b> values;
private List<b> valueList = new .();
public this(params Span<b> 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<b> input)
{
return .(new .(name), "o", null,null,input);
}
public static b Array(StringView name, params Span<b> 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<b> input)
{
return .("-", "o", null, null, input);
}
public static b ArrayA(params Span<b> 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 .()));
}
}

337
src/BofaParser.bf Normal file
View file

@ -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<StringView, Bofa> pResult, List<int64> pErrors)
{
StringStream stream = new .(pToParse, .Reference);
defer delete stream;
Parse(stream, pResult, pErrors);
}
public static void Parse(Stream pToParse, Dictionary<StringView, Bofa> pResult, List<int64> 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));
}
}

View file

@ -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<StringView, Bofa> _contents = new Dictionary<StringView, Bofa>(10); //Storage
private List<Bofa> _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<Bofa> this[StringView pValue]
{
public get {
if(!_contents.ContainsKey(pValue))
return .Err;
return _contents[pValue];
}
}
public Result<Bofa> GetByName(StringView pValue)
{
return this[pValue];
}
}

View file

@ -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<StringView, Bofa> Object = null;
public List<Bofa> Array = null;
public Dictionary<StringView, Bofa> Object;
public List<Bofa> Array;
}

513
src/Extension.bf Normal file
View file

@ -0,0 +1,513 @@
using Bofa.Serialization;
using Bofa;
using System;
using System.Collections;
namespace System
{
static
{
public static mixin cast<T>(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<T> : 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<T> : 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<TKey, TValue> : 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<TKey, TValue> : 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;
}
}
}

View file

@ -0,0 +1,80 @@
namespace Bofa;
using Bofa.Parser;
using System;
extension Bofa
{
[Inline]
private Result<void> _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);
*/
}
}

View file

@ -0,0 +1,9 @@
namespace Bofa.Parser;
enum EParserEntryType
{
Bofa,
Text,
Empty,
Error
}

11
src/Parser/ParserEntry.bf Normal file
View file

@ -0,0 +1,11 @@
namespace Bofa.Parser;
using System;
struct ParserEntry
{
public int64 Line;
public int64 Depth = 0;
public EParserEntryType Type;
public ParserEntryUnion Data;
}

View file

@ -0,0 +1,10 @@
namespace Bofa.Parser;
using System;
[Union]
struct ParserEntryUnion
{
public Bofa Bofa = null;
public StringView Text;
}

View file

@ -0,0 +1,13 @@
namespace Bofa.Serialization;
using System;
struct BofaIncludeAttribute : Attribute
{
public StringView Name;
public this(StringView pName)
{
Name = pName;
}
}

View file

@ -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<BofaIncludeAttribute>())
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<BofaIncludeAttribute>() && f.GetCustomAttribute<BofaIncludeAttribute>() 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));
}
}

View file

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

136
src/Testing/Default.bf Normal file
View file

@ -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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> errors = new .();
BofaParser.Parse(content, output, errors);
Test.Assert(output.Count == 2);
Test.Assert(errors.Count == 2);
DeleteDictionaryAndValues!(output);
delete errors;
}
}

45
src/Testing/Fuzzing.bf Normal file
View file

@ -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!<uint8[4]>(gRand.NextI32()));
Dictionary<StringView, Bofa> output = new .();
List<int64> 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!<uint8[4]>(gRand.NextI32()));
Dictionary<StringView, Bofa> output = new .();
List<int64> errors = new .();
BofaParser.Parse(toParse, output, errors);
DeleteDictionaryAndValues!(output);
delete errors;
delete toParse;
}
}
}

View file

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

View file

@ -1,6 +0,0 @@
namespace Bofa.Testing.Parsing;
class T_BofaError
{
//TODO; add some testcases that fail the parser
}

View file

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

219
src/Testing/Profiling.bf Normal file
View file

@ -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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> 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<StringView, Bofa> output = new .();
List<int64> errors = new .();
BofaParser.Parse(toParse, output, errors);
DeleteDictionaryAndValues!(output);
delete errors;
delete toParse;
}
}
}

View file

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

View file

@ -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<Bofa> this[StringView name]
{
public get {
if(Type != .Object)
return .Err;
if(!Value.Object.ContainsKey(name))
return .Err;
return .Ok(Value.Object[name]);
}
}
public Result<Bofa> 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<T>(Result<T> result, T dfault)
{
result case .Err ? dfault : result
}
}

View file

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

View file

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