syndicate-2017/js/compiler/es5.js

102 lines
3.4 KiB
JavaScript

//===========================================================================
// Copy of ohm-js/examples/ecmascript/es5.js to get browserify+brfs to work
//===========================================================================
/* eslint-env node */
'use strict';
// --------------------------------------------------------------------
// Imports
// --------------------------------------------------------------------
var fs = require('fs');
var path = require('path');
var ohm = require('ohm-js');
// --------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------
function isUndefined(x) { return x === void 0; }
// Take an Array of nodes, and whenever an _iter node is encountered, splice in its
// recursively-flattened children instead.
function flattenIterNodes(nodes) {
var result = [];
for (var i = 0; i < nodes.length; ++i) {
if (nodes[i]._node.ctorName === '_iter') {
result.push.apply(result, flattenIterNodes(nodes[i].children));
} else {
result.push(nodes[i]);
}
}
return result;
}
// Comparison function for sorting nodes based on their interval's start index.
function compareByInterval(node, otherNode) {
return node.interval.startIdx - otherNode.interval.startIdx;
}
function translateNonterminalCode(children, nodeTranslator) {
var flatChildren = flattenIterNodes(children).sort(compareByInterval);
var childResults = flatChildren.map(nodeTranslator);
if (flatChildren.length === 0 || childResults.every(isUndefined)) {
return undefined;
}
var code = '';
var interval = flatChildren[0].interval.collapsedLeft();
for (var i = 0; i < flatChildren.length; ++i) {
if (childResults[i] == null) {
// Grow the interval to include this node.
interval = interval.coverageWith(flatChildren[i].interval.collapsedRight());
} else {
interval = interval.coverageWith(flatChildren[i].interval.collapsedLeft());
code += interval.contents + childResults[i];
interval = flatChildren[i].interval.collapsedRight();
}
}
code += interval.contents;
return code;
}
// Semantic actions for the `modifiedSource` attribute (see below).
var modifiedSourceActions = {
_nonterminal: function(children) {
return translateNonterminalCode(children,
function(n) { return n.modifiedSource; })
|| this.interval.contents;
},
_iter: function(_) {
throw new Error('_iter semantic action should never be hit');
},
_terminal: function() {
return undefined;
}
};
// Instantiate the ES5 grammar.
var contents = fs.readFileSync(path.join(__dirname, 'es5.ohm'));
var g = ohm.grammars(contents).ES5;
var semantics = g.semantics();
// An attribute whose value is either a string representing the modified source code for the
// node, or undefined (which means that the original source code should be used).
semantics.addAttribute('modifiedSource', modifiedSourceActions);
// A simple wrapper around the `modifiedSource` attribute, which always returns a string
// containing the ES5 source code for the node.
semantics.addAttribute('asES5', {
_nonterminal: function(children) {
return isUndefined(this.modifiedSource) ? this.interval.contents : this.modifiedSource;
}
});
module.exports = {
grammar: g,
semantics: semantics,
translateNonterminalCode: translateNonterminalCode
};