reign
Version:
A persistent, typed-objects implementation.
417 lines (390 loc) • 12.8 kB
JavaScript
/* @flow */
import type Backing from "backing";
import {$Address, $CanBeEmbedded, $CanContainReferences} from "../../symbols";
/**
* Creates a function which can initialize a new struct, either
* via a config object or by using default (empty) field values.
*/
export function createInitializeStruct (Partial: PartialType<Object>, fields: StructField<any>[]): (backing: Backing, address: float64, input: ?Object) => void {
const defaults = [];
const setValues = fields.map((field, index) => {
const {name, type, offset} = field;
defaults.push(field.default);
if (isValidIdentifier(name)) {
return `
type${index}.initialize(backing, address + ${offset}, (tmp = input.${name}) !== undefined ? tmp : defaults[${index}]());
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
type${index}.initialize(backing, address + ${offset}, (tmp = input[${sanitizedName}]) !== undefined ? tmp : defaults[${index}]());
`;
}
}).join('');
const setEmpty = fields.map(({name, type, offset}, index) => `
type${index}.initialize(backing, address + ${offset}, defaults[${index}]());
`).join('');
const argNames = ['$Address', 'Partial', 'defaults', ...fields.map((_, index) => `type${index}`)];
const args = [$Address, Partial, defaults, ...fields.map(field => field.type)];
const body = `
"use strict";
return function initializeStruct (backing, address, input) {
if (input == null) {
${setEmpty}
}
else if (input instanceof Partial) {
backing.copy(address, input[$Address], Partial.byteLength);
}
else {
var tmp;
${setValues}
}
};
`;
return (Function(...argNames, body))(...args);
}
/**
* Creates a function which can write a struct, either
* via a config object or by using default (empty) field values.
*/
export function createStoreStruct (Partial: PartialType<Object>, fields: StructField<any>[]): (backing: Backing, address: float64, input: ?Object) => void {
const defaults = [];
const setValues = fields.map((field, index) => {
const {name, type, offset} = field;
defaults.push(field.default);
if (isValidIdentifier(name)) {
return `
type${index}.store(backing, address + ${offset}, (tmp = input.${name}) !== undefined ? tmp : defaults[${index}]);
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
type${index}.store(backing, address + ${offset}, (tmp = input[${sanitizedName}]) !== undefined ? tmp : defaults[${index}]);
`;
}
}).join('');
const setEmpty = fields.map(({name, type, offset}, index) => `
type${index}.store(backing, address + ${offset}, defaults[${index}]);
`).join('');
const argNames = ['$Address', 'Partial', 'defaults', ...fields.map((_, index) => `type${index}`)];
const args = [$Address, Partial, defaults, ...fields.map(field => field.type)];
const body = `
"use strict";
return function storeStruct (backing, address, input) {
if (input == null) {
${setEmpty}
}
else if (input instanceof Partial) {
backing.copy(address, input[$Address], Partial.byteLength);
}
else {
var tmp;
${setValues}
}
};
`;
return (Function(...argNames, body))(...args);
}
/**
* Create the `.accepts()` method for a list of fields.
*/
export function createAccepts (fields: StructField<any>[]): (input: any) => boolean {
const argNames = ['$Address', ...fields.map((_, index) => `type${index}`)];
const args = [$Address, ...fields.map(field => field.type)];
const body = `
"use strict";
return function accepts (input) {
if (input == null || typeof input !== 'object') {
return false;
}
${fields.map(({name, type, offset}, index) => `
if (!type${index}.accepts(input${isValidIdentifier(name) ? `.${name}` : `[${JSON.stringify(name)}]`})) {
return false;
}
`).join('')}
return true;
};
`;
return (Function(...argNames, body))(...args);
}
/**
* Create the `.toJSON()` method for a list of fields.
*/
export function createToJSON (fields: StructField<any>[]): () => Object {
return Function(`
"use strict";
return {
${fields.map(({name}) => {
if (isValidIdentifier(name)) {
return `${name}: this.${name}`;
}
else {
const sanitizedName = JSON.stringify(name);
return `${sanitizedName}: this[${sanitizedName}]`;
}
}).join(',\n ')}
};`);
}
/**
* Create a function which can clear the given struct fields.
*/
export function createClearStruct (fields: StructField<any>[]): ?(backing: Backing, address: float64) => void {
const clearable = fields.filter(({type}) => typeof type.clear === 'function');
const clearers = clearable.map(field => field.type.clear);
const names = clearable.map((_, index) => `clear_${index}`);
const body = `
"use strict";
return function clearStruct (backing, address) {
${names
.map((name, index) => `${name}(backing, address + ${clearable[index].offset});`)
.join('\n ')}
};
`;
return Function(...names, body)(...clearers);
}
/**
* Create a function which can destroy the given struct fields.
*/
export function createStructDestructor (fields: StructField<any>[]): ?(backing: Backing, address: float64) => void {
const clearable = fields.filter(({type}) => {
/* @flowIssue 252 */
return type[$CanContainReferences] && typeof type.clear === 'function'
});
const clearers = clearable.map(field => field.type);
const names = clearable.map((_, index) => `clearable_${index}`);
const body = `
"use strict";
return function destructor (backing, address) {
${names
.map((name, index) => `${name}.clear(backing, address + ${clearable[index].offset});`)
.join('\n ')}
};
`;
return Function(...names, body)(...clearers);
}
/**
* Create a function which can compare two structs by address.
*/
export function createCompareAddresses (fields: StructField<any>[]): (a: float64, b: float64) => int8 {
const checkAddresses = fields.map(({offset}, index) => {
return `
else if ((tmp = type${index}.compareAddresses(backing, a + ${offset}, b + ${offset})) !== 0) {
return tmp;
}
`;
}).join('');
const argNames: string[] = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function compareAddresses (backing, a, b) {
var tmp;
if (a === b) {
return 0;
}
else if (a === 0) {
return -1;
}
else if (b === 0) {
return 1;
}
${checkAddresses}
else {
return 0;
}
};
`;
return (Function(...argNames, body))(...args);
}
/**
* Create a function which can determine whether two structs are structurally equal.
*/
export function createEqual (fields: StructField<any>[]): (a: Object, b: Object) => boolean {
const checkValues = fields.map(({name, type}, index) => {
if (typeof type.equal !== 'function') {
throw new Error(`Type ${type.name} does not have an equal() method.`);
}
if (isValidIdentifier(name)) {
return `
else if (!type${index}.equal(a.${name}, b.${name})) {
return false;
}
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
else if (!type${index}.equal(a[${sanitizedName}], b[${sanitizedName}])) {
return false;
}
`;
}
}).join('');
const argNames = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function equal (a, b) {
if (a === b) {
return true;
}
else if (a == null || b == null) {
return false;
}
${checkValues}
else {
return true;
}
};
`;
return ((Function(...argNames, body))(...args): ((a: Object, b: Object) => boolean));
}
/**
* Create a function which can compare two struct instances.
*/
export function createCompareValues (fields: StructField<any>[]): (a: Object, b: Object) => int8 {
const checkValues = fields.map(({name}, index) => {
if (isValidIdentifier(name)) {
return `
else if ((tmp = type${index}.compareValues(a.${name}, b.${name})) !== 0) {
return tmp;
}
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
else if ((tmp = type${index}.compareValues(a[${sanitizedName}], b[${sanitizedName}])) !== 0) {
return tmp;
}
`;
}
}).join('');
const argNames = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function compareValues (a, b) {
var tmp;
if (a === b) {
return 0;
}
else if (a == null) {
return -1;
}
else if (b == null) {
return 1;
}
${checkValues}
else {
return 0;
}
};
`;
return ((Function(...argNames, body))(...args): ((a: Object, b: Object) => int8));
}
/**
* Create a function which can compare a struct stored at a given address with a given value.
*/
export function createCompareAddressValue (fields: StructField<any>[]): (backing: Backing, address: float64, value: ?Object) => int8 {
const checkAddressValues = fields.map(({name, offset}, index) => {
if (isValidIdentifier(name)) {
return `
else if ((tmp = type${index}.compareAddressValue(backing, address + ${offset}, value.${name})) !== 0) {
return tmp;
}
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
else if ((tmp = type${index}.compareAddressValue(backing, address, value[${sanitizedName}])) !== 0) {
return tmp;
}
`;
}
}).join('');
const argNames: string[] = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function compareAddressValue (backing, address, value) {
var tmp;
if (value == null || typeof value !== 'object') {
return 1;
}
else if (value[$Address] === address) {
return 0;
}
${checkAddressValues}
else {
return 0;
}
};
`;
return (Function('$Address', ...argNames, body))($Address, ...args);
}
/**
* Create a function which can hash structs with the given fields.
*/
export function createHashStruct (fields: StructField<any>[]): (input: Object) => uint32 {
const checkValues = fields.map(({name, type}, index) => {
if (typeof type.hashValue !== 'function') {
throw new Error(`Type ${type.name} does not have a method called "hashValue".`)
}
if (isValidIdentifier(name)) {
return `
hash ^= type${index}.hashValue(input.${name});
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
`;
}
else {
const sanitizedName = JSON.stringify(name);
return `
hash ^= type${index}.hashValue(input[${sanitizedName}]);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
`;
}
}).join('');
const argNames = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function hashStruct (input) {
var hash = 0x811c9dc5;
${checkValues}
return hash >>> 0;
};
`;
return (Function(...argNames, body))(...args);
}
/**
* Creates a function which can return random objects with the same shape as the struct.
*/
export function createRandomValue (fields: StructField<any>[]): (() => Object) {
const properties = fields.map(({name}, index) => {
if (isValidIdentifier(name)) {
return ` ${name}: type${index}.randomValue()`;
}
else {
const sanitizedName = JSON.stringify(name);
return ` ${sanitizedName}: type${index}.randomValue()`;
}
}).join(',\n');
const argNames = fields.map((_, index) => `type${index}`);
const args = fields.map(({type}) => type);
const body = `
"use strict";
return function randomValue () {
return new this({
${properties}
});
};
`;
return ((Function(...argNames, body))(...args): (() => Object));
}
export function isValidIdentifier (name: string): boolean {
return /^([A-Za-z_$])([A-Za-z_$0-9]*)$/.test(name);
}