blob: d2eccf96064bff61d6d414256c1bff4285a80bd1 [file] [log] [blame] [raw]
import {
AsmResultLabel,
AsmResultSource,
ParsedAsmResult,
ParsedAsmResultLine,
} from '../../types/asmresult/asmresult.interfaces.js';
import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
import {assert} from '../assert.js';
import {PropertyGetter} from '../properties.interfaces.js';
import * as utils from '../utils.js';
import {AsmParser} from './asm-parser.js';
import {AsmRegex} from './asmregex.js';
export class AsmParserZ88dk extends AsmParser {
constructor(compilerProps: PropertyGetter) {
super(compilerProps);
this.asmOpcodeRe = /^\s*(?<disasm>.*)\s*;\[(?<address>[\da-f]+)]\s*(?<opcodes>([\da-f]{2} ?)+)/;
this.sourceTag = /^\s+C_LINE\s*(\d+),"([^"]+)(::\w*::\d*::\d*)?"/;
this.labelDef = /^\.([\w$.@]+)$/i;
this.definesGlobal = /^\s*GLOBAL\s*([.A-Z_a-z][\w$.]*)/;
this.directive = /^\s*(C_LINE|MODULE|INCLUDE|SECTION|GLOBAL|EXTERN|IF|ENDIF)/;
}
override processAsm(asmResult: string, filters: ParseFiltersAndOutputOptions): ParsedAsmResult {
if (filters.binary || filters.binaryObject) return this.processBinaryAsm(asmResult, filters);
const startTime = process.hrtime.bigint();
if (filters.commentOnly) {
// Remove any block comments that start and end on a line if we're removing comment-only lines.
asmResult = asmResult.replace(this.blockComments, '');
}
const asm: ParsedAsmResultLine[] = [];
const labelDefinitions: Record<string, number> = {};
let asmLines = utils.splitLines(asmResult);
const startingLineCount = asmLines.length;
if (filters.preProcessLines !== undefined) {
asmLines = filters.preProcessLines(asmLines);
}
const labelsUsed = this.findUsedLabels(asmLines, filters.directives);
let prevLabel = '';
let source: AsmResultSource | undefined | null;
let mayRemovePreviousLabel = true;
let keepInlineCode = false;
let lastOwnSource: AsmResultSource | undefined | null;
const dontMaskFilenames = filters.dontMaskFilenames;
function maybeAddBlank() {
const lastBlank = asm.length === 0 || asm[asm.length - 1].text === '';
if (!lastBlank) asm.push({text: '', source: null, labels: []});
}
const handleSource = line => {
const match = line.match(this.sourceTag);
if (match) {
const sourceLine = parseInt(match[1]);
const file = utils.maskRootdir(match[2]);
if (file) {
if (dontMaskFilenames) {
source = {
file: file,
line: sourceLine,
mainsource: !!this.stdInLooking.test(file),
};
} else {
source = {
file: this.stdInLooking.test(file) ? null : file,
line: sourceLine,
};
}
const sourceCol = parseInt(match[3]);
if (!isNaN(sourceCol) && sourceCol !== 0) {
source.column = sourceCol;
}
} else {
source = null;
}
}
};
let inCustomAssembly = 0;
for (let line of asmLines) {
if (line.trim() === '') {
maybeAddBlank();
continue;
}
if (this.startAppBlock.test(line) || this.startAsmNesting.test(line)) {
inCustomAssembly++;
} else if (this.endAppBlock.test(line) || this.endAsmNesting.test(line)) {
inCustomAssembly--;
}
handleSource(line);
if (source && (source.file === null || source.mainsource)) {
lastOwnSource = source;
}
if (this.endBlock.test(line)) {
source = null;
prevLabel = '';
lastOwnSource = null;
}
if (filters.libraryCode && !lastOwnSource && source && source.file !== null && !source.mainsource) {
if (mayRemovePreviousLabel && asm.length > 0) {
const lastLine = asm[asm.length - 1];
const labelDef = lastLine.text ? lastLine.text.match(this.labelDef) : null;
if (labelDef) {
asm.pop();
keepInlineCode = false;
delete labelDefinitions[labelDef[1]];
} else {
keepInlineCode = true;
}
mayRemovePreviousLabel = false;
}
if (!keepInlineCode) {
continue;
}
} else {
mayRemovePreviousLabel = true;
}
if (filters.commentOnly && this.commentOnly.test(line)) {
continue;
}
if (inCustomAssembly > 0) line = this.fixLabelIndentation(line);
let match = line.match(this.labelDef);
if (!match) match = line.match(this.assignmentDef);
if (match) {
// It's a label definition.
if (labelsUsed[match[1]] === undefined) {
// It's an unused label.
if (filters.labels) {
continue;
}
} else {
// A used label.
prevLabel = match[1];
labelDefinitions[match[1]] = asm.length + 1;
}
}
if (!match && filters.directives) {
// Check for directives only if it wasn't a label; the regexp would
// otherwise misinterpret labels as directives.
if (this.dataDefn.test(line) && prevLabel) {
// We're defining data that's being used somewhere.
} else {
// .inst generates an opcode, so does not count as a directive
if (this.directive.test(line) && !this.instOpcodeRe.test(line)) {
continue;
}
}
}
line = utils.expandTabs(line);
const text = AsmRegex.filterAsmLine(line, filters);
const labelsInLine = match ? [] : this.getUsedLabelsInLine(text);
asm.push({
text: text,
source: this.hasOpcode(line, false) ? source || null : null,
labels: labelsInLine,
});
}
this.removeLabelsWithoutDefinition(asm, labelDefinitions);
const endTime = process.hrtime.bigint();
return {
asm: asm,
labelDefinitions: labelDefinitions,
parsingTime: ((endTime - startTime) / BigInt(1000000)).toString(),
filteredCount: startingLineCount - asm.length,
};
}
override processBinaryAsm(asmResult: string, filters: ParseFiltersAndOutputOptions): ParsedAsmResult {
const startTime = process.hrtime.bigint();
const asm: ParsedAsmResultLine[] = [];
const labelDefinitions = {};
let asmLines = asmResult.split('\n');
const startingLineCount = asmLines.length;
const source = null;
// Handle "error" documents.
if (asmLines.length === 1 && asmLines[0][0] === '<') {
return {
asm: [{text: asmLines[0], source: null}],
};
}
if (filters.preProcessBinaryAsmLines !== undefined) {
asmLines = filters.preProcessBinaryAsmLines(asmLines);
}
for (const line of asmLines) {
const labelsInLine: AsmResultLabel[] = [];
if (asm.length >= this.maxAsmLines) {
if (asm.length === this.maxAsmLines) {
asm.push({
text: '[truncated; too many lines]',
source: null,
labels: labelsInLine,
});
}
continue;
}
const match = line.match(this.asmOpcodeRe);
if (match) {
assert(match.groups);
const address = parseInt(match.groups.address, 16);
const opcodes = (match.groups.opcodes || '').split(' ').filter(x => !!x);
const disassembly = ' ' + AsmRegex.filterAsmLine(match.groups.disasm.trimEnd(), filters);
const destMatch = line.match(this.destRe);
if (destMatch) {
const labelName = destMatch[2];
const startCol = disassembly.indexOf(labelName) + 1;
labelsInLine.push({
name: labelName,
range: {
startCol: startCol,
endCol: startCol + labelName.length,
},
});
}
asm.push({
opcodes: opcodes,
address: address,
text: disassembly,
source: source,
labels: labelsInLine,
});
}
}
const endTime = process.hrtime.bigint();
return {
asm: asm,
labelDefinitions: labelDefinitions,
parsingTime: ((endTime - startTime) / BigInt(1000000)).toString(),
filteredCount: startingLineCount - asm.length,
};
}
}