102 lines
3.4 KiB
JavaScript
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
|
|
};
|