You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
761 lines
24 KiB
JavaScript
761 lines
24 KiB
JavaScript
7 years ago
|
var assert = require("assert");
|
||
|
var Ap = Array.prototype;
|
||
|
var slice = Ap.slice;
|
||
|
var map = Ap.map;
|
||
|
var each = Ap.forEach;
|
||
|
var Op = Object.prototype;
|
||
|
var objToStr = Op.toString;
|
||
|
var funObjStr = objToStr.call(function(){});
|
||
|
var strObjStr = objToStr.call("");
|
||
|
var hasOwn = Op.hasOwnProperty;
|
||
|
|
||
|
// A type is an object with a .check method that takes a value and returns
|
||
|
// true or false according to whether the value matches the type.
|
||
|
|
||
|
function Type(check, name) {
|
||
|
var self = this;
|
||
|
assert.ok(self instanceof Type, self);
|
||
|
|
||
|
// Unfortunately we can't elegantly reuse isFunction and isString,
|
||
|
// here, because this code is executed while defining those types.
|
||
|
assert.strictEqual(objToStr.call(check), funObjStr,
|
||
|
check + " is not a function");
|
||
|
|
||
|
// The `name` parameter can be either a function or a string.
|
||
|
var nameObjStr = objToStr.call(name);
|
||
|
assert.ok(nameObjStr === funObjStr ||
|
||
|
nameObjStr === strObjStr,
|
||
|
name + " is neither a function nor a string");
|
||
|
|
||
|
Object.defineProperties(self, {
|
||
|
name: { value: name },
|
||
|
check: {
|
||
|
value: function(value, deep) {
|
||
|
var result = check.call(self, value, deep);
|
||
|
if (!result && deep && objToStr.call(deep) === funObjStr)
|
||
|
deep(self, value);
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var Tp = Type.prototype;
|
||
|
|
||
|
// Throughout this file we use Object.defineProperty to prevent
|
||
|
// redefinition of exported properties.
|
||
|
exports.Type = Type;
|
||
|
|
||
|
// Like .check, except that failure triggers an AssertionError.
|
||
|
Tp.assert = function(value, deep) {
|
||
|
if (!this.check(value, deep)) {
|
||
|
var str = shallowStringify(value);
|
||
|
assert.ok(false, str + " does not match type " + this);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
function shallowStringify(value) {
|
||
|
if (isObject.check(value))
|
||
|
return "{" + Object.keys(value).map(function(key) {
|
||
|
return key + ": " + value[key];
|
||
|
}).join(", ") + "}";
|
||
|
|
||
|
if (isArray.check(value))
|
||
|
return "[" + value.map(shallowStringify).join(", ") + "]";
|
||
|
|
||
|
return JSON.stringify(value);
|
||
|
}
|
||
|
|
||
|
Tp.toString = function() {
|
||
|
var name = this.name;
|
||
|
|
||
|
if (isString.check(name))
|
||
|
return name;
|
||
|
|
||
|
if (isFunction.check(name))
|
||
|
return name.call(this) + "";
|
||
|
|
||
|
return name + " type";
|
||
|
};
|
||
|
|
||
|
var builtInTypes = {};
|
||
|
exports.builtInTypes = builtInTypes;
|
||
|
|
||
|
function defBuiltInType(example, name) {
|
||
|
var objStr = objToStr.call(example);
|
||
|
|
||
|
Object.defineProperty(builtInTypes, name, {
|
||
|
enumerable: true,
|
||
|
value: new Type(function(value) {
|
||
|
return objToStr.call(value) === objStr;
|
||
|
}, name)
|
||
|
});
|
||
|
|
||
|
return builtInTypes[name];
|
||
|
}
|
||
|
|
||
|
// These types check the underlying [[Class]] attribute of the given
|
||
|
// value, rather than using the problematic typeof operator. Note however
|
||
|
// that no subtyping is considered; so, for instance, isObject.check
|
||
|
// returns false for [], /./, new Date, and null.
|
||
|
var isString = defBuiltInType("", "string");
|
||
|
var isFunction = defBuiltInType(function(){}, "function");
|
||
|
var isArray = defBuiltInType([], "array");
|
||
|
var isObject = defBuiltInType({}, "object");
|
||
|
var isRegExp = defBuiltInType(/./, "RegExp");
|
||
|
var isDate = defBuiltInType(new Date, "Date");
|
||
|
var isNumber = defBuiltInType(3, "number");
|
||
|
var isBoolean = defBuiltInType(true, "boolean");
|
||
|
var isNull = defBuiltInType(null, "null");
|
||
|
var isUndefined = defBuiltInType(void 0, "undefined");
|
||
|
|
||
|
// There are a number of idiomatic ways of expressing types, so this
|
||
|
// function serves to coerce them all to actual Type objects. Note that
|
||
|
// providing the name argument is not necessary in most cases.
|
||
|
function toType(from, name) {
|
||
|
// The toType function should of course be idempotent.
|
||
|
if (from instanceof Type)
|
||
|
return from;
|
||
|
|
||
|
// The Def type is used as a helper for constructing compound
|
||
|
// interface types for AST nodes.
|
||
|
if (from instanceof Def)
|
||
|
return from.type;
|
||
|
|
||
|
// Support [ElemType] syntax.
|
||
|
if (isArray.check(from))
|
||
|
return Type.fromArray(from);
|
||
|
|
||
|
// Support { someField: FieldType, ... } syntax.
|
||
|
if (isObject.check(from))
|
||
|
return Type.fromObject(from);
|
||
|
|
||
|
// If isFunction.check(from), assume that from is a binary predicate
|
||
|
// function we can use to define the type.
|
||
|
if (isFunction.check(from))
|
||
|
return new Type(from, name);
|
||
|
|
||
|
// As a last resort, toType returns a type that matches any value that
|
||
|
// is === from. This is primarily useful for literal values like
|
||
|
// toType(null), but it has the additional advantage of allowing
|
||
|
// toType to be a total function.
|
||
|
return new Type(function(value) {
|
||
|
return value === from;
|
||
|
}, isUndefined.check(name) ? function() {
|
||
|
return from + "";
|
||
|
} : name);
|
||
|
}
|
||
|
|
||
|
// Returns a type that matches the given value iff any of type1, type2,
|
||
|
// etc. match the value.
|
||
|
Type.or = function(/* type1, type2, ... */) {
|
||
|
var types = [];
|
||
|
var len = arguments.length;
|
||
|
for (var i = 0; i < len; ++i)
|
||
|
types.push(toType(arguments[i]));
|
||
|
|
||
|
return new Type(function(value, deep) {
|
||
|
for (var i = 0; i < len; ++i)
|
||
|
if (types[i].check(value, deep))
|
||
|
return true;
|
||
|
return false;
|
||
|
}, function() {
|
||
|
return types.join(" | ");
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Type.fromArray = function(arr) {
|
||
|
assert.ok(isArray.check(arr));
|
||
|
assert.strictEqual(
|
||
|
arr.length, 1,
|
||
|
"only one element type is permitted for typed arrays");
|
||
|
return toType(arr[0]).arrayOf();
|
||
|
};
|
||
|
|
||
|
Tp.arrayOf = function() {
|
||
|
var elemType = this;
|
||
|
return new Type(function(value, deep) {
|
||
|
return isArray.check(value) && value.every(function(elem) {
|
||
|
return elemType.check(elem, deep);
|
||
|
});
|
||
|
}, function() {
|
||
|
return "[" + elemType + "]";
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Type.fromObject = function(obj) {
|
||
|
var fields = Object.keys(obj).map(function(name) {
|
||
|
return new Field(name, obj[name]);
|
||
|
});
|
||
|
|
||
|
return new Type(function(value, deep) {
|
||
|
return isObject.check(value) && fields.every(function(field) {
|
||
|
return field.type.check(value[field.name], deep);
|
||
|
});
|
||
|
}, function() {
|
||
|
return "{ " + fields.join(", ") + " }";
|
||
|
});
|
||
|
};
|
||
|
|
||
|
function Field(name, type, defaultFn, hidden) {
|
||
|
var self = this;
|
||
|
|
||
|
assert.ok(self instanceof Field);
|
||
|
isString.assert(name);
|
||
|
|
||
|
type = toType(type);
|
||
|
|
||
|
var properties = {
|
||
|
name: { value: name },
|
||
|
type: { value: type },
|
||
|
hidden: { value: !!hidden }
|
||
|
};
|
||
|
|
||
|
if (isFunction.check(defaultFn)) {
|
||
|
properties.defaultFn = { value: defaultFn };
|
||
|
}
|
||
|
|
||
|
Object.defineProperties(self, properties);
|
||
|
}
|
||
|
|
||
|
var Fp = Field.prototype;
|
||
|
|
||
|
Fp.toString = function() {
|
||
|
return JSON.stringify(this.name) + ": " + this.type;
|
||
|
};
|
||
|
|
||
|
Fp.getValue = function(obj) {
|
||
|
var value = obj[this.name];
|
||
|
|
||
|
if (!isUndefined.check(value))
|
||
|
return value;
|
||
|
|
||
|
if (this.defaultFn)
|
||
|
value = this.defaultFn.call(obj);
|
||
|
|
||
|
return value;
|
||
|
};
|
||
|
|
||
|
// Define a type whose name is registered in a namespace (the defCache) so
|
||
|
// that future definitions will return the same type given the same name.
|
||
|
// In particular, this system allows for circular and forward definitions.
|
||
|
// The Def object d returned from Type.def may be used to configure the
|
||
|
// type d.type by calling methods such as d.bases, d.build, and d.field.
|
||
|
Type.def = function(typeName) {
|
||
|
isString.assert(typeName);
|
||
|
return hasOwn.call(defCache, typeName)
|
||
|
? defCache[typeName]
|
||
|
: defCache[typeName] = new Def(typeName);
|
||
|
};
|
||
|
|
||
|
// In order to return the same Def instance every time Type.def is called
|
||
|
// with a particular name, those instances need to be stored in a cache.
|
||
|
var defCache = Object.create(null);
|
||
|
|
||
|
function Def(typeName) {
|
||
|
var self = this;
|
||
|
assert.ok(self instanceof Def);
|
||
|
|
||
|
Object.defineProperties(self, {
|
||
|
typeName: { value: typeName },
|
||
|
baseNames: { value: [] },
|
||
|
ownFields: { value: Object.create(null) },
|
||
|
|
||
|
// These two are populated during finalization.
|
||
|
allSupertypes: { value: Object.create(null) }, // Includes own typeName.
|
||
|
supertypeList: { value: [] }, // Linear inheritance hierarchy.
|
||
|
allFields: { value: Object.create(null) }, // Includes inherited fields.
|
||
|
fieldNames: { value: [] }, // Non-hidden keys of allFields.
|
||
|
|
||
|
type: {
|
||
|
value: new Type(function(value, deep) {
|
||
|
return self.check(value, deep);
|
||
|
}, typeName)
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
Def.fromValue = function(value) {
|
||
|
if (value && typeof value === "object") {
|
||
|
var type = value.type;
|
||
|
if (typeof type === "string" &&
|
||
|
hasOwn.call(defCache, type)) {
|
||
|
var d = defCache[type];
|
||
|
if (d.finalized) {
|
||
|
return d;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
var Dp = Def.prototype;
|
||
|
|
||
|
Dp.isSupertypeOf = function(that) {
|
||
|
if (that instanceof Def) {
|
||
|
assert.strictEqual(this.finalized, true);
|
||
|
assert.strictEqual(that.finalized, true);
|
||
|
return hasOwn.call(that.allSupertypes, this.typeName);
|
||
|
} else {
|
||
|
assert.ok(false, that + " is not a Def");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Note that the list returned by this function is a copy of the internal
|
||
|
// supertypeList, *without* the typeName itself as the first element.
|
||
|
exports.getSupertypeNames = function(typeName) {
|
||
|
assert.ok(hasOwn.call(defCache, typeName));
|
||
|
var d = defCache[typeName];
|
||
|
assert.strictEqual(d.finalized, true);
|
||
|
return d.supertypeList.slice(1);
|
||
|
};
|
||
|
|
||
|
// Returns an object mapping from every known type in the defCache to the
|
||
|
// most specific supertype whose name is an own property of the candidates
|
||
|
// object.
|
||
|
exports.computeSupertypeLookupTable = function(candidates) {
|
||
|
var table = {};
|
||
|
var typeNames = Object.keys(defCache);
|
||
|
var typeNameCount = typeNames.length;
|
||
|
|
||
|
for (var i = 0; i < typeNameCount; ++i) {
|
||
|
var typeName = typeNames[i];
|
||
|
var d = defCache[typeName];
|
||
|
assert.strictEqual(d.finalized, true);
|
||
|
for (var j = 0; j < d.supertypeList.length; ++j) {
|
||
|
var superTypeName = d.supertypeList[j];
|
||
|
if (hasOwn.call(candidates, superTypeName)) {
|
||
|
table[typeName] = superTypeName;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return table;
|
||
|
};
|
||
|
|
||
|
Dp.checkAllFields = function(value, deep) {
|
||
|
var allFields = this.allFields;
|
||
|
assert.strictEqual(this.finalized, true);
|
||
|
|
||
|
function checkFieldByName(name) {
|
||
|
var field = allFields[name];
|
||
|
var type = field.type;
|
||
|
var child = field.getValue(value);
|
||
|
return type.check(child, deep);
|
||
|
}
|
||
|
|
||
|
return isObject.check(value)
|
||
|
&& Object.keys(allFields).every(checkFieldByName);
|
||
|
};
|
||
|
|
||
|
Dp.check = function(value, deep) {
|
||
|
assert.strictEqual(
|
||
|
this.finalized, true,
|
||
|
"prematurely checking unfinalized type " + this.typeName);
|
||
|
|
||
|
// A Def type can only match an object value.
|
||
|
if (!isObject.check(value))
|
||
|
return false;
|
||
|
|
||
|
var vDef = Def.fromValue(value);
|
||
|
if (!vDef) {
|
||
|
// If we couldn't infer the Def associated with the given value,
|
||
|
// and we expected it to be a SourceLocation or a Position, it was
|
||
|
// probably just missing a "type" field (because Esprima does not
|
||
|
// assign a type property to such nodes). Be optimistic and let
|
||
|
// this.checkAllFields make the final decision.
|
||
|
if (this.typeName === "SourceLocation" ||
|
||
|
this.typeName === "Position") {
|
||
|
return this.checkAllFields(value, deep);
|
||
|
}
|
||
|
|
||
|
// Calling this.checkAllFields for any other type of node is both
|
||
|
// bad for performance and way too forgiving.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If checking deeply and vDef === this, then we only need to call
|
||
|
// checkAllFields once. Calling checkAllFields is too strict when deep
|
||
|
// is false, because then we only care about this.isSupertypeOf(vDef).
|
||
|
if (deep && vDef === this)
|
||
|
return this.checkAllFields(value, deep);
|
||
|
|
||
|
// In most cases we rely exclusively on isSupertypeOf to make O(1)
|
||
|
// subtyping determinations. This suffices in most situations outside
|
||
|
// of unit tests, since interface conformance is checked whenever new
|
||
|
// instances are created using builder functions.
|
||
|
if (!this.isSupertypeOf(vDef))
|
||
|
return false;
|
||
|
|
||
|
// The exception is when deep is true; then, we recursively check all
|
||
|
// fields.
|
||
|
if (!deep)
|
||
|
return true;
|
||
|
|
||
|
// Use the more specific Def (vDef) to perform the deep check, but
|
||
|
// shallow-check fields defined by the less specific Def (this).
|
||
|
return vDef.checkAllFields(value, deep)
|
||
|
&& this.checkAllFields(value, false);
|
||
|
};
|
||
|
|
||
|
Dp.bases = function() {
|
||
|
var bases = this.baseNames;
|
||
|
|
||
|
assert.strictEqual(this.finalized, false);
|
||
|
|
||
|
each.call(arguments, function(baseName) {
|
||
|
isString.assert(baseName);
|
||
|
|
||
|
// This indexOf lookup may be O(n), but the typical number of base
|
||
|
// names is very small, and indexOf is a native Array method.
|
||
|
if (bases.indexOf(baseName) < 0)
|
||
|
bases.push(baseName);
|
||
|
});
|
||
|
|
||
|
return this; // For chaining.
|
||
|
};
|
||
|
|
||
|
// False by default until .build(...) is called on an instance.
|
||
|
Object.defineProperty(Dp, "buildable", { value: false });
|
||
|
|
||
|
var builders = {};
|
||
|
exports.builders = builders;
|
||
|
|
||
|
// This object is used as prototype for any node created by a builder.
|
||
|
var nodePrototype = {};
|
||
|
|
||
|
// Call this function to define a new method to be shared by all AST
|
||
|
// nodes. The replaced method (if any) is returned for easy wrapping.
|
||
|
exports.defineMethod = function(name, func) {
|
||
|
var old = nodePrototype[name];
|
||
|
|
||
|
// Pass undefined as func to delete nodePrototype[name].
|
||
|
if (isUndefined.check(func)) {
|
||
|
delete nodePrototype[name];
|
||
|
|
||
|
} else {
|
||
|
isFunction.assert(func);
|
||
|
|
||
|
Object.defineProperty(nodePrototype, name, {
|
||
|
enumerable: true, // For discoverability.
|
||
|
configurable: true, // For delete proto[name].
|
||
|
value: func
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return old;
|
||
|
};
|
||
|
|
||
|
// Calling the .build method of a Def simultaneously marks the type as
|
||
|
// buildable (by defining builders[getBuilderName(typeName)]) and
|
||
|
// specifies the order of arguments that should be passed to the builder
|
||
|
// function to create an instance of the type.
|
||
|
Dp.build = function(/* param1, param2, ... */) {
|
||
|
var self = this;
|
||
|
|
||
|
// Calling Def.prototype.build multiple times has the effect of merely
|
||
|
// redefining this property.
|
||
|
Object.defineProperty(self, "buildParams", {
|
||
|
value: slice.call(arguments),
|
||
|
writable: false,
|
||
|
enumerable: false,
|
||
|
configurable: true
|
||
|
});
|
||
|
|
||
|
assert.strictEqual(self.finalized, false);
|
||
|
isString.arrayOf().assert(self.buildParams);
|
||
|
|
||
|
if (self.buildable) {
|
||
|
// If this Def is already buildable, update self.buildParams and
|
||
|
// continue using the old builder function.
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
// Every buildable type will have its "type" field filled in
|
||
|
// automatically. This includes types that are not subtypes of Node,
|
||
|
// like SourceLocation, but that seems harmless (TODO?).
|
||
|
self.field("type", isString, function() { return self.typeName });
|
||
|
|
||
|
// Override Dp.buildable for this Def instance.
|
||
|
Object.defineProperty(self, "buildable", { value: true });
|
||
|
|
||
|
Object.defineProperty(builders, getBuilderName(self.typeName), {
|
||
|
enumerable: true,
|
||
|
|
||
|
value: function() {
|
||
|
var args = arguments;
|
||
|
var argc = args.length;
|
||
|
var built = Object.create(nodePrototype);
|
||
|
|
||
|
assert.ok(
|
||
|
self.finalized,
|
||
|
"attempting to instantiate unfinalized type " + self.typeName);
|
||
|
|
||
|
function add(param, i) {
|
||
|
if (hasOwn.call(built, param))
|
||
|
return;
|
||
|
|
||
|
var all = self.allFields;
|
||
|
assert.ok(hasOwn.call(all, param), param);
|
||
|
|
||
|
var field = all[param];
|
||
|
var type = field.type;
|
||
|
var value;
|
||
|
|
||
|
if (isNumber.check(i) && i < argc) {
|
||
|
value = args[i];
|
||
|
} else if (field.defaultFn) {
|
||
|
// Expose the partially-built object to the default
|
||
|
// function as its `this` object.
|
||
|
value = field.defaultFn.call(built);
|
||
|
} else {
|
||
|
var message = "no value or default function given for field " +
|
||
|
JSON.stringify(param) + " of " + self.typeName + "(" +
|
||
|
self.buildParams.map(function(name) {
|
||
|
return all[name];
|
||
|
}).join(", ") + ")";
|
||
|
assert.ok(false, message);
|
||
|
}
|
||
|
|
||
|
if (!type.check(value)) {
|
||
|
assert.ok(
|
||
|
false,
|
||
|
shallowStringify(value) +
|
||
|
" does not match field " + field +
|
||
|
" of type " + self.typeName
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// TODO Could attach getters and setters here to enforce
|
||
|
// dynamic type safety.
|
||
|
built[param] = value;
|
||
|
}
|
||
|
|
||
|
self.buildParams.forEach(function(param, i) {
|
||
|
add(param, i);
|
||
|
});
|
||
|
|
||
|
Object.keys(self.allFields).forEach(function(param) {
|
||
|
add(param); // Use the default value.
|
||
|
});
|
||
|
|
||
|
// Make sure that the "type" field was filled automatically.
|
||
|
assert.strictEqual(built.type, self.typeName);
|
||
|
|
||
|
return built;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return self; // For chaining.
|
||
|
};
|
||
|
|
||
|
function getBuilderName(typeName) {
|
||
|
return typeName.replace(/^[A-Z]+/, function(upperCasePrefix) {
|
||
|
var len = upperCasePrefix.length;
|
||
|
switch (len) {
|
||
|
case 0: return "";
|
||
|
// If there's only one initial capital letter, just lower-case it.
|
||
|
case 1: return upperCasePrefix.toLowerCase();
|
||
|
default:
|
||
|
// If there's more than one initial capital letter, lower-case
|
||
|
// all but the last one, so that XMLDefaultDeclaration (for
|
||
|
// example) becomes xmlDefaultDeclaration.
|
||
|
return upperCasePrefix.slice(
|
||
|
0, len - 1).toLowerCase() +
|
||
|
upperCasePrefix.charAt(len - 1);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
exports.getBuilderName = getBuilderName;
|
||
|
|
||
|
function getStatementBuilderName(typeName) {
|
||
|
typeName = getBuilderName(typeName);
|
||
|
return typeName.replace(/(Expression)?$/, "Statement");
|
||
|
}
|
||
|
exports.getStatementBuilderName = getStatementBuilderName;
|
||
|
|
||
|
// The reason fields are specified using .field(...) instead of an object
|
||
|
// literal syntax is somewhat subtle: the object literal syntax would
|
||
|
// support only one key and one value, but with .field(...) we can pass
|
||
|
// any number of arguments to specify the field.
|
||
|
Dp.field = function(name, type, defaultFn, hidden) {
|
||
|
assert.strictEqual(this.finalized, false);
|
||
|
this.ownFields[name] = new Field(name, type, defaultFn, hidden);
|
||
|
return this; // For chaining.
|
||
|
};
|
||
|
|
||
|
var namedTypes = {};
|
||
|
exports.namedTypes = namedTypes;
|
||
|
|
||
|
// Like Object.keys, but aware of what fields each AST type should have.
|
||
|
function getFieldNames(object) {
|
||
|
var d = Def.fromValue(object);
|
||
|
if (d) {
|
||
|
return d.fieldNames.slice(0);
|
||
|
}
|
||
|
|
||
|
if ("type" in object) {
|
||
|
assert.ok(
|
||
|
false,
|
||
|
"did not recognize object of type " +
|
||
|
JSON.stringify(object.type)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return Object.keys(object);
|
||
|
}
|
||
|
exports.getFieldNames = getFieldNames;
|
||
|
|
||
|
// Get the value of an object property, taking object.type and default
|
||
|
// functions into account.
|
||
|
function getFieldValue(object, fieldName) {
|
||
|
var d = Def.fromValue(object);
|
||
|
if (d) {
|
||
|
var field = d.allFields[fieldName];
|
||
|
if (field) {
|
||
|
return field.getValue(object);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return object[fieldName];
|
||
|
}
|
||
|
exports.getFieldValue = getFieldValue;
|
||
|
|
||
|
// Iterate over all defined fields of an object, including those missing
|
||
|
// or undefined, passing each field name and effective value (as returned
|
||
|
// by getFieldValue) to the callback. If the object has no corresponding
|
||
|
// Def, the callback will never be called.
|
||
|
exports.eachField = function(object, callback, context) {
|
||
|
getFieldNames(object).forEach(function(name) {
|
||
|
callback.call(this, name, getFieldValue(object, name));
|
||
|
}, context);
|
||
|
};
|
||
|
|
||
|
// Similar to eachField, except that iteration stops as soon as the
|
||
|
// callback returns a truthy value. Like Array.prototype.some, the final
|
||
|
// result is either true or false to indicates whether the callback
|
||
|
// returned true for any element or not.
|
||
|
exports.someField = function(object, callback, context) {
|
||
|
return getFieldNames(object).some(function(name) {
|
||
|
return callback.call(this, name, getFieldValue(object, name));
|
||
|
}, context);
|
||
|
};
|
||
|
|
||
|
// This property will be overridden as true by individual Def instances
|
||
|
// when they are finalized.
|
||
|
Object.defineProperty(Dp, "finalized", { value: false });
|
||
|
|
||
|
Dp.finalize = function() {
|
||
|
// It's not an error to finalize a type more than once, but only the
|
||
|
// first call to .finalize does anything.
|
||
|
if (!this.finalized) {
|
||
|
var allFields = this.allFields;
|
||
|
var allSupertypes = this.allSupertypes;
|
||
|
|
||
|
this.baseNames.forEach(function(name) {
|
||
|
var def = defCache[name];
|
||
|
def.finalize();
|
||
|
extend(allFields, def.allFields);
|
||
|
extend(allSupertypes, def.allSupertypes);
|
||
|
});
|
||
|
|
||
|
// TODO Warn if fields are overridden with incompatible types.
|
||
|
extend(allFields, this.ownFields);
|
||
|
allSupertypes[this.typeName] = this;
|
||
|
|
||
|
this.fieldNames.length = 0;
|
||
|
for (var fieldName in allFields) {
|
||
|
if (hasOwn.call(allFields, fieldName) &&
|
||
|
!allFields[fieldName].hidden) {
|
||
|
this.fieldNames.push(fieldName);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Types are exported only once they have been finalized.
|
||
|
Object.defineProperty(namedTypes, this.typeName, {
|
||
|
enumerable: true,
|
||
|
value: this.type
|
||
|
});
|
||
|
|
||
|
Object.defineProperty(this, "finalized", { value: true });
|
||
|
|
||
|
// A linearization of the inheritance hierarchy.
|
||
|
populateSupertypeList(this.typeName, this.supertypeList);
|
||
|
|
||
|
if (this.buildable && this.supertypeList.lastIndexOf("Expression") >= 0) {
|
||
|
wrapExpressionBuilderWithStatement(this.typeName);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Adds an additional builder for Expression subtypes
|
||
|
// that wraps the built Expression in an ExpressionStatements.
|
||
|
function wrapExpressionBuilderWithStatement(typeName) {
|
||
|
var wrapperName = getStatementBuilderName(typeName);
|
||
|
|
||
|
// skip if the builder already exists
|
||
|
if (builders[wrapperName]) return;
|
||
|
|
||
|
// the builder function to wrap with builders.ExpressionStatement
|
||
|
var wrapped = builders[getBuilderName(typeName)];
|
||
|
|
||
|
// skip if there is nothing to wrap
|
||
|
if (!wrapped) return;
|
||
|
|
||
|
builders[wrapperName] = function() {
|
||
|
return builders.expressionStatement(wrapped.apply(builders, arguments));
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function populateSupertypeList(typeName, list) {
|
||
|
list.length = 0;
|
||
|
list.push(typeName);
|
||
|
|
||
|
var lastSeen = Object.create(null);
|
||
|
|
||
|
for (var pos = 0; pos < list.length; ++pos) {
|
||
|
typeName = list[pos];
|
||
|
var d = defCache[typeName];
|
||
|
assert.strictEqual(d.finalized, true);
|
||
|
|
||
|
// If we saw typeName earlier in the breadth-first traversal,
|
||
|
// delete the last-seen occurrence.
|
||
|
if (hasOwn.call(lastSeen, typeName)) {
|
||
|
delete list[lastSeen[typeName]];
|
||
|
}
|
||
|
|
||
|
// Record the new index of the last-seen occurrence of typeName.
|
||
|
lastSeen[typeName] = pos;
|
||
|
|
||
|
// Enqueue the base names of this type.
|
||
|
list.push.apply(list, d.baseNames);
|
||
|
}
|
||
|
|
||
|
// Compaction loop to remove array holes.
|
||
|
for (var to = 0, from = to, len = list.length; from < len; ++from) {
|
||
|
if (hasOwn.call(list, from)) {
|
||
|
list[to++] = list[from];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
list.length = to;
|
||
|
}
|
||
|
|
||
|
function extend(into, from) {
|
||
|
Object.keys(from).forEach(function(name) {
|
||
|
into[name] = from[name];
|
||
|
});
|
||
|
|
||
|
return into;
|
||
|
};
|
||
|
|
||
|
exports.finalize = function() {
|
||
|
Object.keys(defCache).forEach(function(name) {
|
||
|
defCache[name].finalize();
|
||
|
});
|
||
|
};
|