68kcounter
Version:
68000 ASM source code cycle counter
221 lines (220 loc) • 8.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const timings_1 = require("../timings");
const syntax_1 = require("../syntax");
const nodes_1 = require("./nodes");
const evaluate_1 = __importDefault(require("./evaluate"));
const sizes_1 = __importDefault(require("../sizes"));
const totals_1 = require("../totals");
class Parser {
constructor() {
/** Variable/constants state */
this.vars = {};
/** Macro definitions */
this.macros = {};
/** Running total of parsed statement bytes sizes */
this.totalBytes = 0;
/** The name of the macro currently being defined */
this.currentMacro = null;
/** Start of the current repeating group */
this.reptStart = null;
/** Statements in the current repeating group */
this.reptStatements = [];
/** Are we currently in a BSS section */
this.bss = false;
// Directive groups:
this.assignments = [
syntax_1.Directives["="],
syntax_1.Directives.EQU,
syntax_1.Directives.FEQU,
syntax_1.Directives.SET,
];
this.sections = [
syntax_1.Directives.SECTION,
syntax_1.Directives.BSS,
syntax_1.Directives.BSS_C,
syntax_1.Directives.BSS_F,
syntax_1.Directives.DATA,
syntax_1.Directives.DATA_C,
syntax_1.Directives.DATA_F,
syntax_1.Directives.CODE,
syntax_1.Directives.CODE_C,
syntax_1.Directives.CODE_F,
];
}
/**
* Parse multiple lines of ASM code
*/
parse(input) {
// Split input into lines
const inputLines = input
.replace(/\r\n/g, "\n")
.replace(/\r/g, "\n")
.split("\n");
// Parse individual statements:
const statements = inputLines.map((l) => new nodes_1.StatementNode(l));
// Now do processing to add size/timing info and expand macros:
// Reset state
this.vars = {};
this.macros = {};
// Needs two passes to catch all references.
let lines = [];
for (let i = 0; i < 2; i++) {
lines = this.processStatements(statements);
}
return lines;
}
/**
* Add size and timing info to statements
*/
processStatements(statements) {
this.totalBytes = 0;
this.currentMacro = null;
this.bss = false;
this.reptStart = null;
this.reptStatements = [];
return statements.map(this.processStatement.bind(this));
}
processStatement(statement) {
let line = { statement };
// Currently defining a macro - store statements against this name rather than processing now
if (this.currentMacro) {
if (statement.opcode?.op.name === syntax_1.Directives.ENDM) {
// End macro
this.currentMacro = null;
}
else {
// Add statement to macro
this.macros[this.currentMacro].push(statement);
}
return line;
}
// Inside repeating section:
if (this.reptStart) {
if (statement.opcode?.op.name === syntax_1.Directives.ENDR) {
// End of repeat
line.macroLines = [];
// Expand and process repeated statements
const countOp = this.reptStart.statement.operands[0]?.text;
const reptCount = evaluate_1.default(countOp) || 0;
const statements = this.reptStatements;
this.reptStart = null;
this.reptStatements = [];
// TODO: support REPTN
for (let i = 0; i < reptCount; i++) {
line.macroLines = [
...line.macroLines,
...this.processStatements(statements),
];
}
}
else {
// Add statement to repeated list
this.reptStatements.push(statement);
}
}
// Process types:
else if (statement.isLabel()) {
line = this.processLabel(statement);
}
else if (statement.isMacro()) {
line = this.processMacro(statement);
}
else if (statement.isDirective()) {
line = this.processDirective(statement);
}
else if (statement.isInstruction()) {
line = this.processInstruction(statement);
}
// Calculate total of lines added by macro/rept:
if (line.macroLines) {
const macroTotals = totals_1.calculateTotals(line.macroLines);
line.bytes = macroTotals.bytes;
if (macroTotals.min[0]) {
line.timing = macroTotals.isRange
? {
values: [macroTotals.min, macroTotals.max],
labels: ["Min", "Max"],
}
: {
values: [macroTotals.min],
labels: [],
};
}
}
// Calculate byte size of this statement:
else if (!line.bytes) {
line.bytes = sizes_1.default(statement, this.vars);
line.bss = this.bss;
}
this.totalBytes += line.bytes;
return line;
}
processLabel(statement) {
// Assign running total of bytes to labels names
// This allows expressions to get byte count from ranges e.g. `dcb.b END-START`
this.vars[statement.label.text] = this.totalBytes;
return { statement, bytes: 0, bss: this.bss };
}
processMacro(statement) {
const line = { statement };
const macroName = statement.opcode.op.text;
if (this.macros[macroName]) {
const macroStatements = this.macros[macroName].map(({ text }) => {
for (let i = 1; i <= statement.operands.length; i++) {
const placeholder = "\\" + i;
text = text.replace(placeholder, statement.operands[i - 1].text);
}
return new nodes_1.StatementNode(text);
});
line.macroLines = this.processStatements(macroStatements);
}
return line;
}
processDirective(statement) {
const line = { statement };
// Variable Assignment:
if (statement.label &&
this.assignments.includes(statement.opcode.op.name) &&
statement.operands[0]) {
const value = evaluate_1.default(statement.operands[0].text, this.vars);
if (value) {
this.vars[statement.label.text] = value;
}
}
// Macro definition:
else if (statement.opcode.op.name === syntax_1.Directives.MACRO) {
const macroName = statement.label
? statement.label.text
: statement.operands[0]?.text;
if (macroName) {
this.currentMacro = macroName.toUpperCase();
this.macros[this.currentMacro] = [];
}
}
// Rept start:
else if (statement.opcode.op.name === syntax_1.Directives.REPT) {
this.reptStart = line;
}
// Section:
else if (this.sections.includes(statement.opcode.op.name)) {
// Is this a BSS section?
this.bss =
statement.opcode.op.name.includes("BSS") ||
statement.operands.some((o) => o.text.match(/^bss/i));
}
return line;
}
processInstruction(statement) {
const line = {
statement,
bss: this.bss,
timing: timings_1.instructionTimings(statement, this.vars) || undefined,
};
return line;
}
}
exports.default = Parser;