Committed everything

This commit is contained in:
2021-06-30 21:39:19 +10:00
commit fcfa8e7213
525 changed files with 49440 additions and 0 deletions

View File

@@ -0,0 +1,647 @@
using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
namespace Ink.Runtime
{
/// <summary>
/// Simple custom JSON serialisation implementation that takes JSON-able System.Collections that
/// are produced by the ink engine and converts to and from JSON text.
/// </summary>
public static class SimpleJson
{
public static Dictionary<string, object> TextToDictionary (string text)
{
return new Reader (text).ToDictionary ();
}
public static List<object> TextToArray(string text)
{
return new Reader(text).ToArray();
}
class Reader
{
public Reader (string text)
{
_text = text;
_offset = 0;
SkipWhitespace ();
_rootObject = ReadObject ();
}
public Dictionary<string, object> ToDictionary ()
{
return (Dictionary<string, object>)_rootObject;
}
public List<object> ToArray()
{
return (List<object>)_rootObject;
}
bool IsNumberChar (char c)
{
return c >= '0' && c <= '9' || c == '.' || c == '-' || c == '+' || c == 'E' || c == 'e';
}
bool IsFirstNumberChar(char c)
{
return c >= '0' && c <= '9' || c == '-' || c == '+';
}
object ReadObject ()
{
var currentChar = _text [_offset];
if( currentChar == '{' )
return ReadDictionary ();
else if (currentChar == '[')
return ReadArray ();
else if (currentChar == '"')
return ReadString ();
else if (IsFirstNumberChar(currentChar))
return ReadNumber ();
else if (TryRead ("true"))
return true;
else if (TryRead ("false"))
return false;
else if (TryRead ("null"))
return null;
throw new System.Exception ("Unhandled object type in JSON: "+_text.Substring (_offset, 30));
}
Dictionary<string, object> ReadDictionary ()
{
var dict = new Dictionary<string, object> ();
Expect ("{");
SkipWhitespace ();
// Empty dictionary?
if (TryRead ("}"))
return dict;
do {
SkipWhitespace ();
// Key
var key = ReadString ();
Expect (key != null, "dictionary key");
SkipWhitespace ();
// :
Expect (":");
SkipWhitespace ();
// Value
var val = ReadObject ();
Expect (val != null, "dictionary value");
// Add to dictionary
dict [key] = val;
SkipWhitespace ();
} while ( TryRead (",") );
Expect ("}");
return dict;
}
List<object> ReadArray ()
{
var list = new List<object> ();
Expect ("[");
SkipWhitespace ();
// Empty list?
if (TryRead ("]"))
return list;
do {
SkipWhitespace ();
// Value
var val = ReadObject ();
// Add to array
list.Add (val);
SkipWhitespace ();
} while (TryRead (","));
Expect ("]");
return list;
}
string ReadString ()
{
Expect ("\"");
var sb = new StringBuilder();
for (; _offset < _text.Length; _offset++) {
var c = _text [_offset];
if (c == '\\') {
// Escaped character
_offset++;
if (_offset >= _text.Length) {
throw new Exception("Unexpected EOF while reading string");
}
c = _text[_offset];
switch (c)
{
case '"':
case '\\':
case '/': // Yes, JSON allows this to be escaped
sb.Append(c);
break;
case 'n':
sb.Append('\n');
break;
case 't':
sb.Append('\t');
break;
case 'r':
case 'b':
case 'f':
// Ignore other control characters
break;
case 'u':
// 4-digit Unicode
if (_offset + 4 >=_text.Length) {
throw new Exception("Unexpected EOF while reading string");
}
var digits = _text.Substring(_offset + 1, 4);
int uchar;
if (int.TryParse(digits, System.Globalization.NumberStyles.AllowHexSpecifier, System.Globalization.CultureInfo.InvariantCulture, out uchar)) {
sb.Append((char)uchar);
_offset += 4;
} else {
throw new Exception("Invalid Unicode escape character at offset " + (_offset - 1));
}
break;
default:
// The escaped character is invalid per json spec
throw new Exception("Invalid Unicode escape character at offset " + (_offset - 1));
}
} else if( c == '"' ) {
break;
} else {
sb.Append(c);
}
}
Expect ("\"");
return sb.ToString();
}
object ReadNumber ()
{
var startOffset = _offset;
bool isFloat = false;
for (; _offset < _text.Length; _offset++) {
var c = _text [_offset];
if (c == '.' || c == 'e' || c == 'E') isFloat = true;
if (IsNumberChar (c))
continue;
else
break;
}
string numStr = _text.Substring (startOffset, _offset - startOffset);
if (isFloat) {
float f;
if (float.TryParse (numStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out f)) {
return f;
}
} else {
int i;
if (int.TryParse (numStr, out i)) {
return i;
}
}
throw new System.Exception ("Failed to parse number value: "+numStr);
}
bool TryRead (string textToRead)
{
if (_offset + textToRead.Length > _text.Length)
return false;
for (int i = 0; i < textToRead.Length; i++) {
if (textToRead [i] != _text [_offset + i])
return false;
}
_offset += textToRead.Length;
return true;
}
void Expect (string expectedStr)
{
if (!TryRead (expectedStr))
Expect (false, expectedStr);
}
void Expect (bool condition, string message = null)
{
if (!condition) {
if (message == null) {
message = "Unexpected token";
} else {
message = "Expected " + message;
}
message += " at offset " + _offset;
throw new System.Exception (message);
}
}
void SkipWhitespace ()
{
while (_offset < _text.Length) {
var c = _text [_offset];
if (c == ' ' || c == '\t' || c == '\n' || c == '\r')
_offset++;
else
break;
}
}
string _text;
int _offset;
object _rootObject;
}
public class Writer
{
public Writer()
{
_writer = new StringWriter();
}
public Writer(Stream stream)
{
_writer = new System.IO.StreamWriter(stream, Encoding.UTF8);
}
public void WriteObject(Action<Writer> inner)
{
WriteObjectStart();
inner(this);
WriteObjectEnd();
}
public void WriteObjectStart()
{
StartNewObject(container: true);
_stateStack.Push(new StateElement { type = State.Object });
_writer.Write("{");
}
public void WriteObjectEnd()
{
Assert(state == State.Object);
_writer.Write("}");
_stateStack.Pop();
}
public void WriteProperty(string name, Action<Writer> inner)
{
WriteProperty<string>(name, inner);
}
public void WriteProperty(int id, Action<Writer> inner)
{
WriteProperty<int>(id, inner);
}
public void WriteProperty(string name, string content)
{
WritePropertyStart(name);
Write(content);
WritePropertyEnd();
}
public void WriteProperty(string name, int content)
{
WritePropertyStart(name);
Write(content);
WritePropertyEnd();
}
public void WriteProperty(string name, bool content)
{
WritePropertyStart(name);
Write(content);
WritePropertyEnd();
}
public void WritePropertyStart(string name)
{
WritePropertyStart<string>(name);
}
public void WritePropertyStart(int id)
{
WritePropertyStart<int>(id);
}
public void WritePropertyEnd()
{
Assert(state == State.Property);
Assert(childCount == 1);
_stateStack.Pop();
}
public void WritePropertyNameStart()
{
Assert(state == State.Object);
if (childCount > 0)
_writer.Write(",");
_writer.Write("\"");
IncrementChildCount();
_stateStack.Push(new StateElement { type = State.Property });
_stateStack.Push(new StateElement { type = State.PropertyName });
}
public void WritePropertyNameEnd()
{
Assert(state == State.PropertyName);
_writer.Write("\":");
// Pop PropertyName, leaving Property state
_stateStack.Pop();
}
public void WritePropertyNameInner(string str)
{
Assert(state == State.PropertyName);
_writer.Write(str);
}
void WritePropertyStart<T>(T name)
{
Assert(state == State.Object);
if (childCount > 0)
_writer.Write(",");
_writer.Write("\"");
_writer.Write(name);
_writer.Write("\":");
IncrementChildCount();
_stateStack.Push(new StateElement { type = State.Property });
}
// allow name to be string or int
void WriteProperty<T>(T name, Action<Writer> inner)
{
WritePropertyStart(name);
inner(this);
WritePropertyEnd();
}
public void WriteArrayStart()
{
StartNewObject(container: true);
_stateStack.Push(new StateElement { type = State.Array });
_writer.Write("[");
}
public void WriteArrayEnd()
{
Assert(state == State.Array);
_writer.Write("]");
_stateStack.Pop();
}
public void Write(int i)
{
StartNewObject(container: false);
_writer.Write(i);
}
public void Write(float f)
{
StartNewObject(container: false);
// TODO: Find an heap-allocation-free way to do this please!
// _writer.Write(formatStr, obj (the float)) requires boxing
// Following implementation seems to work ok but requires creating temporary garbage string.
string floatStr = f.ToString(System.Globalization.CultureInfo.InvariantCulture);
if( floatStr == "Infinity" ) {
_writer.Write("3.4E+38"); // JSON doesn't support, do our best alternative
} else if (floatStr == "-Infinity") {
_writer.Write("-3.4E+38"); // JSON doesn't support, do our best alternative
} else if ( floatStr == "NaN" ) {
_writer.Write("0.0"); // JSON doesn't support, not much we can do
} else {
_writer.Write(floatStr);
if (!floatStr.Contains(".") && !floatStr.Contains("E"))
_writer.Write(".0"); // ensure it gets read back in as a floating point value
}
}
public void Write(string str, bool escape = true)
{
StartNewObject(container: false);
_writer.Write("\"");
if (escape)
WriteEscapedString(str);
else
_writer.Write(str);
_writer.Write("\"");
}
public void Write(bool b)
{
StartNewObject(container: false);
_writer.Write(b ? "true" : "false");
}
public void WriteNull()
{
StartNewObject(container: false);
_writer.Write("null");
}
public void WriteStringStart()
{
StartNewObject(container: false);
_stateStack.Push(new StateElement { type = State.String });
_writer.Write("\"");
}
public void WriteStringEnd()
{
Assert(state == State.String);
_writer.Write("\"");
_stateStack.Pop();
}
public void WriteStringInner(string str, bool escape = true)
{
Assert(state == State.String);
if (escape)
WriteEscapedString(str);
else
_writer.Write(str);
}
void WriteEscapedString(string str)
{
foreach (var c in str)
{
if (c < ' ')
{
// Don't write any control characters except \n and \t
switch (c)
{
case '\n':
_writer.Write("\\n");
break;
case '\t':
_writer.Write("\\t");
break;
}
}
else
{
switch (c)
{
case '\\':
case '"':
_writer.Write("\\");
_writer.Write(c);
break;
default:
_writer.Write(c);
break;
}
}
}
}
void StartNewObject(bool container)
{
if (container)
Assert(state == State.None || state == State.Property || state == State.Array);
else
Assert(state == State.Property || state == State.Array);
if (state == State.Array && childCount > 0)
_writer.Write(",");
if (state == State.Property)
Assert(childCount == 0);
if (state == State.Array || state == State.Property)
IncrementChildCount();
}
State state
{
get
{
if (_stateStack.Count > 0) return _stateStack.Peek().type;
else return State.None;
}
}
int childCount
{
get
{
if (_stateStack.Count > 0) return _stateStack.Peek().childCount;
else return 0;
}
}
void IncrementChildCount()
{
Assert(_stateStack.Count > 0);
var currEl = _stateStack.Pop();
currEl.childCount++;
_stateStack.Push(currEl);
}
// Shouldn't hit this assert outside of initial JSON development,
// so it's save to make it debug-only.
[System.Diagnostics.Conditional("DEBUG")]
void Assert(bool condition)
{
if (!condition)
throw new System.Exception("Assert failed while writing JSON");
}
public override string ToString()
{
return _writer.ToString();
}
enum State
{
None,
Object,
Array,
Property,
PropertyName,
String
};
struct StateElement
{
public State type;
public int childCount;
}
Stack<StateElement> _stateStack = new Stack<StateElement>();
TextWriter _writer;
}
}
}