@nirguna/plugin-fasm 1.0.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/README.md +48 -0
- package/lib/add-label-prefix/index.js +50 -0
- package/lib/apply-equality/index.js +8 -0
- package/lib/apply-inc/index.js +8 -0
- package/lib/apply-include/index.js +17 -0
- package/lib/apply-registers/index.js +44 -0
- package/lib/apply-types/index.js +79 -0
- package/lib/convert-args-to-regs/index.js +170 -0
- package/lib/convert-assign-to-add/index.js +5 -0
- package/lib/convert-assign-to-and/index.js +5 -0
- package/lib/convert-assign-to-member/index.js +5 -0
- package/lib/convert-assign-to-mov/index.js +53 -0
- package/lib/convert-assign-to-or/index.js +5 -0
- package/lib/convert-assign-to-shl/index.js +5 -0
- package/lib/convert-assign-to-sub/index.js +5 -0
- package/lib/convert-assign-to-xor/index.js +5 -0
- package/lib/convert-await-to-call/index.js +53 -0
- package/lib/convert-bios-clear-screen-to-int-10/index.js +8 -0
- package/lib/convert-bios-print-line-to-int-10/index.js +52 -0
- package/lib/convert-bios-read-char-to-int-16/index.js +18 -0
- package/lib/convert-bios-read-sector-to-int-13/index.js +80 -0
- package/lib/convert-bios-reboot-to-jmp-far/index.js +5 -0
- package/lib/convert-bios-scroll-to-int-10/index.js +16 -0
- package/lib/convert-const-to-equ/index.js +15 -0
- package/lib/convert-dec-to-hex/index.js +19 -0
- package/lib/convert-declaration-to-mov/index.js +23 -0
- package/lib/convert-do-while-to-jnz/index.js +155 -0
- package/lib/convert-equ-call-to-member/index.js +30 -0
- package/lib/convert-function-to-label/index.js +114 -0
- package/lib/convert-if-to-jmp/index.js +166 -0
- package/lib/convert-if-to-jmp/operator.js +27 -0
- package/lib/convert-linux-exit-to-syscall/index.js +15 -0
- package/lib/convert-linux-write-to-syscall/index.js +48 -0
- package/lib/convert-mov-to-add/index.js +5 -0
- package/lib/convert-return-to-eax/index.js +76 -0
- package/lib/convert-strncmp-to-repe-cmpsb/index.js +23 -0
- package/lib/convert-ternary-to-if/index.js +5 -0
- package/lib/convert-ureg-to-reg/index.js +108 -0
- package/lib/convert-while-to-jz/index.js +158 -0
- package/lib/extract-labeled-block/index.js +68 -0
- package/lib/index.js +93 -0
- package/lib/remove-useless-braces/index.js +5 -0
- package/lib/remove-useless-declarations/index.js +7 -0
- package/lib/remove-useless-promise/index.js +5 -0
- package/lib/split-assign-await-with-assign-eax/index.js +41 -0
- package/lib/split-binary-expression/index.js +31 -0
- package/lib/split-stack-operations/index.js +50 -0
- package/lib/switch-cmp-operands/index.js +19 -0
- package/package.json +56 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {operator, types} from 'putout';
|
|
2
|
+
|
|
3
|
+
const {isArrayExpression} = types;
|
|
4
|
+
const {extract} = operator;
|
|
5
|
+
|
|
6
|
+
export const report = () => `Use '0x13' instead of 'bios.readSector()'`;
|
|
7
|
+
|
|
8
|
+
export const match = () => ({
|
|
9
|
+
'bios.readSector()': (vars, path) => {
|
|
10
|
+
return path.parentPath.isExpressionStatement();
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const replace = () => ({
|
|
15
|
+
'__a = bios.readSector(__object)': ({__object}, path) => {
|
|
16
|
+
const {line} = path.node.loc.start;
|
|
17
|
+
const {
|
|
18
|
+
count = 1,
|
|
19
|
+
buffer = 0,
|
|
20
|
+
sector = 0,
|
|
21
|
+
track = 0,
|
|
22
|
+
disk = 0,
|
|
23
|
+
head = 0,
|
|
24
|
+
} = parseArgs(__object.properties);
|
|
25
|
+
|
|
26
|
+
return `{
|
|
27
|
+
al = ${count};
|
|
28
|
+
bx = ${buffer};
|
|
29
|
+
cl = ${sector};
|
|
30
|
+
ch = ${track};
|
|
31
|
+
dl = ${disk};
|
|
32
|
+
dh = ${head};
|
|
33
|
+
${createReadSector(line)}
|
|
34
|
+
mov(__a, ax);
|
|
35
|
+
}`;
|
|
36
|
+
},
|
|
37
|
+
'__a = bios.readSector()': (vars, path) => {
|
|
38
|
+
const {line} = path.node.loc.start;
|
|
39
|
+
|
|
40
|
+
return `{
|
|
41
|
+
${createReadSector(line)}
|
|
42
|
+
mov(__a, ax);
|
|
43
|
+
}`;
|
|
44
|
+
},
|
|
45
|
+
'bios.readSector()': (vars, path) => {
|
|
46
|
+
const {line} = path.node.loc.start;
|
|
47
|
+
return createReadSector(line);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function createReadSector(line) {
|
|
52
|
+
return `{
|
|
53
|
+
ah = 2;
|
|
54
|
+
int(0x13);
|
|
55
|
+
jnc(__nirguna_read_sector_ok_${line});
|
|
56
|
+
al = 1;
|
|
57
|
+
jmp(__nirguna_read_sector_end_${line});
|
|
58
|
+
__nirguna_read_sector_ok_${line}:
|
|
59
|
+
ax = 0
|
|
60
|
+
__nirguna_read_sector_end_${line}:
|
|
61
|
+
clc();
|
|
62
|
+
}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseArgs(properties) {
|
|
66
|
+
const result = {};
|
|
67
|
+
|
|
68
|
+
for (const {key, value} of properties) {
|
|
69
|
+
const extracted = extract(value);
|
|
70
|
+
|
|
71
|
+
if (isArrayExpression(value)) {
|
|
72
|
+
result[key.name] = `[${extracted}]`;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
result[key.name] = extracted;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const report = () => `Use '0x10' instead of 'bios.readChar()'`;
|
|
2
|
+
|
|
3
|
+
export const match = () => ({
|
|
4
|
+
'bios.scroll()': (vars, path) => {
|
|
5
|
+
return path.parentPath.isExpressionStatement();
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const replace = () => ({
|
|
10
|
+
'bios.scroll()': `{
|
|
11
|
+
cx = 0; // от 00:00
|
|
12
|
+
ax = 0x601; // Прокрутка вверх на одну строку
|
|
13
|
+
dx = 0x184f; // 24:79 (весь экран)
|
|
14
|
+
int(0x10);
|
|
15
|
+
}`,
|
|
16
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {types} from 'putout';
|
|
2
|
+
|
|
3
|
+
const {isProgram} = types;
|
|
4
|
+
|
|
5
|
+
export const report = () => `Use 'equ' instead of 'const'`;
|
|
6
|
+
|
|
7
|
+
export const match = () => ({
|
|
8
|
+
'const __a = __b': (vars, path) => {
|
|
9
|
+
return isProgram(path.parentPath);
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const replace = () => ({
|
|
14
|
+
'const __a = __b': '__a.equ = __b;',
|
|
15
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const report = () => `Use hex index of dec`;
|
|
2
|
+
|
|
3
|
+
export const filter = (path) => {
|
|
4
|
+
const {raw, value} = path.node;
|
|
5
|
+
|
|
6
|
+
if (value < 10)
|
|
7
|
+
return false;
|
|
8
|
+
|
|
9
|
+
return !raw?.startsWith('0x');
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const fix = (path) => {
|
|
13
|
+
const {value} = path.node;
|
|
14
|
+
path.node.raw = '0x' + value.toString(16);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const include = () => [
|
|
18
|
+
'NumericLiteral',
|
|
19
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {types} from 'putout';
|
|
2
|
+
import {isRegister} from '@nirguna/operator-fasm/regs';
|
|
3
|
+
|
|
4
|
+
const {isExportDeclaration} = types;
|
|
5
|
+
|
|
6
|
+
export const report = () => `Use 'mov' instead of 'const'`;
|
|
7
|
+
|
|
8
|
+
export const match = () => ({
|
|
9
|
+
'const __a = __b': ({__a}, path) => {
|
|
10
|
+
if (!isRegister(__a.name))
|
|
11
|
+
return false;
|
|
12
|
+
|
|
13
|
+
return !isExportDeclaration(path.parentPath);
|
|
14
|
+
},
|
|
15
|
+
'let __a = __b': ({__a}) => {
|
|
16
|
+
return isRegister(__a.name);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const replace = () => ({
|
|
21
|
+
'const __a = __b': 'mov(__a, __b)',
|
|
22
|
+
'let __a = __b': 'mov(__a, __b)',
|
|
23
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
template,
|
|
3
|
+
types,
|
|
4
|
+
operator,
|
|
5
|
+
} from 'putout';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
insertAfter,
|
|
9
|
+
replaceWith,
|
|
10
|
+
compare,
|
|
11
|
+
extract,
|
|
12
|
+
} = operator;
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
identifier,
|
|
16
|
+
expressionStatement,
|
|
17
|
+
isCallExpression,
|
|
18
|
+
labeledStatement,
|
|
19
|
+
isBooleanLiteral,
|
|
20
|
+
} = types;
|
|
21
|
+
|
|
22
|
+
const createStartLabel = (line) => `__nirguna_do_while_${line}`;
|
|
23
|
+
const createConditionLabel = (line) => `__nirguna_do_while_condition_${line}`;
|
|
24
|
+
|
|
25
|
+
export const report = () => `Use 'jnz' instead of 'do-while'`;
|
|
26
|
+
|
|
27
|
+
export const match = () => ({
|
|
28
|
+
'do {__body} while (--__a)': ({__a}) => {
|
|
29
|
+
return /^(e?cx|cl)$/.test(__a.name);
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const replace = () => ({
|
|
34
|
+
'do {__body} while (--__a)': ({__body}, path) => {
|
|
35
|
+
const {line} = path.node.loc.start;
|
|
36
|
+
const startLabel = createStartLabel(line);
|
|
37
|
+
const loopExpression = template.ast(`loop(${startLabel})`);
|
|
38
|
+
const conditionLabel = createConditionLabel(line);
|
|
39
|
+
|
|
40
|
+
let conditionExpression = expressionStatement(loopExpression);
|
|
41
|
+
const wasContinue = maybeReplaceContinueWithJmp(path, conditionLabel);
|
|
42
|
+
|
|
43
|
+
if (wasContinue)
|
|
44
|
+
conditionExpression = labeledStatement(identifier(conditionLabel), conditionExpression);
|
|
45
|
+
|
|
46
|
+
__body.body.push(conditionExpression);
|
|
47
|
+
maybeReplaceBreak(path, line);
|
|
48
|
+
|
|
49
|
+
return `${startLabel}: __body`;
|
|
50
|
+
},
|
|
51
|
+
'do {__body} while (!__a)': ({__a, __body}, path) => {
|
|
52
|
+
const {line} = path.node.loc.start;
|
|
53
|
+
const startLabel = createStartLabel(line);
|
|
54
|
+
const conditionLabel = createConditionLabel(line);
|
|
55
|
+
const expression = isCallExpression(__a) ? __a : template.ast(`test(${__a.name}, ${__a.name})`);
|
|
56
|
+
|
|
57
|
+
let conditionExpression = expressionStatement(expression);
|
|
58
|
+
const wasContinue = maybeReplaceContinueWithJmp(path, conditionLabel);
|
|
59
|
+
|
|
60
|
+
if (wasContinue)
|
|
61
|
+
conditionExpression = labeledStatement(identifier(conditionLabel), conditionExpression);
|
|
62
|
+
|
|
63
|
+
__body.body.push(conditionExpression);
|
|
64
|
+
__body.body.push(expressionStatement(template.ast(`jz(${startLabel})`)));
|
|
65
|
+
|
|
66
|
+
return `${startLabel}: __body`;
|
|
67
|
+
},
|
|
68
|
+
'do {__body} while (__a)': ({__a, __body}, path) => {
|
|
69
|
+
const {line} = path.node.loc.start;
|
|
70
|
+
const startLabel = createStartLabel(line);
|
|
71
|
+
const conditionLabel = createConditionLabel(line);
|
|
72
|
+
const [one, two, jnz, test] = parseWhileArgs(__a);
|
|
73
|
+
const expression = isCallExpression(__a) ? __a : template.ast(`${test}(${one}, ${two})`);
|
|
74
|
+
|
|
75
|
+
let conditionExpression = expressionStatement(expression);
|
|
76
|
+
const wasContinue = maybeReplaceContinueWithJmp(path, conditionLabel);
|
|
77
|
+
|
|
78
|
+
if (wasContinue)
|
|
79
|
+
conditionExpression = labeledStatement(identifier(conditionLabel), conditionExpression);
|
|
80
|
+
|
|
81
|
+
__body.body.push(conditionExpression);
|
|
82
|
+
__body.body.push(expressionStatement(template.ast(`${jnz}(${startLabel})`)));
|
|
83
|
+
|
|
84
|
+
maybeReplaceBreak(path, line);
|
|
85
|
+
|
|
86
|
+
return `${startLabel}: __body`;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const parseWhileArgs = (__a) => {
|
|
91
|
+
if (isBooleanLiteral(__a) && __a.value)
|
|
92
|
+
return [
|
|
93
|
+
'al',
|
|
94
|
+
'al',
|
|
95
|
+
'je',
|
|
96
|
+
'cmp',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (isBooleanLiteral(__a) && !__a.value)
|
|
100
|
+
return [
|
|
101
|
+
'al',
|
|
102
|
+
'al',
|
|
103
|
+
'jne',
|
|
104
|
+
'cmp',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
if (compare(__a, '__a === __b'))
|
|
108
|
+
return [
|
|
109
|
+
extract(__a.left),
|
|
110
|
+
extract(__a.right),
|
|
111
|
+
'jz',
|
|
112
|
+
'cmp',
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
return [
|
|
116
|
+
__a.name,
|
|
117
|
+
__a.name,
|
|
118
|
+
'jnz',
|
|
119
|
+
'test',
|
|
120
|
+
];
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
function maybeReplaceBreak(path, line) {
|
|
124
|
+
let wasBreak = false;
|
|
125
|
+
const breakLabel = `__nirguna_do_while_break_${line}`;
|
|
126
|
+
|
|
127
|
+
path.traverse({
|
|
128
|
+
BreakStatement(path) {
|
|
129
|
+
wasBreak = true;
|
|
130
|
+
path.replaceWithSourceString(`jmp(${breakLabel})`);
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (wasBreak) {
|
|
135
|
+
const nextPath = path.getNextSibling();
|
|
136
|
+
const labeledNode = labeledStatement(identifier(breakLabel), nextPath.node || expressionStatement(template.ast('nop()')));
|
|
137
|
+
|
|
138
|
+
if (nextPath.node)
|
|
139
|
+
replaceWith(nextPath, labeledNode);
|
|
140
|
+
else
|
|
141
|
+
insertAfter(path, labeledNode);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function maybeReplaceContinueWithJmp(path, startLabel) {
|
|
146
|
+
let was = false;
|
|
147
|
+
|
|
148
|
+
path.traverse({
|
|
149
|
+
ContinueStatement(path) {
|
|
150
|
+
path.replaceWithSourceString(`jmp(${startLabel})`);
|
|
151
|
+
was = true;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return was;
|
|
155
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {template, types} from 'putout';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
isStringLiteral,
|
|
5
|
+
stringLiteral,
|
|
6
|
+
} = types;
|
|
7
|
+
|
|
8
|
+
export const report = () => `Use '__a.equ.__b' instead of equ(__a, __b)`;
|
|
9
|
+
|
|
10
|
+
const createEqu = template('__a.equ[__b]', {
|
|
11
|
+
placeholderPattern: /__[a-z]/,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const replace = () => ({
|
|
15
|
+
'equ(__a, __b)': ({__a, __b}) => {
|
|
16
|
+
return createEqu({
|
|
17
|
+
__a,
|
|
18
|
+
__b: maybeStringLiteral(__b),
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function maybeStringLiteral(a) {
|
|
24
|
+
if (isStringLiteral(a))
|
|
25
|
+
return a;
|
|
26
|
+
|
|
27
|
+
const hex = '0x' + Number(a.raw).toString(16);
|
|
28
|
+
|
|
29
|
+
return stringLiteral(hex);
|
|
30
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {types, operator} from 'putout';
|
|
2
|
+
|
|
3
|
+
const {compare} = operator;
|
|
4
|
+
const {
|
|
5
|
+
isCallExpression,
|
|
6
|
+
isReturnStatement,
|
|
7
|
+
expressionStatement,
|
|
8
|
+
identifier,
|
|
9
|
+
arrayExpression,
|
|
10
|
+
callExpression,
|
|
11
|
+
isExportNamedDeclaration,
|
|
12
|
+
numericLiteral,
|
|
13
|
+
} = types;
|
|
14
|
+
|
|
15
|
+
export const report = () => `Use 'label' instead of 'function'`;
|
|
16
|
+
|
|
17
|
+
export const filter = (path) => {
|
|
18
|
+
if (isExportNamedDeclaration(path.parentPath))
|
|
19
|
+
return false;
|
|
20
|
+
|
|
21
|
+
if (path.node.params.length)
|
|
22
|
+
return false;
|
|
23
|
+
|
|
24
|
+
const {returnType} = path.node;
|
|
25
|
+
|
|
26
|
+
if (returnType && returnType.typeAnnotation.typeName.name !== 'iret')
|
|
27
|
+
return false;
|
|
28
|
+
|
|
29
|
+
const last = path.node.body.body.at(-1);
|
|
30
|
+
|
|
31
|
+
return !(isCallExpression(last) && last.callee.name === 'ret');
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const replace = () => ({
|
|
35
|
+
'async function __a<__type_params>(__args): __b {__body}': convertFnToLabel(),
|
|
36
|
+
'function __a<__type_params>(__args): __b {__body}': convertFnToLabel(),
|
|
37
|
+
'function __a(__args): __b {__body}': convertFnToLabel(),
|
|
38
|
+
'async function __a(__args): __b {__body}': convertFnToLabel(),
|
|
39
|
+
'async function __a<__type_params>(__args) {__body}': convertFnToLabel('ret'),
|
|
40
|
+
'function __a<__type_params>(__args) {__body}': convertFnToLabel('ret'),
|
|
41
|
+
'function __a(__args) {__body}': convertFnToLabel('ret'),
|
|
42
|
+
'async function __a(__args) {__body}': convertFnToLabel('ret'),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const convertFnToLabel = (ret) => ({__b, __type_params, __body}, path) => {
|
|
46
|
+
addStackOperations({
|
|
47
|
+
__type_params,
|
|
48
|
+
__body,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (__b)
|
|
52
|
+
path.traverse({
|
|
53
|
+
ReturnStatement(path) {
|
|
54
|
+
path.replaceWithSourceString('iret()');
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const last = __body.body.at(-1);
|
|
59
|
+
|
|
60
|
+
if (!compare(last, 'ret()')) {
|
|
61
|
+
const iret = expressionStatement(maybeRet(ret, path) || callExpression(__b.typeName, []));
|
|
62
|
+
__body.body.push(iret);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return '__a: __body';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const maybeRet = (name, path) => {
|
|
69
|
+
if (!name)
|
|
70
|
+
return false;
|
|
71
|
+
|
|
72
|
+
const count = path.__nirguna_args_size;
|
|
73
|
+
const args = [];
|
|
74
|
+
|
|
75
|
+
if (count)
|
|
76
|
+
args.push(numericLiteral(count));
|
|
77
|
+
|
|
78
|
+
return callExpression(identifier(name), args);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function addStackOperations({__body, __type_params = []}) {
|
|
82
|
+
const args = [];
|
|
83
|
+
|
|
84
|
+
for (const {name} of __type_params) {
|
|
85
|
+
args.push(name);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!args.length)
|
|
89
|
+
return;
|
|
90
|
+
|
|
91
|
+
const push = createStackOperation('push', args);
|
|
92
|
+
|
|
93
|
+
const pop = createStackOperation('pop', args
|
|
94
|
+
.slice()
|
|
95
|
+
.reverse());
|
|
96
|
+
|
|
97
|
+
__body.body.unshift(push);
|
|
98
|
+
|
|
99
|
+
const last = __body.body.at(-1);
|
|
100
|
+
|
|
101
|
+
if (isReturnStatement(last))
|
|
102
|
+
__body.body.splice(-1, 1, pop, last);
|
|
103
|
+
else
|
|
104
|
+
__body.body.push(pop);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createStackOperation(name, args) {
|
|
108
|
+
const callee = identifier(name);
|
|
109
|
+
const params = [
|
|
110
|
+
arrayExpression(args),
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
return expressionStatement(callExpression(callee, params));
|
|
114
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {types, operator} from 'putout';
|
|
2
|
+
import {parseOperator} from './operator.js';
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
callExpression,
|
|
6
|
+
labeledStatement,
|
|
7
|
+
identifier,
|
|
8
|
+
isLabeledStatement,
|
|
9
|
+
isBinaryExpression,
|
|
10
|
+
isArrayExpression,
|
|
11
|
+
isLogicalExpression,
|
|
12
|
+
} = types;
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
replaceWith,
|
|
16
|
+
insertAfter,
|
|
17
|
+
extract,
|
|
18
|
+
} = operator;
|
|
19
|
+
|
|
20
|
+
const createName = (suffix, type) => {
|
|
21
|
+
return `__nirguna_fasm_if_${type}_${suffix}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const report = () => `Use 'jmp' instead of 'if'`;
|
|
25
|
+
export const match = () => ({
|
|
26
|
+
'if (__a) __b': ({__a}) => {
|
|
27
|
+
return isBinaryExpression(__a) || isLogicalExpression(__a);
|
|
28
|
+
},
|
|
29
|
+
'if (__a) __b; else __c': ({__a}) => isBinaryExpression(__a),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const replace = ({options}) => {
|
|
33
|
+
let {labelSuffix = 0} = options;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
'if (__a) __b': (vars, path) => {
|
|
37
|
+
const next = getNext(path);
|
|
38
|
+
const name = createName(++labelSuffix, 'end');
|
|
39
|
+
const test = parseTest(path, name);
|
|
40
|
+
|
|
41
|
+
createLabel(next, name);
|
|
42
|
+
|
|
43
|
+
return `{
|
|
44
|
+
${test}
|
|
45
|
+
__b;
|
|
46
|
+
}`;
|
|
47
|
+
},
|
|
48
|
+
'if (__a) __b; else __c': (vars, path) => {
|
|
49
|
+
const next = getNext(path);
|
|
50
|
+
const suffix = ++labelSuffix;
|
|
51
|
+
const endLabel = createName(suffix, 'end');
|
|
52
|
+
const elseLabel = createName(suffix, 'else');
|
|
53
|
+
const test = parseTest(path, elseLabel);
|
|
54
|
+
|
|
55
|
+
createLabel(next, endLabel);
|
|
56
|
+
|
|
57
|
+
return `{
|
|
58
|
+
${test}
|
|
59
|
+
__b;
|
|
60
|
+
jmp(${endLabel});
|
|
61
|
+
${elseLabel}:
|
|
62
|
+
__c;
|
|
63
|
+
}`;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const maybeBraces = (node, value) => {
|
|
69
|
+
if (isArrayExpression(node))
|
|
70
|
+
return `[${value}]`;
|
|
71
|
+
|
|
72
|
+
return value;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function parseBinaryTest({left, right, operator}, options = {}) {
|
|
76
|
+
const {direct = false} = options;
|
|
77
|
+
const extractedLeft = extract(left);
|
|
78
|
+
const extractedRight = extract(right);
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
maybeBraces(left, extractedLeft),
|
|
82
|
+
maybeBraces(right, extractedRight),
|
|
83
|
+
parseOperator(operator, {
|
|
84
|
+
direct,
|
|
85
|
+
}),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseTest(path, name) {
|
|
90
|
+
const {test} = path.node;
|
|
91
|
+
|
|
92
|
+
if (isBinaryExpression(test)) {
|
|
93
|
+
const [first, second, jnz] = parseBinaryTest(test);
|
|
94
|
+
|
|
95
|
+
return `
|
|
96
|
+
cmp(${first}, ${second});
|
|
97
|
+
${jnz}(${name});
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isLogicalExpression(test)) {
|
|
102
|
+
const {
|
|
103
|
+
left,
|
|
104
|
+
right,
|
|
105
|
+
operator,
|
|
106
|
+
} = test;
|
|
107
|
+
|
|
108
|
+
if (operator === '&&') {
|
|
109
|
+
const [firstLeft, secondLeft, jnzLeft] = parseBinaryTest(left);
|
|
110
|
+
const [firstRight, secondRight, jnzRight] = parseBinaryTest(right);
|
|
111
|
+
|
|
112
|
+
return `
|
|
113
|
+
cmp(${firstLeft}, ${secondLeft});
|
|
114
|
+
${jnzLeft}(${name});
|
|
115
|
+
cmp(${firstRight}, ${secondRight});
|
|
116
|
+
${jnzRight}(${name});
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (operator === '||') {
|
|
121
|
+
const [firstLeft, secondLeft, jzLeft] = parseBinaryTest(left, {
|
|
122
|
+
direct: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const [firstRight, secondRight, jnzRight] = parseBinaryTest(right);
|
|
126
|
+
|
|
127
|
+
const nameOr = name.replace('end', 'or');
|
|
128
|
+
|
|
129
|
+
return `
|
|
130
|
+
cmp(${firstLeft}, ${secondLeft});
|
|
131
|
+
${jzLeft}(${nameOr});
|
|
132
|
+
cmp(${firstRight}, ${secondRight});
|
|
133
|
+
${jnzRight}(${name});
|
|
134
|
+
${nameOr}:
|
|
135
|
+
`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createLabel(path, name) {
|
|
141
|
+
const labelName = identifier(name);
|
|
142
|
+
const label = labeledStatement(labelName, path.node);
|
|
143
|
+
|
|
144
|
+
replaceWith(path, label);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const getLatestLabeledStatement = (path) => {
|
|
148
|
+
do {
|
|
149
|
+
path = path.parentPath;
|
|
150
|
+
} while (isLabeledStatement(path.parentPath));
|
|
151
|
+
return path;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function getNext(path) {
|
|
155
|
+
const next = path.getNextSibling();
|
|
156
|
+
|
|
157
|
+
if (next.node)
|
|
158
|
+
return next;
|
|
159
|
+
|
|
160
|
+
if (path.parentPath.isLabeledStatement())
|
|
161
|
+
path = getLatestLabeledStatement(path);
|
|
162
|
+
|
|
163
|
+
insertAfter(path, callExpression(identifier('nop'), []));
|
|
164
|
+
|
|
165
|
+
return path.getNextSibling();
|
|
166
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const OPERATOR = {
|
|
2
|
+
'===': 'jz',
|
|
3
|
+
'!==': 'jnz',
|
|
4
|
+
'>': 'jg',
|
|
5
|
+
'<': 'jl',
|
|
6
|
+
'<=': 'jle',
|
|
7
|
+
'>=': 'jge',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const OPERATOR_REVERSE = {
|
|
11
|
+
'===': 'jnz',
|
|
12
|
+
'!==': 'jz',
|
|
13
|
+
'>': 'jle',
|
|
14
|
+
'<': 'jge',
|
|
15
|
+
'<=': 'jg',
|
|
16
|
+
'>=': 'jl',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function parseOperator(operator, {direct = false}) {
|
|
20
|
+
const customOperator = direct ? OPERATOR : OPERATOR_REVERSE;
|
|
21
|
+
const result = customOperator[operator];
|
|
22
|
+
|
|
23
|
+
if (!result)
|
|
24
|
+
throw Error(`☝️Looks like operator '${operator}' not supported`);
|
|
25
|
+
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const report = () => `Use 'syscall' instead of 'linux.exit()'`;
|
|
2
|
+
|
|
3
|
+
export const match = () => ({
|
|
4
|
+
'linux.exit(__a)': (vars, path) => {
|
|
5
|
+
return path.parentPath.isExpressionStatement();
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const replace = () => ({
|
|
10
|
+
'linux.exit(__a)': `{
|
|
11
|
+
rdi = __a;
|
|
12
|
+
rax = 60;
|
|
13
|
+
syscall();
|
|
14
|
+
}`,
|
|
15
|
+
});
|