scontainers
Version:
A container/collection/iterator library for JavaScript, comfortable to use, performant and versatile.
418 lines (305 loc) • 10.9 kB
JavaScript
"use strict";
function _implSymbol(target, sym, value) {
Object.defineProperty(target, sym, {
value,
configurable: true
});
return target[sym];
}
function _getSymbol(targetSymName, ...traitSets) {
let symbol;
traitSets.forEach(traitSet => {
const sym = traitSet[targetSymName];
if (typeof sym === 'symbol') {
if (!!symbol && symbol !== sym) {
throw new Error(`Symbol ${targetSymName} offered by multiple trait sets.`);
}
symbol = sym;
}
});
if (!symbol) {
throw new Error(`No trait set is providing symbol ${targetSymName}.`);
}
return symbol;
}
function _testTraitSet(traitSet) {
if (!traitSet || typeof traitSet === 'boolean' || typeof traitSet === 'number' || typeof traitSet === 'string') {
throw new Error(`${traitSet} cannot be used as a trait set.`);
}
}
const {
assert,
traits,
options,
language,
semantics
} = require('../utils.js');
_testTraitSet(traits.utils);
_testTraitSet(traits.semantics);
_testTraitSet(traits.descriptors);
const _InnerCollection = _getSymbol("InnerCollection", traits.utils, traits.semantics, traits.descriptors);
const _argKeys = _getSymbol("argKeys", traits.utils, traits.semantics, traits.descriptors);
const _member = _getSymbol("member", traits.utils, traits.semantics, traits.descriptors);
const _call = _getSymbol("call", traits.utils, traits.semantics, traits.descriptors);
const _impl = _getSymbol("impl", traits.utils, traits.semantics, traits.descriptors);
const _return = _getSymbol("return", traits.utils, traits.semantics, traits.descriptors);
const _innerCollectionKey = _getSymbol("innerCollectionKey", traits.utils, traits.semantics, traits.descriptors);
const _defaultGet = _getSymbol("defaultGet", traits.utils, traits.semantics, traits.descriptors);
class CompilationFrame {
constructor(compiler, Type) {
const frame = this;
this.compiler = compiler;
this.Type = Type;
if (options.debug) {
this.innerCalls = [];
this.traceInnerCall = function (traitName) {
const genTrait = traits.generators[traitName];
this.innerCalls.push({
traitName,
generated: !!this[genTrait]
});
};
} // inner: the CompilationFrame for our InnerType
const InnerType = Type[_InnerCollection];
if (InnerType) {
this.inner = new CompilationFrame(compiler, InnerType, this.typeArgMapFn);
} // self: Expression representing the instance of `Type` we're working on
Object.defineProperties(this, {
self: {
get() {
return frame.typeArgMapFn(compiler.getSelf(Type));
}
}
}); // args
this.args = {};
Type[_argKeys].forEach(argKey => {
Object.defineProperty(this.args, argKey, {
get() {
return frame.typeArgMapFn(compiler.getArg(Type, argKey));
}
});
}); // ._Straits.
for (let traitName in traits.generators) {
const genTrait = traits.generators[traitName];
const trait = traits.scontainers[traitName];
const compilationFrame = this;
const traitWrapper = function (...args) {
if (options.debug) {
compilationFrame.traceInnerCall(traitName);
}
const Type = this.Type;
if (Type[genTrait]) {
return Type[genTrait].call(this, ...args);
}
const traitVar = this.compiler.registerConstant(trait, `${traitName}Sym`); //return compiler.getSelf( Type )._Straits.member( traitVar, true )._Straits.call( ...args );
return compilationFrame.self[_member](traitVar, true)[_call](...args);
};
if (trait) {
trait[_impl](this, traitWrapper);
}
genTrait[_impl](this, traitWrapper);
}
}
get typeArgMapFn() {
return this.compiler.typeArgMapFn;
}
set typeArgMapFn(val) {
return this.compiler.typeArgMapFn = val;
}
get ast() {
return this.compiler.ast;
}
get mainFunction() {
return this.compiler.mainFunction;
}
get body() {
return this.compiler.body;
}
set body(val) {
return this.compiler.body = val;
}
createVariable(...args) {
return this.compiler.createVariable(...args);
}
createUniqueVariable(...args) {
return this.compiler.createUniqueVariable(...args);
}
registerConstant(...args) {
return this.compiler.registerConstant(...args);
}
registerConstants(...args) {
return this.compiler.registerConstants(...args);
}
registerParameter(...args) {
return this.compiler.registerParameter(...args);
}
registerParameters(...args) {
return this.compiler.registerParameters(...args);
}
assert(...args) {
return this.compiler.assert(...args);
}
debug(...args) {
return this.compiler.debug(...args);
}
log(...args) {
return this.compiler.log(...args);
}
}
class Compiler extends language.Compiler {
constructor(Type, functionName, params = []) {
super();
this.Type = Type;
this.typeArgMapFn = arg => arg.variable; // type arguments
this.typeArguments = new Map(); // AST manipulation
this.frame = new CompilationFrame(this, this.Type);
this.body = semantics.block();
this.mainFunction = semantics.function(`${Type.name.replace(/\W+/g, '_')}_${functionName}`, params, this.body);
this.ast[_return](this.mainFunction);
}
registerParameter(name) {
const variable = this.createUniqueVariable(name);
this.mainFunction.params.push(variable);
return variable;
} // type arguments
// return an expression for `memberExpr` as member of `TragetType`
// e.g.: in the hierarchy `Array::Map::Filter`...
// makeVarRelative( Array::Map::Filter, 'filterFn' ) = AST::parse( `this.filterFn` )
// makeVarRelative( Array::Map, 'mapFn' ) = AST::parse( `this.wrapped.mapFn` )
// makeVarRelative( Array ) = AST::parse( `this.wrapped.wrapped` )
makeVarRelative(TargetType, memberExpr) {
let Type = this.Type;
let expr = semantics.this();
while (Type !== TargetType) {
const parentKey = Type[_innerCollectionKey];
Type = Type[_InnerCollection];
expr = expr[_member](parentKey);
}
if (memberExpr) {
return expr[_member](memberExpr);
}
return expr;
} // returns the member arguments for `Type`
// e.g. getTipeArguments( Array::Map ) === 'mapFn' - check the object property `._Straits.argKeys`
getTypeArguments(Type) {
return this.typeArguments[_defaultGet](Type, () => new Map());
} // get a member variable for `Type`. very similar to `makeVarRelative`, but returns an object...
// e.g. for `Array::Map::Filter`:
// getArg( Array::Map, 'mapFn' ) {
// name: `mapFn`,
// variable: AST::parse( 'this.wrapped.mapFn' ), // same as `makeVarRelative(Array::Map, 'mapFn')`
// fn: this.wrapped.mapFn
getArg(Type, argKey) {
const typeArgs = this.getTypeArguments(Type);
const arg = typeArgs[_defaultGet](argKey, () => ({
name: argKey,
variable: this.makeVarRelative(Type, semantics.id(argKey)),
fn() {
return this[argKey];
}
}));
return arg;
} // like `getArg`, but operating on an array of args
getArgs(Type, ...argKeys) {
return argKeys.map(argKey => this.getArg(Type, argKey));
} // like `getArg`, but returning the parent for `Type`
getParent(Type, parentKey) {
const ParentType = Type[_InnerCollection];
const parent = this.getSelf(ParentType);
const typeArgs = this.getTypeArguments(Type);
typeArgs.parent = {
name: parentKey,
variable: parent.variable,
fn() {
return this[parentKey];
}
};
return parent;
} // like `getArg`, but returning the instance of type `Type`
getSelf(Type, parentKey) {
const typeArgs = this.getTypeArguments(Type);
const varName = `self${Type.name.replace(/:/g, '')}`;
const arg = typeArgs[_defaultGet]('', () => ({
name: varName,
variable: this.makeVarRelative(Type),
fn() {
return this;
}
}));
return arg;
} // returns an array with all the type arguments that were used:
// all the stuff that was returned by `getArg`, `getArgs`, `getParent`, `getSelf`
getTypeArgumentArray() {
const getTypeArgRec = Type => {
if (!Type) {
return [];
}
return [].concat(getTypeArgRec(Type[_InnerCollection]), Array.from(this.getTypeArguments(Type).values()));
};
return getTypeArgRec(this.Type);
} // returns an array with the values of `instance` for every type argument that was used:
// the value for all the stuff that was returned by `getArg`, `getArgs`, `getParent`, `getSelf`
getTypeArgumentArrayValues(instance) {
const getTypeArgRec = (Type, instance) => {
if (!Type) {
return [];
}
assert(instance instanceof Type, `${instance} is not a ${Type.name}`);
const typeArgs = this.getTypeArguments(Type); // const parent = typeArgs.parent;
const parentKey = Type[_innerCollectionKey];
if (!parentKey) {
return Array.from(typeArgs.values()).map(arg => arg.fn.call(instance));
} //const parentInstance = instance::parent.fn();
const parentInstance = instance[parentKey];
return [].concat(getTypeArgRec(Type[_InnerCollection], parentInstance), Array.from(typeArgs.values()).map(arg => arg.fn.call(instance)));
};
return getTypeArgRec(this.Type, instance);
} // compilation
compile() {
if (options.showGeneratedFunctions) {
const constantNames = Array.from(this.constants.values()).map(variable => variable.name);
console.log(`>>>>>>>>>>>>>>>>>>>>> ${this.mainFunction.id.name}(${constantNames}):`); // console.log( fnFactory.toString() );
console.group();
console.log(this.ast.codegen());
console.groupEnd();
if (options.debug) {
console.log(`==========`);
let {
frame
} = this;
while (frame) {
frame.innerCalls.forEach(ic => {
console.log(`${frame.Type.name}: ${ic.generated ? `✔` : `❌❌❌`} ${ic.traitName}`);
});
frame = frame.inner;
}
}
console.log(`<<<<<<<<<<<<<<<<<<<<<`);
}
const fnFactory = super.compile();
return fnFactory();
} // semantics
assert(expr) {
const assertVar = this.registerConstant(assert, `assert`);
return assertVar[_call](expr);
}
debug() {
return this.log(semantics.lit(this.mainFunction.id.name), semantics.id(`arguments`));
}
log(...args) {
return semantics.id(`console`)[_member](`log`)[_call](...args);
}
}
module.exports = Compiler;
/*
if( require.main === module ) {
const compiler = new Compiler( Type, key, function(){
// this.pushStatement( this.compiler.debug() );
const result = this::factory();
if( result ) {
this.pushStatement( result.return() );
}
});
console.log( compiler.compile() );
}
*/