From b42fad51d258608ff9418376a2e2a2b4b357daff Mon Sep 17 00:00:00 2001 From: Javi Agenjo Date: Fri, 19 Jul 2019 19:41:05 +0200 Subject: [PATCH] c sharp test --- csharp/LiteGraph.cs | 604 +++++++++++++++++ csharp/LiteGraphNodes.cs | 203 ++++++ csharp/LiteGraphTest.cs | 53 ++ csharp/SimpleJSON.cs | 1395 ++++++++++++++++++++++++++++++++++++++ csharp/graph.JSON | 1 + 5 files changed, 2256 insertions(+) create mode 100644 csharp/LiteGraph.cs create mode 100644 csharp/LiteGraphNodes.cs create mode 100644 csharp/LiteGraphTest.cs create mode 100644 csharp/SimpleJSON.cs create mode 100644 csharp/graph.JSON diff --git a/csharp/LiteGraph.cs b/csharp/LiteGraph.cs new file mode 100644 index 000000000..f46bb9340 --- /dev/null +++ b/csharp/LiteGraph.cs @@ -0,0 +1,604 @@ +using System; +using System.Collections; +using System.Collections.Generic; +//using System.Diagnostics; +using UnityEngine; //for debug messages +using SimpleJSON; //to parse the JSON + + +namespace LiteGraph +{ + public enum DataType { NONE, ENUM, NUMBER, STRING, BOOL, VEC2, VEC3 }; + + public struct vec2 { + float x; + float y; + }; + + public struct vec3 + { + float x; + float y; + float z; + }; + + //not used yet... + public class MutableType { + public DataType type; + public bool data_bool; + public float data_number; + public string data_string; + public vec2 data_vec2; + public vec3 data_vec3; + public bool AsBool { get { return data_bool; } } + public int AsEnum { get { return (int)data_number; } } + public float AsFloat { get { return data_number; } } + public string AsString { get { return data_string; } } + public vec2 AsVec2 { get { return data_vec2; } } + public vec3 AsVec3 { get { return data_vec3; } } + public void Set(bool v) { type = DataType.BOOL; data_bool = v; } + public void Set(int v) { type = DataType.ENUM; data_number = v; } + public void Set(float v) { type = DataType.NUMBER; data_number = v; } + public void Set(string v) { type = DataType.STRING; data_string = v; } + public void Set(vec2 v) { type = DataType.VEC2; data_vec2 = v; } + public void Set(vec3 v) { type = DataType.VEC3; data_vec3 = v; } + public override string ToString() { + switch (type) + { + case DataType.NONE: return ""; + case DataType.ENUM: return data_number.ToString(); + case DataType.BOOL: return data_bool.ToString(); + case DataType.NUMBER: return data_number.ToString(); + case DataType.STRING: return data_string; + case DataType.VEC2: return data_vec2.ToString(); + case DataType.VEC3: return data_vec3.ToString(); + } + return ""; + } + }; + + //to store connection info + public class LLink { + public int id; + public int origin_id; + public int origin_slot; + public int target_id; + public int target_slot; + + public DataType data_type; + + public bool data_bool; + public float data_float; + public string data_string; + public vec2 data_vec2; + public vec3 data_vec3; + + public LLink(int id, DataType type, int origin_id, int origin_slot, int target_id, int target_slot) + { + this.id = id; + this.data_type = type; + this.origin_id = origin_id; + this.origin_slot = origin_slot; + this.target_id = target_id; + this.target_slot = target_slot; + } + + public void setData(bool data) + { + data_type = DataType.BOOL; + data_bool = data; + } + + public void setData(float data) + { + data_type = DataType.NUMBER; + data_float = data; + } + + public void setData(string data) + { + data_type = DataType.STRING; + data_string = data; + } + + public void setData(vec2 data) + { + data_type = DataType.VEC2; + data_vec2 = data; + } + + public void setData(vec3 data) + { + data_type = DataType.VEC3; + data_vec3 = data; + } + + } + + //to store slot info + public class LSlot + { + public int num; + public string name; + public DataType type; + public LLink link = null; //for input slots + public List links = new List(); //for output slots + + public LSlot(string name, DataType type) + { + this.name = name; + this.type = type; + } + } + + //the node base class + public class LGraphNode { + public int id = -1; + + public LGraph graph = null; + public int order = -1; + + public List inputs = new List(); + public List outputs = new List(); + + public LGraphNode() + { + } + + public virtual LSlot addInput(string name, DataType type = DataType.NONE) + { + LSlot slot = new LSlot(name, type); + slot.num = inputs.Count; + inputs.Add(slot); + return slot; + } + + public virtual LSlot addOutput(string name, DataType type = DataType.NONE) + { + LSlot slot = new LSlot(name, type); + slot.num = outputs.Count; + outputs.Add(slot); + return slot; + } + + public virtual bool getInputData(int slot_num, bool default_value) + { + LLink link = inputs[slot_num].link; + if (link != null) + return link.data_bool; + return default_value; + } + + public virtual float getInputData(int slot_num, float default_value ) + { + LLink link = inputs[slot_num].link; + if (link != null) + return link.data_float; + return default_value; + } + + public virtual string getInputData(int slot_num, string default_value) + { + LLink link = inputs[slot_num].link; + if (link != null) + return link.data_string; + return default_value; + } + + public virtual vec2 getInputData(int slot_num, vec2 default_value) + { + LLink link = inputs[slot_num].link; + if (link != null) + return link.data_vec2; + return default_value; + } + + public virtual vec3 getInputData(int slot_num, vec3 default_value) + { + LLink link = inputs[slot_num].link; + if (link != null) + return link.data_vec3; + return default_value; + } + + public virtual void setOutputData(int slot_num, bool v) + { + if (inputs.Count <= slot_num) + return; + LSlot slot = outputs[slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + link.setData(v); + } + } + + public virtual void setOutputData(int slot_num, float v) + { + if (outputs.Count <= slot_num) + return; + LSlot slot = outputs[slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + link.setData(v); + } + } + + public virtual void setOutputData(int slot_num, string v) + { + if (outputs.Count <= slot_num) + return; + LSlot slot = outputs[slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + link.setData(v); + } + } + + public virtual void setOutputData(int slot_num, vec2 v) + { + if (outputs.Count <= slot_num) + return; + LSlot slot = outputs[slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + link.setData(v); + } + } + + public virtual void setOutputData(int slot_num, vec3 v) + { + if (outputs.Count <= slot_num) + return; + LSlot slot = outputs[slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + link.setData(v); + } + } + + public virtual void transferData(int input_slot_num, int output_slot_num) + { + LLink input_link = inputs[input_slot_num].link; + if (input_link == null) + return; + + if (outputs.Count <= output_slot_num) + return; + LSlot slot = outputs[output_slot_num]; + if (slot == null) + return; + for (int i = 0; i < slot.links.Count; ++i) + { + LLink link = slot.links[i]; + if (link == null) + return; + switch(input_link.data_type) + { + case DataType.BOOL: link.setData(input_link.data_bool); break; + case DataType.NUMBER: link.setData(input_link.data_float); break; + case DataType.STRING: link.setData(input_link.data_string); break; + case DataType.VEC2: link.setData(input_link.data_vec2); break; + case DataType.VEC3: link.setData(input_link.data_vec3); break; + } + } + } + + public virtual bool connect(int origin_slot, LGraphNode target, int target_slot) + { + if(graph == null) + throw (new Exception("node does not belong to a graph")); + if (graph != target.graph) + throw (new Exception("nodes do not belong to same graph") ); + + LSlot origin_slot_info = this.outputs[origin_slot]; + LSlot target_slot_info = target.inputs[target_slot]; + if (origin_slot_info == null || target_slot_info == null) + return false; + + if(origin_slot_info.type != target_slot_info.type && (origin_slot_info.type != DataType.NONE && target_slot_info.type != DataType.NONE) ) + throw (new Exception("connecting incompatible types")); + + int id = graph.last_link_id++; + LLink link = new LLink(id, origin_slot_info.type, this.id, origin_slot, target.id, target_slot); + + graph.links.Add(link); + origin_slot_info.links.Add(link); + target_slot_info.link = link; + + graph.sortByExecutionOrder(); + return true; + } + + public virtual void onExecute() + { + } + + public virtual void configure(JSONNode json_node) + { + this.id = json_node["id"].AsInt; + this.order = json_node["order"].AsInt; + + //inputs + var json_inputs = json_node["inputs"]; + if (json_inputs != null) + { + JSONNode.Enumerator it = json_inputs.GetEnumerator(); + int i = 0; + while(it.MoveNext()) + { + JSONNode json_slot = it.Current; + string str_type = json_slot["type"]; + DataType type = DataType.NONE; + if (str_type != null && Globals.stringToDataType.ContainsKey(str_type)) + type = Globals.stringToDataType[str_type]; + LSlot slot = null; + if (inputs.Count > i) + slot = inputs[i]; + if(slot == null) + slot = this.addInput( json_slot["name"], type ); + JSONNode json_link = json_slot["link"]; + if (json_link != null) + slot.link = graph.links_by_id[json_link.AsInt]; + ++i; + } + } + + //outputs + var json_outputs = json_node["outputs"]; + if (json_outputs != null) + { + JSONNode.Enumerator it = json_outputs.GetEnumerator(); + int i = 0; + while (it.MoveNext()) + { + JSONNode json_slot = it.Current; + string str_type = json_slot["type"]; + DataType type = DataType.NONE; + if (str_type != null && Globals.stringToDataType.ContainsKey(str_type)) + type = Globals.stringToDataType[str_type]; + LSlot slot = null; + if (outputs.Count > i) + slot = outputs[i]; + if (slot == null) + slot = this.addOutput(json_slot["name"], type); + + JSONNode json_links = json_slot["links"]; + if(json_links != null) + { + JSONNode.Enumerator it2 = json_links.GetEnumerator(); + while (it2.MoveNext()) + { + JSONNode json_link_id = it2.Current; + LLink link = graph.links_by_id[json_link_id.AsInt]; + if (link != null) + slot.links.Add(link); + else + Debug.LogError("Link ID not found!: " + json_link_id); + } + } + ++i; + } + } + + //custom data (properties) + this.onConfigure(json_node); + } + + public virtual void onConfigure(JSONNode json_node) + { + //overwrite this one + } + } + + //namespace to store global litegraph data + public class Globals + { + static public Dictionary stringToDataType = new Dictionary { { "NONE", DataType.NONE }, { "", DataType.NONE }, { "ENUM", DataType.ENUM }, {"NUMBER",DataType.NUMBER }, { "number", DataType.NUMBER }, { "BOOLEAN", DataType.BOOL }, { "boolean", DataType.BOOL }, { "STRING", DataType.STRING }, { "string", DataType.STRING }, { "VEC2", DataType.VEC2 } }; + + static public Dictionary> node_types = new Dictionary>(); + static public void registerType(string name, Func ctor ) + { + node_types.Add(name, ctor); + } + static public LGraphNode createNodeType(string name) + { + if (!node_types.ContainsKey(name)) + { + Debug.Log("Node type not found: " + name); + return null; + } + Func ctor = node_types[name]; + return ctor(); + } + + }; + + //one graph + public class LGraph + { + public List nodes = new List(); + public Dictionary nodes_by_id = new Dictionary(); + public List nodes_in_execution_order = new List(); + public List links = new List(); + public Dictionary links_by_id = new Dictionary(); + + public bool has_errors = false; + public int last_node_id = 0; + public int last_link_id = 0; + public double time = 0; //time in seconds + + public Dictionary outputs = new Dictionary(); + + // Start is called before the first frame update + public LGraph() + { + } + + public void add(LGraphNode node) + { + if (node.graph != null) + throw ( new Exception("already has graph") ); + + node.graph = this; + node.id = last_node_id++; + node.order = node.id; + nodes.Add(node); + nodes_by_id.Add(node.id, node); + } + + public void clear() + { + has_errors = false; + nodes.Clear(); + nodes_by_id.Clear(); + links.Clear(); + links_by_id.Clear(); + nodes_in_execution_order.Clear(); + last_link_id = 0; + last_node_id = 0; + outputs.Clear(); + } + + // Update is called once per frame + public void runStep(float dt = 0) + { + for (int i = 0; i < nodes_in_execution_order.Count; ++i) + { + LGraphNode node = nodes_in_execution_order[i]; + node.onExecute(); + } + time += dt; + } + + public void configure(string data) + { + sortByExecutionOrder(); + } + + public void sortByExecutionOrder() + { + nodes_in_execution_order = nodes.GetRange(0, nodes.Count); + + nodes_in_execution_order.Sort(delegate (LGraphNode a, LGraphNode b) + { + return a.order - b.order; + }); + } + + public void fromJSONText(string text) + { + clear(); + + var root = JSON.Parse(text); + last_node_id = root["last_node_id"].AsInt; + last_link_id = root["last_link_id"].AsInt; + + var json_links = root["links"]; + for (int i = 0; i < json_links.Count; ++i) + { + var json_node = json_links[i]; + int id = json_node[0].AsInt; + int origin_id = json_node[1].AsInt; + int origin_slot = json_node[2].AsInt; + int target_id = json_node[3].AsInt; + int target_slot = json_node[4].AsInt; + JSONNode json_type = json_node[5]; + DataType type = DataType.NONE; + + if(json_type != null && json_type.Value != "0" && Globals.stringToDataType.ContainsKey(json_type) ) + type = Globals.stringToDataType[ json_type ]; + + LLink link = new LLink(id, type, origin_id, origin_slot, target_id, target_slot); + links.Add(link); + links_by_id[link.id] = link; + } + + var json_nodes = root["nodes"]; + for (int i = 0; i < json_nodes.Count; ++i) + { + var json_node = json_nodes[i]; + string node_type = json_node["type"]; + Debug.Log(node_type); + LGraphNode node = LiteGraph.Globals.createNodeType(node_type); + if (node == null) + { + Debug.Log("Error: node type not found: " + node_type); + has_errors = true; + continue; + } + node.graph = this; + nodes.Add(node); + node.configure(json_node); + } + + sortByExecutionOrder(); + } + + public float getOutput(string name, float def_value) + { + if (!outputs.ContainsKey(name)) + return def_value; + return outputs[name]; + } + + } + + public class Main + { + public static void Init() + { + Main.loadNodes(); + } + + public static void loadNodes() + { + Globals.registerType(WatchNode.type, () => new WatchNode() ); + Globals.registerType(RandomNumberNode.type, () => new RandomNumberNode()); + Globals.registerType(ConstNumberNode.type, () => new ConstNumberNode()); + Globals.registerType(TimeNode.type, () => new TimeNode()); + Globals.registerType(ConditionNode.type, () => new ConditionNode()); + Globals.registerType(GraphOutputNode.type, () => new GraphOutputNode()); + Globals.registerType(GateNode.type, () => new GateNode()); + + } + + public static void test() + { + Debug.Log("Testing Graph..."); + LGraph graph = new LGraph(); + LGraphNode node1 = LiteGraph.Globals.createNodeType("math/rand"); + graph.add(node1); + LGraphNode node2 = LiteGraph.Globals.createNodeType("basic/watch"); + graph.add(node2); + node1.connect(0,node2,0); + + for(int i = 0; i < 100; ++i) + graph.runStep(); + } + } +} \ No newline at end of file diff --git a/csharp/LiteGraphNodes.cs b/csharp/LiteGraphNodes.cs new file mode 100644 index 000000000..c96977426 --- /dev/null +++ b/csharp/LiteGraphNodes.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +using SimpleJSON; + +namespace LiteGraph +{ + public class ConstNumberNode : LGraphNode + { + public static string type = "basic/const"; + + public float value = 0; + + public ConstNumberNode() + { + this.addOutput("out", DataType.NUMBER ); + } + + override public void onExecute() + { + this.setOutputData(0, value ); + } + + override public void onConfigure( JSONNode o) + { + JSONNode json_properties = o["properties"]; + value = json_properties["value"].AsFloat; + } + } + + public class RandomNumberNode : LGraphNode + { + public static string type = "math/rand"; + + float min_value = 0; + float max_value = 1; + System.Random random = new System.Random(); + + public RandomNumberNode() + { + this.addOutput("out", DataType.NUMBER ); + } + + override public void onExecute() + { + this.setOutputData(0, (float)(min_value + random.NextDouble() * (max_value - min_value)) ); + } + + override public void onConfigure(JSONNode o) + { + JSONNode json_properties = o["properties"]; + min_value = json_properties["min"].AsFloat; + max_value = json_properties["max"].AsFloat; + } + } + + public class GraphOutputNode : LGraphNode + { + public static string type = "graph/output"; + + string name = ""; + DataType datatype = DataType.NUMBER; + + public GraphOutputNode() + { + this.addInput("out", datatype); + } + + override public void onExecute() + { + switch (datatype) + { + case DataType.NUMBER: + float v = this.getInputData(0, 0); + graph.outputs[name] = v; + break; + } + } + + override public void onConfigure(JSONNode o) + { + JSONNode json_properties = o["properties"]; + name = json_properties["name"]; + if (json_properties.HasKey("type")) + { + string str_type = json_properties["type"]; + if (Globals.stringToDataType.ContainsKey(str_type)) + { + datatype = Globals.stringToDataType[str_type]; + this.inputs[0].type = datatype; + } + } + } + } + + public class ConditionNode : LGraphNode + { + public static string type = "math/condition"; + + public enum OPERATION { NONE,GREATER,LOWER,EQUAL,NEQUAL,GEQUAL,LEQUAL,OR,AND }; + static public Dictionary strToOperation = new Dictionary { { "NONE", OPERATION.NONE }, { ">", OPERATION.GREATER }, { "<", OPERATION.LOWER }, { "==", OPERATION.EQUAL }, { "!=", OPERATION.NEQUAL }, { "<=", OPERATION.LEQUAL }, { ">=", OPERATION.GEQUAL }, { "&&", OPERATION.AND }, { "||", OPERATION.OR } }; + + public float A = 0; + public float B = 0; + public OPERATION OP = OPERATION.EQUAL; + + public ConditionNode() + { + this.addOutput("A", DataType.NUMBER ); + this.addOutput("B", DataType.NUMBER ); + this.addOutput("out", DataType.BOOL ); + } + + override public void onExecute() + { + float A = this.getInputData(0,this.A); + float B = this.getInputData(1,this.B); + + bool v = false; + switch(OP) + { + case OPERATION.NONE: v = false; break; + case OPERATION.GREATER: v = A > B; break; + case OPERATION.LOWER: v = A < B; break; + case OPERATION.EQUAL: v = A == B; break; + case OPERATION.NEQUAL: v = A != B; break; + case OPERATION.GEQUAL: v = A >= B; break; + case OPERATION.LEQUAL: v = A <= B; break; + case OPERATION.OR: v = (A != 0) || (B != 0); break; + case OPERATION.AND: v = (A != 0) && (B != 0); break; + } + this.setOutputData(0, v); + } + + override public void onConfigure(JSONNode o) + { + JSONNode json_properties = o["properties"]; + + A = json_properties["A"].AsFloat; + B = json_properties["B"].AsFloat; + string op = json_properties["OP"]; + if (strToOperation.ContainsKey(op)) + OP = strToOperation[op]; + else + Debug.Log("Wrong operation type: " + op); + } + } + + public class GateNode : LGraphNode + { + public static string type = "math/gate"; + + public GateNode() + { + this.addInput("v", DataType.BOOL); + this.addInput("A"); + this.addInput("B"); + this.addOutput("out"); + } + + override public void onExecute() + { + bool v = this.getInputData(0, true); + this.transferData(v ? 1 : 2, 0); + } + } + + public class TimeNode : LGraphNode + { + public static string type = "basic/time"; + + public TimeNode() + { + this.addOutput("in ms", DataType.NUMBER ); + this.addOutput("in sec", DataType.NUMBER); + } + + override public void onExecute() + { + this.setOutputData(0, (float)(graph.time * 1000)); + this.setOutputData(1, (float)graph.time); + } + } + + + public class WatchNode : LGraphNode + { + public static string type = "basic/watch"; + + public WatchNode() + { + this.addInput("in", DataType.NUMBER); + } + + override public void onExecute() + { + float v = this.getInputData(0, 0); + //Debug.Log("Watch: " + v.ToString()); + } + } +} diff --git a/csharp/LiteGraphTest.cs b/csharp/LiteGraphTest.cs new file mode 100644 index 000000000..194871ad9 --- /dev/null +++ b/csharp/LiteGraphTest.cs @@ -0,0 +1,53 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +using LiteGraph; + + +public class LiteGraphTest : MonoBehaviour +{ + + public TextAsset graph_file = null; + + private LGraph graph = null; + public bool graph_has_errors = false; + + public float output_value = 0; + + // Start is called before the first frame update + void Start() + { + System.Diagnostics.Debug.WriteLine("Test!"); + LiteGraph.Main.Init(); + + graph = new LGraph(); + + + if (!graph_file) + { + Debug.Log("Testing Base Graph..."); + LGraphNode node1 = LiteGraph.Globals.createNodeType("math/rand"); + graph.add(node1); + LGraphNode node2 = LiteGraph.Globals.createNodeType("basic/watch"); + graph.add(node2); + node1.connect(0, node2, 0); + } + else { + Debug.Log("Testing File Graph..."); + string text = graph_file.text; + graph.fromJSONText(text); + } + + graph_has_errors = graph.has_errors; + + } + + // Update is called once per frame + void Update() + { + if(graph != null) + graph.runStep( Time.deltaTime ); + output_value = graph.getOutput("output",0); + } +} diff --git a/csharp/SimpleJSON.cs b/csharp/SimpleJSON.cs new file mode 100644 index 000000000..749142a62 --- /dev/null +++ b/csharp/SimpleJSON.cs @@ -0,0 +1,1395 @@ +/* * * * * + * A simple JSON Parser / builder + * ------------------------------ + * + * It mainly has been written as a simple JSON parser. It can build a JSON string + * from the node-tree, or generate a node tree from any valid JSON string. + * + * Written by Bunny83 + * 2012-06-09 + * + * [2012-06-09 First Version] + * - provides strongly typed node classes and lists / dictionaries + * - provides easy access to class members / array items / data values + * - the parser now properly identifies types. So generating JSON with this framework should work. + * - only double quotes (") are used for quoting strings. + * - provides "casting" properties to easily convert to / from those types: + * int / float / double / bool + * - provides a common interface for each node so no explicit casting is required. + * - the parser tries to avoid errors, but if malformed JSON is parsed the result is more or less undefined + * - It can serialize/deserialize a node tree into/from an experimental compact binary format. It might + * be handy if you want to store things in a file and don't want it to be easily modifiable + * + * [2012-12-17 Update] + * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree + * Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator + * The class determines the required type by it's further use, creates the type and removes itself. + * - Added binary serialization / deserialization. + * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) + * The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top + * - The serializer uses different types when it comes to store the values. Since my data values + * are all of type string, the serializer will "try" which format fits best. The order is: int, float, double, bool, string. + * It's not the most efficient way but for a moderate amount of data it should work on all platforms. + * + * [2017-03-08 Update] + * - Optimised parsing by using a StringBuilder for token. This prevents performance issues when large + * string data fields are contained in the json data. + * - Finally refactored the badly named JSONClass into JSONObject. + * - Replaced the old JSONData class by distict typed classes ( JSONString, JSONNumber, JSONBool, JSONNull ) this + * allows to propertly convert the node tree back to json without type information loss. The actual value + * parsing now happens at parsing time and not when you actually access one of the casting properties. + * + * [2017-04-11 Update] + * - Fixed parsing bug where empty string values have been ignored. + * - Optimised "ToString" by using a StringBuilder internally. This should heavily improve performance for large files + * - Changed the overload of "ToString(string aIndent)" to "ToString(int aIndent)" + * + * [2017-11-29 Update] + * - Removed the IEnumerator implementations on JSONArray & JSONObject and replaced it with a common + * struct Enumerator in JSONNode that should avoid garbage generation. The enumerator always works + * on KeyValuePair, even for JSONArray. + * - Added two wrapper Enumerators that allows for easy key or value enumeration. A JSONNode now has + * a "Keys" and a "Values" enumerable property. Those are also struct enumerators / enumerables + * - A KeyValuePair can now be implicitly converted into a JSONNode. This allows + * a foreach loop over a JSONNode to directly access the values only. Since KeyValuePair as well as + * all the Enumerators are structs, no garbage is allocated. + * - To add Linq support another "LinqEnumerator" is available through the "Linq" property. This + * enumerator does implement the generic IEnumerable interface so most Linq extensions can be used + * on this enumerable object. This one does allocate memory as it's a wrapper class. + * - The Escape method now escapes all control characters (# < 32) in strings as uncode characters + * (\uXXXX) and if the static bool JSONNode.forceASCII is set to true it will also escape all + * characters # > 127. This might be useful if you require an ASCII output. Though keep in mind + * when your strings contain many non-ascii characters the strings become much longer (x6) and are + * no longer human readable. + * - The node types JSONObject and JSONArray now have an "Inline" boolean switch which will default to + * false. It can be used to serialize this element inline even you serialize with an indented format + * This is useful for arrays containing numbers so it doesn't place every number on a new line + * - Extracted the binary serialization code into a seperate extension file. All classes are now declared + * as "partial" so an extension file can even add a new virtual or abstract method / interface to + * JSONNode and override it in the concrete type classes. It's of course a hacky approach which is + * generally not recommended, but i wanted to keep everything tightly packed. + * - Added a static CreateOrGet method to the JSONNull class. Since this class is immutable it could + * be reused without major problems. If you have a lot null fields in your data it will help reduce + * the memory / garbage overhead. I also added a static setting (reuseSameInstance) to JSONNull + * (default is true) which will change the behaviour of "CreateOrGet". If you set this to false + * CreateOrGet will not reuse the cached instance but instead create a new JSONNull instance each time. + * I made the JSONNull constructor private so if you need to create an instance manually use + * JSONNull.CreateOrGet() + * + * [2018-01-09 Update] + * - Changed all double.TryParse and double.ToString uses to use the invariant culture to avoid problems + * on systems with a culture that uses a comma as decimal point. + * + * [2018-01-26 Update] + * - Added AsLong. Note that a JSONNumber is stored as double and can't represent all long values. However + * storing it as string would work. + * - Added static setting "JSONNode.longAsString" which controls the default type that is used by the + * LazyCreator when using AsLong + * + * [2018-04-25 Update] + * - Added support for parsing single values (JSONBool, JSONString, JSONNumber, JSONNull) as top level value. + * + * [2019-02-18 Update] + * - Added HasKey(key) and GetValueOrDefault(key, default) to the JSONNode class to provide way to read + * values conditionally without creating a LazyCreator + * + * [2019-03-25 Update] + * - Added static setting "allowLineComments" to the JSONNode class which is true by default. This allows + * "//" line comments when parsing json text as long as it's not within quoted text. All text after // up + * to the end of the line is completely ignored / skipped. This makes it easier to create human readable + * and editable files. Note that stripped comments are not read, processed or preserved in any way. So + * this feature is only relevant for human created files. + * - Explicitly strip BOM (Byte Order Mark) when parsing to avoid getting it leaked into a single primitive + * value. That's a rare case but better safe than sorry. + * - Allowing adding the empty string as key + * + * The MIT License (MIT) + * + * Copyright (c) 2012-2017 Markus Göbel (Bunny83) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * * * * */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace SimpleJSON +{ + public enum JSONNodeType + { + Array = 1, + Object = 2, + String = 3, + Number = 4, + NullValue = 5, + Boolean = 6, + None = 7, + Custom = 0xFF, + } + public enum JSONTextMode + { + Compact, + Indent + } + + public abstract partial class JSONNode + { + #region Enumerators + public struct Enumerator + { + private enum Type { None, Array, Object } + private Type type; + private Dictionary.Enumerator m_Object; + private List.Enumerator m_Array; + public bool IsValid { get { return type != Type.None; } } + public Enumerator(List.Enumerator aArrayEnum) + { + type = Type.Array; + m_Object = default(Dictionary.Enumerator); + m_Array = aArrayEnum; + } + public Enumerator(Dictionary.Enumerator aDictEnum) + { + type = Type.Object; + m_Object = aDictEnum; + m_Array = default(List.Enumerator); + } + public KeyValuePair Current + { + get { + if (type == Type.Array) + return new KeyValuePair(string.Empty, m_Array.Current); + else if (type == Type.Object) + return m_Object.Current; + return new KeyValuePair(string.Empty, null); + } + } + public bool MoveNext() + { + if (type == Type.Array) + return m_Array.MoveNext(); + else if (type == Type.Object) + return m_Object.MoveNext(); + return false; + } + } + public struct ValueEnumerator + { + private Enumerator m_Enumerator; + public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public JSONNode Current { get { return m_Enumerator.Current.Value; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public ValueEnumerator GetEnumerator() { return this; } + } + public struct KeyEnumerator + { + private Enumerator m_Enumerator; + public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public string Current { get { return m_Enumerator.Current.Key; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public KeyEnumerator GetEnumerator() { return this; } + } + + public class LinqEnumerator : IEnumerator>, IEnumerable> + { + private JSONNode m_Node; + private Enumerator m_Enumerator; + internal LinqEnumerator(JSONNode aNode) + { + m_Node = aNode; + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + public KeyValuePair Current { get { return m_Enumerator.Current; } } + object IEnumerator.Current { get { return m_Enumerator.Current; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + + public void Dispose() + { + m_Node = null; + m_Enumerator = new Enumerator(); + } + + public IEnumerator> GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + + public void Reset() + { + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + } + + #endregion Enumerators + + #region common interface + + public static bool forceASCII = false; // Use Unicode by default + public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber + public static bool allowLineComments = true; // allow "//"-style comments at the end of a line + + public abstract JSONNodeType Tag { get; } + + public virtual JSONNode this[int aIndex] { get { return null; } set { } } + + public virtual JSONNode this[string aKey] { get { return null; } set { } } + + public virtual string Value { get { return ""; } set { } } + + public virtual int Count { get { return 0; } } + + public virtual bool IsNumber { get { return false; } } + public virtual bool IsString { get { return false; } } + public virtual bool IsBoolean { get { return false; } } + public virtual bool IsNull { get { return false; } } + public virtual bool IsArray { get { return false; } } + public virtual bool IsObject { get { return false; } } + + public virtual bool Inline { get { return false; } set { } } + + public virtual void Add(string aKey, JSONNode aItem) + { + } + public virtual void Add(JSONNode aItem) + { + Add("", aItem); + } + + public virtual JSONNode Remove(string aKey) + { + return null; + } + + public virtual JSONNode Remove(int aIndex) + { + return null; + } + + public virtual JSONNode Remove(JSONNode aNode) + { + return aNode; + } + + public virtual IEnumerable Children + { + get + { + yield break; + } + } + + public IEnumerable DeepChildren + { + get + { + foreach (var C in Children) + foreach (var D in C.DeepChildren) + yield return D; + } + } + + public virtual bool HasKey(string aKey) + { + return false; + } + + public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + return aDefault; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); + return sb.ToString(); + } + + public virtual string ToString(int aIndent) + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); + return sb.ToString(); + } + internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); + + public abstract Enumerator GetEnumerator(); + public IEnumerable> Linq { get { return new LinqEnumerator(this); } } + public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } } + public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } } + + #endregion common interface + + #region typecasting properties + + + public virtual double AsDouble + { + get + { + double v = 0.0; + if (double.TryParse(Value,NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + return v; + return 0.0; + } + set + { + Value = value.ToString(CultureInfo.InvariantCulture); + } + } + + public virtual int AsInt + { + get { return (int)AsDouble; } + set { AsDouble = value; } + } + + public virtual float AsFloat + { + get { return (float)AsDouble; } + set { AsDouble = value; } + } + + public virtual bool AsBool + { + get + { + bool v = false; + if (bool.TryParse(Value, out v)) + return v; + return !string.IsNullOrEmpty(Value); + } + set + { + Value = (value) ? "true" : "false"; + } + } + + public virtual long AsLong + { + get + { + long val = 0; + if (long.TryParse(Value, out val)) + return val; + return 0L; + } + set + { + Value = value.ToString(); + } + } + + public virtual JSONArray AsArray + { + get + { + return this as JSONArray; + } + } + + public virtual JSONObject AsObject + { + get + { + return this as JSONObject; + } + } + + + #endregion typecasting properties + + #region operators + + public static implicit operator JSONNode(string s) + { + return new JSONString(s); + } + public static implicit operator string(JSONNode d) + { + return (d == null) ? null : d.Value; + } + + public static implicit operator JSONNode(double n) + { + return new JSONNumber(n); + } + public static implicit operator double(JSONNode d) + { + return (d == null) ? 0 : d.AsDouble; + } + + public static implicit operator JSONNode(float n) + { + return new JSONNumber(n); + } + public static implicit operator float(JSONNode d) + { + return (d == null) ? 0 : d.AsFloat; + } + + public static implicit operator JSONNode(int n) + { + return new JSONNumber(n); + } + public static implicit operator int(JSONNode d) + { + return (d == null) ? 0 : d.AsInt; + } + + public static implicit operator JSONNode(long n) + { + if (longAsString) + return new JSONString(n.ToString()); + return new JSONNumber(n); + } + public static implicit operator long(JSONNode d) + { + return (d == null) ? 0L : d.AsLong; + } + + public static implicit operator JSONNode(bool b) + { + return new JSONBool(b); + } + public static implicit operator bool(JSONNode d) + { + return (d == null) ? false : d.AsBool; + } + + public static implicit operator JSONNode(KeyValuePair aKeyValue) + { + return aKeyValue.Value; + } + + public static bool operator ==(JSONNode a, object b) + { + if (ReferenceEquals(a, b)) + return true; + bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; + bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; + if (aIsNull && bIsNull) + return true; + return !aIsNull && a.Equals(b); + } + + public static bool operator !=(JSONNode a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion operators + + [ThreadStatic] + private static StringBuilder m_EscapeBuilder; + internal static StringBuilder EscapeBuilder + { + get { + if (m_EscapeBuilder == null) + m_EscapeBuilder = new StringBuilder(); + return m_EscapeBuilder; + } + } + internal static string Escape(string aText) + { + var sb = EscapeBuilder; + sb.Length = 0; + if (sb.Capacity < aText.Length + aText.Length / 10) + sb.Capacity = aText.Length + aText.Length / 10; + foreach (char c in aText) + { + switch (c) + { + case '\\': + sb.Append("\\\\"); + break; + case '\"': + sb.Append("\\\""); + break; + case '\n': + sb.Append("\\n"); + break; + case '\r': + sb.Append("\\r"); + break; + case '\t': + sb.Append("\\t"); + break; + case '\b': + sb.Append("\\b"); + break; + case '\f': + sb.Append("\\f"); + break; + default: + if (c < ' ' || (forceASCII && c > 127)) + { + ushort val = c; + sb.Append("\\u").Append(val.ToString("X4")); + } + else + sb.Append(c); + break; + } + } + string result = sb.ToString(); + sb.Length = 0; + return result; + } + + private static JSONNode ParseElement(string token, bool quoted) + { + if (quoted) + return token; + string tmp = token.ToLower(); + if (tmp == "false" || tmp == "true") + return tmp == "true"; + if (tmp == "null") + return JSONNull.CreateOrGet(); + double val; + if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) + return val; + else + return token; + } + + public static JSONNode Parse(string aJSON) + { + Stack stack = new Stack(); + JSONNode ctx = null; + int i = 0; + StringBuilder Token = new StringBuilder(); + string TokenName = ""; + bool QuoteMode = false; + bool TokenIsQuoted = false; + while (i < aJSON.Length) + { + switch (aJSON[i]) + { + case '{': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + stack.Push(new JSONObject()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + break; + + case '[': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + stack.Push(new JSONArray()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + break; + + case '}': + case ']': + if (QuoteMode) + { + + Token.Append(aJSON[i]); + break; + } + if (stack.Count == 0) + throw new Exception("JSON Parse: Too many closing brackets"); + + stack.Pop(); + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + if (stack.Count > 0) + ctx = stack.Peek(); + break; + + case ':': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + TokenName = Token.ToString(); + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '"': + QuoteMode ^= true; + TokenIsQuoted |= QuoteMode; + break; + + case ',': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '\r': + case '\n': + break; + + case ' ': + case '\t': + if (QuoteMode) + Token.Append(aJSON[i]); + break; + + case '\\': + ++i; + if (QuoteMode) + { + char C = aJSON[i]; + switch (C) + { + case 't': + Token.Append('\t'); + break; + case 'r': + Token.Append('\r'); + break; + case 'n': + Token.Append('\n'); + break; + case 'b': + Token.Append('\b'); + break; + case 'f': + Token.Append('\f'); + break; + case 'u': + { + string s = aJSON.Substring(i + 1, 4); + Token.Append((char)int.Parse( + s, + System.Globalization.NumberStyles.AllowHexSpecifier)); + i += 4; + break; + } + default: + Token.Append(C); + break; + } + } + break; + case '/': + if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i+1] == '/') + { + while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ; + break; + } + Token.Append(aJSON[i]); + break; + case '\uFEFF': // remove / ignore BOM (Byte Order Mark) + break; + + default: + Token.Append(aJSON[i]); + break; + } + ++i; + } + if (QuoteMode) + { + throw new Exception("JSON Parse: Quotation marks seems to be messed up."); + } + if (ctx == null) + return ParseElement(Token.ToString(), TokenIsQuoted); + return ctx; + } + + } + // End of JSONNode + + public partial class JSONArray : JSONNode + { + private List m_List = new List(); + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Array; } } + public override bool IsArray { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_List.Count) + return new JSONLazyCreator(this); + return m_List[aIndex]; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_List.Count) + m_List.Add(value); + else + m_List[aIndex] = value; + } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this); } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + m_List.Add(value); + } + } + + public override int Count + { + get { return m_List.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + m_List.Add(aItem); + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_List.Count) + return null; + JSONNode tmp = m_List[aIndex]; + m_List.RemoveAt(aIndex); + return tmp; + } + + public override JSONNode Remove(JSONNode aNode) + { + m_List.Remove(aNode); + return aNode; + } + + public override IEnumerable Children + { + get + { + foreach (JSONNode N in m_List) + yield return N; + } + } + + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('['); + int count = m_List.Count; + if (inline) + aMode = JSONTextMode.Compact; + for (int i = 0; i < count; i++) + { + if (i > 0) + aSB.Append(','); + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append(']'); + } + } + // End of JSONArray + + public partial class JSONObject : JSONNode + { + public Dictionary m_Dict = new Dictionary(); + + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Object; } } + public override bool IsObject { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); } + + + public override JSONNode this[string aKey] + { + get + { + if (m_Dict.ContainsKey(aKey)) + return m_Dict[aKey]; + else + return new JSONLazyCreator(this, aKey); + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = value; + else + m_Dict.Add(aKey, value); + } + } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + return m_Dict.ElementAt(aIndex).Value; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_Dict.Count) + return; + string key = m_Dict.ElementAt(aIndex).Key; + m_Dict[key] = value; + } + } + + public override int Count + { + get { return m_Dict.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + + if (aKey != null) + { + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = aItem; + else + m_Dict.Add(aKey, aItem); + } + else + m_Dict.Add(Guid.NewGuid().ToString(), aItem); + } + + public override JSONNode Remove(string aKey) + { + if (!m_Dict.ContainsKey(aKey)) + return null; + JSONNode tmp = m_Dict[aKey]; + m_Dict.Remove(aKey); + return tmp; + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + var item = m_Dict.ElementAt(aIndex); + m_Dict.Remove(item.Key); + return item.Value; + } + + public override JSONNode Remove(JSONNode aNode) + { + try + { + var item = m_Dict.Where(k => k.Value == aNode).First(); + m_Dict.Remove(item.Key); + return aNode; + } + catch + { + return null; + } + } + + public override bool HasKey(string aKey) + { + return m_Dict.ContainsKey(aKey); + } + + public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + JSONNode res; + if (m_Dict.TryGetValue(aKey, out res)) + return res; + return aDefault; + } + + public override IEnumerable Children + { + get + { + foreach (KeyValuePair N in m_Dict) + yield return N.Value; + } + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('{'); + bool first = true; + if (inline) + aMode = JSONTextMode.Compact; + foreach (var k in m_Dict) + { + if (!first) + aSB.Append(','); + first = false; + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); + if (aMode == JSONTextMode.Compact) + aSB.Append(':'); + else + aSB.Append(" : "); + k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append('}'); + } + + } + // End of JSONObject + + public partial class JSONString : JSONNode + { + private string m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.String; } } + public override bool IsString { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(); } + + + public override string Value + { + get { return m_Data; } + set + { + m_Data = value; + } + } + + public JSONString(string aData) + { + m_Data = aData; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); + } + public override bool Equals(object obj) + { + if (base.Equals(obj)) + return true; + string s = obj as string; + if (s != null) + return m_Data == s; + JSONString s2 = obj as JSONString; + if (s2 != null) + return m_Data == s2.m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONString + + public partial class JSONNumber : JSONNode + { + private double m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Number; } } + public override bool IsNumber { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(CultureInfo.InvariantCulture); } + set + { + double v; + if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + m_Data = v; + } + } + + public override double AsDouble + { + get { return m_Data; } + set { m_Data = value; } + } + public override long AsLong + { + get { return (long)m_Data; } + set { m_Data = value; } + } + + public JSONNumber(double aData) + { + m_Data = aData; + } + + public JSONNumber(string aData) + { + Value = aData; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append(Value); + } + private static bool IsNumeric(object value) + { + return value is int || value is uint + || value is float || value is double + || value is decimal + || value is long || value is ulong + || value is short || value is ushort + || value is sbyte || value is byte; + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (base.Equals(obj)) + return true; + JSONNumber s2 = obj as JSONNumber; + if (s2 != null) + return m_Data == s2.m_Data; + if (IsNumeric(obj)) + return Convert.ToDouble(obj) == m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONNumber + + public partial class JSONBool : JSONNode + { + private bool m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } + public override bool IsBoolean { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(); } + set + { + bool v; + if (bool.TryParse(value, out v)) + m_Data = v; + } + } + public override bool AsBool + { + get { return m_Data; } + set { m_Data = value; } + } + + public JSONBool(bool aData) + { + m_Data = aData; + } + + public JSONBool(string aData) + { + Value = aData; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append((m_Data) ? "true" : "false"); + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (obj is bool) + return m_Data == (bool)obj; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONBool + + public partial class JSONNull : JSONNode + { + static JSONNull m_StaticInstance = new JSONNull(); + public static bool reuseSameInstance = true; + public static JSONNull CreateOrGet() + { + if (reuseSameInstance) + return m_StaticInstance; + return new JSONNull(); + } + private JSONNull() { } + + public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } + public override bool IsNull { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return "null"; } + set { } + } + public override bool AsBool + { + get { return false; } + set { } + } + + public override bool Equals(object obj) + { + if (object.ReferenceEquals(this, obj)) + return true; + return (obj is JSONNull); + } + public override int GetHashCode() + { + return 0; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONNull + + internal partial class JSONLazyCreator : JSONNode + { + private JSONNode m_Node = null; + private string m_Key = null; + public override JSONNodeType Tag { get { return JSONNodeType.None; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public JSONLazyCreator(JSONNode aNode) + { + m_Node = aNode; + m_Key = null; + } + + public JSONLazyCreator(JSONNode aNode, string aKey) + { + m_Node = aNode; + m_Key = aKey; + } + + private T Set(T aVal) where T : JSONNode + { + if (m_Key == null) + m_Node.Add(aVal); + else + m_Node.Add(m_Key, aVal); + m_Node = null; // Be GC friendly. + return aVal; + } + + public override JSONNode this[int aIndex] + { + get { return new JSONLazyCreator(this); } + set { Set(new JSONArray()).Add(value); } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this, aKey); } + set { Set(new JSONObject()).Add(aKey, value); } + } + + public override void Add(JSONNode aItem) + { + Set(new JSONArray()).Add(aItem); + } + + public override void Add(string aKey, JSONNode aItem) + { + Set(new JSONObject()).Add(aKey, aItem); + } + + public static bool operator ==(JSONLazyCreator a, object b) + { + if (b == null) + return true; + return System.Object.ReferenceEquals(a, b); + } + + public static bool operator !=(JSONLazyCreator a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj == null) + return true; + return System.Object.ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return 0; + } + + public override int AsInt + { + get { Set(new JSONNumber(0)); return 0; } + set { Set(new JSONNumber(value)); } + } + + public override float AsFloat + { + get { Set(new JSONNumber(0.0f)); return 0.0f; } + set { Set(new JSONNumber(value)); } + } + + public override double AsDouble + { + get { Set(new JSONNumber(0.0)); return 0.0; } + set { Set(new JSONNumber(value)); } + } + + public override long AsLong + { + get + { + if (longAsString) + Set(new JSONString("0")); + else + Set(new JSONNumber(0.0)); + return 0L; + } + set + { + if (longAsString) + Set(new JSONString(value.ToString())); + else + Set(new JSONNumber(value)); + } + } + + public override bool AsBool + { + get { Set(new JSONBool(false)); return false; } + set { Set(new JSONBool(value)); } + } + + public override JSONArray AsArray + { + get { return Set(new JSONArray()); } + } + + public override JSONObject AsObject + { + get { return Set(new JSONObject()); } + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONLazyCreator + + public static class JSON + { + public static JSONNode Parse(string aJSON) + { + return JSONNode.Parse(aJSON); + } + } +} diff --git a/csharp/graph.JSON b/csharp/graph.JSON new file mode 100644 index 000000000..dedf57db5 --- /dev/null +++ b/csharp/graph.JSON @@ -0,0 +1 @@ +{"last_node_id":15,"last_link_id":26,"nodes":[{"id":5,"type":"graph/output","pos":[1265,331],"size":[180,60],"flags":{"collapsed":false},"order":4,"mode":0,"inputs":[{"name":"","type":"number","link":24}],"properties":{"name":"output","type":"number"}},{"id":13,"type":"basic/watch","pos":[1228,213],"size":{"0":140,"1":26},"flags":{},"order":5,"mode":0,"inputs":[{"name":"value","type":0,"link":25,"label":"0.000"}],"properties":{}},{"id":9,"type":"math/condition","pos":[754,261],"size":[180,61],"flags":{},"order":2,"mode":0,"inputs":[{"name":"A","type":"number","link":9},{"name":"B","type":"number","link":10}],"outputs":[{"name":"true","type":"boolean","links":[21]},{"name":"false","type":"boolean","links":null}],"properties":{"A":6.957165000028908,"B":4,"OP":"<"}},{"id":7,"type":"basic/time","pos":[406,262],"size":{"0":140,"1":46},"flags":{},"order":0,"mode":0,"outputs":[{"name":"in ms","type":"number","links":null},{"name":"in sec","type":"number","links":[9,22]}],"properties":{}},{"id":10,"type":"basic/const","pos":[298,560],"size":{"0":140,"1":26},"flags":{},"order":1,"mode":0,"outputs":[{"name":"value","type":"number","links":[10,23],"label":"4.000"}],"properties":{"value":4}},{"id":15,"type":"math/gate","pos":[961,483],"size":{"0":140,"1":66},"flags":{},"order":3,"mode":0,"inputs":[{"name":"v","type":"boolean","link":21},{"name":"A","type":0,"link":22},{"name":"B","type":0,"link":23}],"outputs":[{"name":"out","links":[24,25]}],"properties":{}}],"links":[[9,7,1,9,0,"number"],[10,10,0,9,1,"number"],[21,9,0,15,0,"boolean"],[22,7,1,15,1,0],[23,10,0,15,2,0],[24,15,0,5,0,"number"],[25,15,0,13,0,0]],"groups":[],"config":{},"version":0.4} \ No newline at end of file