(function (mod){ if (typeof exports == "object" && typeof module == "object") return exports.init = mod; if (typeof define == "function" && define.amd) return define({ init: mod} ); tern.def = { init: mod} ; } )(function (exports, infer){ "use strict"; function hop(obj, prop){ return Object.prototype.hasOwnProperty.call(obj, prop); } var TypeParser = exports.TypeParser = function (spec, start, base, forceNew){ this.pos = start || 0; this.spec = spec; this.base = base; this.forceNew = forceNew; } ; TypeParser.prototype = { eat: function (str){ if (_AN_Read_length("length", str) == 1? this.spec.charAt(this.pos) == str: this.spec.indexOf(str, this.pos) == this.pos) { this.pos += _AN_Read_length("length", str); return true ; } } , word: function (re){ var word = "", ch, re = re || /[\w$]/; while ((ch = this.spec.charAt(this.pos)) && re.test(ch)){ word += ch; ++this.pos; } return word; } , error: function (){ throw new Error("Unrecognized type spec: " + this.spec + " (at " + this.pos + ")") } , parseFnType: function (name, top){ var args = [] , names = [] ; if (!this.eat(")")) for (var i = 0; ; ++i){ var colon = this.spec.indexOf(": ", this.pos), argname; if (colon != -1) { argname = this.spec.slice(this.pos, colon); if (/^[$\w?]+$/.test(argname)) this.pos = colon + 2; else argname = null ; } names.push(argname); args.push(this.parseType()); if (!this.eat(", ")) { this.eat(")") || this.error(); break ; } } var retType, computeRet, computeRetStart, fn; if (this.eat(" -> ")) { if (top && this.spec.indexOf("!", this.pos) > -1) { retType = infer.ANull; computeRetStart = this.pos; computeRet = this.parseRetType(); } else retType = this.parseType(); } else retType = infer.ANull; if (top && (fn = this.base)) infer.Fn.call(this.base, name, infer.ANull, args, names, retType); else fn = new infer.Fn(name, infer.ANull, args, names, retType); if (computeRet) fn.computeRet = computeRet; if (computeRetStart != null ) fn.computeRetSource = this.spec.slice(computeRetStart, this.pos); return fn; } , parseType: function (name, top){ if (this.eat("fn(")) { return this.parseFnType(name, top); } else if (this.eat("[")) { var inner = this.parseType(); this.eat("]") || this.error(); if (top && this.base) { infer.Arr.call(this.base, inner); return this.base; } return new infer.Arr(inner); } else if (this.eat("+")) { var path = this.word(/[\w$<>\.!]/); var base = parsePath(path + ".prototype"); if (!(base instanceof infer.Obj)) base = parsePath(path); if (!(base instanceof infer.Obj)) return base; if (top && this.forceNew) return new infer.Obj(base); return infer.getInstance(base); } else if (this.eat("?")) { return infer.ANull; } else { var spec = this.word(/[\w$<>\.!]/), cx = infer.cx(); switch (spec){ case "number": return cx.num; case "string": return cx.str; case "bool": return cx.bool; case "": return cx.topScope; } if (cx.localDefs && spec in cx.localDefs) return cx.localDefs[spec]; return parsePath(spec); } } , parseBaseRetType: function (){ if (this.eat("[")) { var inner = this.parseRetType(); this.eat("]") || this.error(); return function (self, args){ return new infer.Arr(inner(self, args)); } ; } else if (this.eat("+")) { var base = this.parseRetType(); return function (self, args){ return infer.getInstance(base(self, args)); } ; } else if (this.eat("!")) { var arg = this.word(/\d/); if (arg) { arg = Number(arg); return function (_self, args){ return args[arg] || infer.ANull; } ; } else if (this.eat("this")) { return function (self){ return self; } ; } else if (this.eat("custom:")) { var fname = this.word(/[\w$]/); return customFunctions[fname] || function (){ return infer.ANull; } ; } else this.error(); } var t = this.parseType(); return function (){ return t; } ; } , extendRetType: function (base){ var propName = this.word(/[\w<>$!]/) || this.error(); if (propName == "!ret") return function (self, args){ var lhs = base(self, args); if (lhs.retval) return lhs.retval; var rv = new infer.AVal(); lhs.propagate(new infer.IsCallee(infer.ANull, [] , null , rv)); return rv; } ; return function (self, args){ return base(self, args).getProp(propName); } ; } , parseRetType: function (){ var tp = this.parseBaseRetType(); while (this.eat("."))tp = this.extendRetType(tp); return tp; } } ; function parseType(spec, name, base, forceNew){ var type = new TypeParser(spec, null , base, forceNew).parseType(name, true ); if (/^fn\(/.test(spec)) for (var i = 0; i < _AN_Read_length("length", type.args); ++i)(function (i){ var arg = type.args[i]; if (arg instanceof infer.Fn && arg.args && _AN_Read_length("length", arg.args)) addEffect(type, function (_self, fArgs){ var fArg = fArgs[i]; if (fArg) fArg.propagate(new infer.IsCallee(infer.cx().topScope, arg.args, null , infer.ANull)); } ); } )(i); return type; } function addEffect(fn, handler, replaceRet){ var oldCmp = fn.computeRet, rv = fn.retval; fn.computeRet = function (self, args, argNodes){ var handled = handler(self, args, argNodes); var old = oldCmp? oldCmp(self, args, argNodes): rv; return replaceRet? handled: old; } ; } var parseEffect = exports.parseEffect = function (effect, fn){ var m; if (effect.indexOf("propagate ") == 0) { var p = new TypeParser(effect, 10); var getOrigin = p.parseRetType(); if (!p.eat(" ")) p.error(); var getTarget = p.parseRetType(); addEffect(fn, function (self, args){ getOrigin(self, args).propagate(getTarget(self, args)); } ); } else if (effect.indexOf("call ") == 0) { var andRet = effect.indexOf("and return ", 5) == 5; var p = new TypeParser(effect, andRet? 16: 5); var getCallee = p.parseRetType(), getSelf = null , getArgs = [] ; if (p.eat(" this=")) getSelf = p.parseRetType(); while (p.eat(" "))getArgs.push(p.parseRetType()); addEffect(fn, function (self, args){ var callee = getCallee(self, args); var slf = getSelf? getSelf(self, args): infer.ANull, as = [] ; for (var i = 0; i < _AN_Read_length("length", getArgs); ++i)as.push(getArgs[i](self, args)); var result = andRet? new infer.AVal(): infer.ANull; callee.propagate(new infer.IsCallee(slf, as, null , result)); return result; } , andRet); } else if (m = effect.match(/^custom (\S+)\s*(.*)/)) { var customFunc = customFunctions[m[1]]; if (customFunc) addEffect(fn, m[2]? customFunc(m[2]): customFunc); } else if (effect.indexOf("copy ") == 0) { var p = new TypeParser(effect, 5); var getFrom = p.parseRetType(); p.eat(" "); var getTo = p.parseRetType(); addEffect(fn, function (self, args){ var from = getFrom(self, args), to = getTo(self, args); from.forAllProps(function (prop, val, local){ if (local && prop != "") to.propagate(new infer.PropHasSubset(prop, val)); } ); } ); } else { throw new Error("Unknown effect type: " + effect) } } ; var currentTopScope; var parsePath = exports.parsePath = function (path){ var cx = infer.cx(), cached = cx.paths[path], origPath = path; if (cached != null ) return cached; cx.paths[path] = infer.ANull; var base = currentTopScope || cx.topScope; if (cx.localDefs) for (var name in cx.localDefs){ if (path.indexOf(name) == 0) { if (path == name) return cx.paths[path] = cx.localDefs[path]; if (path.charAt(_AN_Read_length("length", name)) == ".") { base = cx.localDefs[name]; path = path.slice(_AN_Read_length("length", name) + 1); break ; } } } var parts = path.split("."); for (var i = 0; i < _AN_Read_length("length", parts) && base != infer.ANull; ++i){ var prop = parts[i]; if (prop.charAt(0) == "!") { if (prop == "!proto") { base = (base instanceof infer.Obj && base.proto) || infer.ANull; } else { var fn = base.getFunctionType(); if (!fn) { base = infer.ANull; } else if (prop == "!ret") { base = fn.retval && fn.retval.getType() || infer.ANull; } else { var arg = fn.args && fn.args[Number(prop.slice(1))]; base = (arg && arg.getType()) || infer.ANull; } } } else if (base instanceof infer.Obj) { var propVal = (prop == "prototype" && base instanceof infer.Fn)? base.getProp(prop): base.props[prop]; if (!propVal || propVal.isEmpty()) base = infer.ANull; else base = propVal.types[0]; } } cx.paths[origPath] = base == infer.ANull? null : base; return base; } ; function emptyObj(ctor){ var empty = Object.create(ctor.prototype); empty.props = Object.create(null ); empty.isShell = true ; return empty; } function isSimpleAnnotation(spec){ if (!spec["!type"] || /^fn\(/.test(spec["!type"] )) return false ; for (var prop in spec)if (prop != "!type" && prop != "!doc" && prop != "!url" && prop != "!span" && prop != "!data") return false ; return true ; } function passOne(base, spec, path){ if (!base) { var tp = spec["!type"] ; if (tp) { if (/^fn\(/.test(tp)) base = emptyObj(infer.Fn); else if (tp.charAt(0) == "[") base = emptyObj(infer.Arr); else throw new Error("Invalid !type spec: " + tp) } else if (spec["!stdProto"] ) { base = infer.cx().protos[spec["!stdProto"] ]; } else { base = emptyObj(infer.Obj); } base.name = path; } for (var name in spec)if (hop(spec, name) && name.charCodeAt(0) != 33) { var inner = spec[name]; if (typeof inner == "string" || isSimpleAnnotation(inner)) continue ; var prop = base.defProp(name); passOne(prop.getType(), inner, path? path + "." + name: name).propagate(prop); } return base; } function passTwo(base, spec, path){ if (base.isShell) { delete base.isShell; var tp = spec["!type"] ; if (tp) { parseType(tp, path, base); } else { var proto = spec["!proto"] && parseType(spec["!proto"] ); infer.Obj.call(base, proto instanceof infer.Obj? proto: true , path); } } var effects = spec["!effects"] ; if (effects && base instanceof infer.Fn) for (var i = 0; i < _AN_Read_length("length", effects); ++i)parseEffect(effects[i], base); copyInfo(spec, base); for (var name in spec)if (hop(spec, name) && name.charCodeAt(0) != 33) { var inner = spec[name], known = base.defProp(name), innerPath = path? path + "." + name: name; var type = known.getType(); if (typeof inner == "string") { if (type) continue ; parseType(inner, innerPath).propagate(known); } else { if (!isSimpleAnnotation(inner)) { passTwo(type, inner, innerPath); } else if (!type) { parseType(inner["!type"] , innerPath, null , true ).propagate(known); type = known.getType(); if (type instanceof infer.Obj) copyInfo(inner, type); } else continue ; if (inner["!doc"] ) known.doc = inner["!doc"] ; if (inner["!url"] ) _AN_Write_url("url", known, false , inner["!url"] ); if (inner["!span"] ) known.span = inner["!span"] ; } } } function copyInfo(spec, type){ if (spec["!doc"] ) type.doc = spec["!doc"] ; if (spec["!url"] ) _AN_Write_url("url", type, false , spec["!url"] ); if (spec["!span"] ) type.span = spec["!span"] ; if (spec["!data"] ) type.metaData = spec["!data"] ; } function runPasses(type, arg){ var parent = infer.cx().parent, pass = parent && parent.passes && parent.passes[type]; if (pass) for (var i = 0; i < _AN_Read_length("length", pass); i++ )pass[i](arg); } function doLoadEnvironment(data, scope){ var cx = infer.cx(); infer.addOrigin(cx.curOrigin = data["!name"] || "env#" + _AN_Read_length("length", cx.origins)); cx.localDefs = cx.definitions[cx.curOrigin] = Object.create(null ); runPasses("preLoadDef", data); passOne(scope, data); var def = data["!define"] ; if (def) { for (var name in def){ var spec = def[name]; cx.localDefs[name] = typeof spec == "string"? parsePath(spec): passOne(null , spec, name); } for (var name in def){ var spec = def[name]; if (typeof spec != "string") passTwo(cx.localDefs[name], def[name], name); } } passTwo(scope, data); runPasses("postLoadDef", data); cx.curOrigin = cx.localDefs = null ; } exports.load = function (data, scope){ if (!scope) scope = infer.cx().topScope; var oldScope = currentTopScope; currentTopScope = scope; try { doLoadEnvironment(data, scope); } finally{ currentTopScope = oldScope; } } ; var customFunctions = Object.create(null ); infer.registerFunction = function (name, f){ customFunctions[name] = f; } ; var IsCreated = infer.constraint("created, target, spec", { addType: function (tp){ if (tp instanceof infer.Obj && this.created++ < 5) { var derived = new infer.Obj(tp), spec = this.spec; if (spec instanceof infer.AVal) spec = spec.getType(); if (spec instanceof infer.Obj) for (var prop in spec.props){ var cur = spec.props[prop].types[0]; var p = derived.defProp(prop); if (cur && cur instanceof infer.Obj && cur.props.value) { var vtp = cur.props.value.getType(); if (vtp) p.addType(vtp); } } _AN_Read_target("target", this).addType(derived); } } } ); infer.registerFunction("Object_create", function (_self, args, argNodes){ if (argNodes && _AN_Read_length("length", argNodes) && argNodes[0].type == "Literal" && argNodes[0].value == null ) return new infer.Obj(); var result = new infer.AVal(); if (args[0]) args[0].propagate(new IsCreated(0, result, args[1])); return result; } ); var IsBound = infer.constraint("self, args, target", { addType: function (tp){ if (!(tp instanceof infer.Fn)) return ; _AN_Read_target("target", this).addType(new infer.Fn(tp.name, tp.self, tp.args.slice(_AN_Read_length("length", this.args)), tp.argNames.slice(_AN_Read_length("length", this.args)), tp.retval)); this.self.propagate(tp.self); for (var i = 0; i < Math.min(_AN_Read_length("length", tp.args), _AN_Read_length("length", this.args)); ++i)this.args[i].propagate(tp.args[i]); } } ); infer.registerFunction("Function_bind", function (self, args){ if (!_AN_Read_length("length", args)) return infer.ANull; var result = new infer.AVal(); self.propagate(new IsBound(args[0], args.slice(1), result)); return result; } ); infer.registerFunction("Array_ctor", function (_self, args){ var arr = new infer.Arr(); if (_AN_Read_length("length", args) != 1 || !args[0].hasType(infer.cx().num)) { var content = arr.getProp(""); for (var i = 0; i < _AN_Read_length("length", args); ++i)args[i].propagate(content); } return arr; } ); return exports; } );