assembly-yasm-helper 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/.vscodeignore +10 -0
- package/.vsixmanifest +44 -0
- package/LICENSE +21 -0
- package/Themes/tasmDark.tmTheme.json +134 -0
- package/Themes/tasmHC.tmTheme.json +136 -0
- package/Themes/tasmLight.tmTheme.json +137 -0
- package/bin/assembly-yasm-lsp.js +2 -0
- package/changelog.md +130 -0
- package/configs/language-configuration.json +49 -0
- package/icon.png +0 -0
- package/lsp/server.js +423 -0
- package/out/core/compilerEngine.js +77 -0
- package/out/core/completionEngine.js +259 -0
- package/out/core/diagnosticEngine.js +305 -0
- package/out/core/foldingEngine.js +55 -0
- package/out/core/hoverEngine.js +62 -0
- package/out/core/referencesEngine.js +39 -0
- package/out/core/semanticEngine.js +64 -0
- package/out/data/enums.js +35 -0
- package/out/data/instructionSignatures.js +422 -0
- package/out/data/keywords.js +359 -0
- package/out/data/operandTypes.js +10 -0
- package/out/data/structs.js +132 -0
- package/out/engine/memoryAddressParser.js +45 -0
- package/out/engine/memoryState.js +38 -0
- package/out/engine/memoryTokenizer.js +28 -0
- package/out/extension.js +146 -0
- package/out/instructionRegistry.js +27 -0
- package/out/providers/compilerProvider.js +77 -0
- package/out/providers/completionProvider.js +73 -0
- package/out/providers/definitionProvider.js +41 -0
- package/out/providers/diagnosticProvider.js +32 -0
- package/out/providers/foldingProvider.js +18 -0
- package/out/providers/hoverProvider.js +25 -0
- package/out/providers/referencesProvider.js +28 -0
- package/out/providers/renameProvider.js +70 -0
- package/out/providers/semanticTokensProvider.js +29 -0
- package/out/providers/signatureHelpProvider.js +125 -0
- package/out/providers/symbolProvider.js +66 -0
- package/out/registry.js +167 -0
- package/out/scanner.js +174 -0
- package/out/tokenizer.js +67 -0
- package/out/utils.js +89 -0
- package/out/vsc/fileData.js +21 -0
- package/package.json +241 -0
- package/readme.md +80 -0
- package/snippets.json +141 -0
- package/syntaxes/assembly.tmLanguage.json +315 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const vscode = require("vscode");
|
|
3
|
+
const { KEYWORD_MAP } = require("../data/keywords");
|
|
4
|
+
const { INSTRUCTION_SIGNATURES } = require("../data/instructionSignatures");
|
|
5
|
+
|
|
6
|
+
// Convert an OperandType bitfield to a readable string
|
|
7
|
+
const _ts = b => ((b&3)===3)?'r/m':(b&1)?'reg':(b&2)?'mem':(b&4)?'imm':(b&8)?'label':'?';
|
|
8
|
+
|
|
9
|
+
class AsmSignatureHelpProvider {
|
|
10
|
+
|
|
11
|
+
constructor(registry) { this.registry = registry; }
|
|
12
|
+
|
|
13
|
+
provideSignatureHelp(document, position, token, context) {
|
|
14
|
+
if (!vscode.workspace.getConfiguration('assembly').get('enableSignatureHelp')) return null;
|
|
15
|
+
|
|
16
|
+
const line = document.lineAt(position.line).text.slice(0, position.character);
|
|
17
|
+
|
|
18
|
+
const commentIdx = line.indexOf(';');
|
|
19
|
+
if (commentIdx !== -1 && commentIdx < position.character) return null;
|
|
20
|
+
|
|
21
|
+
const trimmed = line.trimStart();
|
|
22
|
+
const words = trimmed.split(/\s+/);
|
|
23
|
+
if (words.length < 2) return null;
|
|
24
|
+
|
|
25
|
+
const opcode = words[0].toLowerCase();
|
|
26
|
+
const kw = KEYWORD_MAP.get(opcode);
|
|
27
|
+
const sigs = INSTRUCTION_SIGNATURES[opcode];
|
|
28
|
+
const macro = this.registry?.findMacro(opcode);
|
|
29
|
+
|
|
30
|
+
// count commas after opcode to determine active parameter index
|
|
31
|
+
const afterOpcode = line.slice(line.toLowerCase().indexOf(opcode) + opcode.length);
|
|
32
|
+
const commaCount = (afterOpcode.match(/,/g) || []).length;
|
|
33
|
+
|
|
34
|
+
const result = new vscode.SignatureHelp();
|
|
35
|
+
|
|
36
|
+
if (sigs?.length) {
|
|
37
|
+
result.signatures = sigs.map(form => {
|
|
38
|
+
const label = form.length
|
|
39
|
+
? `${opcode} ${form.map(_ts).join(', ')}`
|
|
40
|
+
: opcode;
|
|
41
|
+
const sig = new vscode.SignatureInformation(label, kw?.def || '');
|
|
42
|
+
let searchFrom = opcode.length + (form.length ? 1 : 0);
|
|
43
|
+
for (const bits of form) {
|
|
44
|
+
const name = _ts(bits);
|
|
45
|
+
const start = label.indexOf(name, searchFrom);
|
|
46
|
+
const end = start + name.length;
|
|
47
|
+
sig.parameters.push(new vscode.ParameterInformation([start, end]));
|
|
48
|
+
searchFrom = end + 1;
|
|
49
|
+
}
|
|
50
|
+
return sig;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// pick the best matching signature based on already-typed operands
|
|
54
|
+
const typedParts = afterOpcode.split(',');
|
|
55
|
+
let activeIdx = 0;
|
|
56
|
+
if (commaCount > 0) {
|
|
57
|
+
const completedOps = typedParts.slice(0, commaCount).map(o => o.trim());
|
|
58
|
+
for (let i = 0; i < sigs.length; i++) {
|
|
59
|
+
if (sigs[i].length < typedParts.length) continue;
|
|
60
|
+
if (completedOps.every((op, j) => !op || (this._classify(op) & sigs[i][j]))) {
|
|
61
|
+
activeIdx = i;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
result.activeSignature = activeIdx;
|
|
68
|
+
result.activeParameter = Math.min(commaCount, sigs[activeIdx].length - 1);
|
|
69
|
+
|
|
70
|
+
} else if (kw?.data) {
|
|
71
|
+
// fallback: single signature from keyword data
|
|
72
|
+
const sig = new vscode.SignatureInformation(kw.data, kw.def || '');
|
|
73
|
+
const paramStr = kw.data.slice(opcode.length).trim();
|
|
74
|
+
if (paramStr) {
|
|
75
|
+
let searchFrom = kw.data.length - paramStr.length;
|
|
76
|
+
for (const p of paramStr.split(',')) {
|
|
77
|
+
const name = p.trim();
|
|
78
|
+
const start = kw.data.indexOf(name, searchFrom);
|
|
79
|
+
const end = start + name.length;
|
|
80
|
+
sig.parameters.push(new vscode.ParameterInformation([start, end]));
|
|
81
|
+
searchFrom = end + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
result.signatures = [sig];
|
|
85
|
+
result.activeSignature = 0;
|
|
86
|
+
result.activeParameter = Math.min(commaCount, sig.parameters.length - 1);
|
|
87
|
+
|
|
88
|
+
} else if (macro?.argCount > 0) {
|
|
89
|
+
// user-defined macro: build signature from argCount
|
|
90
|
+
const params = Array.from({ length: macro.argCount }, (_, i) => `%${i + 1}`);
|
|
91
|
+
const label = `${macro.name} ${params.join(', ')}`;
|
|
92
|
+
const sig = new vscode.SignatureInformation(label, `(Macro) ${macro.name} — ${macro.argCount} arg${macro.argCount !== 1 ? 's' : ''}`);
|
|
93
|
+
let searchFrom = macro.name.length + 1;
|
|
94
|
+
for (const p of params) {
|
|
95
|
+
const start = label.indexOf(p, searchFrom);
|
|
96
|
+
const end = start + p.length;
|
|
97
|
+
sig.parameters.push(new vscode.ParameterInformation([start, end]));
|
|
98
|
+
searchFrom = end + 1;
|
|
99
|
+
}
|
|
100
|
+
result.signatures = [sig];
|
|
101
|
+
result.activeSignature = 0;
|
|
102
|
+
result.activeParameter = Math.min(commaCount, params.length - 1);
|
|
103
|
+
|
|
104
|
+
} else {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Lightweight operand classifier for signature matching
|
|
112
|
+
_classify(op) {
|
|
113
|
+
if (op.includes('[')) return 2; // MEM
|
|
114
|
+
const tokens = op.split(/\s+/).filter(t => t.length > 0);
|
|
115
|
+
if (tokens.some(t => this._isReg(t))) return 1; // REG
|
|
116
|
+
if (tokens.length === 1 && /^(0x[0-9a-f]+|[0-9][0-9a-f]*[hbd]?)$/i.test(tokens[0])) return 4; // IMM
|
|
117
|
+
return 8 | 4; // LABEL | IMM
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_isReg(w) {
|
|
121
|
+
return /^(r(ax|bx|cx|dx|si|di|bp|sp)|e(ax|bx|cx|dx|si|di|bp|sp)|[abcd][xhl]|[sd]il|[bs]pl|r\d{1,2}[bdw]?|[xyz]mm\d+|k[0-7]|[cdefgs]s|[re]ip|rflags|eflags)$/i.test(w);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { AsmSignatureHelpProvider };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const vscode = require("vscode");
|
|
3
|
+
|
|
4
|
+
class AsmDocumentSymbolProvider {
|
|
5
|
+
constructor(registry) {
|
|
6
|
+
this.registry = registry;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
provideDocumentSymbols(document, token) {
|
|
10
|
+
if (!vscode.workspace.getConfiguration('assembly').get('enableDocumentSymbols')) return [];
|
|
11
|
+
|
|
12
|
+
const symbols = [];
|
|
13
|
+
|
|
14
|
+
for (const proc of this.registry.procs) {
|
|
15
|
+
if (proc.line === undefined) continue;
|
|
16
|
+
const range = new vscode.Range(proc.line, 0, proc.line, 0);
|
|
17
|
+
symbols.push(new vscode.DocumentSymbol(
|
|
18
|
+
proc.name,
|
|
19
|
+
'(Procedure)',
|
|
20
|
+
vscode.SymbolKind.Function,
|
|
21
|
+
range,
|
|
22
|
+
range
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const v of this.registry.vars) {
|
|
27
|
+
if (v.line === undefined) continue;
|
|
28
|
+
const range = new vscode.Range(v.line, 0, v.line, 0);
|
|
29
|
+
symbols.push(new vscode.DocumentSymbol(
|
|
30
|
+
v.name,
|
|
31
|
+
v.type + (v.section ? ' [' + v.section + ']' : ''),
|
|
32
|
+
vscode.SymbolKind.Variable,
|
|
33
|
+
range,
|
|
34
|
+
range
|
|
35
|
+
));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const macro of this.registry.macros) {
|
|
39
|
+
if (macro.line === undefined) continue;
|
|
40
|
+
const range = new vscode.Range(macro.line, 0, macro.line, 0);
|
|
41
|
+
symbols.push(new vscode.DocumentSymbol(
|
|
42
|
+
macro.name,
|
|
43
|
+
'(Macro)',
|
|
44
|
+
vscode.SymbolKind.Module,
|
|
45
|
+
range,
|
|
46
|
+
range
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const label of this.registry.labels) {
|
|
51
|
+
if (label.line === undefined) continue;
|
|
52
|
+
const range = new vscode.Range(label.line, 0, label.line, 0);
|
|
53
|
+
symbols.push(new vscode.DocumentSymbol(
|
|
54
|
+
label.name,
|
|
55
|
+
'(Label)',
|
|
56
|
+
vscode.SymbolKind.Key,
|
|
57
|
+
range,
|
|
58
|
+
range
|
|
59
|
+
));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return symbols;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { AsmDocumentSymbolProvider };
|
package/out/registry.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { KEYWORD_MAP } = require("./data/keywords");
|
|
4
|
+
|
|
5
|
+
class SymbolRegistry {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
this.clear();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
clear() {
|
|
12
|
+
|
|
13
|
+
// arrays (สำหรับ iterate)
|
|
14
|
+
this.vars = [];
|
|
15
|
+
this.procs = [];
|
|
16
|
+
this.macros = [];
|
|
17
|
+
this.labels = [];
|
|
18
|
+
this.labelsEE = [];
|
|
19
|
+
this.structs = [];
|
|
20
|
+
|
|
21
|
+
this.structureInfo = [];
|
|
22
|
+
this.includedFiles = [];
|
|
23
|
+
|
|
24
|
+
// fast lookup maps
|
|
25
|
+
this.varMap = new Map();
|
|
26
|
+
this.procMap = new Map();
|
|
27
|
+
this.macroMap = new Map();
|
|
28
|
+
this.labelMap = new Map();
|
|
29
|
+
|
|
30
|
+
// duplicate guard
|
|
31
|
+
this.varSet = new Set();
|
|
32
|
+
this.procSet = new Set();
|
|
33
|
+
this.macroSet = new Set();
|
|
34
|
+
this.labelSet = new Set();
|
|
35
|
+
|
|
36
|
+
// section index
|
|
37
|
+
this.sectionIndex = new Map();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --------------------------------
|
|
41
|
+
// SECTION INDEX
|
|
42
|
+
// --------------------------------
|
|
43
|
+
|
|
44
|
+
addToSection(section, symbol) {
|
|
45
|
+
|
|
46
|
+
if (!section) return;
|
|
47
|
+
|
|
48
|
+
const key = section.toLowerCase();
|
|
49
|
+
|
|
50
|
+
if (!this.sectionIndex.has(key)) {
|
|
51
|
+
this.sectionIndex.set(key, []);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.sectionIndex.get(key).push(symbol);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getSectionSymbols(section) {
|
|
58
|
+
return this.sectionIndex.get(section.toLowerCase()) || [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --------------------------------
|
|
62
|
+
// VARIABLES
|
|
63
|
+
// --------------------------------
|
|
64
|
+
|
|
65
|
+
addVariable(v) {
|
|
66
|
+
|
|
67
|
+
const key = v.name.toLowerCase();
|
|
68
|
+
|
|
69
|
+
if (this.varSet.has(key)) return;
|
|
70
|
+
|
|
71
|
+
this.varSet.add(key);
|
|
72
|
+
|
|
73
|
+
this.vars.push(v);
|
|
74
|
+
this.varMap.set(key, v);
|
|
75
|
+
|
|
76
|
+
this.addToSection(v.section, v);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
findVariable(name) {
|
|
80
|
+
return this.varMap.get(name.toLowerCase());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --------------------------------
|
|
84
|
+
// PROCEDURES
|
|
85
|
+
// --------------------------------
|
|
86
|
+
|
|
87
|
+
addProcedure(p) {
|
|
88
|
+
|
|
89
|
+
const key = p.name.toLowerCase();
|
|
90
|
+
|
|
91
|
+
if (this.procSet.has(key)) return;
|
|
92
|
+
|
|
93
|
+
this.procSet.add(key);
|
|
94
|
+
|
|
95
|
+
this.procs.push(p);
|
|
96
|
+
this.procMap.set(key, p);
|
|
97
|
+
|
|
98
|
+
this.addToSection(p.section, p);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
findProcedure(name) {
|
|
102
|
+
return this.procMap.get(name.toLowerCase());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --------------------------------
|
|
106
|
+
// MACROS
|
|
107
|
+
// --------------------------------
|
|
108
|
+
|
|
109
|
+
addMacro(name, line, argCount = 0) {
|
|
110
|
+
|
|
111
|
+
const key = name.toLowerCase();
|
|
112
|
+
|
|
113
|
+
if (this.macroSet.has(key)) return;
|
|
114
|
+
|
|
115
|
+
this.macroSet.add(key);
|
|
116
|
+
|
|
117
|
+
const sym = { name, line, argCount };
|
|
118
|
+
this.macros.push(sym);
|
|
119
|
+
this.macroMap.set(key, sym);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
findMacro(name) {
|
|
123
|
+
return this.macroMap.get(name.toLowerCase());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --------------------------------
|
|
127
|
+
// LABELS
|
|
128
|
+
// --------------------------------
|
|
129
|
+
|
|
130
|
+
addLabel(name, line) {
|
|
131
|
+
|
|
132
|
+
const key = name.toLowerCase();
|
|
133
|
+
|
|
134
|
+
if (this.labelSet.has(key)) return;
|
|
135
|
+
|
|
136
|
+
this.labelSet.add(key);
|
|
137
|
+
|
|
138
|
+
const sym = { name, line };
|
|
139
|
+
this.labels.push(sym);
|
|
140
|
+
this.labelMap.set(key, sym);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
findLabel(name) {
|
|
144
|
+
|
|
145
|
+
const key = name.toLowerCase();
|
|
146
|
+
|
|
147
|
+
if (this.labelMap.has(key)) {
|
|
148
|
+
return this.labelMap.get(key);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return this.labelsEE.find(
|
|
152
|
+
l => l.name.toLowerCase() === key
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --------------------------------
|
|
157
|
+
// KEYWORDS
|
|
158
|
+
// --------------------------------
|
|
159
|
+
|
|
160
|
+
getKeyword(word) {
|
|
161
|
+
if (!word) return undefined;
|
|
162
|
+
return KEYWORD_MAP.get(word.toLowerCase());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = { SymbolRegistry };
|
package/out/scanner.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { AsmTokenizer } = require("./tokenizer");
|
|
6
|
+
const { Utils } = require("./utils");
|
|
7
|
+
const { Info, Procedure, Label } = require("./data/structs");
|
|
8
|
+
|
|
9
|
+
class DocumentScanner {
|
|
10
|
+
|
|
11
|
+
constructor(registry, currentFilePath = null) {
|
|
12
|
+
this.tokenizer = new AsmTokenizer();
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
this.currentFilePath = currentFilePath;
|
|
15
|
+
this.currentSection = "";
|
|
16
|
+
|
|
17
|
+
// regex cache
|
|
18
|
+
this.varRegex = /\b(db|dw|dd|dq|dt|resb|resw|resd|resq|equ)\b/i;
|
|
19
|
+
this.labelRegex = /^\s*((?:%%)?[A-Za-z_.$?][\w.$?]*):/;
|
|
20
|
+
this.procRegex = /^\s*([A-Za-z_.$?][\w.$?]*)\s+proc\b/i;
|
|
21
|
+
this.macroRegex = /^\s*%macro\b/i;
|
|
22
|
+
this.defineRegex = /^\s*%(define|assign)\b/i;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async scan(documentLines, clearPrevious = true) {
|
|
26
|
+
if (clearPrevious) {
|
|
27
|
+
this.registry.clear();
|
|
28
|
+
if (this.currentFilePath) {
|
|
29
|
+
this.registry.includedFiles.push(path.normalize(this.currentFilePath));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
this.currentSection = "";
|
|
33
|
+
|
|
34
|
+
for (let x = 0; x < documentLines.length; x++) {
|
|
35
|
+
const ctx = this._buildContext(documentLines[x]);
|
|
36
|
+
|
|
37
|
+
if (this._detectSection(ctx)) continue;
|
|
38
|
+
this._detectLabel(ctx, x);
|
|
39
|
+
this._detectVariable(ctx, x, clearPrevious);
|
|
40
|
+
await this._detectProcedure(ctx, x, documentLines);
|
|
41
|
+
await this._detectInclude(ctx);
|
|
42
|
+
this._detectMacro(ctx, x);
|
|
43
|
+
this._detectDefine(ctx);
|
|
44
|
+
this._detectExtern(ctx, x);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// -------------------------------------------------------
|
|
49
|
+
// Context builder — tokenizes each line once
|
|
50
|
+
// -------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
_buildContext(line) {
|
|
53
|
+
const clean = Utils.clearSpace(line);
|
|
54
|
+
return {
|
|
55
|
+
raw: line,
|
|
56
|
+
clean,
|
|
57
|
+
lower: clean.toLowerCase(),
|
|
58
|
+
noComment: line.replace(/;.*$/, "").trim(),
|
|
59
|
+
words: this.tokenizer.tokenize(line)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// -------------------------------------------------------
|
|
64
|
+
// Detection methods
|
|
65
|
+
// -------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
_detectSection(ctx) {
|
|
68
|
+
if (!ctx.lower.startsWith("section") && !ctx.lower.startsWith("segment")) return false;
|
|
69
|
+
if (ctx.words.length > 1) this.currentSection = ctx.words[1].toLowerCase();
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_detectLabel(ctx, x) {
|
|
74
|
+
const match = ctx.noComment.match(this.labelRegex);
|
|
75
|
+
if (match && !this.registry.findLabel(match[1])) {
|
|
76
|
+
this.registry.addLabel(match[1], x);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MASM label directive
|
|
80
|
+
if (ctx.lower.startsWith("label") && ctx.words.length >= 2) {
|
|
81
|
+
const name = ctx.words[1];
|
|
82
|
+
this.registry.labelsEE.push(
|
|
83
|
+
new Label(name, ctx.raw.substring(ctx.raw.indexOf(name) + name.length))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_detectVariable(ctx, x, clearPrevious) {
|
|
89
|
+
if (!clearPrevious) return;
|
|
90
|
+
if (ctx.words.length < 2 || !this.varRegex.test(ctx.words[1])) return;
|
|
91
|
+
|
|
92
|
+
this.registry.addVariable({
|
|
93
|
+
name: ctx.words[0],
|
|
94
|
+
type: ctx.words[1],
|
|
95
|
+
section: this.currentSection,
|
|
96
|
+
line: x
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async _detectProcedure(ctx, x, documentLines) {
|
|
101
|
+
const match = ctx.clean.match(this.procRegex);
|
|
102
|
+
if (!match || this.registry.findProcedure(match[1])) return;
|
|
103
|
+
|
|
104
|
+
const name = match[1];
|
|
105
|
+
const des = this._parseProcDocComments(documentLines, x, name);
|
|
106
|
+
|
|
107
|
+
const proc = new Procedure(name, des);
|
|
108
|
+
proc.section = this.currentSection;
|
|
109
|
+
proc.line = x;
|
|
110
|
+
this.registry.addProcedure(proc);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_parseProcDocComments(documentLines, procLine, name) {
|
|
114
|
+
const des = new Info("", "");
|
|
115
|
+
const text = [];
|
|
116
|
+
|
|
117
|
+
for (let ptr = procLine - 1; ptr >= 0; ptr--) {
|
|
118
|
+
const prev = Utils.clearSpace(documentLines[ptr]);
|
|
119
|
+
if (!prev.startsWith(";")) break;
|
|
120
|
+
text.push(documentLines[ptr].substring(documentLines[ptr].indexOf(";") + 1));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (const t of text) {
|
|
124
|
+
if (t.startsWith("@out: ")) des.output.push(t.substring(t.indexOf(" ", t.indexOf("@out: "))));
|
|
125
|
+
else if (t.startsWith("@arg: ")) des.params.push(Utils.clearSpace(t.substring(t.indexOf(" ", t.indexOf("@arg: ")))));
|
|
126
|
+
else des.des += t;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
des.name = name;
|
|
130
|
+
return des;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async _detectInclude(ctx) {
|
|
134
|
+
if (!ctx.lower.startsWith("%include") && !ctx.lower.startsWith("include")) return;
|
|
135
|
+
|
|
136
|
+
const fileMatch = ctx.raw.match(/['"](.*?)['"]/);
|
|
137
|
+
if (!fileMatch || !this.currentFilePath) return;
|
|
138
|
+
|
|
139
|
+
const baseDir = path.dirname(this.currentFilePath);
|
|
140
|
+
const normalized = path.normalize(path.resolve(baseDir, fileMatch[1]));
|
|
141
|
+
|
|
142
|
+
if (!fs.existsSync(normalized) || this.registry.includedFiles.includes(normalized)) return;
|
|
143
|
+
|
|
144
|
+
const filedata = fs.readFileSync(normalized, "utf8");
|
|
145
|
+
this.registry.includedFiles.push(normalized);
|
|
146
|
+
|
|
147
|
+
const oldSection = this.currentSection;
|
|
148
|
+
await this.scan(filedata.split(/\r?\n/), false);
|
|
149
|
+
this.currentSection = oldSection;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_detectMacro(ctx, x) {
|
|
153
|
+
if (!this.macroRegex.test(ctx.raw) || ctx.words.length <= 1) return;
|
|
154
|
+
const macroName = ctx.words[1];
|
|
155
|
+
const argCount = ctx.words.length > 2 ? parseInt(ctx.words[2], 10) : 0;
|
|
156
|
+
if (!this.registry.findMacro(macroName))
|
|
157
|
+
this.registry.addMacro(macroName, x, isNaN(argCount) ? 0 : argCount);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_detectDefine(ctx) {
|
|
161
|
+
if (!this.defineRegex.test(ctx.raw) || ctx.words.length <= 1) return;
|
|
162
|
+
const defineName = ctx.words[1];
|
|
163
|
+
if (!this.registry.defines) this.registry.defines = [];
|
|
164
|
+
if (!this.registry.defines.includes(defineName)) this.registry.defines.push(defineName);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_detectExtern(ctx, x) {
|
|
168
|
+
if (ctx.words[0]?.toLowerCase() !== "extern" || ctx.words.length <= 1) return;
|
|
169
|
+
const externName = ctx.words[1];
|
|
170
|
+
if (!this.registry.findLabel(externName)) this.registry.addLabel(externName, x);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { DocumentScanner };
|
package/out/tokenizer.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
class AsmTokenizer {
|
|
4
|
+
|
|
5
|
+
tokenize(line) {
|
|
6
|
+
|
|
7
|
+
const tokens = [];
|
|
8
|
+
let current = "";
|
|
9
|
+
let inString = false;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < line.length; i++) {
|
|
12
|
+
|
|
13
|
+
const ch = line[i];
|
|
14
|
+
|
|
15
|
+
// comment
|
|
16
|
+
if (!inString && ch === ';') {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// string start/end
|
|
21
|
+
if (ch === '"' || ch === "'") {
|
|
22
|
+
|
|
23
|
+
current += ch;
|
|
24
|
+
|
|
25
|
+
if (inString) {
|
|
26
|
+
tokens.push(current);
|
|
27
|
+
current = "";
|
|
28
|
+
inString = false;
|
|
29
|
+
} else {
|
|
30
|
+
inString = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (inString) {
|
|
37
|
+
current += ch;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// separators
|
|
42
|
+
if (/\s|,|\[|\]|\(|\)/.test(ch)) {
|
|
43
|
+
|
|
44
|
+
if (current.length) {
|
|
45
|
+
tokens.push(current);
|
|
46
|
+
current = "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!/\s/.test(ch)) {
|
|
50
|
+
tokens.push(ch);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
current += ch;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (current.length) {
|
|
60
|
+
tokens.push(current);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return tokens;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { AsmTokenizer };
|
package/out/utils.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { KeywordType } = require("./data/enums");
|
|
4
|
+
|
|
5
|
+
class Utils {
|
|
6
|
+
|
|
7
|
+
static typeMap = {
|
|
8
|
+
[KeywordType.instruction]: "(Command)",
|
|
9
|
+
[KeywordType.memoryAllocation]: "(Memory)",
|
|
10
|
+
[KeywordType.precompiled]: "(Instruction)",
|
|
11
|
+
[KeywordType.register]: "(Register)",
|
|
12
|
+
[KeywordType.savedWord]: "(Saved)",
|
|
13
|
+
[KeywordType.size]: "(Size)",
|
|
14
|
+
[KeywordType.label]: "(Label)",
|
|
15
|
+
[KeywordType.macro]: "(Macro)",
|
|
16
|
+
[KeywordType.method]: "(Procedure)",
|
|
17
|
+
[KeywordType.structure]: "(Structure)",
|
|
18
|
+
[KeywordType.variable]: "(Variable)"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
static getType(type) {
|
|
22
|
+
return Utils.typeMap[type] || "(Unknown)";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static clearSpace(str) {
|
|
26
|
+
return str.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static splitLine(line) {
|
|
30
|
+
|
|
31
|
+
return line
|
|
32
|
+
.split(/[,\s\[\]\(\)]+/)
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static isNumberStr(str) {
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
/^0x[0-9a-f]+$/i.test(str) ||
|
|
41
|
+
/^[0-9]+$/i.test(str) ||
|
|
42
|
+
/^[0-9a-f]+h$/i.test(str) ||
|
|
43
|
+
/^[01]+b$/i.test(str) ||
|
|
44
|
+
/^[0-9]+d$/i.test(str)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static getNumMsg(word) {
|
|
50
|
+
|
|
51
|
+
let base;
|
|
52
|
+
let value;
|
|
53
|
+
|
|
54
|
+
if (/^0x/i.test(word)) {
|
|
55
|
+
|
|
56
|
+
base = 16;
|
|
57
|
+
value = Number.parseInt(word, 16);
|
|
58
|
+
|
|
59
|
+
} else if (word.endsWith("h")) {
|
|
60
|
+
|
|
61
|
+
base = 16;
|
|
62
|
+
value = Number.parseInt(word.slice(0, -1), 16);
|
|
63
|
+
|
|
64
|
+
} else if (word.endsWith("b")) {
|
|
65
|
+
|
|
66
|
+
base = 2;
|
|
67
|
+
value = Number.parseInt(word.slice(0, -1), 2);
|
|
68
|
+
|
|
69
|
+
} else {
|
|
70
|
+
|
|
71
|
+
base = 10;
|
|
72
|
+
value = Number.parseInt(word, 10);
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (Number.isNaN(value)) return null;
|
|
77
|
+
|
|
78
|
+
let s = `(${base === 16 ? "Hexadecimal" : base === 2 ? "Binary" : "Decimal"} Number) ${word}:\n`;
|
|
79
|
+
|
|
80
|
+
if (base !== 10) s += `\tDecimal: ${value.toString(10)}\n`;
|
|
81
|
+
if (base !== 16) s += `\tHex: ${value.toString(16)}h / 0x${value.toString(16).toUpperCase()}\n`;
|
|
82
|
+
if (base !== 2) s += `\tBinary: ${value.toString(2)}b\n`;
|
|
83
|
+
|
|
84
|
+
return s;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { Utils };
|