/src/sys/dotnet/fanx/serial/ObjDecoder.cs
C# | 756 lines | 474 code | 91 blank | 191 comment | 144 complexity | e03fb2fb8bb3f570561adbbfa95e26ac MD5 | raw file
Possible License(s): CC-BY-SA-3.0
- //
- // Copyright (c) 2007, Brian Frank and Andy Frank
- // Licensed under the Academic Free License version 3.0
- //
- // History:
- // 10 Sep 07 Andy Frank Creation
- //
- using System.Collections;
- using Fan.Sys;
- using Fanx.Util;
- namespace Fanx.Serial
- {
- /// <summary>
- /// ObjDecoder parses an object tree from an input stream.
- /// </summary>
- public class ObjDecoder
- {
- //////////////////////////////////////////////////////////////////////////
- // Static
- //////////////////////////////////////////////////////////////////////////
- public static object decode(string s)
- {
- return new ObjDecoder(FanStr.@in(s), null).readObj();
- }
- //////////////////////////////////////////////////////////////////////////
- // Constructor
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// Construct for input stream.
- /// </summary>
- public ObjDecoder(InStream @in, Map options)
- {
- tokenizer = new Tokenizer(@in);
- this.options = options;
- consume();
- }
- //////////////////////////////////////////////////////////////////////////
- // Parse
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// Read an object from the stream.
- /// </summary>
- public object readObj()
- {
- readHeader();
- return readObj(null, null, true);
- }
- /// <summary>
- /// header := [using]*
- /// </summary>
- private void readHeader()
- {
- while (curt == Token.USING)
- {
- Using u = readUsing();
- if (usings == null) usings = new Using[8];
- if (numUsings >= usings.Length)
- {
- Using[] temp = new Using[usings.Length*2];
- System.Array.Copy(usings, 0, temp, 0, numUsings);
- usings = temp;
- }
- usings[numUsings++] = u;
- }
- }
- /// <summary>
- /// using := usingPod | usingType | usingAs
- /// usingPod := "using" podName
- /// usingType := "using" podName::typeName
- /// usingAs := "using" podName::typeName "as" name
- /// </summary>
- private Using readUsing()
- {
- consume();
- int line = tokenizer.m_line;
- string podName = consumeId("Expecting pod name");
- Pod pod = Pod.find(podName, false);
- if (pod == null) throw err("Unknown pod: " + podName);
- if (curt != Token.DOUBLE_COLON)
- {
- endOfStmt(line);
- return new UsingPod(pod);
- }
- consume();
- string typeName = consumeId("Expecting type name");
- Type t = pod.type(typeName, false);
- if (t == null) throw err("Unknown type: " + podName + "::" + typeName);
- if (curt == Token.AS)
- {
- consume();
- typeName = consumeId("Expecting using as name");
- }
- endOfStmt(line);
- return new UsingType(t, typeName);
- }
- /// <summary>
- /// obj := literal | simple | complex
- /// </summary>
- private object readObj(Field curField, Type peekType, bool root)
- {
- // literals are stand alone
- if (Token.isLiteral(curt))
- {
- object val = tokenizer.m_val;
- consume();
- return val;
- }
- // [ is always list/map collection
- if (curt == Token.LBRACKET)
- return readCollection(curField, peekType);
- // at this point all remaining options must start
- // with a type signature - if peekType is non-null
- // then we've already read the type signature
- int line = tokenizer.m_line;
- Type t = (peekType != null) ? peekType : readType();
- // type: type#"
- // simple: type(
- // list/map: type[
- // complex: type || type{
- if (curt == Token.LPAREN)
- return readSimple(line, t);
- else if (curt == Token.POUND)
- return readTypeOrSlotLiteral(line, t);
- else if (curt == Token.LBRACKET)
- return readCollection(curField, t);
- else
- return readComplex(line, t, root);
- }
- /// <summary>
- /// typeLiteral := type "#"
- /// slotLiteral := type "#" id
- /// </summary>
- private object readTypeOrSlotLiteral(int line, Type t)
- {
- consume(Token.POUND, "Expected '#' for type literal");
- if (curt == Token.ID && !isEndOfStmt(line))
- {
- string slotName = consumeId("slot literal name");
- return t.slot(slotName);
- }
- else
- {
- return t;
- }
- }
- /// <summary>
- /// simple := type "(" str ")"
- /// </summary>
- private object readSimple(int line, Type t)
- {
- // parse: type(str)
- consume(Token.LPAREN, "Expected ( in simple");
- string str = consumeStr("Expected string literal for simple");
- consume(Token.RPAREN, "Expected ) in simple");
- // lookup the fromStr method
- t.finish();
- Method m = t.method("fromStr", false);
- if (m == null)
- throw err("Missing method: " + t.qname() + ".fromStr", line);
- // invoke parse method to translate into instance
- try
- {
- return m.invoke(null, new object[] { str });
- }
- catch (ParseErr.Val e)
- {
- throw ParseErr.make(e.err().msg() + " [Line " + line + "]").val;
- }
- catch (System.Exception e)
- {
- throw ParseErr.make(e.ToString() + " [Line " + line + "]", e).val;
- }
- }
- //////////////////////////////////////////////////////////////////////////
- // Complex
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// complex := type [fields]
- /// fields := "{" field (eos field)* "}"
- /// field := name "=" obj
- /// </summary>
- private object readComplex(int line, Type t, bool root)
- {
- Map toSet = new Map(Sys.FieldType, Sys.ObjType.toNullable());
- List toAdd = new List(Sys.ObjType.toNullable());
- // read fields/collection into toSet/toAdd
- readComplexFields(t, toSet, toAdd);
- // get the make constructor
- Method makeCtor = t.method("make", false);
- if (makeCtor == null || !makeCtor.isPublic())
- throw err("Missing public constructor " + t.qname() + ".make", line);
- // get argument lists
- List args = null;
- if (root && options != null)
- args = (List)options.get("makeArgs");
- // construct object
- object obj = null;
- bool setAfterCtor = true;
- try
- {
- // if first parameter is an function then pass toSet
- // as an it-block for setting the fields
- Param p = (Param)makeCtor.@params().first();
- if (args == null && p != null && p.type().fits(Sys.FuncType))
- {
- args = new List(Sys.ObjType).add(Field.makeSetFunc(toSet));
- setAfterCtor = false;
- }
- // invoke make to construct object
- obj = makeCtor.callList(args);
- }
- catch (System.Exception e)
- {
- throw err("Cannot make " + t + ": " + e, line, e);
- }
- // set fields (if not passed to ctor as it-block)
- if (setAfterCtor && toSet.size() > 0)
- {
- IDictionaryEnumerator en = toSet.pairsIterator();
- while (en.MoveNext())
- {
- complexSet(obj, (Field)en.Key, en.Value, line);
- }
- }
- // add
- if (toAdd.size() > 0)
- {
- Method addMethod = t.method("add", false);
- if (addMethod == null) throw err("Method not found: " + t.qname() + ".add", line);
- for (int i=0; i<toAdd.sz(); ++i)
- complexAdd(t, obj, addMethod, toAdd.get(i), line);
- }
- return obj;
- }
- private void readComplexFields(Type t, Map toSet, List toAdd)
- {
- if (curt != Token.LBRACE) return;
- consume();
- // fields and/or collection items
- while (curt != Token.RBRACE)
- {
- // try to read "id =" to see if we have a field
- int line = tokenizer.m_line;
- bool readField = false;
- if (curt == Token.ID)
- {
- string name = consumeId("Expected field name");
- if (curt == Token.EQ)
- {
- // we have "id =" so read field
- consume();
- readComplexSet(t, line, name, toSet);
- readField = true;
- }
- else
- {
- // pushback to reset on start of collection item
- tokenizer.undo(tokenizer.m_type, tokenizer.m_val, tokenizer.m_line);
- curt = tokenizer.reset(Token.ID, name, line);
- }
- }
- // if we didn't read a field, we assume a collection item
- if (!readField) readComplexAdd(t, line, toAdd);
- if (curt == Token.COMMA) consume();
- else endOfStmt(line);
- }
- consume(Token.RBRACE, "Expected '}'");
- }
- void readComplexSet(Type t, int line, string name, Map toSet)
- {
- // resolve field
- Field field = t.field(name, false);
- if (field == null) throw err("Field not found: " + t.qname() + "." + name, line);
- // parse value
- object val = readObj(field, null, false);
- try
- {
- // if const field, then make val immutable
- if (field.isConst()) val = OpUtil.toImmutable(val);
- }
- catch (System.Exception ex)
- {
- throw err("Cannot make object const for " + field.qname() + ": " + ex, line, ex);
- }
- // add to map
- toSet.set(field, val);
- }
- void complexSet(object obj, Field field, object val, int line)
- {
- try
- {
- if (field.isConst())
- field.set(obj, OpUtil.toImmutable(val), false);
- else
- field.set(obj, val);
- }
- catch (System.Exception ex)
- {
- throw err("Cannot set field " + field.qname() + ": " + ex, line, ex);
- }
- }
- void readComplexAdd(Type t, int line, List toAdd)
- {
- object val = readObj(null, null, false);
- // add to list
- toAdd.add(val);
- }
- void complexAdd(Type t, object obj, Method addMethod, object val, int line)
- {
- try
- {
- addMethod.invoke(obj, new object[] { val });
- }
- catch (System.Exception ex)
- {
- throw err("Cannot call " + t.qname() + ".add: " + ex, line, ex);
- }
- }
- //////////////////////////////////////////////////////////////////////////
- // Collection
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// collection := list | map
- /// </summary>
- private object readCollection(Field curField, Type t)
- {
- // opening [
- consume(Token.LBRACKET, "Expecting '['");
- // if this could be a map type signature:
- // [qname:qname]
- // [qname:qname][]
- // [qname:qname][][] ...
- // or it could just be the type signature of
- // of a embedded simple, complex, or list
- Type peekType = null;
- if (curt == Token.ID && t == null)
- {
- // peek at the type
- peekType = readType();
- // if we have [mapType] then this is non-inferred type signature
- if (curt == Token.RBRACKET && peekType is MapType)
- {
- t = peekType; peekType = null;
- consume();
- while (curt == Token.LRBRACKET) { consume(); t = t.toListOf(); }
- if (curt == Token.QUESTION) { consume(); t = t.toNullable(); }
- if (curt == Token.POUND) { consume(); return t; }
- consume(Token.LBRACKET, "Expecting '['");
- }
- }
- // handle special case of [,]
- if (curt == Token.COMMA && peekType == null)
- {
- consume();
- consume(Token.RBRACKET, "Expecting ']'");
- return new List(toListOfType(t, curField, false));
- }
- // handle special case of [:]
- if (curt == Token.COLON && peekType == null)
- {
- consume();
- consume(Token.RBRACKET, "Expecting ']'");
- return new Map(toMapType(t, curField, false));
- }
- // read first list item or first map key
- object first = readObj(null, peekType, false);
- // now we can distinguish b/w list and map
- if (curt == Token.COLON)
- return readMap(toMapType(t, curField, true), first);
- else
- return readList(toListOfType(t, curField, true), first);
- }
- /// <summary>
- /// list := "[" obj ("," obj)* "]"
- /// </summary>
- private object readList(Type of, object first)
- {
- // setup accumulator
- object[] acc = new object[8];
- int n = 0;
- acc[n++] = first;
- // parse list items
- while (curt != Token.RBRACKET)
- {
- consume(Token.COMMA, "Expected ','");
- if (curt == Token.RBRACKET) break;
- if (n >= acc.Length)
- {
- object[] temp = new object[n*2];
- System.Array.Copy(acc, 0, temp, 0, n);
- acc = temp;
- }
- acc[n++] = readObj(null, null, false);
- }
- consume(Token.RBRACKET, "Expected ']'");
- // infer type if needed
- if (of == null) of = Type.common(acc, n);
- return new List(of, acc, n);
- }
- /// <summary>
- /// map := "[" mapPair ("," mapPair)* "]"
- /// mapPair := obj ":" + obj
- /// </summary>
- private object readMap(MapType mapType, object firstKey)
- {
- // setup accumulator
- Hashtable map = new Hashtable();
- // finish first pair
- consume(Token.COLON, "Expected ':'");
- map[firstKey] = readObj(null, null, false);
- // parse map pairs
- while (curt != Token.RBRACKET)
- {
- consume(Token.COMMA, "Expected ','");
- if (curt == Token.RBRACKET) break;
- object key = readObj(null, null, false);
- consume(Token.COLON, "Expected ':'");
- object val = readObj(null, null, false);
- map[key] = val;
- }
- consume(Token.RBRACKET, "Expected ']'");
- // infer type if necessary
- if (mapType == null)
- {
- int size = map.Count;
- object[] keys = new object[map.Count];
- object[] vals = new object[map.Count];
- IDictionaryEnumerator en = map.GetEnumerator();
- int i = 0;
- while (en.MoveNext())
- {
- keys[i] = en.Key;
- vals[i] = en.Value;
- i++;
- }
- Type k = Type.common(keys, size);
- Type v = Type.common(vals, size);
- mapType = new MapType(k, v);
- }
- return new Map((MapType)mapType, map);
- }
- /// <summary>
- /// Figure out the type of the list:
- /// 1) if t was explicit then use it
- /// 2) if we have field typed as a list, then use its definition
- /// 3) if inferred is false, then drop back to list of Obj
- /// 4) If inferred is true then return null and we'll infer the common type
- /// </summary>
- private Type toListOfType(Type t, Field curField, bool infer)
- {
- if (t != null) return t;
- if (curField != null)
- {
- Type ft = curField.type().toNonNullable();
- if (ft is ListType) return ((ListType)ft).m_v;
- }
- if (infer) return null;
- return Sys.ObjType.toNullable();
- }
- /// <summary>
- /// Figure out the map type:
- /// 1) if t was explicit then use it (check that it was a map type)
- /// 2) if we have field typed as a map , then use its definition
- /// 3) if inferred is false, then drop back to Obj:Obj
- /// 4) If inferred is true then return null and we'll infer the common key/val types
- /// </summary>
- private MapType toMapType(Type t, Field curField, bool infer)
- {
- if (t != null)
- {
- try { return (MapType)t; }
- catch (System.InvalidCastException) { throw err("Invalid map type: " + t); }
- }
- if (curField != null)
- {
- Type ft = curField.type().toNonNullable();
- if (ft is MapType) return (MapType)ft;
- }
- if (infer) return null;
- return defaultMapType;
- }
- private static readonly MapType defaultMapType = new MapType(Sys.ObjType, Sys.ObjType.toNullable());
- //////////////////////////////////////////////////////////////////////////
- // Type
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// type := listSig | mapSig1 | mapSig2 | qname
- /// listSig := type "[]"
- /// mapSig1 := type ":" type
- /// mapSig2 := "[" type ":" type "]"
- ///
- /// Note: the mapSig2 with brackets is handled by the
- /// method succinctly named readMapTypeOrCollection().
- /// </summary>
- private Type readType() { return readType(false); }
- private Type readType(bool lbracket)
- {
- Type t = readSimpleType();
- if (curt == Token.QUESTION)
- {
- consume();
- t = t.toNullable();
- }
- if (curt == Token.COLON)
- {
- consume();
- t = new MapType(t, readType());
- }
- while (curt == Token.LRBRACKET)
- {
- consume();
- t = t.toListOf();
- }
- if (curt == Token.QUESTION)
- {
- consume();
- t = t.toNullable();
- }
- return t;
- }
- /// <summary>
- /// qname := [podName "::"] typeName
- /// </summary>
- private Type readSimpleType()
- {
- // parse identifier
- int line = tokenizer.m_line;
- string n = consumeId("Expected type signature");
- // check for using imported name
- if (curt != Token.DOUBLE_COLON)
- {
- for (int i=0; i<numUsings; ++i)
- {
- Type t = usings[i].resolve(n);
- if (t != null) return t;
- }
- throw err("Unresolved type name: " + n);
- }
- // must be fully qualified
- consume(Token.DOUBLE_COLON, "Expected :: in type qname");
- string typeName = consumeId("Expected type name");
- // resolve pod
- Pod pod = Pod.find(n, false);
- if (pod == null) throw err("Pod not found: " + n, line);
- // resolve type
- Type type = pod.type(typeName, false);
- if (type == null) throw err("Type not found: " + n+ "::" + typeName, line);
- return type;
- }
- //////////////////////////////////////////////////////////////////////////
- // Error Handling
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// Create error reporting exception.
- /// </summary>
- internal static System.Exception err(string msg, int line) { return err(msg, line, null); }
- internal static System.Exception err(string msg, int line, System.Exception ex)
- {
- return IOErr.make(msg + " [Line " + line + "]", ex).val;
- }
- /// <summary>
- /// Create exception based on tokenizers current line.
- /// </summary>
- private System.Exception err(string msg)
- {
- return err(msg, tokenizer.m_line);
- }
- //////////////////////////////////////////////////////////////////////////
- // Tokens
- //////////////////////////////////////////////////////////////////////////
- /// <summary>
- /// Consume the current token as a identifier.
- /// </summary>
- private string consumeId(string expected)
- {
- verify(Token.ID, expected);
- string id = (string)tokenizer.m_val;
- consume();
- return id;
- }
- /// <summary>
- /// Consume the current token as a string literal.
- /// </summary>
- private string consumeStr(string expected)
- {
- verify(Token.STR_LITERAL, expected);
- string id = (string)tokenizer.m_val;
- consume();
- return id;
- }
- /// <summary>
- /// Check that the current token matches the
- /// specified type, and then consume it.
- /// </summary>
- private void consume(int type, string expected)
- {
- verify(type, expected);
- consume();
- }
- /// <summary>
- /// Check that the current token matches the specified
- /// type, but do not consume it.
- /// </summary>
- private void verify(int type, string expected)
- {
- if (curt != type)
- throw err(expected + ", not '" + Token.toString(curt) + "'");
- }
- /// <summary>
- /// Consume the current token.
- /// </summary>
- private void consume()
- {
- curt = tokenizer.next();
- }
- /// <summary>
- /// Is current token part of the next statement?
- /// </summary>
- private bool isEndOfStmt(int lastLine)
- {
- if (curt == Token.EOF) return true;
- if (curt == Token.SEMICOLON) return true;
- return lastLine < tokenizer.m_line;
- }
- /// <summary>
- /// Statements can be terminated with a semicolon, end of line or } end of block.
- /// </summary>
- private void endOfStmt(int lastLine)
- {
- if (curt == Token.EOF) return;
- if (curt == Token.SEMICOLON) { consume(); return; }
- if (lastLine < tokenizer.m_line) return;
- if (curt == Token.RBRACE) return;
- throw err("Expected end of statement: semicolon, newline, or end of block; not '" + Token.toString(curt) + "'");
- }
- //////////////////////////////////////////////////////////////////////////
- // Using
- //////////////////////////////////////////////////////////////////////////
- internal abstract class Using
- {
- internal abstract Type resolve(string name);
- }
- internal class UsingPod : Using
- {
- internal UsingPod(Pod p) { pod = p; }
- internal override Type resolve(string n) { return pod.type(n, false); }
- Pod pod;
- }
- internal class UsingType : Using
- {
- internal UsingType(Type t, string n) { type = t; name = n; }
- internal override Type resolve(string n) { return name == n ? type : null; }
- string name;
- Type type;
- }
- //////////////////////////////////////////////////////////////////////////
- // Fields
- //////////////////////////////////////////////////////////////////////////
- internal Tokenizer tokenizer; // tokenizer
- internal int curt; // current token type
- internal Map options; // decode option name/value pairs
- internal Using[] usings; // using imports
- internal int numUsings = 0; // number of using imports
- }
- }