diff --git a/src/JSON/JSON.bf b/src/JSON/JSON.bf new file mode 100644 index 0000000..e7445ba --- /dev/null +++ b/src/JSON/JSON.bf @@ -0,0 +1,337 @@ +/* + JSON I/O based on Noel Berry's C# JSON library + https://github.com/NoelFB/JSON +*/ + +using System.IO; +using System; +using System.Collections; + +namespace Strawberry +{ + public class JSON + { + public enum Types { Null, Bool, Number, String, Array, Object }; + + public Types Type { get; private set; } + public bool Bool; + public float Number; + public String String; + + private List array; + private Dictionary children; + + public this() + { + Type = .Object; + children = new Dictionary(); + } + + public this(bool value) + { + Type = .Bool; + Bool = value; + } + + public this(int value) + { + Type = .Number; + Number = value; + } + + public this(float value) + { + Type = .Number; + Number = value; + } + + public this(String value) + { + Type = .String; + String = new String(value); + } + + public this(params JSON[] value) + { + Type = .Array; + array = new List(value.GetEnumerator()); + } + + private this(Types type) + { + Type = type; + } + + public ~this() + { + if (String != null) + delete String; + + if (array != null) + { + for (let a in array) + delete a; + delete array; + } + + if (children != null) + { + for (let a in children) + { + delete a.key; + delete a.value; + } + delete children; + } + } + + // Array + + public JSON this[int index] + { + get + { + Runtime.Assert(Type == .Array); + return array[index]; + } + + set + { + Runtime.Assert(Type == .Array); + array[index] = value; + } + } + + public void ArrayPush(JSON json) + { + Runtime.Assert(Type == .Array); + array.Add(json); + } + + public void ArrayRemoveAt(int index) + { + Runtime.Assert(Type == .Array); + let a = array[index]; + array.RemoveAt(index); + delete a; + } + + public void ArrayClear() + { + Runtime.Assert(Type == .Array); + for (let a in array) + delete a; + array.Clear(); + } + + // Object + + public JSON this[String key] + { + get + { + Runtime.Assert(Type == .Object); + + if (children.ContainsKey(key)) + return children[key]; + else + return null; + } + + set + { + Runtime.Assert(Type == .Object); + + //Remove previous child under that key + RemoveChild(key); + + //Add new child + children.Add(key, value); + } + } + + public void RemoveChild(String key) + { + Runtime.Assert(Type == .Object); + if (children.ContainsKey(key)) + delete children.GetAndRemove(key).Value.value; + } + + // Operators + + static public implicit operator JSON(bool val) + { + return new JSON(val); + } + + static public implicit operator JSON(int val) + { + return new JSON(val); + } + + static public implicit operator JSON(float val) + { + return new JSON(val); + } + + static public implicit operator JSON(String val) + { + return new JSON(val); + } + + static public implicit operator bool(JSON json) + { + Runtime.Assert(json.Type == .Bool); + return json.Bool; + } + + static public implicit operator int(JSON json) + { + Runtime.Assert(json.Type == .Number); + return (int)json.Number; + } + + static public implicit operator float(JSON json) + { + Runtime.Assert(json.Type == .Number); + return json.Number; + } + + static public implicit operator String(JSON json) + { + Runtime.Assert(json.Type == .String); + return json.String; + } + + static public JSON CreateArray() + { + let json = new JSON(.Array); + json.array = new List(); + return json; + } + + static public JSON CreateNull() + { + return new JSON(.Null); + } + + // Out + + public void ToString(String strBuffer, int tabLevel) + { + switch (Type) + { + case .Null: + strBuffer.Append("null"); + break; + + case .Bool: + Bool.ToString(strBuffer); + break; + + case .Number: + Number.ToString(strBuffer); + break; + + case .String: + strBuffer.Append('"'); + strBuffer.Append(String); + strBuffer.Append('"'); + break; + + case .Array: + strBuffer.Append("[\n"); + for (int i < array.Count) + { + for (let t < tabLevel + 1) + strBuffer.Append('\t'); + array[i].ToString(strBuffer, tabLevel + 1); + if (i < array.Count - 1) + strBuffer.Append(','); + strBuffer.Append('\n'); + } + for (let t < tabLevel) + strBuffer.Append('\t'); + strBuffer.Append("]"); + break; + + case .Object: + strBuffer.Append("{\n"); + int c = 0; + for (let o in children) + { + for (let t < tabLevel + 1) + strBuffer.Append('\t'); + strBuffer.Append('"'); + strBuffer.Append(o.key); + strBuffer.Append("\": "); + o.value.ToString(strBuffer, tabLevel + 1); + + c++; + if (c < children.Count) + strBuffer.Append(','); + strBuffer.Append('\n'); + } + for (let t < tabLevel) + strBuffer.Append('\t'); + strBuffer.Append("}"); + } + } + + public override void ToString(String strBuffer) + { + ToString(strBuffer, 0); + } + + public void ToStream(Stream stream) + { + let str = scope String(); + ToString(str); + for (let i < str.Length) + stream.Write(str[i]); + } + + public void ToFile(String filePath) + { + if (File.Exists(filePath)) + File.Delete(filePath); + + FileStream stream = scope FileStream(); + stream.Create(filePath, .Write); + ToStream(stream); + stream.Close(); + } + + // In + + static public JSON FromString(String string) + { + return (scope JSONReader(string)).JSON; + } + + static public JSON FromStream(Stream stream) + { + String str = scope String(); + while (stream.Position < stream.Length) + { + let char = stream.Read(); + if (char case .Err) + Runtime.FatalError("File read error!"); + else + str.Append(char); + } + + return (scope JSONReader(str)).JSON; + } + + static public JSON FromFile(String filePath) + { + FileStream stream = scope FileStream(); + stream.Open(filePath, .Read); + let json = FromStream(stream); + stream.Close(); + + return json; + } + } +} diff --git a/src/JSON/JSONReader.bf b/src/JSON/JSONReader.bf new file mode 100644 index 0000000..72a85dd --- /dev/null +++ b/src/JSON/JSONReader.bf @@ -0,0 +1,229 @@ +using System; +using System.IO; + +namespace Strawberry +{ + public class JSONReader + { + private enum Tokens { Key, Null, Bool, Number, String, ObjectStart, ObjectEnd, ArrayStart, ArrayEnd }; + + public JSON JSON { get; private set; } + + private int index; + private String data; + private String buffer = new String() ~ delete _; + private bool boolValue; + private float numberValue; + + private bool EndOfFile => index >= data.Length; + + public this(String data) + { + index = 0; + this.data = data; + + let t = Read(); + if (t != .ObjectStart) + Runtime.FatalError("Expected {"); + JSON = ReadObject(); + } + + public JSON ReadObject() + { + JSON json = new JSON(); + String currentKey = null; + + while (!EndOfFile) + { + let t = Read(); + + if (currentKey == null) + { + if (t == .ObjectEnd) + break; + else if (t != .Key) + Runtime.FatalError("Expected key"); + + currentKey = new String(buffer); + } + else + { + switch (t) + { + case .Null: + json[currentKey] = Strawberry.JSON.CreateNull(); + + case .Bool: + json[currentKey] = boolValue; + + case .Number: + json[currentKey] = numberValue; + + case .String: + json[currentKey] = buffer; + + case .ObjectStart: + json[currentKey] = ReadObject(); + + case .ArrayStart: + json[currentKey] = ReadArray(); + + default: + Runtime.FatalError("Unexpected token"); + } + + currentKey = null; + } + } + + return json; + } + + public JSON ReadArray() + { + JSON json = Strawberry.JSON.CreateArray(); + + L: while (!EndOfFile) + { + let t = Read(); + switch (t) + { + case .ArrayEnd: + break L; + + case .Null: + json.ArrayPush(Strawberry.JSON.CreateNull()); + + case .Bool: + json.ArrayPush(boolValue); + + case .Number: + json.ArrayPush(numberValue); + + case .String: + json.ArrayPush(buffer); + + case .ObjectStart: + json.ArrayPush(ReadObject()); + + case .ArrayStart: + json.ArrayPush(ReadArray()); + + default: + Calc.Log(t); + Runtime.FatalError("Unexpected token"); + } + } + + return json; + } + + private char8 Step() + { + if (index < data.Length) + return data[index++]; + else + return 0; + } + + private char8 Peek() + { + if (index < data.Length) + return data[index]; + else + return 0; + } + + private bool IsWhitespace(char8 char) + { + return char == ' ' || char == '\t' || char == '\n' || char == '\r'; + } + + private Tokens Read() + { + bool inComment = false; + buffer.Clear(); + + while (!EndOfFile) + { + char8 c = Step(); + + //Finish Comments + if (inComment) + { + if (c == '\n' || c == '\r') + inComment = false; + continue; + } + + switch (c) + { + case '#': + case '/' when Peek() == '/': + inComment = true; + continue; + + case ' ': + case '\n': + case '\t': + case '\r': + case ':': + case ',': + continue; + + case '{': + return Tokens.ObjectStart; + + case '}': + return Tokens.ObjectEnd; + + case '[': + return Tokens.ArrayStart; + + case ']': + return Tokens.ArrayEnd; + + case '"': + while (!EndOfFile && Peek() != '"') + buffer.Append(Step()); + Step(); + + while (IsWhitespace(Peek())) + Step(); + + if (Peek() == ':') + return Tokens.Key; + else + return Tokens.String; + + default: + buffer.Append(c); + while (!EndOfFile && !(" \r\n,:{}[]#").Contains(Peek())) + buffer.Append(Step()); + + if (buffer.Equals("null", .InvariantCultureIgnoreCase)) + return Tokens.Null; + else if (buffer.Equals("true", .InvariantCultureIgnoreCase)) + { + boolValue = true; + return Tokens.Bool; + } + else if (buffer.Equals("false", .InvariantCultureIgnoreCase)) + { + boolValue = false; + return Tokens.Bool; + } + else + { + if (float.Parse(buffer) case .Err) + Calc.Log(buffer); + numberValue = float.Parse(buffer); + return Tokens.Number; + } + } + } + + Runtime.FatalError("Malformed JSON"); + } + } +}