babelute
Version:
Internal Domain Specific (Multi)Modeling javascript framework
438 lines (354 loc) • 15.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.lexicons = exports.developToAtoms = exports.developOneLevel = exports.initializer = exports.init = exports.registerLexicon = exports.getLexicon = exports.createLexicon = exports.Lexicon = undefined;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // removed in production
/**
* Babelute Lexicon class and helpers.
* @author Gilles Coomans
* @licence MIT
* @copyright 2016-2017 Gilles Coomans
*/
var _lexem = require('../lexem');
var _lexem2 = _interopRequireDefault(_lexem);
var _babelute = require('../babelute');
var _babelute2 = _interopRequireDefault(_babelute);
var _firstLevel = require('./first-level');
var _firstLevel2 = _interopRequireDefault(_firstLevel);
var _initializer = require('./initializer');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
/**
* Lexicons dico : where to store public (registered) lexicon
* @type {Object}
* @private
*/
var lexicons = {};
/**
* Lexicon class : helpers to store and manage DSL's API.
*
* A __Lexicon__ is just an object aimed to handle, store and construct easily a DSL (its lexicon - i.e. the bunch of words that compose it)
* and its related Atomic/FirstLevel/SecondLevel Babelute subclasses, and their initializers.
*
* One DSL = One lexicon.
*
* A lexicon could extend another lexicon to manage dialects.
*
* You should never use frontaly the constructor (aka never use new Lexicon in your app). Use createLexicon or createDialect in place.
*
* @public
*/
var Lexicon = function () {
/**
* @param {string} name the lexicon name
* @param {?Lexicon} parent an optional parent lexicon to be extended here
*/
function Lexicon(name, parent) {
var _this = this;
_classCallCheck(this, Lexicon); // all assertions will be removed in production
/**
* the parent lexicon (if any)
* @type {Lexicon}
* @public
*/
this.parent = parent;
parent = parent || {};
/**
* the lexicon's name
* @type {String}
*/
this.name = name;
// the three APIs :
/**
* interpretable sentences API (finally always made from syntactic/semantic atoms (aka last level))
* @type {Babelute}
* @protected
*/
this.Atomic = initClass(parent.Atomic || _babelute2.default);
/**
* "document" sentences API (first level : aka all methods has been replaced by fake atomic methods)
* @type {Babelute}
* @protected
*/
this.FirstLevel = initClass(parent.FirstLevel || _firstLevel2.default);
/**
* AST-provider API aka the whole tree between first level and last level. Never use it directly : its used under the hood by {@link developOneLevel} method.
* @type {Babelute}
* @protected
*/
this.SecondLevel = _babelute2.default.extends(parent.SecondLevel || _babelute2.default);
/**
* the secondLevel instance
* @type {Babelute}
* @protected
*/
this.secondLevel = new this.SecondLevel();
if (parent.Atomic) Object.keys(parent.Atomic.initializer).forEach(function (key) {
(0, _initializer.addToInitializer)(_this.Atomic.Initializer, key);
(0, _initializer.addToInitializer)(_this.FirstLevel.Initializer, key);
});
}
/**
* add atomic lexem (atoms) to lexicon
* @param {string[]} atomsArray array of atoms name (as string)
* @return {Lexicon} the lexicon itself
*/
_createClass(Lexicon, [{
key: 'addAtoms',
value: function addAtoms(atomsArray) {
var _this2 = this;
atomsArray.forEach(function (name) {
return addAtom(_this2, name);
});
return this;
}
/**
* add compounds lexems to lexicon
* @param {Function} producer a function that take a babelute initializer as argument and that return an object containing methods (lexems) to add to lexicon
* @return {Lexicon} the lexicon itself
*/
}, {
key: 'addCompounds',
value: function addCompounds(producer) {
var _this3 = this;
// Atomic API is produced with Atomic initializer
var atomicMethods = producer(this.Atomic.initializer, false);
for (var i in atomicMethods) {
this.Atomic.prototype[i] = atomicMethods[i];
} // SecondLevel API is simply produced with the related FirstLevel initializer.
// (so same producer method, same api, but different handler for inner composition)
// is the only thing to do to gain capability to handle full AST. (see docs)
var secondLevelCompounds = producer(this.FirstLevel.initializer, true);
for (var j in secondLevelCompounds) {
this.SecondLevel.prototype[j] = secondLevelCompounds[j];
}Object.keys(atomicMethods).forEach(function (key) {
_this3.FirstLevel.prototype[key] = _firstLevel2.default.getFirstLevelMethod(_this3.name, key);
(0, _initializer.addToInitializer)(_this3.Atomic.Initializer, key);
(0, _initializer.addToInitializer)(_this3.FirstLevel.Initializer, key);
});
return this;
}
/**
* add aliases lexems to lexicon (aliases are like shortcuts : they are added as this to Atomic, FirstLevel and SecondLevel API)
* @param {Object} methods an object containing methods (lexems) to add to lexicon
* @return {Lexicon} the lexicon itself
*/
}, {
key: 'addAliases',
value: function addAliases(producer) {
var _this4 = this;
var producerType = typeof producer === 'undefined' ? 'undefined' : _typeof(producer);
var methods = producerType === 'function' ? producer() : producer;
Object.keys(methods).forEach(function (key) {
_this4.Atomic.prototype[key] = _this4.FirstLevel.prototype[key] = _this4.SecondLevel.prototype[key] = methods[key];
(0, _initializer.addToInitializer)(_this4.Atomic.Initializer, key);
(0, _initializer.addToInitializer)(_this4.FirstLevel.Initializer, key);
});
return this;
}
/**
* @protected
*/
}, {
key: 'use',
value: function use(babelute, name, args, firstLevel) {
var instance = firstLevel ? this.FirstLevel.instance : this.Atomic.instance;
if (!instance[name]) throw new Error('Babelute (' + this.name + ') : method not found : ' + name);
instance[name].apply(babelute, args);
}
/**
* return lexicon's initializer instance. (atomic or firstlevel depending on argument)
* @public
* @param {Boolean} firstLevel true if you want firstLevel initializer, false overwise.
* @return {Initializer} the needed initializer instance
*/
}, {
key: 'initializer',
value: function initializer(firstLevel) {
return firstLevel ? this.FirstLevel.initializer : this.Atomic.initializer;
}
/**
* Create a dialect from this lexicon. a dialect is also a Lexicon.
* @param {String} name the new lexicon name
* @return {Lexicon} the new Lexicon that inherit from this one
*/
}, {
key: 'createDialect',
value: function createDialect(name) {
return new Lexicon(name, this);
}
}]);
return Lexicon;
}();
/**
* Add syntactical atom lexem to lexicon (actually to inner classes that reflect API). A syntactical Atom method is a function that only add one lexem.
* @private
*/
function addAtom(lexicon, name) {
lexicon.Atomic.prototype[name] = lexicon.FirstLevel.prototype[name] = lexicon.SecondLevel.prototype[name] = _firstLevel2.default.getFirstLevelMethod(lexicon.name, name);
(0, _initializer.addToInitializer)(lexicon.Atomic.Initializer, name);
(0, _initializer.addToInitializer)(lexicon.FirstLevel.Initializer, name);
}
/**
* babelute lexicon's Classes initialisation
* @private
*/
function initClass(BaseClass) {
var Class = _babelute2.default.extends(BaseClass);
(0, _initializer.createInitializer)(Class, BaseClass.Initializer);
Class.instance = new Class();
return Class;
}
/**
* Way to create lexicon instances
* @public
* @param {string} name the name of the lexicon
* @param {Lexicon} parent a lexicon instance as parent for this one (optional)
* @return {Lexicon} a lexicon instance
*/
function createLexicon(name) {
var parent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
return new Lexicon(name, parent);
}
/**
* getLexicon registred lexicon by name
*
* @param {string} lexiconName the lexicon's name
* @return {Lexicon} the lexicon
* @throws {Error} If lexicon not found with lexiconName
*/
function getLexicon(lexiconName) {
var lexicon = lexicons[lexiconName];
if (!lexicon) throw new Error('lexicon not found : ' + lexiconName);
return lexicon;
}
/**
* registerLexicon lexicon by name
* @param {Lexicon} lexicon the lexicon instance to registerLexicon
* @param {?string} name lexicon name (optional : if not provided : use the one from lexicon itself)
*/
function registerLexicon(lexicon) {
var name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
lexicons[name || lexicon.name] = lexicon;
}
/*
* _lexicon handeling
*/
// implementation of already declared method in Babelute's proto
_babelute2.default.prototype._lexicon = function (lexiconName) {
return new (getLexicon(lexiconName).Atomic)(this._lexems);
};
_firstLevel2.default.prototype._lexicon = function (lexiconName) {
return new (getLexicon(lexiconName).FirstLevel)(this._lexems);
};
/*
* translation through lexicon (already delcared in Babelute proto)
* @TODO: translation and each and if
* each :
* .each(collec, handler)
* translated to
* .each(collec, wrap(handler, translationInfos))
*
* ==> should translate automatically output from handler
* if ==> same things : wrap handlers
* ==> while translating : when lexem.name === "each" (or "if") (should always be present in target lexicon)
* ==> apply wrapping
*/
_babelute2.default.prototype._translateLexemsThrough = function (lexicon) {
var firstLevel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var map = lexicon instanceof Lexicon ? null : lexicon;
return this._translateLexems(function (lexem) {
if (map) lexicon = map[lexem.lexicon];
if (!lexicon) return null;
var args = translateArgs(lexem.args, lexicon, firstLevel);
var b = new (firstLevel ? lexicon.FirstLevel : lexicon.Atomic)();
return b[lexem.name] && b[lexem.name].apply(b, _toConsumableArray(args));
});
};
function translateArgs(args, lexicon, firstLevel) {
var result = [];
for (var i = 0, len = args.length; i < len; ++i) {
if (args[i] && args[i].__babelute__) result.push(args[i]._translateLexemsThrough(lexicon, firstLevel));else result.push(args[i]);
}return result;
}
/**
* _use handeling
*/
// implementation of already declared method in Babelute's proto
_babelute2.default.prototype._use = function (babelute /* could be a string in "lexiconName:methodName" format */) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return babelute ? use(this, babelute, args, false) : this;
};
// implementation of already declared method in Babelute's proto
_firstLevel2.default.prototype._use = function (babelute /* could be a string in "lexiconName:methodName" format */ /*, ...args */) {
return babelute ? use(this, babelute, [].slice.call(arguments, 1), true) : this;
};
function use(self, babelute, args, firstLevel) {
if (typeof babelute === 'string') {
var splitted = babelute.split(':');
getLexicon(splitted[0]).use(self, splitted[1], args, firstLevel);
} else self._lexems = self._lexems.concat(babelute._lexems);
return self;
}
/**
* return a new babelute from needed lexicon
* @param {string} lexiconName the lexicon from where to take api
* @param {Boolean} asFirstLevel True if it needs to return a FirstLevel instance. False or ommitted : returns an Atomic instance.
* @return {[type]} the babelute instance (either an Atomic or a FirstLevel)
* @throws {Error} If lexicon not found with lexiconName
*/
function init(lexiconName, asFirstLevel) {
if (lexiconName) return new (getLexicon(lexiconName)[asFirstLevel ? 'FirstLevel' : 'Atomic'])();else if (asFirstLevel) return new _firstLevel2.default();
return new _babelute2.default();
}
/**
* develop a FirstLevel compounds-words-lexem through SecondLevel API. It returns the FirstLevel sentence corresponding to lexem's semantic developement.
* @param {Lexem} lexem the lexem to develop
* @param {?Lexicon} lexicon the optional lexicon to use
* @return {FirstLevel} the developed sentence
* @throws {Error} If lexicon not found with lexem.lexicon
* @throws {Error} If method not found in lexicon
*/
function developOneLevel(lexem) {
var lexicon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
lexicon = lexicon || getLexicon(lexem.lexicon);
return lexicon.secondLevel[lexem.name].apply(new lexicon.FirstLevel(), lexem.args);
}
/**
* develop a FirstLevel lexem through Atomic API. Return the atomic representation of the lexem (in its own language).
* @param {Lexem} lexem the lexem to develop
* @param {?Lexicon} lexicon the optional lexicon to use
* @return {Babelute} the developed sentence
* @throws {Error} If lexicon not found with lexem.lexicon
* @throws {Error} If method not found in lexicon
*/
function developToAtoms(lexem) {
var lexicon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
lexicon = lexicon || getLexicon(lexem.lexicon);
return lexicon.Atomic.instance[lexem.name].apply(new lexicon.Atomic(), lexem.args);
}
/**
* Provide Babelute Subclass "initializer" object (the one with all the flattened shortcut api for starting sentences easily)
* @param {string} lexiconName The lexiconName where catch the Babelute Class from where getLexicon or create the initializer object.
* @param {boolean} asFirstLevel true if should return a first-level instance. false to return an atomic instance.
* @return {Object} An initializer object with shortcuted API from lexicon's Atomic prototype
* @throws {Error} If lexicon not found with lexiconName
*/
function initializer(lexiconName, asFirstLevel) {
if (!asFirstLevel) return getLexicon(lexiconName).Atomic.initializer;
return getLexicon(lexiconName).FirstLevel.initializer;
}
exports.Lexicon = Lexicon;
exports.createLexicon = createLexicon;
exports.getLexicon = getLexicon;
exports.registerLexicon = registerLexicon;
exports.init = init;
exports.initializer = initializer;
exports.developOneLevel = developOneLevel;
exports.developToAtoms = developToAtoms;
exports.lexicons = lexicons;