UNPKG

babelute

Version:

Internal Domain Specific (Multi)Modeling javascript framework

438 lines (354 loc) 15.6 kB
'use strict'; 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;