// Copyright (c) 2019 μ΅μ μ
// Distributed under the BSD License, Version 1.0.
using System.Collections.Generic;
using System.Text;
//==========================================================================================
// μ½λ λ³Έλ¬Έ
//==========================================================================================
#region [ Base CSON Element ]
/// <summary>
/// <see cref="CSONElement"/>κ° μ·¨ν μ μλ ννμ μ’
λ₯λ₯Ό λνλ
λλ€.
/// </summary>
public enum CSONElementType {
Unknown,
/// <summary>
/// Key(string), Value(<see cref="CSONElement"/>)μ μμΌλ‘ μ΄λ£¨μ΄μ§ <see cref="CSONElement"/>μ νμ νν μ€ νλμ
λλ€.
/// </summary>
Object,
/// <summary>
/// Index(int), Value(<see cref="CSONElement"/>)μ μμΌλ‘ μ΄λ£¨μ΄μ§ <see cref="CSONElement"/>μ νμ νν μ€ νλμ
λλ€.
/// <para>λ°°μ΄μ κ° μμλ 리μ€νΈμ ννλ‘ λ€λ£¨μ΄μ§λλ€.</para>
/// </summary>
Array,
/// <summary>
/// λ°°μ΄μ μν μμμ μμΉκ° μ°μμ μ΄μ§ μμ μλ£κ΅¬μ‘°λ₯Ό λ€λ£¨κΈ°μ μ ν©ν νμ ννμ
λλ€.
/// <para>λ°°μ΄μ μλ―Έμ λ§κ² κ° μμκ° λ€λ£¨μ΄μ§λλ€.</para>
/// </summary>
SparseArray,
/// <summary>
/// μμμ μΈ λ°μ΄ν° νμ
<see cref="string"/>μ ννλ₯Ό μ·¨νκ³ μλ <see cref="CSONElement"/>μ
λλ€.
/// </summary>
String,
/// <summary>
/// μμμ μΈ λ°μ΄ν° νμ
<see cref="int"/>μ ννλ₯Ό μ·¨νκ³ μλ <see cref="CSONElement"/>μ
λλ€.
/// </summary>
Integer,
// TODO: CSON FIxedReal ꡬν
FixedReal,
// TODO: CSON FloatReal ꡬν
FloatReal
}
/// <summary>
/// Custom JSON μλ£κ΅¬μ‘°λ₯Ό ꡬμ±νλ λ¨μ, Elementμ
λλ€.
/// </summary>
public abstract class CSONElement
{
/// <summary>
/// ν΄λΉ <see cref="CSONElement"/>κ° μ·¨νκ³ μλ μλ£νμ
λλ€.
/// </summary>
public CSONElementType Type { get; protected set; }
}
#endregion
#region[ CSON Object{} ]
/// <summary>
/// Key(string), Value(<see cref="CSONElement"/>) μμΌλ‘ μ΄λ£¨μ΄μ§ <see cref="CSONElement"/>λ€μ κ°λ μλ£νμ
λλ€.
/// </summary>
public class CSONObject : CSONElement
{
public Dictionary<string, CSONElement> Elements { get; protected set; } = new Dictionary<string, CSONElement>();
public CSONObject(){ Type = CSONElementType.Object;}
public bool Contains(string key) { return Elements.ContainsKey(key); }
public void Remove(string key) { Elements.Remove(key); }
public void Clear() { Elements.Clear(); }
public CSONElement this[string key] { get { return Elements[key]; } set { Elements[key] = value; } }
public void Add(string key, CSONElement Element){Elements.Add(key, Element); }
public void Add(string key, string value) { Elements.Add(key, new CSONString() { Value = value }); }
public void Add(string key, int value) { Elements.Add(key, new CSONInteger() { Value = value }); }
public override string ToString()
{
string result = "{";
bool first = true;
foreach (var Element in Elements)
{
if (!first)
result += ", ";
result += $"\"{Element.Key}\": {Element.Value.ToString()}";
first = false;
}
result += "}";
return result;
}
}
#endregion
#region [ CSON Sparse Array<> ]
public class CSONSparseArray : CSONElement
{
public Dictionary<int, CSONElement> Items { get; protected set; } = new Dictionary<int, CSONElement>();
public CSONSparseArray() { Type = CSONElementType.SparseArray; }
public bool Contains(int index) { return Items.ContainsKey(index); }
public void Remove(int index) { Items.Remove(index); }
public void Clear() { Items.Clear(); }
public CSONElement this[int index] { get { return Items[index]; } set { Items[index] = value; } }
public void Add(int index, CSONElement Element) { Items.Add(index, Element); }
public void Add(int index, string value) { Items.Add(index, new CSONString() { Value = value }); }
public void Add(int index, int value) { Items.Add(index, new CSONInteger() { Value = value }); }
public override string ToString()
{
string result = "<";
bool first = true;
foreach (var item in Items)
{
if (!first)
result += ", ";
result += $"{item.Key}: {item.Value.ToString()}";
first = false;
}
result += ">";
return result;
}
}
#endregion
#region [ CSON Array[] ]
public class CSONArray : CSONElement
{
public List<CSONElement> Items { get; protected set; } = new List<CSONElement>();
public CSONArray(){Type = CSONElementType.Array;}
public bool Contains(CSONElement Element) { return Items.Contains(Element); }
public void RemoveAt(int index) { Items.RemoveAt(index); }
public void Clear() { Items.Clear(); }
public CSONElement this[int index] { get { return Items[index]; } set { Items[index] = value; } }
public void Add(CSONElement Element) { Items.Add(Element); }
public void Add(string value) { Items.Add( new CSONString() { Value = value }); }
public void Add(int value) { Items.Add( new CSONInteger() { Value = value }); }
public override string ToString()
{
string result = "[";
bool first = true;
foreach (var element in Items)
{
if (!first)
result += ", ";
result += element.ToString();
first = false;
}
result += "]";
return result;
}
}
#endregion
#region [ Primitive CSON Elements ]
public class CSONString : CSONElement
{
public string Value;
public CSONString() { Type = CSONElementType.String; }
public override string ToString(){ return $"\"{Value}\""; }
}
public class CSONInteger : CSONElement
{
public int Value;
public CSONInteger() { Type = CSONElementType.Integer; }
public override string ToString() { return $"{Value}"; }
}
public class CSONReal : CSONElement
{
public double Value;
public CSONReal() { Type = CSONElementType.FixedReal; }
public override string ToString() { return $"{Value}"; }
}
#endregion
//==========================================================================================
// Parser
//==========================================================================================
#region [ CSON Parser ]
public class CSONParser
{
private enum TokenType
{
Default, String, Integer, Real, CommentReady, Comment, CommentEndReady,
Seperator, Parameter, ObjectOpen, ArrayOpen, SparseOpen, ObjectClose, ArrayClose, SparseClose
}
private class ParseToken
{
public TokenType Type;
public string Value;
}
private List<ParseToken> Tokenize(char[] CSONCharArray)
{
var tokens = new List<ParseToken>();
TokenType currentToken = TokenType.Default;
StringBuilder tokenBuilder = new StringBuilder();
string tokenString;
for(int ci=0;ci<CSONCharArray.Length;ci++)
{
var c = CSONCharArray[ci];
switch (currentToken)
{
case TokenType.Integer:
case TokenType.Real:
switch (c)
{
case '.':
if (currentToken == TokenType.Real)
throw new System.Exception($"μ¬λ°λ₯΄μ§ μμ μ«μ μμ.");
currentToken = TokenType.Real;
tokenBuilder.Append(c);
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
tokenBuilder.Append(c);
break;
default:
tokenString = tokenBuilder.ToString();
tokens.Add(new ParseToken() { Type = currentToken, Value = tokenString });
tokenBuilder.Clear();
//
currentToken = TokenType.Default; //....?
ci--;
break;
}
break;
case TokenType.String:
switch (c)
{
case '"':
tokenString = tokenBuilder.ToString();
tokens.Add(new ParseToken() { Type = TokenType.String, Value = tokenString });
tokenBuilder.Clear();
//
currentToken = TokenType.Default; //....?
break;
default:
tokenBuilder.Append(c);
break;
}
break;
case TokenType.CommentEndReady:
case TokenType.Comment:
switch (c)
{
case '*':
currentToken = TokenType.CommentEndReady;
break;
case '/':
if (currentToken == TokenType.CommentEndReady)
{
currentToken = TokenType.Default; //....?
}
break;
}
break;
case TokenType.CommentReady:
case TokenType.Default:
switch (c)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// λν΄νΈκ° μλ μν© = μμ acceptν λ¬Έμκ° μμμ.
if (currentToken != TokenType.Default)
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμκ° μ νλ¨.");
currentToken = TokenType.Integer;
tokenBuilder.Append(c);
break;
case '"':
// λν΄νΈκ° μλ μν© = μμ acceptν λ¬Έμκ° μμμ.
if (currentToken != TokenType.Default)
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμκ° μ νλ¨.");
currentToken = TokenType.String;
break;
case '/':
// λν΄νΈκ° μλ μν© = μμ acceptν λ¬Έμκ° μμμ.
if (currentToken != TokenType.Default)
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμκ° μ νλ¨.");
// μ½λ©νΈ μ€λΉ μμ
currentToken = TokenType.CommentReady;
break;
case ' ':
case '\t':
// λν΄νΈκ° μλ μν© = μμ acceptν λ¬Έμκ° μμμ.
if (currentToken != TokenType.Default)
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμκ° μ νλ¨.");
// κΈ°λ³Έμ μΌλ‘ 곡백μ 무μν¨
break;
case '*':
// μ½λ©νΈ μ€λΉ
if (currentToken == TokenType.CommentReady)
{
currentToken = TokenType.Comment;
break;
}
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμ {c}.");
case '{':
tokens.Add(new ParseToken() { Type = TokenType.ObjectOpen });
break;
case '[':
tokens.Add(new ParseToken() { Type = TokenType.ArrayOpen });
break;
case '<':
tokens.Add(new ParseToken() { Type = TokenType.SparseOpen });
break;
case '>':
tokens.Add(new ParseToken() { Type = TokenType.SparseClose });
break;
case ']':
tokens.Add(new ParseToken() { Type = TokenType.ArrayClose });
break;
case '}':
tokens.Add(new ParseToken() { Type = TokenType.ObjectClose });
break;
case ':':
tokens.Add(new ParseToken() { Type = TokenType.Parameter });
break;
case ',':
tokens.Add(new ParseToken() { Type = TokenType.Seperator });
break;
default:
throw new System.Exception($"μμνμ§ λͺ»ν λ¬Έμ {c}.");
}
break;
}
}
if (currentToken != TokenType.Default)
throw new System.Exception($"CSONμ΄ μ¬λ°λ₯΄κ² μ’
κ²°λμ§ λͺ»ν¨.");
return tokens;
}
private CSONElement ParseRecur(List<ParseToken> tokens, ref int readPtr)
{
CSONElement baseElement = null;
var token = tokens[readPtr];
readPtr++;
switch (token.Type)
{
case TokenType.String:
baseElement = new CSONString() { Value = token.Value };
break;
case TokenType.Integer:
baseElement = new CSONInteger() { Value = int.Parse(token.Value) };
break;
case TokenType.Real:
baseElement = new CSONReal() { Value = double.Parse(token.Value) };
break;
case TokenType.ObjectOpen:
baseElement = new CSONObject();
while (readPtr < tokens.Count)
{
var finish = false;
var subtoken = tokens[readPtr];
readPtr++;
switch (subtoken.Type)
{
case TokenType.Seperator:/*Do Nothing*/break;
case TokenType.String:
{
if(tokens[readPtr].Type != TokenType.Parameter)
throw new System.Exception($"μκΈ°μΉ λͺ»ν {subtoken.Type}.");
var paraToken = tokens[readPtr + 1];
readPtr += 2;
switch (paraToken.Type)
{
case TokenType.String:
((CSONObject)baseElement).Add(subtoken.Value, new CSONString() { Value = paraToken.Value });
break;
case TokenType.Integer:
((CSONObject)baseElement).Add(subtoken.Value, new CSONInteger() { Value = int.Parse(paraToken.Value) });
break;
case TokenType.Real:
((CSONObject)baseElement).Add(subtoken.Value, new CSONReal() { Value = double.Parse(paraToken.Value) });
break;
case TokenType.ObjectOpen:
case TokenType.SparseOpen:
case TokenType.ArrayOpen:
readPtr--;
((CSONObject)baseElement).Add(subtoken.Value, ParseRecur(tokens, ref readPtr));
break;
}
}
break;
case TokenType.ObjectClose:
finish = true;
break;
default:
throw new System.Exception($"μκΈ°μΉ λͺ»ν {subtoken.Type}.");
}
if (finish)
break;
}
break;
case TokenType.SparseOpen:
baseElement = new CSONSparseArray();
while (readPtr < tokens.Count)
{
var finish = false;
var subtoken = tokens[readPtr];
readPtr++;
switch (subtoken.Type)
{
case TokenType.Seperator:/*Do Nothing*/break;
case TokenType.Integer:
{
if (tokens[readPtr].Type != TokenType.Parameter)
throw new System.Exception($"μκΈ°μΉ λͺ»ν {subtoken.Type}.");
var paraToken = tokens[readPtr + 1];
readPtr += 2;
switch (paraToken.Type)
{
case TokenType.String:
((CSONSparseArray)baseElement).Add(int.Parse(subtoken.Value), new CSONString() { Value = paraToken.Value });
break;
case TokenType.Integer:
((CSONSparseArray)baseElement).Add(int.Parse(subtoken.Value), new CSONInteger() { Value = int.Parse(paraToken.Value) });
break;
case TokenType.Real:
((CSONSparseArray)baseElement).Add(int.Parse(subtoken.Value), new CSONReal() { Value = double.Parse(paraToken.Value) });
break;
case TokenType.ObjectOpen:
case TokenType.SparseOpen:
case TokenType.ArrayOpen:
readPtr--;
((CSONSparseArray)baseElement).Add(int.Parse(subtoken.Value), ParseRecur(tokens, ref readPtr));
break;
}
}
break;
case TokenType.ArrayClose:
finish = true;
break;
default:
throw new System.Exception($"μκΈ°μΉ λͺ»ν {subtoken.Type}.");
}
if (finish)
break;
}
break;
case TokenType.ArrayOpen:
baseElement = new CSONArray();
while (readPtr < tokens.Count)
{
var finish = false;
var subtoken = tokens[readPtr];
readPtr++;
switch (subtoken.Type)
{
case TokenType.Seperator:/*Do Nothing*/break;
case TokenType.String:
((CSONArray)baseElement).Add(new CSONString() { Value = subtoken.Value });
break;
case TokenType.Integer:
((CSONArray)baseElement).Add(new CSONInteger() { Value = int.Parse(subtoken.Value) });
break;
case TokenType.Real:
((CSONArray)baseElement).Add(new CSONReal() { Value = double.Parse(subtoken.Value) });
break;
case TokenType.ObjectOpen:
case TokenType.SparseOpen:
case TokenType.ArrayOpen:
readPtr--;
((CSONArray)baseElement).Add(ParseRecur(tokens, ref readPtr));
break;
case TokenType.ArrayClose:
finish = true;
break;
default:
throw new System.Exception($"μκΈ°μΉ λͺ»ν {subtoken.Type}.");
}
if (finish)
break;
}
break;
}
return baseElement;
}
public CSONElement Parse(string CSONString)
{
CSONString += " "; //곡백μ μΆκ°ν΄μ λΉ-λΈλ‘ ꡬ쑰μ λ°μ΄ν°κ° μκ³ λ¦¬μ¦ μ€κ°μ λκΈ°λ μΌμ΄ μλλ‘ ν¨
// μ£Όμμ λ°°μ λκ³ λ¬Έμμ΄μ΄ ν ν° ννλ‘ λ¬ΆμΈ λ¦¬μ€νΈλ₯Ό λ°νλ°μ΅λλ€.
List<ParseToken> tokens = Tokenize(CSONString.ToCharArray());
int readPtr = 0;
return ParseRecur(tokens, ref readPtr);
}
}
#endregion