/src/sys/js/fanx/ObjDecoder.js
JavaScript | 707 lines | 422 code | 86 blank | 199 comment | 139 complexity | 0dcb7f5f327ac07c96899ea82c0fcd1e MD5 | raw file
Possible License(s): CC-BY-SA-3.0
- //
- // Copyright (c) 2009, Brian Frank and Andy Frank
- // Licensed under the Academic Free License version 3.0
- //
- // History:
- // 9 May 09 Andy Frank Creation
- // 20 May 09 Andy Frank Refactor to new OO model
- //
- /**
- * ObjDecoder parses an object tree from an input stream.
- */
- function fanx_ObjDecoder(input, options)
- {
- this.tokenizer = new fanx_Tokenizer(input);
- this.options = options;
- this.curt = null;
- this.usings = [];
- this.numUsings = 0;
- this.consume();
- }
- //////////////////////////////////////////////////////////////////////////
- // Parse
- //////////////////////////////////////////////////////////////////////////
- /**
- * Read an object from the stream.
- */
- fanx_ObjDecoder.prototype.readObj = function()
- {
- this.readHeader();
- return this.$readObj(null, null, true);
- }
- /**
- * header := [using]*
- */
- fanx_ObjDecoder.prototype.readHeader = function()
- {
- while (this.curt == fanx_Token.USING)
- this.usings[this.numUsings++] = this.readUsing();
- }
- /**
- * using := usingPod | usingType | usingAs
- * usingPod := "using" podName
- * usingType := "using" podName::typeName
- * usingAs := "using" podName::typeName "as" name
- */
- fanx_ObjDecoder.prototype.readUsing = function()
- {
- var line = this.tokenizer.line;
- this.consume();
- var podName = this.consumeId("Expecting pod name");
- var pod = fan.sys.Pod.find(podName, false);
- if (pod == null) throw this.err("Unknown pod: " + podName);
- if (this.curt != fanx_Token.DOUBLE_COLON)
- {
- this.endOfStmt(line);
- return new fanx_UsingPod(pod);
- }
- this.consume();
- var typeName = this.consumeId("Expecting type name");
- var t = pod.type(typeName, false);
- if (t == null) throw this.err("Unknown type: " + podName + "::" + typeName);
- if (this.curt == fanx_Token.AS)
- {
- this.consume();
- typeName = this.consumeId("Expecting using as name");
- }
- this.endOfStmt(line);
- return new fanx_UsingType(t, typeName);
- }
- /**
- * obj := literal | simple | complex
- */
- fanx_ObjDecoder.prototype.$readObj = function(curField, peekType, root)
- {
- // literals are stand alone
- if (fanx_Token.isLiteral(this.curt))
- {
- var val = this.tokenizer.val;
- this.consume();
- return val;
- }
- // [ is always list/map collection
- if (this.curt == fanx_Token.LBRACKET)
- return this.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
- var line = this.tokenizer.line;
- var t = (peekType != null) ? peekType : this.readType();
- // type: type#
- // simple: type(
- // list/map: type[
- // complex: type || type{
- if (this.curt == fanx_Token.LPAREN)
- return this.readSimple(line, t);
- else if (this.curt == fanx_Token.POUND)
- return this.readTypeOrSlotLiteral(line, t);
- else if (this.curt == fanx_Token.LBRACKET)
- return this.readCollection(curField, t);
- else
- return this.readComplex(line, t, root);
- }
- /**
- * typeLiteral := type "#"
- * slotLiteral := type "#" id
- */
- fanx_ObjDecoder.prototype.readTypeOrSlotLiteral = function(line, t)
- {
- this.consume(fanx_Token.POUND, "Expected '#' for type literal");
- if (this.curt == fanx_Token.ID && !this.isEndOfStmt(line))
- {
- var slotName = this.consumeId("slot literal name");
- return t.slot(slotName);
- }
- else
- {
- return t;
- }
- }
- /**
- * simple := type "(" str ")"
- */
- fanx_ObjDecoder.prototype.readSimple = function(line, t)
- {
- // parse: type(str)
- this.consume(fanx_Token.LPAREN, "Expected ( in simple");
- var str = this.consumeStr("Expected string literal for simple");
- this.consume(fanx_Token.RPAREN, "Expected ) in simple");
- // TEMP
- try
- {
- var script = "fan." + t.pod().$name() + "." + t.$name() + ".fromStr('" + str + "')";
- var val = eval(script);
- return val;
- }
- catch (e)
- {
- throw fan.sys.ParseErr.make(e.toString() + " [Line " + this.line + "]", e);
- }
- // lookup the fromString method
- // TODO
- // 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 (Throwable e)
- // {
- // throw ParseErr.make(e.toString() + " [Line " + line + "]", e).val;
- // }
- }
- //////////////////////////////////////////////////////////////////////////
- // Complex
- //////////////////////////////////////////////////////////////////////////
- /**
- * complex := type [fields]
- * fields := "{" field (eos field)* "}"
- * field := name "=" obj
- */
- fanx_ObjDecoder.prototype.readComplex = function(line, t, root)
- {
- var toSet = fan.sys.Map.make(fan.sys.Field.$type, fan.sys.Obj.$type.toNullable());
- var toAdd = fan.sys.List.make(fan.sys.Obj.$type.toNullable());
- // read fields/collection into toSet/toAdd
- this.readComplexFields(t, toSet, toAdd);
- // get the make constructor
- var makeCtor = t.method("make", false);
- if (makeCtor == null || !makeCtor.isPublic())
- throw this.err("Missing public constructor " + t.qname() + ".make", line);
- // get argument lists
- var args = null;
- if (root && this.options != null)
- args = this.options.get("makeArgs");
- // construct object
- var obj = null;
- var setAfterCtor = true;
- try
- {
- // if first parameter is an function then pass toSet
- // as an it-block for setting the fields
- var p = makeCtor.params().first();
- if (args == null && p != null && p.type().fits(fan.sys.Func.$type))
- {
- args = fan.sys.List.make(fan.sys.Obj.$type).add(fan.sys.Field.makeSetFunc(toSet));
- setAfterCtor = false;
- }
- // invoke make to construct object
- obj = makeCtor.callList(args);
- }
- catch (e)
- {
- throw this.err("Cannot make " + t + ": " + e, line, e);
- }
- // set fields (if not passed to ctor as it-block)
- if (setAfterCtor && toSet.size() > 0)
- {
- var keys = toSet.keys();
- for (var i=0; i<keys.size(); i++)
- {
- var field = keys.get(i);
- var val = toSet.get(field);
- this.complexSet(obj, field, val, line);
- }
- }
- // add
- if (toAdd.size() > 0)
- {
- var addMethod = t.method("add", false);
- if (addMethod == null) throw this.err("Method not found: " + t.qname() + ".add", line);
- for (var i=0; i<toAdd.size(); ++i)
- this.complexAdd(t, obj, addMethod, toAdd.get(i), line);
- }
- return obj;
- }
- fanx_ObjDecoder.prototype.readComplexFields = function(t, toSet, toAdd)
- {
- if (this.curt != fanx_Token.LBRACE) return;
- this.consume();
- // fields and/or collection items
- while (this.curt != fanx_Token.RBRACE)
- {
- // try to read "id =" to see if we have a field
- var line = this.tokenizer.line;
- var readField = false;
- if (this.curt == fanx_Token.ID)
- {
- var name = this.consumeId("Expected field name");
- if (this.curt == fanx_Token.EQ)
- {
- // we have "id =" so read field
- this.consume();
- this.readComplexSet(t, line, name, toSet);
- readField = true;
- }
- else
- {
- // pushback to reset on start of collection item
- this.tokenizer.undo(this.tokenizer.type, this.tokenizer.val, this.tokenizer.line);
- this.curt = this.tokenizer.reset(fanx_Token.ID, name, line);
- }
- }
- // if we didn't read a field, we assume a collection item
- if (!readField) this.readComplexAdd(t, line, toAdd);
- if (this.curt == fanx_Token.COMMA) this.consume();
- else this.endOfStmt(line);
- }
- this.consume(fanx_Token.RBRACE, "Expected '}'");
- }
- fanx_ObjDecoder.prototype.readComplexSet = function(t, line, name, toSet)
- {
- // resolve field
- var field = t.field(name, false);
- if (field == null) throw this.err("Field not found: " + t.qname() + "." + name, line);
- // parse value
- var val = this.$readObj(field, null, false);
- try
- {
- // if const field, then make val immutable
- if (field.isConst()) val = fan.sys.ObjUtil.toImmutable(val);
- }
- catch (ex)
- {
- throw this.err("Cannot make object const for " + field.qname() + ": " + ex, line, ex);
- }
- // add to map
- toSet.set(field, val);
- }
- fanx_ObjDecoder.prototype.complexSet = function(obj, field, val, line)
- {
- try
- {
- if (field.isConst())
- field.set(obj, fan.sys.ObjUtil.toImmutable(val), false);
- else
- field.set(obj, val);
- }
- catch (ex)
- {
- throw this.err("Cannot set field " + field.qname() + ": " + ex, line, ex);
- }
- }
- fanx_ObjDecoder.prototype.readComplexAdd = function(t, line, toAdd)
- {
- var val = this.$readObj(null, null, false);
- // add to list
- toAdd.add(val);
- }
- fanx_ObjDecoder.prototype.complexAdd = function(t, obj, addMethod, val, line)
- {
- try
- {
- addMethod.invoke(obj, fan.sys.List.make(fan.sys.Obj.$type, [val]));
- }
- catch (ex)
- {
- throw this.err("Cannot call " + t.qname() + ".add: " + ex, line, ex);
- }
- }
- //////////////////////////////////////////////////////////////////////////
- // Collection
- //////////////////////////////////////////////////////////////////////////
- /**
- * collection := list | map
- */
- fanx_ObjDecoder.prototype.readCollection = function(curField, t)
- {
- // opening [
- this.consume(fanx_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
- var peekType = null;
- if (this.curt == fanx_Token.ID && t == null)
- {
- // peek at the type
- peekType = this.readType();
- // if we have [mapType] then this is non-inferred type signature
- if (this.curt == fanx_Token.RBRACKET && peekType instanceof fan.sys.MapType)
- {
- t = peekType; peekType = null;
- this.consume();
- while (this.curt == fanx_Token.LRBRACKET) { this.consume(); t = t.toListOf(); }
- if (this.curt == fanx_Token.QUESTION) { this.consume(); t = t.toNullable(); }
- if (this.curt == fanx_Token.POUND) { this.consume(); return t; }
- this.consume(fanx_Token.LBRACKET, "Expecting '['");
- }
- // if the type was a FFI JavaType, this isn't a collection
- // if (peekType != null && peekType.isJava())
- // return this.$readObj(curField, peekType, false);
- }
- // handle special case of [,]
- if (this.curt == fanx_Token.COMMA && peekType == null)
- {
- this.consume();
- this.consume(fanx_Token.RBRACKET, "Expecting ']'");
- return fan.sys.List.make(this.toListOfType(t, curField, false), []);
- }
- // handle special case of [:]
- if (this.curt == fanx_Token.COLON && peekType == null)
- {
- this.consume();
- this.consume(fanx_Token.RBRACKET, "Expecting ']'");
- return fan.sys.Map.make(this.toMapType(t, curField, false));
- }
- // read first list item or first map key
- var first = this.$readObj(null, peekType, false);
- // now we can distinguish b/w list and map
- if (this.curt == fanx_Token.COLON)
- return this.readMap(this.toMapType(t, curField, true), first);
- else
- return this.readList(this.toListOfType(t, curField, true), first);
- }
- /**
- * list := "[" obj ("," obj)* "]"
- */
- fanx_ObjDecoder.prototype.readList = function(of, first)
- {
- // setup accumulator
- var acc = [];
- acc.push(first)
- // parse list items
- while (this.curt != fanx_Token.RBRACKET)
- {
- this.consume(fanx_Token.COMMA, "Expected ','");
- if (this.curt == fanx_Token.RBRACKET) break;
- acc.push(this.$readObj(null, null, false));
- }
- this.consume(fanx_Token.RBRACKET, "Expected ']'");
- // infer type if needed
- if (of == null) of = fan.sys.Type.common(acc);
- return fan.sys.List.make(of, acc);
- }
- /**
- * map := "[" mapPair ("," mapPair)* "]"
- * mapPair := obj ":" + obj
- */
- fanx_ObjDecoder.prototype.readMap = function(mapType, firstKey)
- {
- // create map
- var map = mapType == null
- ? fan.sys.Map.make(fan.sys.Obj.$type, fan.sys.Obj.$type.toNullable())
- : fan.sys.Map.make(mapType);
- // finish first pair
- this.consume(fanx_Token.COLON, "Expected ':'");
- map.set(firstKey, this.$readObj(null, null, false));
- // parse map pairs
- while (this.curt != fanx_Token.RBRACKET)
- {
- this.consume(fanx_Token.COMMA, "Expected ','");
- if (this.curt == fanx_Token.RBRACKET) break;
- var key = this.$readObj(null, null, false);
- this.consume(fanx_Token.COLON, "Expected ':'");
- var val = this.$readObj(null, null, false);
- map.set(key, val);
- }
- this.consume(fanx_Token.RBRACKET, "Expected ']'");
- // infer type if necessary
- if (mapType == null)
- {
- var size = map.size();
- var k = fan.sys.Type.common(map.keys().m_values);
- var v = fan.sys.Type.common(map.vals().m_values);
- map.m_type = new fan.sys.MapType(k, v);
- }
- return map;
- }
- /**
- * 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
- */
- fanx_ObjDecoder.prototype.toListOfType = function(t, curField, infer)
- {
- if (t != null) return t;
- if (curField != null)
- {
- var ft = curField.type().toNonNullable();
- if (ft instanceof fan.sys.ListType) return ft.v;
- }
- if (infer) return null;
- return fan.sys.Obj.$type.toNullable();
- }
- /**
- * 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
- */
- fanx_ObjDecoder.prototype.toMapType = function(t, curField, infer)
- {
- if (t instanceof fan.sys.MapType)
- return t;
- if (curField != null)
- {
- var ft = curField.type().toNonNullable();
- if (ft instanceof fan.sys.MapType) return ft;
- }
- if (infer) return null;
- if (fanx_ObjDecoder.defaultMapType == null)
- fanx_ObjDecoder.defaultMapType =
- new fan.sys.MapType(fan.sys.Obj.$type, fan.sys.Obj.$type.toNullable());
- return fanx_ObjDecoder.defaultMapType;
- }
- //////////////////////////////////////////////////////////////////////////
- // Type
- //////////////////////////////////////////////////////////////////////////
- /**
- * 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().
- */
- fanx_ObjDecoder.prototype.readType = function(lbracket)
- {
- if (lbracket === undefined) lbracket = false;
- var t = this.readSimpleType();
- if (this.curt == fanx_Token.QUESTION)
- {
- this.consume();
- t = t.toNullable();
- }
- if (this.curt == fanx_Token.COLON)
- {
- this.consume();
- t = new fan.sys.MapType(t, this.readType());
- }
- while (this.curt == fanx_Token.LRBRACKET)
- {
- this.consume();
- t = t.toListOf();
- }
- if (this.curt == fanx_Token.QUESTION)
- {
- this.consume();
- t = t.toNullable();
- }
- return t;
- }
- /**
- * qname := [podName "::"] typeName
- */
- fanx_ObjDecoder.prototype.readSimpleType = function()
- {
- // parse identifier
- var line = this.tokenizer.line;
- var n = this.consumeId("Expected type signature");
- // check for using imported name
- if (this.curt != fanx_Token.DOUBLE_COLON)
- {
- for (var i=0; i<this.numUsings; ++i)
- {
- var t = this.usings[i].resolve(n);
- if (t != null) return t;
- }
- throw this.err("Unresolved type name: " + n);
- }
- // must be fully qualified
- this.consume(fanx_Token.DOUBLE_COLON, "Expected ::");
- var typeName = this.consumeId("Expected type name");
- // resolve pod
- var pod = fan.sys.Pod.find(n, false);
- if (pod == null) throw this.err("Pod not found: " + n, line);
- // resolve type
- var type = pod.type(typeName, false);
- if (type == null) throw this.err("Type not found: " + n + "::" + typeName, line);
- return type;
- }
- //////////////////////////////////////////////////////////////////////////
- // Error Handling
- //////////////////////////////////////////////////////////////////////////
- /**
- * Create exception based on tokenizers current line.
- */
- fanx_ObjDecoder.prototype.err = function(msg)
- {
- return fanx_ObjDecoder.err(msg, this.tokenizer.line);
- }
- //////////////////////////////////////////////////////////////////////////
- // Tokens
- //////////////////////////////////////////////////////////////////////////
- /**
- * Consume the current token as a identifier.
- */
- fanx_ObjDecoder.prototype.consumeId = function(expected)
- {
- this.verify(fanx_Token.ID, expected);
- var id = this.tokenizer.val;
- this.consume();
- return id;
- }
- /**
- * Consume the current token as a String literal.
- */
- fanx_ObjDecoder.prototype.consumeStr = function(expected)
- {
- this.verify(fanx_Token.STR_LITERAL, expected);
- var id = this.tokenizer.val;
- this.consume();
- return id;
- }
- /**
- * Check that the current token matches the
- * specified type, and then consume it.
- */
- fanx_ObjDecoder.prototype.consume = function(type, expected)
- {
- if (type != undefined)
- this.verify(type, expected);
- this.curt = this.tokenizer.next();
- }
- /**
- * Check that the current token matches the specified
- * type, but do not consume it.
- */
- fanx_ObjDecoder.prototype.verify = function(type, expected)
- {
- if (this.curt != type)
- throw this.err(expected + ", not '" + fanx_Token.toString(this.curt) + "'");
- }
- /**
- * Is current token part of the next statement?
- */
- fanx_ObjDecoder.prototype.isEndOfStmt = function(lastLine)
- {
- if (this.curt == fanx_Token.EOF) return true;
- if (this.curt == fanx_Token.SEMICOLON) return true;
- return lastLine < this.tokenizer.line;
- }
- /**
- * Statements can be terminated with a semicolon, end of line or } end of block.
- */
- fanx_ObjDecoder.prototype.endOfStmt = function(lastLine)
- {
- if (this.curt == fanx_Token.SEMICOLON) { this.consume(); return; }
- if (lastLine < this.tokenizer.line) return;
- if (this.curt == fanx_Token.RBRACE) return;
- throw this.err("Expected end of statement: semicolon, newline, or end of block; not '" + fanx_Token.toString(this.curt) + "'");
- }
- //////////////////////////////////////////////////////////////////////////
- // Static
- //////////////////////////////////////////////////////////////////////////
- fanx_ObjDecoder.decode = function(s)
- {
- return new fanx_ObjDecoder(fan.sys.InStream.makeForStr(s), null).readObj();
- }
- fanx_ObjDecoder.err = function(msg, line)
- {
- return fan.sys.IOErr.make(msg + " [Line " + line + "]");
- }
- fanx_ObjDecoder.defaultMapType = null;
- //////////////////////////////////////////////////////////////////////////
- // Using
- //////////////////////////////////////////////////////////////////////////
- function fanx_UsingPod(p) { this.pod = p; }
- fanx_UsingPod.prototype.resolve = function(n)
- {
- return this.pod.type(n, false);
- }
- function fanx_UsingType(t,n) { this.type = t; this.name = n; }
- fanx_UsingType.prototype.resolve = function(n)
- {
- return this.name.equals(n) ? this.type : null;
- }