diff --git a/src/SharpConnect.Data/Es/EsDocParser.cs b/src/SharpConnect.Data/Es/EsDocParser.cs index 4dbac46..3aabe4a 100644 --- a/src/SharpConnect.Data/Es/EsDocParser.cs +++ b/src/SharpConnect.Data/Es/EsDocParser.cs @@ -9,14 +9,41 @@ public EaseDocParser(EaseDocument blankdoc) { _easeDoc = blankdoc; } - + public bool EnableExtension { get; set; } protected override EsElem CreateElement() => _easeDoc.CreateElement(); protected override EsArr CreateArray() => _easeDoc.CreateArray(); protected override void AddElementAttribute(EsElem targetElem, string key, object value) { - targetElem.AppendAttribute(key, value); + if (EnableExtension) + { + //our extension + if (key == "!n" && value is string name) + { + targetElem.Name = name; + return; + } + else if (key == "!c") + { + if (value is EsArr arr) + { + //extract arr + int j = arr.Count; + for (int i = 0; i < j; ++i) + { + targetElem.AppendChild(arr[i]); + } + } + else + { + throw new System.NotSupportedException(); + //targetElem.AppendChild(value); + } + return; + } + } + targetElem.AppendAttribute(key, value); } protected override void AddArrayElement(EsArr targetArray, object value) diff --git a/src/SharpConnect.Data/TreeModel/EsDataImpl.cs b/src/SharpConnect.Data/TreeModel/EsDataImpl.cs index cbb9b8d..614cbf8 100644 --- a/src/SharpConnect.Data/TreeModel/EsDataImpl.cs +++ b/src/SharpConnect.Data/TreeModel/EsDataImpl.cs @@ -7,7 +7,6 @@ namespace SharpConnect.Data { public class EaseDocument : EsDoc { - //Dictionary _stringTable = new Dictionary(); public EaseDocument() { @@ -18,42 +17,29 @@ public EsElem CreateElement(string elementName) elem.Name = elementName; return elem; } - + public bool EnableExtension { get; set; } public EsElem CreateElement() => new EaseElement(); - public EsArr CreateArray() => new EaseArray(); - - //public int GetStringIndex(string str) - //{ - // _stringTable.TryGetValue(str, out int found); - // return found; - //} - public EsElem DocumentElement { get; set; } + public EsAttr CreateAttribute(string key, object value) => new EaseAttribute(key, value); - public EsElem Parse(string jsonstr) + } + + public static class EaseDocumentExtensions + { + public static object Parse(this EaseDocument doc, string jsonstr) { - return Parse(jsonstr.ToCharArray()); + return Parse(doc, jsonstr.ToCharArray()); } - public EsElem Parse(char[] jsonstr) + public static object Parse(this EaseDocument doc, char[] jsonstr) { - var parser = new EaseDocParser(this); + var parser = new EaseDocParser(doc); + parser.EnableExtension = doc.EnableExtension; parser.Parse(jsonstr); - return parser.CurrentElement as EsElem; + return parser.CurrentValue; } - public EsAttr CreateAttribute(string key, object value) => new EaseAttribute(key, value); } - - static class EsElemHelper - { - public static EsElem CreateXmlElementForDynamicObject(EsDoc doc) - { - var elem = new EaseElement(); - elem.Name = "!j"; - return elem; - } - } class EaseArray : EsArr { List _member = new List(); @@ -87,14 +73,23 @@ public IEnumerable GetIter() class EaseElement : EsElem { - List _childNodes; - + List _childNodes; Dictionary _attrs = new Dictionary(); List _attrsValues = new List(); +#if DEBUG + static int s_dbugTotal; + public readonly int dbugId = s_dbugTotal++; +#endif public EaseElement() { Name = ""; +#if DEBUG + if (dbugId == 409) + { + + } +#endif } public string Name { get; set; } public int AttributeCount => _attrs.Count; @@ -111,11 +106,11 @@ public IEnumerable GetAttributeIterForward() } } - public void AppendChild(EsElem element) + public void AppendChild(object element) { if (_childNodes == null) { - _childNodes = new List(); + _childNodes = new List(); } _childNodes.Add(element); } @@ -165,6 +160,10 @@ public EsAttr GetAttribute(string key) return null; } public object UserData { get; set; } + +#if DEBUG + public override string ToString() => Name; +#endif } class EaseAttribute : EsAttr @@ -179,6 +178,11 @@ public EaseAttribute(string name, object value) public override string ToString() => Name + ":" + Value; } + public class EsElemJsonWriteParameters + { + public bool WithElemName { get; set; } + public StringBuilder Output { get; set; } + } public static class EsElemExtensionMethods { public static string GetAttrValueOrDefaultAsString(this EsElem esElem, string attrName) @@ -256,18 +260,24 @@ public static EsElem GetAttributeValueAsElem(this EsElem esElem, string attrName } //----------------------------------------------------------------------- - public static void WriteJson(this EsDoc doc, StringBuilder sb) + public static void WriteJson(this EsDoc doc, EsElemJsonWriteParameters writePars) { //write to var docElem = doc.DocumentElement; if (docElem != null) { - WriteJson(docElem, sb); + WriteJson(docElem, writePars); } } - - static void WriteJson(EsArr esArr, StringBuilder sb) + public static void WriteJson(this EsDoc doc, StringBuilder stbuilder) + { + EsElemJsonWriteParameters pars = new EsElemJsonWriteParameters(); + pars.Output = stbuilder; + WriteJson(doc, pars); + } + static void WriteJson(EsArr esArr, EsElemJsonWriteParameters pars) { + StringBuilder sb = pars.Output; sb.Append('['); int j = esArr.Count; for (int i = 0; i < j; ++i) @@ -276,13 +286,18 @@ static void WriteJson(EsArr esArr, StringBuilder sb) { sb.Append(','); } - WriteJson(esArr[i], sb); + WriteJson(esArr[i], pars); } sb.Append(']'); } public static void WriteJson(this EsElem esElem, StringBuilder sb) { + WriteJson(esElem, new EsElemJsonWriteParameters { Output = sb }); + } + public static void WriteJson(this EsElem esElem, EsElemJsonWriteParameters pars) + { + StringBuilder sb = pars.Output; EaseElement elem = (EaseElement)esElem; sb.Append('{'); //check docattr= @@ -292,18 +307,26 @@ public static void WriteJson(this EsElem esElem, StringBuilder sb) { //TODO: review here if we want auto element name or not? //use specific name - //stBuilder.Append("\"!n\":\""); - //stBuilder.Append(leqE.Name); - //stBuilder.Append('"'); + if (pars.WithElemName && elem.Name != null) + { + sb.Append("\"!n\":\""); + //TODO: review string escape here *** + sb.Append(elem.Name); + sb.Append('"'); + attrCount = 1; + } } else { //use default elementname - sb.Append("\"!n\":\""); - //TODO: review string escape here *** - sb.Append(elem.Name); - sb.Append('"'); - attrCount = 1; + if (elem.Name != null) + { + sb.Append("\"!n\":\""); + //TODO: review string escape here *** + sb.Append(elem.Name); + sb.Append('"'); + attrCount = 1; + } } foreach (EsAttr attr in elem.GetAttributeIterForward()) { @@ -319,7 +342,7 @@ public static void WriteJson(this EsElem esElem, StringBuilder sb) sb.Append(attr.Name); //TODO: review escape string here sb.Append('"'); sb.Append(':'); - WriteJson(attr.Value, sb); + WriteJson(attr.Value, pars); attrCount++; } //------------------- @@ -339,7 +362,7 @@ public static void WriteJson(this EsElem esElem, StringBuilder sb) { sb.Append(','); } - WriteJson(elem.GetChild(i), sb); + WriteJson(elem.GetChild(i), pars); } sb.Append(']'); } @@ -347,21 +370,24 @@ public static void WriteJson(this EsElem esElem, StringBuilder sb) sb.Append('}'); } - + public static string ToJsonString(this EsElem esElem) { + //TODO: review here again var stbuilder = new StringBuilder(); - esElem.WriteJson(stbuilder); + var pars = new EsElemJsonWriteParameters(); + pars.Output = stbuilder; + esElem.WriteJson(pars); return stbuilder.ToString(); } - - public static void WriteJson(object elem, StringBuilder sb) + public static void WriteJson(object elem, EsElemJsonWriteParameters pars) { //recursive #if DEBUG Type t = elem.GetType(); #endif + StringBuilder sb = pars.Output; if (elem == null) { sb.Append("null"); @@ -394,17 +420,17 @@ public static void WriteJson(object elem, StringBuilder sb) { sb.Append(','); } - WriteJson(a.GetValue(i), sb); + WriteJson(a.GetValue(i), pars); } sb.Append(']'); } else if (elem is EaseElement ease_elem) { - WriteJson(ease_elem, sb); + WriteJson(ease_elem, pars); } else if (elem is EsArr es_arr) { - WriteJson(es_arr, sb); + WriteJson(es_arr, pars); } else if (elem is DateTime d) { diff --git a/src/SharpConnect.Data/TreeModel/EsDataInterface.cs b/src/SharpConnect.Data/TreeModel/EsDataInterface.cs index 1a6a2da..ec794a4 100644 --- a/src/SharpConnect.Data/TreeModel/EsDataInterface.cs +++ b/src/SharpConnect.Data/TreeModel/EsDataInterface.cs @@ -10,7 +10,7 @@ public interface EsElem string Name { get; set; } IEnumerable GetAttributeIterForward(); void RemoveAttribute(string key); - void AppendChild(EsElem element); + void AppendChild(object element); void AppendAttribute(string key, object value); object GetAttributeValue(string key); EsAttr GetAttribute(string key); @@ -19,8 +19,10 @@ public interface EsElem int ChildCount { get; } int AttributeCount { get; } object GetChild(int index); - object UserData { get; set; } + object UserData { get; set; } //TODO review this again } + + /// /// ease attribute /// @@ -51,4 +53,19 @@ public interface EsDoc EsArr CreateArray(); EsElem DocumentElement { get; set; } } + + public static class EsElemExtensions + { + public static IEnumerable GetChildNodeIter(this EsElem elem) + { + int n = elem.ChildCount; + for (int i = 0; i < n; ++i) + { + if (elem.GetChild(i) is EsElem childElem) + { + yield return childElem; + } + } + } + } } \ No newline at end of file diff --git a/src/SharpConnect.Data/TreeModel/EsParser.cs b/src/SharpConnect.Data/TreeModel/EsParser.cs index 72a5dfc..734a6a0 100644 --- a/src/SharpConnect.Data/TreeModel/EsParser.cs +++ b/src/SharpConnect.Data/TreeModel/EsParser.cs @@ -5,7 +5,25 @@ namespace SharpConnect.Data { + public enum EsValueHint : byte + { + Unknown, + None, //None => no attribute here (!= null) + + True, False, Null, + + + StringLiteral, + StringLiteralWithSomeEscape, + IntegerNumber, + NumberWithFractionPart, + NumberWithExponentialPart, + Identifier, + Comment,//extension + Object, + Array, + } /// /// event-driven json-like parser /// @@ -17,37 +35,38 @@ enum EsElementKind Object, Array } + enum ParsingState { - _0_Init, - _1_ObjectKey, - _2_CollectStringLiteral, - _3_StringEscape, - _4_FinishKeyPartWaitForSemiColon, - _5_ExpectObjectValueOrArrayElement, - _6_AfterObjectValueOrArrayElement, - _7_CollectNumberLiteral, - _8_CollectIdentifier, + _1_ExpectObjectValueOrArrayElement, + _2_ExpectObjectKey, + _3_WaitForColon, + _4_WaitForCommaOrEnd, } - enum NumberPart + + enum NumberPart : byte { IntegerPart, FractionPart, - E, //e or E - ESign,//e and sign ExponentialPart, } - protected enum ValueHint + + + + public struct NumberParts { - Unknown, - StringLiteral, - IntegerNumber, - NumberWithFractionPart, - NumberWithExponentialPart, - NumberWithSignedExponentialPart, - Identifier, - Comment,//extension + public bool integer_minus; + public int integer_at; + public byte integer_len; //255 + // + public ushort fraction_offset; //offset from intger_at + public byte fraction_len; //255 + // + public bool exponent_minus; + public ushort exponent_offset; //offset from intger_at + public byte exponent_len; //255 } + #if DEBUG public static bool dbug_EnableLogParser = false; public static int dbug_file_count = 0; @@ -79,32 +98,353 @@ protected virtual void EndArray() { } - protected virtual void NewKey(StringBuilder tmpBuffer, ValueHint valueHint) + protected virtual void NewKey(int start, int len) { - } - protected virtual void NewValue(StringBuilder tmpBuffer, ValueHint valueHint) + protected virtual void NewValue(int start, int len) { - } - protected virtual void OnError(ref int currentIndex) + protected virtual void Comma() { } + protected virtual void OnParseEnd() { + } + protected virtual void OnParseStart() + { } - protected virtual void OnParseEnd() + protected virtual void NotifyError() { + } + + static void ReadIdentifier(EsParserBase p, int startAt, out int latestIndex) + { + p.CollectedValueHint = EsValueHint.Identifier; + int pos = startAt + 1; + char[] sourceBuffer = p._sourceBuffer; + int lim = sourceBuffer.Length; + do + { + char c = sourceBuffer[pos]; + if (c == '_' || char.IsLetterOrDigit(c)) + { + //collect + pos++; + } + else + { + latestIndex = pos - 1; + return; + } + } while (pos < lim); + // + latestIndex = pos; } - protected virtual void OnParseStart() + static void ReadStringLiteral(EsParserBase p, char escapeChar, int startAt, out int latestIndex) { + char[] sourceBuffer = p._sourceBuffer; + + p.CollectedValueHint = EsValueHint.StringLiteral; + + int pos = startAt + 1; + char c = sourceBuffer[pos]; + int lim = sourceBuffer.Length - 1; + + if (escapeChar == '"') + { + while (c != '"' && pos < lim) + { + //read until stop + if (c == '\\') //escape + { + p.CollectedValueHint = EsValueHint.StringLiteralWithSomeEscape; + //escape mode 1 char + if (pos + 1 < lim) + { + //read next char + char c2 = sourceBuffer[pos + 1]; + switch (c2) + { + default: + //error + throw new NotSupportedException(); + break; + case '"': + pos++; + break; + //case '\'': //extension + // pos++; + // break; + case '/': + pos++; + break; + case '\\': + pos++; + break; + case 'b': // backspace + pos++; + break; + case 'f': //form ffed + pos++; + break; + case 'n': //newline + pos++; + break; + case 'r': //carriage return + pos++; + break; + case 't'://t + pos++; + break; + case 'u': + if (pos < lim - 4) + { + //json spec + //this follow by 4 chars + //for extension we check if it match with 4 chars or not + uint c_uint = ParseUnicode( + sourceBuffer[pos + 1], + sourceBuffer[pos + 2], + sourceBuffer[pos + 3], + sourceBuffer[pos + 4]); + pos += 4; + } + break; + } + } + else + { + //collect more + } + } + pos++; + c = sourceBuffer[pos]; + } + } + else if (escapeChar == '\'') + { + //this is our extension + while (c != '\'' && pos < lim) + { + //read until stop + if (c == '\\') //escape + { + p.CollectedValueHint = EsValueHint.StringLiteralWithSomeEscape; + //escape mode 1 char + if (pos + 1 < lim) + { + //read next char + char c2 = sourceBuffer[pos + 1]; + switch (c2) + { + default: + //error + throw new NotSupportedException(); + break; + case '"': + pos++; + break; + case '\'': //extension + pos++; + break; + case '/': + pos++; + break; + case '\\': + pos++; + break; + case 'b': // backspace + pos++; + break; + case 'f': //form ffed + pos++; + break; + case 'n': //newline + pos++; + break; + case 'r': //carriage return + pos++; + break; + case 't'://t + pos++; + break; + case 'u': + if (pos < lim - 4) + { + //json spec + //this follow by 4 chars + //for extension we check if it match with 4 chars or not + uint c_uint = ParseUnicode( + sourceBuffer[pos + 1], + sourceBuffer[pos + 2], + sourceBuffer[pos + 3], + sourceBuffer[pos + 4]); + pos += 4; + } + break; + } + } + else + { + //collect more + } + } + pos++; + c = sourceBuffer[pos]; + } + } + + latestIndex = pos; } - protected virtual void NotifyError() + + + + static void ReadNumberLiteral(EsParserBase p, int startAt, out int latestIndex) { + char[] sourceBuffer = p._sourceBuffer; + NumberPart state = NumberPart.IntegerPart; + int lim = sourceBuffer.Length - 1; + int i = startAt; + int collect = 0; + //10-based + NumberParts numParts = new NumberParts(); + numParts.integer_at = startAt; + + int integer_part_count = 0; + int fraction_part_count = 0; + int exponent_part_count = 0; + for (; i < lim; ++i) + { + char c = sourceBuffer[i]; + switch (state) + { + case NumberPart.IntegerPart: + { + if (c == '-') + { + //start integer with minus + numParts.integer_minus = true; + numParts.integer_at++; + collect++; + } + else if (char.IsDigit(c)) + { + //collect more + + //accum + integer_part_count++; + collect++; + } + else if (c == '.') + { + //fraction + collect++; + numParts.fraction_offset = (ushort)((i + 1) - numParts.integer_at); + state = NumberPart.FractionPart; + } + else + { + //this char is not part of the literal number + goto EXIT; + //break + //summary and return + } + } + break; + case NumberPart.FractionPart: + { + //fraction part + if (char.IsDigit(c)) + { + //same state + //collect more + fraction_part_count++; + collect++; + } + else if (c == 'e' || c == 'E') + { + //exponent part + //base 10 + collect++; + + + if (i + 1 < lim) + { + state = NumberPart.ExponentialPart; + i++; + c = sourceBuffer[i + 1]; + if (c == '+') + { + //ok + numParts.exponent_offset = (ushort)((i + 2) - numParts.integer_at); + } + else if (c == '-') + { + numParts.exponent_minus = true; + numParts.exponent_offset = (ushort)((i + 2) - numParts.integer_at); + } + else + { + //must be number + numParts.exponent_offset = (ushort)((i + 1) - numParts.integer_at); + goto case NumberPart.ExponentialPart; + } + } + + } + else + { + //this char is not part of the literal number + goto EXIT; + } + } + break; + case NumberPart.ExponentialPart: + { + //after exponent part + if (char.IsDigit(c)) + { + //collect more + exponent_part_count++; + collect++; + } + else + { + //summary and return + //this char is not part of the literal number + goto EXIT; + } + } + break; + } + } + EXIT: + //-------------------- + //summary + numParts.integer_len = (byte)integer_part_count; + numParts.fraction_len = (byte)fraction_part_count; + numParts.exponent_len = (byte)exponent_part_count; + + switch (state) + { + case NumberPart.IntegerPart: + p.CollectedValueHint = EsValueHint.IntegerNumber; + break; + case NumberPart.FractionPart: + p.CollectedValueHint = EsValueHint.NumberWithFractionPart; + break; + case NumberPart.ExponentialPart: + p.CollectedValueHint = EsValueHint.NumberWithExponentialPart; + break; + } + p.CollectedNumberParts = numParts; + latestIndex = startAt + collect - 1; } - static void ReadSingleLineComment(char[] sourceBuffer, int startAt, ref int latestIndex) + static void ReadSingleLineComment(EsParserBase p, int startAt, out int latestIndex) { + char[] sourceBuffer = p._sourceBuffer; latestIndex = startAt; for (; latestIndex < sourceBuffer.Length; ++latestIndex) { @@ -137,8 +477,9 @@ static void ReadSingleLineComment(char[] sourceBuffer, int startAt, ref int late } } } - static void ReadBlockComment(char[] sourceBuffer, int startAt, ref int latestIndex) + static void ReadBlockComment(EsParserBase p, int startAt, out int latestIndex) { + char[] sourceBuffer = p._sourceBuffer; latestIndex = startAt; for (; latestIndex < sourceBuffer.Length; ++latestIndex) { @@ -185,110 +526,141 @@ bool IsSuccess } + protected char[] _sourceBuffer; - public virtual void Parse(char[] sourceBuffer) - { - OnParseStart(); - //-------------------------------------------------------------- - EsElementKind currentElementKind = EsElementKind.Unknown; - Stack elemKindStack = new Stack(); - //-------------------------------------------------------------- - IsSuccess = true; - StringBuilder myBuffer = new StringBuilder(); - //string lastestKey = ""; - ParsingState currentState = ParsingState._0_Init; - int j = sourceBuffer.Length; + protected EsValueHint CollectedValueHint { get; private set; } + protected NumberParts CollectedNumberParts { get; private set; } - bool isInKeyPart = false; - NumberPart numberPart = NumberPart.IntegerPart; + Stack _isObjectStack = new Stack(); + public virtual void Parse(char[] buff) + { + _sourceBuffer = buff; + IsSuccess = true; - //WARNING: custom version, about ending with comma - //we may use implicit comma feature, - //in case we start new line but forget a comma, - //we auto add comma + ParsingState currentState = ParsingState._1_ExpectObjectValueOrArrayElement; + EsElementKind currentElementKind = EsElementKind.Unknown; + _isObjectStack.Clear(); - //bool implicitComma = false; - char openStringWithChar = '"'; - int i = 0; - ValueHint currentValueHint = ValueHint.Unknown; - for (i = 0; i < j; i++) + for (int i = 0; i < buff.Length; i++) { + char c = buff[i]; - if (!IsSuccess) + if (char.IsWhiteSpace(c)) { - OnError(ref i); - //handle the error **** - //#if DEBUG - // if (dbug_EnableLogParser) - // { - // dbugDataFormatParser.IndentLevel = myKeyStack.Count; - // dbugDataFormatParser.WriteLine("fail at pos=" + i + " on " + currentState); - // } - //#endif - break; //break from loop + continue; } + else if (c == '/') + { + //extension: comment syntax + if (i < buff.Length - 1) //has next + { + char next_c = buff[i + 1]; + if (next_c == '/') + { + ReadSingleLineComment(this, i, out int latestIndex); + i = latestIndex; - //-------------------------- - char c = sourceBuffer[i]; + continue; + } + else if (next_c == '*') + { + //inline comment + ReadBlockComment(this, i, out int latestIndex); + i = latestIndex; + continue; + } + else + { + IsSuccess = false; + NotifyError(); + return; + } + } + else + { + IsSuccess = false; + NotifyError(); + return; + } + } + //----------------------- #if DEBUG - if (dbug_EnableLogParser) + //System.Diagnostics.Debug.WriteLine(i + ":" + c + ":" + currentState); + if (i == 10) { - dbugEsParserLogger.WriteLine(new string('\t', elemKindStack.Count) + i + " ," + c.ToString() + "," + currentState); + } #endif - //-------------------------- + switch (currentState) { - case ParsingState._0_Init: + case ParsingState._1_ExpectObjectValueOrArrayElement: { switch (c) { case '{': - BeginObject(); - //change current element kind after notification - elemKindStack.Push(currentElementKind); - currentElementKind = EsElementKind.Object; - - myBuffer.Length = 0;//clear - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; + { + _isObjectStack.Push(currentElementKind); + BeginObject(); //event + currentState = ParsingState._2_ExpectObjectKey; + currentElementKind = EsElementKind.Object; + } break; - case '/': + case '[': + { + _isObjectStack.Push(currentElementKind); + BeginArray();//event + currentState = ParsingState._1_ExpectObjectValueOrArrayElement; //on the same state -- value state + currentElementKind = EsElementKind.Array; + } + break; + case ']': { - //----------------------- - //comment syntax - if (i < j - 1) + if (currentElementKind == EsElementKind.Array) + { + //empty arr + currentElementKind = _isObjectStack.Pop(); + } + else { - char next_c = sourceBuffer[i + 1]; - if (next_c == '/') - { - int latestIndex = i + 1; - ReadSingleLineComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else if (next_c == '*') - { - //inline comment - int latestIndex = i + 1; - ReadBlockComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else - { - IsSuccess = false; - NotifyError(); - } + IsSuccess = false; + NotifyError(); + return; } - //----------------------- + } + break; + case '"': //standard + case '\''://extension + { + //TODO: string escape here + ReadStringLiteral(this, c, i, out int latestIndex); + NewValue(i, latestIndex - i + 1); + i = latestIndex; + currentState = ParsingState._4_WaitForCommaOrEnd; } break; default: { - if (char.IsWhiteSpace(c)) + //iden + if (char.IsLetter(c) || c == '_') + { + //parse as true, false, null + //or other iden + //parse as idenitifer + + ReadIdentifier(this, i, out int latestIndex); + NewValue(i, latestIndex - i + 1); + i = latestIndex; + currentState = ParsingState._4_WaitForCommaOrEnd; + } + else if (char.IsDigit(c) || (c == '-')) { - //same state - continue; + //number + ReadNumberLiteral(this, i, out int latestIndex); + NewValue(i, latestIndex - i + 1); + i = latestIndex; + currentState = ParsingState._4_WaitForCommaOrEnd; } else { @@ -300,724 +672,104 @@ public virtual void Parse(char[] sourceBuffer) } } break; - case ParsingState._1_ObjectKey: + case ParsingState._2_ExpectObjectKey: { - //TODO: review here again - //in json spec, not support '\"' in keypart - + //literal string + //+ our extension=> iden if (c == '"' || c == '\'') { - //** - openStringWithChar = c; - currentValueHint = ValueHint.StringLiteral; - currentState = ParsingState._2_CollectStringLiteral; + + ReadStringLiteral(this, c, i, out int latestIndex); + //new key from literal string, not include escape char on start and begin + NewKey(i + 1, latestIndex - i + 1 - 2);//event//*** + i = latestIndex; + currentState = ParsingState._3_WaitForColon; } - else if (char.IsWhiteSpace(c)) + else if (char.IsLetter(c) || c == '_') { - continue; + ReadIdentifier(this, i, out int latestIndex); + //new key from literal string + NewKey(i, latestIndex - i + 1);//event + i = latestIndex; + currentState = ParsingState._3_WaitForColon; } else if (c == '}') { - //this is empty - if (currentElementKind != EsElementKind.Object) - { - NotifyError(); - IsSuccess = false; - } - else - { -#if DEBUG - if (myBuffer.Length > 0) - { - //error at this state - } -#endif - isInKeyPart = false; - EndObject();//end current object - //1. close current object - //2. pop current object and switch back - //to prev state *** - if (elemKindStack.Count > 0) - { - //switch back - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } + //no key + //this is empty object + EndObject(); + currentElementKind = _isObjectStack.Pop(); } - else if (char.IsLetter(c) || c == '_') + else { - currentValueHint = ValueHint.Identifier; - myBuffer.Append(c); //collect identifier - currentState = ParsingState._8_CollectIdentifier; //collect identifier + IsSuccess = false; + NotifyError(); + //and stop + return; } - else if (c == '/') + } + break; + case ParsingState._3_WaitForColon: + { + if (c == ':') { - //----------------------- - //comment syntax - if (i < j - 1) - { - char next_c = sourceBuffer[i + 1]; - if (next_c == '/') - { - int latestIndex = i + 1; - ReadSingleLineComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else if (next_c == '*') - { - //inline comment - int latestIndex = i + 1; - ReadBlockComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else - { - IsSuccess = false; - NotifyError(); - } - } - //----------------------- + //value of the key + currentState = ParsingState._1_ExpectObjectValueOrArrayElement; } else { - //extension*** - // - - //number or other token will error in keypart*** - NotifyError(); IsSuccess = false; - break; + NotifyError(); + return; } } break; - case ParsingState._2_CollectStringLiteral: + case ParsingState._4_WaitForCommaOrEnd: { - //collecting string - if (c == '\\') + //after literal string, literal number, array, object + // + if (c == ',') { - currentState = ParsingState._3_StringEscape; - } - else if (c == openStringWithChar) - { - //close current string collection - currentValueHint = ValueHint.StringLiteral; + Comma(); - if (isInKeyPart) + if (currentElementKind == EsElementKind.Object) + { + currentState = ParsingState._2_ExpectObjectKey; + } + else if (currentElementKind == EsElementKind.Array) { - NewKey(myBuffer, currentValueHint); - myBuffer.Length = 0;//clear - currentState = ParsingState._4_FinishKeyPartWaitForSemiColon; + currentState = ParsingState._1_ExpectObjectValueOrArrayElement; } else { - NewValue(myBuffer, currentValueHint); - myBuffer.Length = 0;//clear - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - + IsSuccess = false; + NotifyError(); + return; } } - else + else if (c == '}') { - myBuffer.Append(c); + EndObject(); + currentElementKind = _isObjectStack.Pop(); } - } - break; - case ParsingState._3_StringEscape: - { - switch (c) + else if (c == ']') { - case '"': - { - myBuffer.Append('\"'); - } - break; - case '\'': - { - myBuffer.Append('\''); - } - break; - case '/': - { - myBuffer.Append('/'); - } - break; - case '\\': - { - myBuffer.Append('\\'); - } - break; - case 'b': - { - myBuffer.Append('\b'); - } - break; - case 'f': - { - myBuffer.Append('\f'); - } - break; - case 'r': - { - myBuffer.Append('\r'); - } - break; - case 'n': - { - myBuffer.Append('\n'); - } - break; - case 't': - { - myBuffer.Append('\t'); - } - break; - case 'u': - { - //unicode char in hexa digit - //TODO: review here if we have enough char to parse *** - if (i < j - 4) - { - //json spec - //this follow by 4 chars - //for extension we check if it match with 4 chars or not - uint c_uint = ParseUnicode( - sourceBuffer[i + 1], - sourceBuffer[i + 2], - sourceBuffer[i + 3], - sourceBuffer[i + 4]); - myBuffer.Append((char)c_uint); - i += 4; - } - else - { - //error - IsSuccess = false; - NotifyError(); - } - } - break; - default: - { - NotifyError(); - IsSuccess = false; - } - break; - } - //switch back to state 2_collectStringLiteral - currentState = ParsingState._2_CollectStringLiteral; - } - break; - case ParsingState._4_FinishKeyPartWaitForSemiColon: - { - //wait for : - if (c == ':') - { - myBuffer.Length = 0;//clear - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; //object's value part - } - else if (char.IsWhiteSpace(c)) - { - continue; - } - else if (c == '/') - { - //----------------------- - //comment syntax - if (i < j - 1) - { - char next_c = sourceBuffer[i + 1]; - if (next_c == '/') - { - int latestIndex = i + 1; - ReadSingleLineComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else if (next_c == '*') - { - //inline comment - int latestIndex = i + 1; - ReadBlockComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else - { - IsSuccess = false; - NotifyError(); - } - } - //----------------------- - } - else - { - //TODO: add recovery extension here - NotifyError(); - IsSuccess = false; - break; - } - } - break; - case ParsingState._5_ExpectObjectValueOrArrayElement: - { - //in value part *** - //of object or array - - if (c == '"' || c == '\'') - { - //TODO: string escape here - openStringWithChar = c; - //string val - currentState = ParsingState._2_CollectStringLiteral; - } - else if (char.IsDigit(c) || (c == '-')) - { - //TODO: - //support extension + - myBuffer.Append(c); - //number - currentValueHint = ValueHint.IntegerNumber; - numberPart = NumberPart.IntegerPart; - currentState = ParsingState._7_CollectNumberLiteral; - } - else if (c == '{') - { - //store current object in stack - BeginObject(); - elemKindStack.Push(currentElementKind); - currentElementKind = EsElementKind.Object; - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; - } - else if (c == '[') - { - BeginArray(); - elemKindStack.Push(currentElementKind); - currentElementKind = EsElementKind.Array; - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; //on the same state -- value state - } - else if (c == ']') - { - if (currentElementKind != EsElementKind.Array) - { - NotifyError(); - IsSuccess = false; - } - else - { - EndArray();//end current array - - if (elemKindStack.Count > 0) - { - - currentElementKind = elemKindStack.Pop(); - } - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } - else if (c == '/') - { - //----------------------- - //comment syntax - if (i < j - 1) - { - char next_c = sourceBuffer[i + 1]; - if (next_c == '/') - { - int latestIndex = i + 1; - ReadSingleLineComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else if (next_c == '*') - { - //inline comment - int latestIndex = i + 1; - ReadBlockComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else - { - IsSuccess = false; - NotifyError(); - } - } - //----------------------- - } - else if (char.IsWhiteSpace(c)) - { - continue; - } - else - { - //we collect other character into buffer - //so we can collect - //null, true, false - //or other identifier *** - currentState = ParsingState._8_CollectIdentifier; - myBuffer.Append(c); - } - } - break; - case ParsingState._6_AfterObjectValueOrArrayElement: - { - switch (c) - { - case ',': - switch (currentElementKind) - { - default: throw new NotSupportedException(); - case EsElementKind.Object: - currentState = ParsingState._1_ObjectKey; - isInKeyPart = true; - break; - case EsElementKind.Array: - //array - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; - break; - } - break; - case ']': - if (currentElementKind != EsElementKind.Array) - { - //error - throw new NotSupportedException(); - } - EndArray(); - - //close current array - //then push value back to prev stored value - if (elemKindStack.Count > 0) - { - - //current value must be array - currentElementKind = elemKindStack.Pop(); - } - break; - case '}': - - if (currentElementKind != EsElementKind.Object) - { - //error - throw new NotSupportedException(); - } - EndObject(); - - if (elemKindStack.Count > 0) - { - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - break; - case '/': - { - //----------------------- - //comment syntax - if (i < j - 1) - { - char next_c = sourceBuffer[i + 1]; - if (next_c == '/') - { - int latestIndex = i + 1; - ReadSingleLineComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else if (next_c == '*') - { - //inline comment - int latestIndex = i + 1; - ReadBlockComment(sourceBuffer, latestIndex + 1, ref latestIndex); - i = latestIndex; - } - else - { - IsSuccess = false; - NotifyError(); - } - } - //----------------------- - } - break; - default: - //? - //TODO: error recovery / or handle error with some extension - break; - } - } - break; - case ParsingState._7_CollectNumberLiteral: - { - //------------------------------------------------------ - //TODO: review pass sign state of number literal - //check if we support hex liternal or binary literal - //this is extension to normal json *** - //------------------------------------------------------ - - if (char.IsDigit(c)) - { - myBuffer.Append(c); - } - else if (c == '.') - { - if (numberPart == NumberPart.IntegerPart) - { - myBuffer.Append(c); - numberPart = NumberPart.FractionPart; - currentValueHint = ValueHint.NumberWithFractionPart; - } - else - { - NotifyError(); - IsSuccess = false; - break; - } - } - else if (c == 'e' || c == 'E') - { - myBuffer.Append(c); - switch (numberPart) - { - case NumberPart.IntegerPart: - numberPart = NumberPart.E; - currentValueHint = ValueHint.NumberWithExponentialPart; - break; - case NumberPart.FractionPart: - numberPart = NumberPart.E; - currentValueHint = ValueHint.NumberWithExponentialPart; - break; - default: - NotifyError(); - IsSuccess = false; - break; - } - } - else if (c == '-' || c == '+') - { - myBuffer.Append(c); - switch (numberPart) - { - case NumberPart.E://after e - numberPart = NumberPart.ESign; - break; - default: - NotifyError(); - IsSuccess = false; - break; - } - } - else if (c == ']') - { - NewValue(myBuffer, currentValueHint); - //-------------------------- - myBuffer.Length = 0; //clear EndArray(); - - if (elemKindStack.Count > 0) - { - - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } - else if (c == '}') - { - NewValue(myBuffer, currentValueHint); - myBuffer.Length = 0; //clear - EndObject(); - - if (elemKindStack.Count > 0) - { - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } - else if (c == ',') - { - NewValue(myBuffer, currentValueHint); - //clear - myBuffer.Length = 0; - switch (currentElementKind) - { - default: throw new NotSupportedException(); - case EsElementKind.Array: - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; - break; - case EsElementKind.Object: - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; - break; - } - } - else if (c == '\r') - { - //stop here - if (i < j - 1) - { - if (sourceBuffer[i + 1] == '\n') - { - //\r\n - i++; - - NewValue(myBuffer, currentValueHint); - //clear - myBuffer.Length = 0; - - switch (currentElementKind) - { - default: throw new NotSupportedException(); - case EsElementKind.Array: - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; - break; - case EsElementKind.Object: - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; - break; - } - } - else - { - //only R - } - } - } - else if (c == '\n') - { - //stop - NewValue(myBuffer, currentValueHint); - //clear - myBuffer.Length = 0; - - switch (currentElementKind) - { - default: throw new NotSupportedException(); - case EsElementKind.Array: - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; - break; - case EsElementKind.Object: - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; - break; - } + currentElementKind = _isObjectStack.Pop(); } else { - IsSuccess = false; NotifyError(); - } - } - break; - case ParsingState._8_CollectIdentifier: - { - - currentValueHint = ValueHint.Identifier; - switch (c) - { - case ':': - //stop collect identifier and - if (isInKeyPart) - { - NewKey(myBuffer, currentValueHint); - myBuffer.Length = 0;//clear - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; //object's value part - isInKeyPart = false; - } - else - { - NotifyError(); - IsSuccess = false; - } - break; - case '}': - if (isInKeyPart) - { - NotifyError(); - IsSuccess = false; - } - else - { - NewValue(myBuffer, currentValueHint); - myBuffer.Length = 0; //clear - EndObject(); - - if (elemKindStack.Count > 0) - { - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } - break; - case ']': - if (isInKeyPart) - { - NotifyError(); - IsSuccess = false; - } - else - { - NewValue(myBuffer, currentValueHint); - myBuffer.Length = 0; //clear - EndArray(); - - if (elemKindStack.Count > 0) - { - - currentElementKind = elemKindStack.Pop(); - } - currentState = ParsingState._6_AfterObjectValueOrArrayElement; - } - break; - case ',': - if (isInKeyPart) - { - NotifyError(); - IsSuccess = false; - } - else - { - NewValue(myBuffer, currentValueHint); - myBuffer.Length = 0; //clear - switch (currentElementKind) - { - default: throw new NotSupportedException(); - case EsElementKind.Array: - isInKeyPart = false; - currentState = ParsingState._5_ExpectObjectValueOrArrayElement; - break; - case EsElementKind.Object: - isInKeyPart = true; - currentState = ParsingState._1_ObjectKey; - break; - } - } - break; - default: - - if (char.IsWhiteSpace(c)) - { - //stop collect identifier*** - //wait for : - currentState = ParsingState._4_FinishKeyPartWaitForSemiColon; - } - else - { - myBuffer.Append(c); - } - break; + return; } } break; } } - - OnParseEnd(); - } - static uint ParseSingleChar(char c1) + static uint ParseHex(char c1) { switch (c1) { @@ -1042,31 +794,28 @@ static uint ParseSingleChar(char c1) } static uint ParseUnicode(char c1, char c2, char c3, char c4) { - return (ParseSingleChar(c1) << 16) | - (ParseSingleChar(c2) << 8) | - (ParseSingleChar(c3) << 4) | - (ParseSingleChar(c4) << 0); + return (ParseHex(c1) << 12) | + (ParseHex(c2) << 8) | + (ParseHex(c3) << 4) | + (ParseHex(c4) << 0); } - - } + public abstract class EsParserBase : EsParserBase where E : class where A : class { - enum CurrentObject - { - Object, - Array - } + Stack _keyStack = new Stack(); Stack _elemStack = new Stack(); - object _currentElem = null; + string _currentKey = null; + object _currentValue = null; + public EsParserBase() { @@ -1088,34 +837,31 @@ protected override void BeginObject() _keyStack.Push(_currentKey); } _currentKey = null; - if (_currentElem != null) + if (_currentValue != null) { - _elemStack.Push(_currentElem); + _elemStack.Push(_currentValue); } - _currentElem = CreateElement(); + _currentValue = CreateElement(); } void InternalPopCurrentObjectAndPushToPrevContext() { //current element should be object - object c_object = _currentElem; + object c_object = _currentValue; if (_elemStack.Count > 0) { //pop from stack - _currentElem = _elemStack.Pop(); + _currentValue = _elemStack.Pop(); _currentKey = null; - if (c_object == _currentElem) + if (c_object == _currentValue) { throw new System.Exception(); } - - E c_elem = null; - A c_arr = null; - if ((c_elem = _currentElem as E) != null) + if (_currentValue is E c_elem) { _currentKey = _keyStack.Pop(); AddElementAttribute(c_elem, _currentKey, c_object); } - else if ((c_arr = _currentElem as A) != null) + else if (_currentValue is A c_arr) { AddArrayElement(c_arr, c_object); } @@ -1136,11 +882,11 @@ protected override void BeginArray() _keyStack.Push(_currentKey); } _currentKey = null; - if (_currentElem != null) + if (_currentValue != null) { - _elemStack.Push(_currentElem); + _elemStack.Push(_currentValue); } - _currentElem = CreateArray(); + _currentValue = CreateArray(); } protected override void EndArray() { @@ -1150,74 +896,129 @@ protected override void OnParseEnd() { } - protected override void NewKey(StringBuilder tmpBuffer, ValueHint valueHint) + protected override void NewKey(int start, int len) { - _currentKey = tmpBuffer.ToString(); + _currentKey = new string(_sourceBuffer, start, len); } - protected override void NewValue(StringBuilder tmpBuffer, ValueHint valueHint) + + string ParseStringWithSomeEscape(int start, int len) { + //TODO: use pool + StringBuilder sb = new StringBuilder(); + int end = start + len; +#if DEBUG + string dbug_preview = new string(_sourceBuffer, start, len); +#endif + for (int i = start; i < end; ++i) + { + char c = _sourceBuffer[i]; + if (c == '\\') + { + //escape 1 + i++;//consume + c = _sourceBuffer[i]; + } + sb.Append(c); + } + return sb.ToString(); + } + protected override void NewValue(int start, int len) + { + //current object + string iden = ""; object c_object = null; - switch (valueHint) + switch (CollectedValueHint) { - default: - case ValueHint.Comment: - throw new System.NotSupportedException(); - case ValueHint.Identifier: - string iden = tmpBuffer.ToString(); - switch (iden) + default: throw new NotSupportedException(); + case EsValueHint.Identifier: { - case "true": - c_object = true; - break; - case "false": - c_object = false; - break; - case "null": - c_object = null; - break; - default: - c_object = iden; - break; + iden = new string(_sourceBuffer, start, len); + switch (iden) + { + case "true": + c_object = true; + break; + case "false": + c_object = false; + break; + case "null": + c_object = null; + break; + default: + c_object = iden; + break; + } } break; - case ValueHint.StringLiteral: - c_object = tmpBuffer.ToString(); + case EsValueHint.StringLiteralWithSomeEscape: + c_object = ParseStringWithSomeEscape(start + 1, len - 2); break; - case ValueHint.IntegerNumber: - c_object = int.Parse(tmpBuffer.ToString()); + case EsValueHint.StringLiteral: + c_object = new string(_sourceBuffer, start + 1, len - 2); break; - case ValueHint.NumberWithFractionPart: - case ValueHint.NumberWithSignedExponentialPart: - case ValueHint.NumberWithExponentialPart: - c_object = double.Parse(tmpBuffer.ToString()); + case EsValueHint.IntegerNumber: + iden = new string(_sourceBuffer, start, len); + if (len > 0) + { + if (len < 10) + { + c_object = int.Parse(iden); + } + else + { + //signed + long number = long.Parse(iden); + if (number >= int.MinValue && number <= int.MaxValue) + { + //int32 range + c_object = (int)number; + } + else + { + c_object = number; + } + } + } + else + { + //number len=0 + throw new NotSupportedException(); + } + break; + case EsValueHint.NumberWithFractionPart: + case EsValueHint.NumberWithExponentialPart: + iden = new string(_sourceBuffer, start, len); + c_object = double.Parse(iden); break; - } - E c_elem = null; - A c_arr = null; - if ((c_elem = _currentElem as E) != null) + if (_currentValue is E c_elem) { AddElementAttribute(c_elem, _currentKey, c_object); } - else if ((c_arr = _currentElem as A) != null) + else if (_currentValue is A c_arr) { AddArrayElement(c_arr, c_object); } else { - throw new System.NotSupportedException(); - } + if (_currentValue == null) + { + _currentValue = c_object; + } + else + { + throw new System.NotSupportedException(); + } + } } + protected override void NotifyError() { base.NotifyError(); } - protected override void OnError(ref int currentIndex) - { - base.OnError(ref currentIndex); - } - public object CurrentElement => _currentElem; + + public object CurrentValue => _currentValue; } } \ No newline at end of file diff --git a/src/SharpConnect.Data/TreeModel/EsUniqueStringTable.cs b/src/SharpConnect.Data/TreeModel/EsUniqueStringTable.cs index fdd7053..87be21f 100644 --- a/src/SharpConnect.Data/TreeModel/EsUniqueStringTable.cs +++ b/src/SharpConnect.Data/TreeModel/EsUniqueStringTable.cs @@ -25,15 +25,12 @@ public int GetStringIndex(string str) { return 0; } - int foundIndex; - if (_dic.TryGetValue(str, out foundIndex)) - { - return foundIndex; - } - else + + if (!_dic.TryGetValue(str, out int foundIndex)) { - return -1; + foundIndex = -1; } + return foundIndex; } public int AddStringIfNotExist(string str) @@ -43,9 +40,8 @@ public int AddStringIfNotExist(string str) { return 0; } - //--------------------------------------- - int foundIndex; - if (_dic.TryGetValue(str, out foundIndex)) + + if (_dic.TryGetValue(str, out int foundIndex)) { return foundIndex; } @@ -64,17 +60,6 @@ public int AddStringIfNotExist(string str) public string GetString(int index) => _list[index]; - public IEnumerable WordIter - { - get - { - foreach (string str in _dic.Keys) - { - yield return str; - } - } - } - public List GetStringList() => _list; public EsUniqueStringTable Clone()