Skip to content

Commit de37c1f

Browse files
Gerrnperlxsro
authored andcommitted
feat(formatting): Provides case formatting.
- 提供寄存器、指令、伪指令、运算符的格式化 - 可以选择是否在逗号后添加空格 - 对齐单行注释到下一行的指令
1 parent b9d2f18 commit de37c1f

File tree

1 file changed

+156
-23
lines changed

1 file changed

+156
-23
lines changed

masm-tasm/src/language/AsmDocumentFormattingEdit.ts

Lines changed: 156 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import * as vscode from "vscode";
22
import { eolString } from "../utils/eol";
33
import { DocInfo, linetype, Asmline } from "./scanDoc";
44

5+
type caseType = "upper" | "lower" | "title" | "off";
6+
57
interface FormatConfig {
68
tab: boolean;
79
tabSize: number;
@@ -24,24 +26,31 @@ interface FormatConfig {
2426
* - `title`: `Mov` `Jmp`
2527
* - `off`: keep the original case
2628
*/
27-
instructionCase: "upper" | "lower" | "title" | "off";
29+
instructionCase: caseType;
2830
/**
2931
* The case of registers
3032
*/
31-
registerCase: "upper" | "lower" | "title" | "off";
32-
33+
registerCase: caseType;
3334
/**
3435
* The case of directives
3536
*/
36-
directiveCase: "upper" | "lower" | "title" | "off";
37+
directiveCase: caseType;
38+
/**
39+
* The case of operators
40+
*/
41+
operatorCase: caseType;
3742
/**
3843
* Whether to align the operands
3944
*/
4045
alignOperand: boolean;
4146
/**
4247
* Whether to align the comments
4348
*/
44-
alignComment: boolean;
49+
alignTrailingComment: boolean;
50+
/**
51+
* Whether to align the single line comments
52+
*/
53+
alignSingleLineComment: boolean;
4554
/**
4655
* Whether to add a space after the comma
4756
*/
@@ -61,12 +70,14 @@ export class AsmDocFormat implements vscode.DocumentFormattingEditProvider {
6170
tab: !options.insertSpaces,
6271
tabSize: options.tabSize,
6372
align: "label",
64-
instructionCase: "off",
65-
registerCase: "off",
66-
directiveCase: "off",
67-
alignOperand: false,
68-
alignComment: false,
69-
spaceAfterComma: "off",
73+
instructionCase: "title",
74+
registerCase: "upper",
75+
directiveCase: "lower",
76+
operatorCase: "lower",
77+
alignOperand: true,
78+
alignTrailingComment: true,
79+
alignSingleLineComment: true,
80+
spaceAfterComma: "always",
7081
};
7182
const textedits: vscode.TextEdit[] = [];
7283
const docinfo = DocInfo.getDocInfo(document);
@@ -83,6 +94,7 @@ export class AsmDocFormat implements vscode.DocumentFormattingEditProvider {
8394
else {
8495
newText = align(docinfo.lines, item, config);
8596
}
97+
postFormat(newText, config);
8698
const range = document.validateRange(item.range);
8799
textedits.push(
88100
new vscode.TextEdit(range, newText.join(eolString(document.eol)))
@@ -93,6 +105,34 @@ export class AsmDocFormat implements vscode.DocumentFormattingEditProvider {
93105
}
94106
}
95107

108+
function postFormat(text: string[], config: FormatConfig) {
109+
for (let i = text.length - 1; i >= 0; i--) {
110+
// test if current line is a a comment only line, use regexp
111+
let line = text[i];
112+
// Remove the trailing spaces
113+
text[i] = text[i].trimEnd();
114+
if (/^\s*;/.test(line)) {
115+
// Align the single line comment
116+
if (config.alignSingleLineComment && i !== text.length) {
117+
line = line.trimStart();
118+
// align the single line comment to the next line, if next line is not empty
119+
const nextLine = text[i + 1];
120+
if (nextLine && nextLine !== "") {
121+
const match = nextLine.match(/^\s*/);
122+
if (match) {
123+
text[i] = match[0] + line;
124+
}
125+
}
126+
}
127+
}
128+
else if (config.directiveCase !== 'off') {
129+
// Convert the case of directives
130+
text[i] = convertDirectiveCase(text[i], config.directiveCase);
131+
}
132+
}
133+
return text;
134+
}
135+
96136
/**
97137
* Recursively align the code in a node
98138
* @param lines
@@ -257,22 +297,30 @@ function formatLine(
257297
const isVariable = line.type === linetype.variable;
258298
if (isLabel || isVariable) {
259299
const alignSize = config.align === 'indent' && isLabel ? config.tabSize - 1 : size.name;
260-
output.push(
261-
formatLabelLine(line, alignOpt, {
262-
...size,
263-
name: alignSize,
264-
}, config)
265-
);
300+
const str = formatLabelLine(line, alignOpt, {
301+
...size,
302+
name: alignSize,
303+
}, config);
304+
output.push(str);
266305
}
267306
else if (line.type === linetype.onlycomment) {
268307
output.push(indentStr(config) + line.comment);
269308
}
270309
else if (line.main) {
271310
let str = line.main.replace(/\s+/, " ");
311+
272312
if (line.comment) {
273-
//后补充空格
274-
str += space(size.name + 1 + size.operator + 1 + size.operand - str.length);
275-
str += indentStr(config, config.tabSize * 2) + line.comment;
313+
if (config.alignTrailingComment) {
314+
//后补充空格
315+
str += space(size.name + 1 + size.operator + 1 + size.operand - str.length) + indentStr(config, config.tabSize * 2);
316+
}
317+
else {
318+
const match = line.str.match(/\s*(?=;)/);
319+
if (match) {
320+
str += match[0];
321+
}
322+
}
323+
str += line.comment;
276324
}
277325
output.push(str);
278326
}
@@ -308,12 +356,36 @@ function formatLabelLine(
308356
}
309357
str += line.name ? space(indent) : indentStr(config, indent);
310358
}
311-
str += line.operator;
359+
str += convertCase(line.operator ?? '', config.instructionCase);
312360
if (line.operand || line.comment) { //操作码后补充空格
313-
str += `${space(size.operator - operatorLength)} ${line.operand}`;
361+
if (config.alignOperand) {
362+
str += space(size.operator - operatorLength);
363+
}
364+
str += ' ';
365+
let operand = line.operand ?? '';
366+
if (config.registerCase !== 'off' && line.operand && isLabel) {
367+
operand = convertRegisterCase(operand, config.registerCase);
368+
}
369+
if (config.operatorCase !== 'off' && line.operand) {
370+
operand = convertOperatorCase(operand, config.operatorCase);
371+
}
372+
if (config.spaceAfterComma !== 'off' && line.operand) {
373+
operand = adjustSpaceAfterComma(operand, config.spaceAfterComma === 'always');
374+
}
375+
str += operand;
314376
}
315377
if (line.comment) { //操作数后补充空格
316-
str += `${space(size.operand - operandLength)}${indentStr(config)}${line.comment}`;
378+
if (config.alignTrailingComment) {
379+
str += `${space(size.operand - operandLength)}${indentStr(config)}`;
380+
}
381+
else {
382+
// get the original \s before `;` in line.str
383+
const match = line.str.match(/\s*(?=;)/);
384+
if (match) {
385+
str += match[0];
386+
}
387+
}
388+
str += line.comment;
317389
}
318390
return str;
319391
}
@@ -339,4 +411,65 @@ function indentStr(config: { tab: boolean, tabSize: number }, size?: number) {
339411
const indenter = tab ? "\t" : space(tabSize);
340412
return indenter.repeat(Math.floor(size / tabSize)) + // initial indent
341413
space(size % tabSize); // align indent
414+
}
415+
416+
function convertCase(word: string, toCase: caseType) {
417+
switch (toCase) {
418+
case 'upper':
419+
return word.toUpperCase();
420+
case 'lower':
421+
return word.toLowerCase();
422+
case 'title':
423+
if (word.length === 0) {
424+
return word;
425+
}
426+
// Find the first letter
427+
const firstIndex = word.search(/[a-zA-Z]/);
428+
if (firstIndex === -1) {
429+
return word;
430+
}
431+
const first = word[firstIndex];
432+
const rest = word.slice(firstIndex + 1);
433+
return word.slice(0, firstIndex) + first.toUpperCase() + rest.toLowerCase();
434+
default:
435+
return word;
436+
}
437+
}
438+
439+
function convertCaseFor(str: string, toCase: caseType, regex: RegExp) {
440+
return str.replace(regex, (match) => {
441+
if (!match) {
442+
return match;
443+
}
444+
return convertCase(match, toCase);
445+
});
446+
}
447+
448+
449+
function convertRegisterCase(str: string, toCase: caseType) {
450+
const regex = /(?<!;.*?)\b((?<general>EAX|EBX|ECX|EDX|AX|BX|CX|DX|AL|AH|BL|BH|CL|CH|DL|DH)|(?<segment>CS|DS|ES|FS|GS|SS)|(?<pointer>DI|SI|BP|SP|IP)|(?<control>CR[01234])|(?<ProtectedMode>GDTR|IDTR|LDTR|TR)|(?<DebugTest>DR[0-7]|TR[3-7])|(?<float>R[0-7]))\b(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/gi;
451+
452+
return convertCaseFor(str, toCase, regex);
453+
}
454+
455+
function convertDirectiveCase(str: string, toCase: caseType) {
456+
const regex = /(?<!;.*?)(?<!\S)((?<x64>\.ALLOCSTACK|\.ENDPROLOG|PROC|\.PUSHFRAME|\.PUSHREG|\.SAVEREG|\.SAVEXMM128|\.SETFRAME)|(?<CodeLabels>ALIGN|EVEN|LABEL|ORG)|(?<ConditionalAssembly>ELSE|ELSEIF|ELSEIF2|IF|IF2|IFB|IFNB|IFDEF|IFNDEF|IFDIF|IFDIFI|IFE|IFIDN|IFIDNI)|(?<ConditionalControlFlow>\.BREAK|\.CONTINUE|\.ELSE|\.ELSEIF|\.ENDIF|\.ENDW|\.IF|\.REPEAT|\.UNTIL|\.UNTILCXZ|\.WHILE)|(?<ConditionalError>\.ERR|\.ERR2|\.ERRB|\.ERRDEF|\.ERRDIF|\.ERRDIFI|\.ERRE|\.ERRIDN|\.ERRIDNI|\.ERRNB|\.ERRNDEF|\.ERRNZ)|(?<DataAllocation>DB|DW|DD|DQ|DF|DT|ALIGN|BYTE|SBYTE|DWORD|SDWORD|EVEN|FWORD|LABEL|ORG|QWORD|REAL4|REAL8|REAL10|TBYTE|WORD|SWORD)|(?<Equates>=|EQU|TEXTEQU)|(?<ListingControl>\.CREF|\.LIST|\.LISTALL|\.LISTIF|\.LISTMACRO|\.LISTMACROALL|\.NOCREF|\.NOLIST|\.NOLISTIF|\.NOLISTMACRO|PAGE|SUBTITLE|\.TFCOND|TITLE)|(?<Macros>ENDM|EXITM|GOTO|LOCAL|MACRO|PURGE)|(?<Miscellaneous>ALIAS|ASSUME|COMMENT|ECHO|END|\.FPO|INCLUDE|INCLUDELIB|MMWORD|OPTION|POPCONTEXT|PUSHCONTEXT|\.RADIX|\.SAFESEH|XMMWORD|YMMWORD)|(?<Procedures>ENDP|INVOKE|PROC|PROTO)|(?<Processor>\.386|\.386P|\.387|\.486|\.486P|\.586|\.586P|\.686|\.686P|\.K3D|\.MMX|\.XMM)|(?<RepeatBlocks>ENDM|FOR|FORC|GOTO|REPEAT|WHILE)|(?<Scope>COMM|EXTERN|EXTERNDEF|INCLUDELIB|PUBLIC)|(?<Segment>\.ALPHA|ASSUME|\.DOSSEG|END|ENDS|GROUP|SEGMENT|\.SEQ)|(?<SimplifiedSegment>\.CODE|\.CONST|\.DATA|\.DATA\?|\.DOSSEG|\.EXIT|\.FARDATA|\.FARDATA\?|\.MODEL|\.STACK|\.STARTUP)|(?<String>CATSTR|INSTR|SIZESTR|SUBSTR)|(?<StructureAndRecord>ENDS|RECORD|STRUCT|TYPEDEF|UNION))\b(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/gi;
457+
458+
return convertCaseFor(str, toCase, regex);
459+
}
460+
461+
function convertOperatorCase(str: string, toCase: caseType) {
462+
const regex = /(?<!;.*?)(?<!\S)(ABS|ADDR|AND|DUP|REP|EQ|GE|GT|HIGH|HIGH32|HIGHWORD|IMAGEREL|LE|LENGTH|LENGTHOF|LOW|LOW32|LOWWORD|LROFFSET|LT|MASK|MOD|NE|NOT|OFFSET|OPATTR|OR|PTR|SEG|SHL|.TYPE|SECTIONREL|SHORT|SHR|SIZE|SIZEOF|THIS|TYPE|WIDTH|XOR)\b(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/gi;
463+
464+
return convertCaseFor(str, toCase, regex);
465+
}
466+
467+
function adjustSpaceAfterComma(str: string, space: boolean) {
468+
const regex = /(?<!;.*?)(\s*),(\s*)(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/gi;
469+
if (space) {
470+
return str.replace(regex, ', ');
471+
}
472+
else {
473+
return str.replace(regex, ',');
474+
}
342475
}

0 commit comments

Comments
 (0)