UNPKG

cor-lang

Version:
1,938 lines (1,515 loc) 49.5 kB
(function(cor){ var EcmaReservedKeywords = [ // Ecma-262 Keyword 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', 'debugger', 'function', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', // Ecma-262 FutureReservedWord 'class', 'enum', 'extends', 'const', 'export', 'import', // Ecma-262 FutureReservedWord (in strict mode) 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static' ]; var EcmaNativeClasses = [ // Ecma-262 'Arguments', 'Array', 'Boolean', 'Date', 'Error', 'Function', 'JSON', 'Math', 'Number', 'Object', 'RegExp', 'String' ]; var Class = cor.Class, yy = cor.yy, hasProp = Object.prototype.hasOwnProperty, slice = Array.prototype.slice, translationTable = { 'nil': 'null', 'func': 'function ', 'if': 'if ', 'else': ' else ', 'for': 'for ', 'in': ' in ', 'switch': 'switch ', 'case': 'case ', 'return': 'return ', 'break': 'break;', 'continue': 'continue;', 'try': 'try ', 'catch': ' catch ', 'finally': ' finally ', 'throw': 'throw ', '=': ' = ', ',': ', ', ':': ': ', '+': ' + ', '-': ' - ', '*': ' * ', '/': ' / ', '%': ' % ', '<': ' < ', '>': ' > ', '<<': ' << ', '>>': ' >> ', '>>>': ' >>> ', '==': ' === ', '!=': ' !== ', '+=': ' += ', '-=': ' -= ', '*=': ' *= ', '/=': ' *= ', '%=': ' %= ', '~=': ' ~= ', '>=': ' >= ', '<=': ' <= ', '<<=': ' <<= ', '>>=': ' >>= ', '>>>=': ' >>>= ', '&&': ' && ', '||': ' || ', '|': ' | ', '&': ' & ', '^': ' ^ ' }; var builtinFn = [ 'error', 'super', 'regex', 'chan', 'timeout', 'copy' ]; function isBuiltinFn(name) { return builtinFn.indexOf(name) !== -1; } function isEcmaReservedKeyWord(name) { return EcmaReservedKeywords.indexOf(name) !== -1; } yy.parseError = function parseError (msg, hash, replaceMsg) { var filename = yy.env.filename; //is non recoverable parser error? if (hash && hasProp.call(hash, 'loc') && hash.expected) { switch (hash.text) { case '\n': hash.text = 'end of line'; break; case '' : hash.text = 'end of input'; break; } msg = replaceMsg ? msg : 'unexpected ' + hash.text; } else { msg = replaceMsg ? msg : 'unexpected ' + msg; } msg += ' at ' + filename + ':' + hash.loc.first_line; throw msg; } /* There is two types of routes: - Delegate : ends with file extensions, example `filename.js` - Public : is tested against `^[a-z_-]+$` regex Routes are tested in the same order as types above, if the route does not match to any of before types then it will be proccessed by `packagize` function which transform routes according to Cor package convention. */ yy.generateRoute = function(route) { var parsed, rFileNameExt = /([\s\S]+)*(^|\/)([\w\-]+)*(\.[\w\-]+)*$/, rPublic = /^[a-z_-]+$/; // replace \ by / function normalize(route) { return route.replace(/\\/g, '/').replace(/\/+/g, '/'); } // Public modules if (rPublic.test(route)) { return normalize(route); } // Delegate, is a route that has explicit file extension // example: jquery.js, mylib.cor // parsed[4] is the file extension // so if parsed[4]? then is delegate route parsed = rFileNameExt.exec(route); if (parsed && parsed[4]) { return normalize(route); } // resturn route as is return route; } // iterate recursevely in preorder starting from the node passed // as first parameter, it executes the function passed as second parameter // in each visited node, iteration ends if the function returns false function preorder(node, fn) { if (!(node instanceof yy.Node)) { return; } var i, ch = node.children, len = ch.length; for (i = 0; i < len; i++) { if (fn(ch[i]) === false) { return; } preorder(ch[i], fn); } } function flattenToLine(node, lineno) { preorder(node, function(node) { node.lineno = lineno; }) } function moveToLine(node, offset) { preorder(node, function(node) { node.lineno += offset; }) } function stringifyNode(node) { var i, ret = '', ch = node.children, len = ch.length; if (ch instanceof Array) { for (i = 0; i < len; i++) { ret += stringifyNode(ch[i]); } } else { ret = ch; } return ret; } function getLesserLineNumber(node){ var i = 0, len, lineno, selLine, ch; if (node) { selLine = node.lineno; ch = node.children; } if (!ch) { return lineno; } len = ch.length; for (i = 0; i < len; i++) { lineno = getLesserLineNumber(ch[i]); if (lineno < selLine) { selLine = lineno; } } return selLine; }; // the base class for all AST nodes yy.Node = Class({ type: 'Node', _runtimePrefix: 'CRL.', scope: null, types: null, init: function init(children) { this.children = []; this.lineno = yy.env.loc.first_line; this.loc = Object.create(yy.env.loc); this.yy = yy; // setup parent node for later referencing this.adopt(slice.call(arguments)); this.initNode(); }, // adopt an array of nodes adopt: function(children) { var i = 0, len; for (len = children.length; i < len; i++) { if (children[i]) { this.children[i] = children[i]; this.children[i].parent = this; } } }, initNode: function() { // virtual }, runtimeFn: function(name) { return this.runtimePrefix(name + '('); }, runtimePrefix: function(txt) { this.yy.env.usesRuntime = true; return this._runtimePrefix + txt; }, error: function(txt, lineno) { throw 'Error: ' + txt + ' at ' + yy.env.filename + ':' + lineno; }, compile: function() { // virtual } }); // List of nodes yy.List = Class(yy.Node, { type: 'List', children: null, init: function() { this.children = []; this.adopt(slice.call(arguments)); }, add: function() { this.children = this.children.concat(slice.call(arguments)); this.adopt(this.children); }, addFront: function() { this.children = slice.call(arguments).concat(this.children); this.adopt(this.children); }, last: function() { return this.children[this.children.length - 1]; } }); // Literals are the smallest units to be compiled // its children is a string which must be returned // to write as compiled code // most of the nodes compiles by constructing yy.Lit-s // and adopting as children to be later readed by the compiler yy.Lit = yy.LiteralNode = Class(yy.Node, { type: 'Lit, LiteralNode', init: function(ch, yloc) { this.children = ch; this.lineno = isNaN(yloc) ? yloc.first_line : yloc; this.loc = yloc; this.yy = yy; this.initNode(); }, compile: function() { var txt = this.children, t; if (hasProp.call(translationTable, txt)) { txt = translationTable[txt]; } return txt; } }); yy.ObjectPropertyNode = Class(yy.Lit, { type: 'ObjectPropertyNode', compile: function() { return this.children; } }) // Single line comment // is a comment starting by `//` and ends in the next EOL yy.SingleLineCommentNode = Class(yy.Lit, { type: 'SingleLineCommentNode', compile: function() { this.children = [ new yy.Lit(this.children, this.lineno) ]; } }); // A comment beginning and ending with `---` // this kind of comments must have a new line before and after yy.MultiLineCommentNode = Class(yy.Lit, { type: 'MultiLineCommentNode', compile: function() { this.children = this.children .replace(/^(\s*)---/, '$1/*') .replace(/---(\s*)$/, '*/$1'); var i, str, lineno = this.lineno, splitted = this.children.split(/\r\n|\n/), len = splitted.length; for (i = 0; i < len; i++) { str = splitted[i].replace(/^\s+/, ''); splitted[i] = new yy.Lit(str, lineno + i); } this.children = splitted; } }); // A value is anithing that can be assigned, // an object literal, a string, boolean ... yy.ValueList = Class(yy.List, { type: 'ValueList', compile: function() { var ch, i = this.children.length; while (--i) { ch = this.children[i]; if (!ch || ch.children === ',') { this.children.pop(); } else { break; } } } }); // Node to wrap a single line expression or Inc-Dec statement yy.SimpleStmtNode = Class(yy.Node, { type: 'SimpleStmtNode', initNode: function() { this.base('initNode', arguments); var i = 0, len = this.children.length, item; for (; i < len; i++) { item = this.children[i]; if (item instanceof yy.VarNode) { item.markAsLocalVar(); item.children = ''; if (this.children[i + 1]) { this.children[i + 1].children = ''; i++; } } } } }); // Cor nodes such as modules, functions and clases knows // has their own context to know about variable scoping. // This is the base class yy.ContextAwareNode = Class(yy.Node, { type: 'ContextAwareNode', context: null, initNode: function() { this.base('initNode', arguments); this.context = this.yy.env.popContext(); this.context.ownerNode = this; }, compile: function() { this.yy.env.newContext(this.context); } }); // A module is a root node of the AST yy.ModuleNode = Class(yy.ContextAwareNode, { type: 'ModuleNode', initializerName: 'init', compile: function() { this.base('compile', arguments); var i, item, name, nameLineno, isQualified, initialize = '', footer = '', names = {}, ls = this.children[0], len = ls.children.length; for (i = 0; i < len; i++) { item = ls.children[i]; if (item instanceof yy.FunctionNode) { if (typeof item.name === 'undefined') { this.error('nameless function', getLesserLineNumber(item)); } name = item.name; nameLineno = item.nameLineno; if (name === this.initializerName) { initialize = this.initializerName + '.call(this);'; } this.context.ignoreVar(name); } else if (item instanceof yy.AssignmentNode) { item = item.children[0]; if (item.children.length > 1) { isQualified = true; continue; } else { item = item.children[0]; name = item.children; nameLineno = item.lineno; } } else if (item instanceof yy.ClassNode) { name = item.className; nameLineno = item.children[1].lineno; } if (name) { if (hasProp.call(names, name)) { this.error("Can not redeclare '" + name + "'", nameLineno); } names[name] = true; this.yy.env.addExported(name); this.context.addLocalVar(name); } name = null; } if (ls) { initialize += this.getExport(); footer = initialize !== '' ? ';' + initialize : ''; ls.children.unshift(new yy.Lit(this.context.compileVars(), 1)); ls.children.push(new yy.Lit(footer, this.lineno)); } }, getExport: function() { var name, ret = '', basen = basename(this.yy.env.filename), exported = this.yy.env.getExported(); function basename(filename) { var parsed = /([a-zA-Z-0-9_\-]*)([a-zA-A-0-9_\-\.]*)$/.exec(filename); return parsed ? parsed[1] : ''; } function isCapitalized(txt) { return /^[A-Z]/.test(txt); } if (isCapitalized(basen)) { if (exported.hasOwnProperty(basen)) { return 'module.exports = ' + basen + ';'; } else { this.error('undeclared default exported value', 1); } } for (name in exported) { if (name !== this.initializerName) { ret += 'exports.' + name + ' = ' + exported[name] + '; '; } } return ret; } }); // Node for function and class blocks yy.BlockNode = Class(yy.Node, { type: 'BlockNode', init: function() { this.base('init', arguments); var node, i, ch, len; if (this.children[1] instanceof yy.List) { ch = this.children[1].children; len = ch.length; for (i = 0; i < len; i++) { node = ch[i]; if (node instanceof yy.FunctionNode && typeof node.name === 'undefined') { this.error('nameless function', getLesserLineNumber(node)); } } } } }); // Node for dot-expression syntax: `a.b.c` yy.SelectorExprNode = Class(yy.Node, { type: 'SelectorExprNode' }); // Expression such as !x, -x... yy.UnaryExprNode = Class(yy.Node, { type: 'UnaryExprNode' }); // Expression wrapped by `(` and `)` yy.AssociationNode = Class(yy.Node, { type: 'AssociationNode' }); // The Cor functions definition // it initializes variables used in its context yy.FunctionNode = Class(yy.ContextAwareNode, { type: 'FunctionNode', name: null, nameLineno: null, initNode: function() { this.base('initNode', arguments); var ch = this.children; if (ch[1]) { this.name = ch[1].children; this.nameLineno = ch[1].lineno; } if (!(this.children[5] instanceof yy.BlockNode)) { this.children[5] = new yy.BlockNode( new yy.Lit('{', ch[4].lineno), new yy.Lit('return', getLesserLineNumber(ch[5])), new yy.List(ch[5]), new yy.Lit('}', ch[5].lineno) ); } this.block = this.children[5]; }, compile: function() { // declare scoped vars this.block.children[0].children = ' {' + this.context.compileVars(); this.base('compile', arguments); } }); yy.SliceNode = Class(yy.Node, { type: 'SliceNode', initNode: function() { this.base('initNode', arguments); this.start = this.children[2]; this.len = this.children[4]; }, compile: function() { var lit, start = this.start, len = this.len, ch = this.children; if (start === undefined) { start = new yy.Lit('0', ch[1].lineno); } this.children = [ ch[0], new yy.Lit('.slice(', ch[1].lineno), start ]; if (len !== undefined) { if (len instanceof yy.UnaryExprNode && typeof len.children[1].children === 'string') { lit = new yy.Lit(stringifyNode(len), len.lineno); lit.loc = len.children[1].loc; len = lit; } this.children.push( new yy.Lit(', ', ch[3].lineno), len ); } this.children.push(new yy.Lit(')', ch[5].lineno)); } }); yy.ObjectConstructorNode = Class(yy.Node, { type: 'ObjectConstructorNode', isLiteral: false, initNode: function() { this.base('initNode', arguments); this.className = this.children[1] ? this.children[1].children : null; this.constructorArgs = this.children[2]; }, compile: function() { var qn, ch = this.children, prefix = 'new ', constrArgs = this.constructorArgs, className = this.className; if (constrArgs) { if (constrArgs.keyValue) { if (className) { ch.splice(2, 0, new yy.Lit('(', ch[2].children[0].lineno)); ch.push(3, 0, new yy.Lit(')', ch[3].children[2].lineno)); } else { prefix = ''; this.isLiteral = true; } } else { if (!className) { ch[1] = new yy.Lit('Object', ch[0].lineno); } } } else { qn = ch[1]; ch.push(new yy.Lit('()', qn.children[qn.children.length - 1].lineno)); } ch[0] = new yy.Lit(prefix, ch[0].lineno); } }); yy.ObjectConstructorArgsNode = Class(yy.Node, { type: 'ObjectConstructorArgsNode', initNode: function() { var ch = this.children; if (!ch[1]) { this.keyValue = true; } if (ch[3]) { // key-value this.keyValue = true; this.checkKeyNames(ch[1]); } }, checkKeyNames: function(list) { var elements = list.children, names = {}, i, name, element, len = elements.length; for (i = 0; i < len; i++) { element = elements[i]; if (!(element instanceof yy.Lit || element.children[0] instanceof yy.Str)){ name = element.children[0].children; if (hasProp.call(names, name)) { this.error('Can not repeat object key "' + name + '"', element.children[0].lineno); } names[name] = true; } } }, compile: function() { if (this.keyValue) { this.children[0].children = '{'; this.children[2].children = '}'; if (!this.parent.isLiteral) { this.children.splice(2, 0, new yy.Lit(', _conf: true', this.children[2].lineno)) } } else { this.children[0].children = '('; this.children[2].children = ')'; } } }); yy.ArrayConstructorNode = Class(yy.Node, { type: 'ArrayConstructorNode', compile: function() { var ch = this.children[1]; if (ch && (ch.children.length % 2) === 0) { ch.children.pop(); } } }) yy.TypeAssertNode = Class(yy.Node, { type: 'TypeAssertNode', initNode: function() { this.base('initNode', arguments); this.typeParam = this.children[3]; }, compile: function() { var ch = this.children; this.children = [ new yy.Lit(this.runtimeFn('assertType'), ch[0].lineno), ch[0] ]; if (this.typeParam) { this.children.push(new yy.Lit(', ', ch[1].lineno)); this.children.push(this.typeParam); } this.children.push(new yy.Lit(')', ch[4].lineno)); } }); yy.AssignmentNode = Class(yy.Node, { type: 'AssignmentNode', rUpper: /^[A-Z]+$/, initNode: function() { this.base('initNode', arguments); var ch = this.children; if (ch[0] instanceof yy.VarNode) { ch[0].markAsUsedVar(); } } }); yy.VarNode = Class(yy.Node, { type: 'VarNode', name: null, initNode: function() { this.base('initNode', arguments); this.context = this.yy.env.context(); this.name = this.children[0].children; if (isEcmaReservedKeyWord(this.name)) { this.children[0].children = this.name += '_'; } }, markAsUsedVar: function() { this.context.addUsedVar(this.name); }, markAsLocalVar: function() { this.context.addLocalVar(this.name); } }); yy.Str = yy.StringNode = Class(yy.Lit, { type: 'Str, StringNode', compile: function() { var i, str, newNode, lineno = this.lineno, splitted = this.children.split(/\r\n|\n/), len = splitted.length; for (i = 0; i < len; i++) { str = splitted[i].replace(/^\s+/, ''); if (i < len - 1) { str += '\\'; } newNode = new yy.Lit(str, lineno + i); newNode.loc = { first_line: lineno + i, first_column: this.loc.first_column }; splitted[i] = newNode; } this.children = splitted; } }); yy.UseNode = Class(yy.Node, { type: 'UseNode', rAlias: /([\w\-]+)*(?:\.[\w\-]+)*$/, rClearName: /[^\w]/, extractedAlias: '', initNode: function() { this.base('initNode', arguments); var parsed; this.aliasNode = this.children[2]; this.targetNode = this.children[1]; this.route = this.yy.generateRoute(this.targetNode.children.substring(1, this.targetNode.children.length - 1)); // trim quotes this.alias = this.aliasNode ? this.aliasNode.children : ''; if (!this.route) { this.error('invalid route format', this.targetNode.lineno); } parsed = this.rAlias.exec(this.route); if (parsed) { this.extractedAlias = (parsed[1] || '').replace(this.rClearName, '_'); } this.yy.env.context().addLocalVar(this.alias || this.extractedAlias); }, compile: function() { var ch = this.children, route = this.route, alias = this.alias || this.extractedAlias; ch[0].children = 'require('; if (alias) { if (! this.aliasNode) { this.aliasNode = new yy.Lit(alias, ch[0].lineno); this.aliasNode.loc = ch[0].loc; } this.aliasNode.children += ' = '; } this.targetNode.children = "'" + route + "'"; this.children = [ this.aliasNode, ch[0], this.targetNode, new yy.Lit(');', ch[1].lineno) ]; } }); yy.MeNode = Class(yy.Lit, { type: 'MeNode', compile: function() { if (! this.insideClassContext()) { this.error("Using 'me' identifier outside a class context", this.lineno); } return this.base('compile', arguments); }, insideClassContext: function(){ var ctx = this.yy.env.context(); while (true) { if (ctx.ownerNode instanceof yy.ClassNode) { return true; } else if (ctx.parent){ ctx = ctx.parent; } else { return false; } } } }); yy.ClassNode = Class(yy.ContextAwareNode, { type: 'ClassNode', className: null, superClassName: null, initializerNode: null, initializerName: 'init', initNode: function() { this.base('initNode', arguments); var ch = this.children, cname = ch[1].children; this.className = cname; this.superClassName = this.getSuperClassName(); this.block = ch[3]; this.propertiesNames = []; this.propertySet = new yy.PropertySetNode(); this.propertySet.parent = this; this.methodSet = new yy.MethodSetNode(); this.methodSet.parent = this; this.setupSets(this.block); if (this.propertiesNames.length > 0) { this.hasProperties = true; } this.yy.env.registerClass(this); this.yy.env.context().addLocalVar(this.className); }, getSuperClassName: function() { if (! this.children[2]) { return null; } return stringifyNode(this.children[2].children[1]); }, setupSets: function(block) { var i, member, pos = -1, members = block.children[1] ? block.children[1].children : [], methods = [], properties = [], names = {}, methodFound = false; for (i = 0; i < members.length; i++) { pos++; member = members[i]; if (hasProp.call(names, member.name)) { this.error('Redeclaring "' + member.name + '" in a class body', member.nameLineno); } if (member instanceof yy.MethodNode) { if (member.name === this.initializerName) { if (pos !== 0) { this.error('"' + this.initializerName + '" must the first method in a class body', member.lineno); } this.initializerNode = member; } methods.push(members.splice(i, 1)[0]); methodFound = true; i--; } else if (methodFound === true) { this.error('Declareing property "' + member.name + '" after method declaration', member.nameLineno); } else { properties.push(members.splice(i, 1)[0]); this.propertiesNames.push(member.name); this.context.ignoreVar(member.name); i--; } if (member.name === this.className) { this.error('The member "' + member.name + '" is named equal to the owner class', member.nameLineno); } names[member.name] = true; } this.propertySet.adopt(properties); if (properties.length) { this.propertySet.lineno = properties[properties.length - 1].lineno; } else { this.propertySet.lineno = block.children[0].lineno; } this.methodSet.adopt(methods); if (methods.length) { this.methodSet.lineno = methods[methods.length - 1].lineno; } }, compileWithInit: function() { var i, len, newNode, extendsStr = '', ch = this.children; if (this.superClassName) { extendsStr = ', ' + this.superClassName; } newNode = new yy.Lit(this.className + ' = function ' + this.className, ch[0].lineno); newNode.loc = ch[1].loc; this.children = [ newNode, this.methodSet ]; if (this.superClassName) { this.methodSet.children[0].children[1].children += this.runtimeFn('subclass') + this.className + extendsStr +');'; } }, compileWithoutInit: function() { var i, len, newNode, ch = this.children, superInitStr = '', extendsStr = '', prepareInitStr = '', argsStr = this.propertiesNames.join(', '); if (this.hasProperties) { prepareInitStr = 'var _conf;_conf=((_conf=arguments[0])&&_conf._conf)?_conf:null; ' } if (this.superClassName) { extendsStr = ', ' + this.superClassName; if (this.hasProperties) { superInitStr = this.superClassName + '.prototype.constructor.call(this, _conf);'; } else { superInitStr = this.superClassName + '.prototype.constructor.apply(this, arguments);'; } } this.children = []; newNode = new yy.Lit(this.className + ' = function ' + this.className, ch[0].lineno); newNode.loc = ch[1].loc; this.children.push(newNode); newNode = new yy.Lit('('+ argsStr +'){' + prepareInitStr + superInitStr, ch[1].lineno); newNode.loc = ch[1].loc; this.children.push(newNode); this.children.push(this.propertySet); this.children.push(new yy.Lit('};', this.propertySet.lineno)); if (this.superClassName) { newNode = new yy.Lit(this.runtimeFn('subclass') + this.className + extendsStr +');', this.propertySet.lineno); newNode.loc = ch[2].loc; this.children.push(newNode); } this.children.push(this.methodSet); }, compile: function() { this.base('compile', arguments); if (this.initializerNode) { this.compileWithInit(); } else { this.compileWithoutInit(); } } }); yy.PropertySetNode = Class(yy.Node, { type: 'PropertySetNode' }); yy.PropertyNode = Class(yy.Node, { type: 'PropertyNode', name: null, nameLineno: null, hasDefaultValue: false, initNode: function() { this.base('initNode', arguments); this.name = this.children[0].children; this.nameLineno = this.children[0].lineno; if (this.children.length > 1) { this.hasDefaultValue = true; } }, compile: function() { var str = '', ch = this.children; ch[0].children = 'this.' + this.name; str = '=(_conf&&_conf.hasOwnProperty(\'' + this.name + '\'))?_conf.' + this.name + ':' + this.name; if (this.hasDefaultValue) { str += '==void 0?'; ch.splice(3, 0, new yy.Lit(':' + this.name, ch[2].lineno)); } ch[1].children = str; } }); yy.MethodSetNode = Class(yy.Node,{ type: 'MethodSetNode' }); yy.MethodNode = Class(yy.Node, { type: 'MethodNode', name: null, nameLineno: null, isInitializer: null, initNode: function() { this.base('initNode', arguments); this.name = this.children[0].name; this.nameLineno = this.children[0].children[1].lineno; }, compileInit: function() { var callSuper = false, superInitStr = '', superClassName = this.parent.parent.superClassName; if (superClassName) { preorder(this, function(node) { if (node instanceof yy.CallNode && node.name == 'super') { callSuper = true; return false; } }) if (!callSuper) { superInitStr = superClassName + '.prototype.constructor.apply(this, arguments);'; this.children[0].block.children.splice(1, 0, new yy.Lit(superInitStr, this.children[0].block.children[0].lineno)); } } this.isInitializer = true; this.children[0].children.splice(0, 2); this.children[0].context.addLocalVar('me', 'this'); }, compile: function() { if (this === this.parent.parent.initializerNode) { this.compileInit(); } else { var className = this.parent.parent.className; this.children[0].children[0].children = className + '.prototype.' + this.name + ' = function '; this.children[0].context.addLocalVar('me', 'this'); } } }); yy.CallNode = Class(yy.Node, { type: 'CallNode', name: null, initNode: function() { this.base('initNode', arguments); this.context = this.yy.env.context(); if (this.children[0] instanceof yy.VarNode) { this.name = this.children[0].name; } }, compile: function() { var ch = this.children, last, builtin; builtin = this[this.name + 'Builtin']; if (this.name && isBuiltinFn(this.name) && builtin) { builtin.call(this); } this.base('compile', arguments); }, superBuiltin: function() { var methodName, cls, newNode, ch = this.children, stub = '', ctx = this.yy.env.context(); if (!(ctx.ownerNode.parent instanceof yy.MethodNode)) { this.error("can not call 'super' builtin function outside of method scope", ch[3].lineno); } cls = ctx.ownerNode.parent.parent.parent; if (!cls.superClassName) { this.error("callign 'super' inside a class which does not inherit", ch[3].lineno); } if (ctx.ownerNode.parent.isInitializer) { methodName = 'constructor'; } else { methodName = ctx.ownerNode.parent.name; } if (ch[2]) { stub += cls.superClassName + '.prototype.' + methodName + '.call'; this.children[1].children = '(me, '; newNode = new yy.Lit(stub, ch[0].lineno); newNode.loc = ch[0].loc; this.children.splice(0, 1, newNode); } else { stub += cls.superClassName + '.prototype.' + methodName + '.apply'; this.children[1].children = '(me, arguments'; newNode = new yy.Lit(stub, ch[0].lineno); newNode.loc = ch[0].loc; this.children.splice(0, 1, newNode); } }, errorBuiltin: function() { var ch = this.children; if (this.parent instanceof yy.SimpleStmtNode) { // no arguments if (!ch[2]) { ch[2] = new yy.Lit('_error', ch[0].lineno); } ch[0].children[0].children = 'throw'; ch.splice(1, 1); ch.splice(2, 1); } else { this.children = [ new yy.Lit('_error', ch[0].lineno) ] } }, regexBuiltin: function() { if (!this.children[2]) { this.error('invalid regular expression pattern', this.children[0].lineno); } var flags, ch = this.children, params = ch[2], patternNode = params.children[0], flagsNode = params.children[2], regStart = /^\'/, regEnd = /\'$/, regDelim = /\//g, strDelim = "\\'", newLine = /\n(\s+)?/g, rFlags = /[gimy]+/, rEscape = /\\(?=[bBdDsSwW])/g; function cleanPattern(p) { return p.replace(newLine, '').replace(regDelim, '\\/'); } if (patternNode instanceof yy.StringNode && (flagsNode instanceof yy.StringNode || flagsNode == void 0)) { patternNode.children = cleanPattern(patternNode.children).replace(regStart, '\/') .replace(regEnd, '\/') .replace(newLine, '\\n') .replace(strDelim, "'"); if (patternNode.children === '//') { this.error('invalid regular expression pattern', patternNode.lineno); } if (flagsNode) { flags = flagsNode.children.replace(regStart, '').replace(regEnd, ''); if (flags !== '' && !rFlags.test(flags)) { this.error('invalid regular expression flags', flagsNode.lineno); } patternNode.children += flags; } this.children = [ patternNode ]; return; } else { ch[0].children[0].children = this.runtimePrefix('regex'); } if (patternNode instanceof yy.StringNode) { // special symbols // bBdDsSwW patternNode.children = cleanPattern(patternNode.children).replace(rEscape, '\\\\'); } }, chanBuiltin: function() { var ch = this.children; ch[0].children[0].children = this.runtimePrefix('chan'); }, timeoutBuiltin: function() { if (! isInGoExpr(this)) { this.error('unexpected timeout operation', this.lineno); } var ch = this.children; ch[0].children[0].children = this.runtimePrefix('timeout'); ch.unshift(new yy.Lit('yield ', getLesserLineNumber(ch[0]))) }, copyBuiltin: function() { var ch = this.children; ch[0].children[0].children = this.runtimePrefix('copyObj'); } }); yy.IfNode = Class(yy.Node, { type: 'IfNode', compile: function() { var ch = this.children; ch.splice(1, 0, new yy.Lit('(', ch[0].lineno)); ch.splice(3, 0, new yy.Lit(') ', ch[2].lineno)); } }); yy.ElseNode = Class(yy.Node, { type: 'ElseNode', compile: function() { var ch = this.children; // else if if (ch.length === 2) { ch.splice(1, 0, new yy.Lit(' ', ch[0].lineno)); } } }); yy.SwitchNode = Class(yy.Node, { type: 'SwitchNode', compile: function() { this.base('initNode', arguments); var ch = this.children; // no expresion if (ch[1] === undefined) { ch[1] = new yy.Lit('true', ch[0].lineno); } ch.splice(1, 0, new yy.Lit('(', ch[0].lineno)); ch.splice(3, 0, new yy.Lit(') ', ch[2].lineno)); } }); yy.CaseNode = Class(yy.Node, { type: 'CaseNode', compile: function() { this.base('compile', arguments); var ch = this.children, ls; this.handleFallThrough(ch[1]); //if is not "default" if (ch[3]) { ls = ch[3]; ls.children.push(new yy.Lit(' break; ', ls.last().lineno - 1)); } }, handleFallThrough: function(exprList) { var i, ls = exprList.children, len = ls.length; for (i = 0; i < len; i++) { if (ls[i].children === ',') { ls[i].children = ': case '; } } } }); yy.ForNode = Class(yy.Node, { type: 'ForNode', compile: function() { var ch = this.children; if (ch.length <= 3) { ch[0].children = 'while '; } if (ch.length === 2) { ch.splice(1, 0, new yy.Lit('true', ch[0].lineno)); } ch.splice(1, 0, new yy.Lit('(', ch[0].lineno)); ch.splice(ch.length - 1, 0, new yy.Lit(') ', getLesserLineNumber(ch[ch.length - 1]))); } }); // God save me. yy.ForInNode = Class(yy.Node, { type: 'ForInNode', compile: function() { var ctx = yy.env.context(), ch = this.children, k, v, str1, str2, str3, $i, $len, $coll, $keys; if (ch.length === 5) { /* for v in coll { } for (var $coll = coll, $keys = CRL_keys($coll), $i = 0, $len = $keys.length, v; $i < $len; $i++) {v = $coll[$keys[$i]];} */ v = ch[1].children[0].children; $i = ctx.generateVar('i'); $len = ctx.generateVar('len'); $coll = ctx.generateVar('coll'); $keys = ctx.generateVar('keys'); str1 = '(var ' + $coll + ' = '; str2 = ', ' + $keys + ' = ' + this.runtimeFn('keys') + $coll + '), ' + $i + ' = 0, ' + $len + ' = ' + $keys + '.length, ' + v + '; ' + $i + ' < ' + $len + '; ' + $i + '++) '; str3 = v + ' = ' + $coll + '[' + $keys + '[' + $i + ']];'; ch[1].markAsLocalVar(); ch.splice(1, 2, new yy.Lit(str1, ch[2].lineno)); ch.splice(3, 0, new yy.Lit(str2, ch[2].lineno)); ch[4].children.splice(1, 0, new yy.Lit(str3, ch[4].children[0].lineno)); } else { /* for k, v in coll { } for (var $coll = coll, $keys = CRL_keys($coll), $i = 0, $len = $keys.length, k, v; $i < $len; $i++) {k = $keys[$i]; v = $coll[k]; } */ k = ch[1].children[0].children; v = ch[3].children[0].children; $i = ctx.generateVar('i'); $len = ctx.generateVar('len'); $coll = ctx.generateVar('coll'); $keys = ctx.generateVar('keys'); str1 = '(var ' + $coll + ' = '; str2 = ', ' + $keys + ' = ' + this.runtimeFn('keys') + $coll + '), ' + $i + ' = 0, ' + $len + ' = ' + $keys + '.length, ' + k + ', ' + v + '; ' + $i + ' < ' + $len + '; ' + $i + '++) '; str3 = k + ' = ' + $keys + '[' + $i + ']; ' + v + ' = ' + $coll + '[' + k + '];'; ch[1].markAsLocalVar(); ch[3].markAsLocalVar(); ch.splice(1, 4, new yy.Lit(str1, ch[4].lineno)); ch.splice(3, 0, new yy.Lit(str2, ch[2].lineno)); ch[4].children.splice(1, 0, new yy.Lit(str3, ch[4].children[0].lineno)); } } }); yy.ForInRangeNode = Class(yy.Node, { type: 'ForInRangeNode', compile: function() { var ctx = yy.env.context(), ch = this.children, i, from, to; i = ch[1].children[0].children; from = ch[3] || new yy.Lit('0', ch[0].lineno); to = ch[5] || new yy.Lit('9e9', ch[0].lineno); /* for i in n:m { } for (var i = n; i < m; i++) { } */ this.children = [ new yy.Lit('for (var ' + i + ' = ', ch[0].lineno), from, new yy.Lit('; ' + i + ' < ', from.lineno), to, new yy.Lit('; ' + i + '++) ', to.lineno), ch[6], ]; } }); yy.CatchNode = Class(yy.Node, { type: 'CatchNode', compile: function() { var ch = this.children; ch[0].children = 'try { '; ch.splice(2, 0, new yy.Lit('; } catch (_error) ', ch[1].lineno)); } }); yy.CoalesceNode = Class(yy.Node, { type: 'CoalesceNode', ref: null, initNode: function() { if (this.children[0] instanceof yy.VarNode) { this.ref = this.children[0].name; } else { this.ref = yy.env.context().generateVar('ref'); this.yy.env.context().addLocalVar(this.ref); } }, compile: function() { var ref = this.ref, ch = this.children; // optimize resulting code ovoiding ref generation if (ch[0] instanceof yy.VarNode) { this.children = [ ch[0], new yy.Lit(' != null && '+ ref + ' != void 0 ? ' + ref + ' : ', ch[0].lineno), ch[2] ]; } else { this.children = [ new yy.Lit('(' + ref + ' = ', ch[0].lineno), ch[0], new yy.Lit(', ' + ref + ' != null && '+ ref + ' != void 0 ? ' + ref + ' : ', ch[0].lineno), ch[2], new yy.Lit(')', ch[2].lineno), ]; } } }); yy.ExistenceNode = Class(yy.Node, { type: 'ExistenceNode', ref: null, subject: null, init: function(sub) { this.subject = sub; if (sub instanceof yy.Lit) { this.error('Invalid operation with ' + sub.children, sub.lineno); } this.base('init', sub.children); }, initNode: function() { if (this.children[0] instanceof yy.VarNode ) { this.ref = this.children[0].name; } else { this.ref = yy.env.context().generateVar('ref'); this.yy.env.context().addLocalVar(this.ref); } }, compile: function() { var oldNode, newNode, condition, ref = this.ref, ch = this.children; if (this.subject instanceof yy.VarNode) { ref = this.ref = this.subject.name = this.subject.name; } // if call node if (this.subject instanceof yy.CallNode) { condition = ' !== \'function\' ? '; } // otherwise else { condition = ' === \'undefined\' || '+ ref + ' === null ? '; } // replace the first node of the // subject by new ref VarNode oldNode = this.subject.children[0]; newNode = new yy.VarNode(oldNode.children); newNode.loc = oldNode.loc; newNode.lineno = oldNode.lineno; newNode.name = oldNode.name; this.subject.children[0] = newNode; // optimize resulting code avoiding ref generation if (ch[0] instanceof yy.VarNode) { this.children = [ new yy.Lit('typeof ' + ref + condition, ch[0].lineno), new yy.Lit('void 0 : ' + ref, this.subject.lineno), this.subject, ]; } else if (ch[0] instanceof yy.Lit) { this.children = [ new yy.Lit('typeof ' + ref + condition, ch[0].lineno), new yy.Lit('void 0 : ' + ref, this.subject.lineno), this.subject, ]; } else { this.children = [ new yy.Lit('typeof (' + ref + ' = ', ch[0].lineno), ch[0], new yy.Lit(')' + condition, ch[0].lineno), new yy.Lit('void 0 : ' + ref, ch[ch.length - 1].lineno), this.subject, ]; } // re-adopt this.adopt(this.children); } }); yy.UnaryExistenceNode = Class(yy.ExistenceNode, { type: 'UnaryExistenceNode', initNode: function() { var ch = this.children; if (ch.length == 1 && (this.subject instanceof yy.VarNode)) { this.ref = this.subject.name; this.usingVar = true; } else { this.ref = yy.env.context().generateVar('ref'); this.yy.env.context().addLocalVar(this.ref); } }, compile: function() { var ch = this.children, ref = this.ref, condition = '!(typeof ' + ref + ' === \'undefined\' || ' + ref + ' === null)'; if (this.usingVar) { ch.splice(0, 1); } else { ch.splice(0, 0, new yy.Lit('((' + ref + ' = ', getLesserLineNumber(ch[0]))); ch.push(new yy.Lit('), ', this.lineno)); condition += ')'; } ch.push(new yy.Lit(condition, this.lineno)); } }) // check if a node is inside a `go` expression function isInGoExpr(node) { var goExprFound = false; while(node.parent) { node = node.parent; if (node instanceof yy.ContextAwareNode && !goExprFound) { return false; } if (node instanceof yy.GoExprNode) { goExprFound = true; } } return goExprFound; } yy.GoExprNode = Class(yy.Node, { type: 'GoExprNode', compile: function() { var ch = this.children, fnNode = ch[1]; ch[0].children = this.runtimePrefix('go(function* go()'); fnNode.children[fnNode.children.length - 1].children += ', this)'; } }) yy.SendAsyncNode = Class(yy.Node, { type: 'SendAsyncNode', compile: function() { if (! isInGoExpr(this)) { this.error('unexpected async operation', this.lineno); } var ch = this.children; ch[1].children = ','; ch.splice(0, 0, new yy.Lit('yield ' + this.runtimeFn('send'), ch[0].lineno)); ch.push(new yy.Lit(')', ch[ch.length - 1].lineno)); } }) yy.ReceiveAsyncNode = Class(yy.Node, { type: 'ReceiveAsyncNode', compile: function() { if (! isInGoExpr(this)) { this.error('unexpected async operation', this.lineno); } var ch = this.children; ch[0].children = 'yield ' + this.runtimeFn('receive'); ch.push(new yy.Lit(')', ch[ch.length - 1].lineno)); } }) yy.TemplateLiteralNode = Class(yy.Node, { type: 'TemplateLiteralNode', compile: function() { var str, list, i, len, item, ch = this.children; if (ch.length === 1) { // simple template ch[0] = new yy.StringNode(ch[0].children.substr(1), ch[0].loc); } else { // interpolation str = ch[0].children; ch[0] = new yy.StringNode(str.substring(1, str.length-1) + "' + ", ch[0].loc); str = ch[2].children; ch[2] = new yy.StringNode(" + '" + str.substring(1), ch[2].loc); list = ch[1]; for (i = -1, len = list.children.length-2; i < len;) { i+=2; item = list.children[i]; str = item.children; list.children[i] = new yy.StringNode(" + '" + str.substring(1, str.length-1) + "' + ", item.loc); } } } }) })(typeof cor === 'undefined' ? {} : cor);