blob: b6c9010ccef43faf8fb105d22a315cfe3cb870e3 [file] [log] [blame] [raw]
// Copyright (c) 2012-2016, Matt Godbolt
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
var _ = require('underscore-node');
var expandTabs = require('./utils').expandTabs;
var sourceTag = /^;\s*([0-9]+)\s*:/;
var ignoreAll = /^\s*include listing\.inc$/;
var fileFind = /^; File\s+(.*)$/;
var gccExplorerDir = /\\gcc-explorer-compiler/; // has to match part of the path in compile.js (ugly)
// Parse into:
// * optional leading whitespace
// * middle part
// * comment part
var parseRe = /^(\s*)([^;]*)(;.*)*$/;
var isProc = /.*PROC$/;
var isEndp = /.*ENDP$/;
var constDef = /^([a-zA-Z_$@][a-zA-Z_$@0-9.]*)\s*=.*$/;
var labelFind = /[.a-zA-Z_@$][a-zA-Z_$@0-9.]*/g;
var labelDef = /^(.*):$/;
// Anything identifier-looking with a "@@" in the middle, and a comment at the end
// is treated as a mangled name. The comment will be used to replace the identifier.
var mangledIdentifier = /\?[^ ]+@@[^ ]+/;
var commentedLine = /([^;]+);\s*(.*)/;
var numberRe = /^\s+(([0-9a-f]+\b\s*)([0-9a-f][0-9a-f]\b\s*)*)(.*)/;
function debug() {
// console.log.apply(console, arguments);
}
function demangle(line) {
var match, comment;
if (!(match = line.match(mangledIdentifier))) return line;
if (!(comment = line.match(commentedLine))) return line;
return comment[1].trimRight().replace(match[0], comment[2]);
}
function AddrOpcoder() {
var self = this;
this.opcodes = [];
this.offset = null;
var prevOffset = -1;
var prevOpcodes = [];
this.hasOpcodes = function () {
return self.offset !== null;
};
this.onLine = function (line) {
var match = line.match(numberRe);
self.opcodes = [];
self.offset = null;
if (!match) {
prevOffset = -1;
return line;
}
var restOfLine = match[4];
var numbers = match[1].split(/\s+/).filter(function (x) {
return x;
}).map(function (x) {
return parseInt(x, 16);
});
// If restOfLine is empty, we should accumulate offset opcodes...
if (restOfLine === "") {
if (prevOffset < 0) {
// First in a batch of opcodes, so first is the offset
prevOffset = numbers[0];
prevOpcodes = numbers.splice(1);
} else {
prevOpcodes = prevOpcodes.concat(numbers);
}
} else {
if (prevOffset >= 0) {
// we had something from a prior line
self.offset = prevOffset;
self.opcodes = prevOpcodes.concat(numbers);
prevOffset = -1;
} else {
self.offset = numbers[0];
self.opcodes = numbers.splice(1);
}
}
return " " + restOfLine;
};
}
function ClParser(filters) {
this.filters = filters;
this.opcoder = new AddrOpcoder();
this.result = [];
this.inMain = false;
this.source = null;
this.labels = {};
this.currentLabel = null;
debug("Parser created");
}
ClParser.prototype._add = function (obj) {
var lastWasEmpty = this.result.length === 0 || this.result[this.result.length - 1].text === "";
if (obj.text === "" && lastWasEmpty) return;
if (this.currentLabel) obj.label = this.currentLabel;
obj.text = expandTabs(obj.text); // TODO where best?
if (this.filters.binary && this.opcoder.hasOpcodes()) {
obj.opcodes = this.opcoder.opcodes;
obj.address = this.opcoder.offset;
}
if (obj.keep) {
this.markUsed(obj.lineLabels);
}
this.result.push(obj);
debug(obj);
};
ClParser.prototype.markUsed = function (lineLabels) {
_.each(lineLabels, function (val, label) {
if (!this.labels[label]) {
debug("Marking " + label + " as used");
}
this.labels[label] = true;
}, this);
};
ClParser.prototype.addLine = function (line) {
if (!!line.match(ignoreAll)) return;
line = this.opcoder.onLine(line);
if (line.trim() === "") {
this._add({keep: true, text: "", source: null});
return;
}
var match;
if (!!(match = line.match(fileFind))) {
this.inMain = !!match[1].match(gccExplorerDir);
return;
}
if (!!(match = line.match(sourceTag))) {
if (this.inMain)
this.source = parseInt(match[1]);
return;
}
line = demangle(line);
match = line.match(parseRe);
if (!match) {
throw new Error("Unable to parse '" + line + "'");
}
var isIndented = match[1] !== "";
var command = match[2];
var comment = match[3] || "";
var lineLabels = {};
_.each(command.match(labelFind), function (label) {
lineLabels[label] = true;
}, this);
if (isIndented && this.opcoder.hasOpcodes()) {
this._add({keep: true, lineLabels: lineLabels, text: " " + command + comment, source: this.source});
} else {
var keep = !this.filters.directives;
if (command.match(isProc))
keep = true;
if (command.match(isEndp)) {
keep = true;
this.source = null;
this.currentLabel = null;
}
var tempDef = false;
if (!!(match = command.match(labelDef))) {
keep = !this.filters.labels;
this.currentLabel = match[1];
debug(match, this.currentLabel);
}
if (!!(match = command.match(constDef))) {
keep = !this.filters.labels;
this.currentLabel = match[1];
debug(match, this.currentLabel);
tempDef = true;
}
this._add({keep: keep, lineLabels: lineLabels, text: command + comment, source: null});
if (tempDef) this.currentLabel = null;
}
};
ClParser.prototype.findUsedInternal = function () {
var changed = false;
_.each(this.result, function (obj) {
if (obj.keep || !this.labels[obj.label]) {
return;
}
debug("Keeping", obj);
obj.keep = true;
this.markUsed(obj.lineLabels);
changed = true;
}, this);
debug("changed", changed);
return changed;
};
ClParser.prototype.findUsed = function () {
// TODO write tests that cover dependent labels being used.
var MaxIterations = 100;
for (var i = 0; i < MaxIterations; ++i) {
if (!this.findUsedInternal())
return;
}
};
ClParser.prototype.get = function () {
this.findUsed();
var lastWasEmpty = true;
return _.chain(this.result)
.filter(function (elem) {
if (!elem.keep) return false;
var thisIsEmpty = elem.text === "";
if (thisIsEmpty && lastWasEmpty) return false;
lastWasEmpty = thisIsEmpty;
return true;
})
.map(function (elem) {
return _.pick(elem, ['opcodes', 'address', 'source', 'text']);
})
.value();
};
module.exports = {
ClParser: ClParser
};