@sie-js/vkp 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/.editorconfig +6 -0
- package/.github/workflows/ci.yml +18 -0
- package/.prettierrc.json +21 -0
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/jest.config.js +4 -0
- package/package.json +22 -0
- package/src/VkpParseError.js +68 -0
- package/src/index.js +127 -0
- package/src/index.test.js +403 -0
- package/src/lexer.js +46 -0
- package/src/parser.js +497 -0
package/.editorconfig
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Publish Package to npmjs
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-node@v4
|
|
11
|
+
with:
|
|
12
|
+
node-version: '20.x'
|
|
13
|
+
registry-url: 'https://registry.npmjs.org'
|
|
14
|
+
- run: npm ci
|
|
15
|
+
- run: npm test
|
|
16
|
+
- run: npm publish --access public
|
|
17
|
+
env:
|
|
18
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.prettierrc.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"arrowParens": "always",
|
|
3
|
+
"bracketSameLine": false,
|
|
4
|
+
"bracketSpacing": true,
|
|
5
|
+
"semi": true,
|
|
6
|
+
"experimentalTernaries": false,
|
|
7
|
+
"singleQuote": false,
|
|
8
|
+
"jsxSingleQuote": false,
|
|
9
|
+
"quoteProps": "as-needed",
|
|
10
|
+
"trailingComma": "none",
|
|
11
|
+
"singleAttributePerLine": false,
|
|
12
|
+
"htmlWhitespaceSensitivity": "css",
|
|
13
|
+
"vueIndentScriptAndStyle": false,
|
|
14
|
+
"proseWrap": "preserve",
|
|
15
|
+
"insertPragma": false,
|
|
16
|
+
"printWidth": 200,
|
|
17
|
+
"requirePragma": false,
|
|
18
|
+
"tabWidth": 4,
|
|
19
|
+
"useTabs": true,
|
|
20
|
+
"embeddedLanguageFormatting": "auto"
|
|
21
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kirill Zhumarin <kirill.zhumarin@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@sie-js/vkp)
|
|
2
|
+
|
|
3
|
+
# SUMMARY
|
|
4
|
+
|
|
5
|
+
Parser and utils for the `.VKP` files format which is used in the V-Klay.
|
|
6
|
+
|
|
7
|
+
# INSTALL
|
|
8
|
+
```bash
|
|
9
|
+
npm i @sie-js/vkp
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
# USAGE
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import { vkpNormalize, vkpParse } from '@sie-js/vkp';
|
|
17
|
+
|
|
18
|
+
// Convert from windows-1251 to UTF-8 + replace CRLF to LF
|
|
19
|
+
let patchText = vkpNormalize(fs.readFileSync('../patches/patches/E71v45/10732-ElfPack-18_03_2024-v3_2_2.vkp'));
|
|
20
|
+
|
|
21
|
+
// Parse patch
|
|
22
|
+
let vkp = vkpParse(patchText);
|
|
23
|
+
console.dir(vkp, { depth: null });
|
|
24
|
+
|
|
25
|
+
if (vkp.warnings.length || vkp.errors.length) {
|
|
26
|
+
for (let warn of vkp.warnings) {
|
|
27
|
+
console.log(`Warning: ${warn.message}`);
|
|
28
|
+
console.log("```");
|
|
29
|
+
console.log(warn.codeFrame(patchText));
|
|
30
|
+
console.log("```");
|
|
31
|
+
console.log("");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (let err of vkp.errors) {
|
|
35
|
+
console.log(`Error: ${err.message}`);
|
|
36
|
+
console.log("```");
|
|
37
|
+
console.log(err.codeFrame(patchText));
|
|
38
|
+
console.log("```");
|
|
39
|
+
console.log("");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("");
|
|
43
|
+
}
|
|
44
|
+
```
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sie-js/vkp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
9
|
+
"gen-parser": "nearleyc src/vkp.ne -o src/grammar.js && prettier -w src/grammar.js",
|
|
10
|
+
"pub": "npm publish --access public"
|
|
11
|
+
},
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"iconv-lite": "^0.6.3",
|
|
16
|
+
"moo": "^0.5.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"glob": "^10.3.12",
|
|
20
|
+
"jest": "^29.7.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class VkpParseError extends Error {
|
|
2
|
+
loc;
|
|
3
|
+
hint;
|
|
4
|
+
constructor(message, loc, hint) {
|
|
5
|
+
super(`${message} at line ${loc.line} col ${loc.column}${hint ? "\n" + hint : ""}`);
|
|
6
|
+
this.name = "VkpParseError";
|
|
7
|
+
this.loc = loc;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
codeFrame(text) {
|
|
11
|
+
return codeFrame(text, this.loc.line, this.loc.column);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function codeFrame(text, lineNum, colNum) {
|
|
16
|
+
let lines = text.split(/\r\n|\n/);
|
|
17
|
+
let maxLineNumLen = lines.length.toString().length + 1;
|
|
18
|
+
let out = "";
|
|
19
|
+
let n = 1;
|
|
20
|
+
for (let line of lines) {
|
|
21
|
+
if (Math.abs(n - lineNum) > 3) {
|
|
22
|
+
n++;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
out += `${n == lineNum ? '>' : ' '}${n.toString().padStart(maxLineNumLen, ' ')} | ${tab2spaces(line)}\n`;
|
|
26
|
+
if (n == lineNum) {
|
|
27
|
+
out += ` ${" ".repeat(maxLineNumLen, ' ')} | ${str2spaces(line.substr(0, colNum - 1))}^\n`;
|
|
28
|
+
}
|
|
29
|
+
n++;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getLocByOffset(text, offset) {
|
|
35
|
+
let line = 0;
|
|
36
|
+
let column = 1;
|
|
37
|
+
for (let i = 0; i < text.length; i++) {
|
|
38
|
+
let c = text.charAt(i);
|
|
39
|
+
if (c == "\n" || (c == "\r" && text.charAt(i + 1) == "\n")) {
|
|
40
|
+
column = 1;
|
|
41
|
+
line++;
|
|
42
|
+
}
|
|
43
|
+
if (i == offset)
|
|
44
|
+
return { line, column };
|
|
45
|
+
}
|
|
46
|
+
return { line, column: 1 };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function str2spaces(line) {
|
|
50
|
+
return tab2spaces(line).replace(/./g, ' ');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tab2spaces(line) {
|
|
54
|
+
let newStr = "";
|
|
55
|
+
let virtualSymbols = 0;
|
|
56
|
+
for (let i = 0; i < line.length; i++) {
|
|
57
|
+
let c = line.charAt(i);
|
|
58
|
+
if (c == "\t") {
|
|
59
|
+
let spacesCnt = 4 - virtualSymbols % 4;
|
|
60
|
+
newStr += " ".repeat(spacesCnt);
|
|
61
|
+
virtualSymbols += spacesCnt;
|
|
62
|
+
} else {
|
|
63
|
+
virtualSymbols++;
|
|
64
|
+
newStr += c;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return newStr;
|
|
68
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import iconv from 'iconv-lite';
|
|
2
|
+
import { vkpRawParser } from './parser.js';
|
|
3
|
+
import { VkpParseError } from './VkpParseError.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PRAGMAS = {
|
|
6
|
+
warn_no_old_on_apply: true,
|
|
7
|
+
warn_if_new_exist_on_apply: true,
|
|
8
|
+
warn_if_old_exist_on_undo: true,
|
|
9
|
+
undo: true,
|
|
10
|
+
old_equal_ff: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function vkpParse(text, options) {
|
|
14
|
+
options = {
|
|
15
|
+
allowEmptyOldData: false,
|
|
16
|
+
allowPlaceholders: false,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let vkp = {
|
|
21
|
+
ast: null,
|
|
22
|
+
valid: false,
|
|
23
|
+
writes: [],
|
|
24
|
+
warnings: [],
|
|
25
|
+
errors: [],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let pragmas = {...DEFAULT_PRAGMAS};
|
|
29
|
+
let pragma2loc = {};
|
|
30
|
+
let offsetCorrector;
|
|
31
|
+
|
|
32
|
+
vkpRawParser(text, {
|
|
33
|
+
onPragma(value, loc) {
|
|
34
|
+
if (value.pragma.action == "enable") {
|
|
35
|
+
if (pragmas[value.pragma.name]) {
|
|
36
|
+
vkp.warnings.push(new VkpParseError(`Useless "#pragma ${value.pragma.action} ${value.pragma.name}" has no effect`, loc, `You can safely remove this line.`));
|
|
37
|
+
} else {
|
|
38
|
+
pragmas[value.pragma.name] = true;
|
|
39
|
+
pragma2loc[value.pragma.name] = loc;
|
|
40
|
+
}
|
|
41
|
+
} else if (value.pragma.action == "disable") {
|
|
42
|
+
if (!pragmas[value.pragma.name]) {
|
|
43
|
+
vkp.warnings.push(new VkpParseError(`Useless "#pragma ${value.pragma.action} ${value.pragma.name}" has no effect`, loc, `You can safely remove this line.`));
|
|
44
|
+
} else {
|
|
45
|
+
pragma2loc[value.pragma.name] = loc;
|
|
46
|
+
pragmas[value.pragma.name] = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
onPatchData(data, loc) {
|
|
51
|
+
let oldData = data.old ? data.old.buffer : null;
|
|
52
|
+
let newData = data.new.buffer;
|
|
53
|
+
|
|
54
|
+
if (data.new.placeholders > 0) {
|
|
55
|
+
if (!options.allowPlaceholders)
|
|
56
|
+
vkp.errors.push(new VkpParseError(`Found placeholder instead of real patch data`, data.new.loc));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (pragmas.old_equal_ff && !oldData)
|
|
60
|
+
oldData = Buffer.alloc(newData.length).fill(0xFF);
|
|
61
|
+
|
|
62
|
+
if (oldData&& oldData.length < newData.length)
|
|
63
|
+
vkp.errors.push(new VkpParseError(`Old data (${oldData.length} bytes) is less than new data (${newData.length} bytes)`, data.old.loc));
|
|
64
|
+
|
|
65
|
+
if (pragmas.warn_no_old_on_apply && !oldData) {
|
|
66
|
+
if (!options.allowEmptyOldData)
|
|
67
|
+
vkp.warnings.push(new VkpParseError(`Old data is not specified`, data.new.loc, `Undo operation is impossible!`));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
vkp.writes.push({
|
|
71
|
+
addr: (offsetCorrector ? offsetCorrector.value : 0) + data.address,
|
|
72
|
+
size: newData.length,
|
|
73
|
+
old: oldData,
|
|
74
|
+
new: newData,
|
|
75
|
+
loc,
|
|
76
|
+
pragmas: {...pragmas}
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
onOffset(value, loc) {
|
|
80
|
+
offsetCorrector = { value: value.offset, text: value.text, loc };
|
|
81
|
+
},
|
|
82
|
+
onWarning(e) {
|
|
83
|
+
vkp.warnings.push(e);
|
|
84
|
+
},
|
|
85
|
+
onError(e) {
|
|
86
|
+
vkp.errors.push(e);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
let unsinishedPragmas = [];
|
|
91
|
+
for (let k in pragmas) {
|
|
92
|
+
if (pragmas[k] !== DEFAULT_PRAGMAS[k]) {
|
|
93
|
+
let cancel = pragmas[k] ? `#pragma disable ${k}` : `#pragma enable ${k}`;
|
|
94
|
+
vkp.warnings.push(new VkpParseError(`Uncanceled pragma "${k}"`, pragma2loc[k], `Please put "${cancel}" at the end of the patch.`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (offsetCorrector && offsetCorrector.value != 0)
|
|
99
|
+
vkp.warnings.push(new VkpParseError(`Uncanceled offset ${offsetCorrector.text}`, offsetCorrector.loc, `Please put "+0" at the end of the patch.`));
|
|
100
|
+
|
|
101
|
+
vkp.valid = (vkp.errors.length == 0);
|
|
102
|
+
|
|
103
|
+
return vkp;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function vkpDetectContent(text) {
|
|
107
|
+
let trimmedText = text.replace(/\/\*.*?\*\//gs, '').replace(/(\/\/|;|#).*?$/mg, '');
|
|
108
|
+
if (trimmedText.match(/^\s*(0x[a-f0-9]+|[a-f0-9]+)\s*:[^\\/]/mi))
|
|
109
|
+
return "PATCH";
|
|
110
|
+
if (!trimmedText.trim().length)
|
|
111
|
+
return "EMPTY";
|
|
112
|
+
if (text.match(/;!к патчу прикреплён файл, https?:\/\//i))
|
|
113
|
+
return "DOWNLOAD_STUB";
|
|
114
|
+
return "UNKNOWN";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// CP1251 -> UTF-8 + CRLF -> LF
|
|
118
|
+
function vkpNormalize(text) {
|
|
119
|
+
return iconv.decode(text, 'windows-1251').replace(/(\r\n|\n|\r)/g, "\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// UTF-8 -> CP1251 + LF -> CRLF
|
|
123
|
+
function vkpCanonicalize(text) {
|
|
124
|
+
return iconv.encode(text.replace(/(\r\n|\n|\r)/g, "\r\n"), 'windows-1251');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { vkpParse, vkpRawParser, vkpNormalize, vkpCanonicalize, vkpDetectContent };
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { vkpParse } from './index.js';
|
|
2
|
+
|
|
3
|
+
test('warn: useless pragma', () => {
|
|
4
|
+
let vkp = vkpParse(`#pragma enable warn_no_old_on_apply`);
|
|
5
|
+
expect(vkp.valid).toBe(true);
|
|
6
|
+
expect(vkp.warnings.length).toBe(1);
|
|
7
|
+
expect(vkp.warnings[0]).toHaveProperty("message", "Useless \"#pragma enable warn_no_old_on_apply\" has no effect at line 1 col 1\nYou can safely remove this line.");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('warn: uncanceled pragma', () => {
|
|
11
|
+
let vkp = vkpParse(`#pragma disable warn_no_old_on_apply`);
|
|
12
|
+
expect(vkp.valid).toBe(true);
|
|
13
|
+
expect(vkp.warnings.length).toBe(1);
|
|
14
|
+
expect(vkp.warnings[0]).toHaveProperty("message", "Uncanceled pragma \"warn_no_old_on_apply\" at line 1 col 1\nPlease put \"#pragma enable warn_no_old_on_apply\" at the end of the patch.");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('warn: uncanceled offset', () => {
|
|
18
|
+
let vkp = vkpParse(`+123`);
|
|
19
|
+
expect(vkp.valid).toBe(true);
|
|
20
|
+
expect(vkp.warnings.length).toBe(1);
|
|
21
|
+
expect(vkp.warnings[0]).toHaveProperty("message", "Uncanceled offset +123 at line 1 col 1\nPlease put \"+0\" at the end of the patch.");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('warn: bad comments', () => {
|
|
25
|
+
let vkp = vkpParse(`
|
|
26
|
+
*/
|
|
27
|
+
/* comment...
|
|
28
|
+
`);
|
|
29
|
+
expect(vkp.valid).toBe(true);
|
|
30
|
+
expect(vkp.warnings.length).toBe(2);
|
|
31
|
+
expect(vkp.warnings[0]).toHaveProperty("message", "Trailing multiline comment end at line 2 col 3");
|
|
32
|
+
expect(vkp.warnings[1]).toHaveProperty("message", "Unfinished multiline comment at line 3 col 3");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('warn: no old data', () => {
|
|
36
|
+
let vkp = vkpParse(`
|
|
37
|
+
AA: BB
|
|
38
|
+
`);
|
|
39
|
+
expect(vkp.valid).toBe(true);
|
|
40
|
+
expect(vkp.warnings.length).toBe(1);
|
|
41
|
+
expect(vkp.warnings[0]).toHaveProperty("message", "Old data is not specified at line 2 col 7\nUndo operation is impossible!");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('error: space after number', () => { // thanks Viktor89
|
|
45
|
+
let vkp = vkpParse(`
|
|
46
|
+
AAAA: BB 0i123; comment
|
|
47
|
+
AAAA: BB 0x12; comment
|
|
48
|
+
AAAA: BB CC; comment
|
|
49
|
+
`);
|
|
50
|
+
expect(vkp.valid).toBe(false);
|
|
51
|
+
expect(vkp.warnings.length).toBe(0);
|
|
52
|
+
expect(vkp.errors.length).toBe(2);
|
|
53
|
+
expect(vkp.errors[0]).toHaveProperty("message", "No whitespace between number and comment at line 2 col 17");
|
|
54
|
+
expect(vkp.errors[1]).toHaveProperty("message", "No whitespace between number and comment at line 3 col 16");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('error: placeholder', () => {
|
|
58
|
+
let vkp = vkpParse(`AAAA: BB XX`);
|
|
59
|
+
expect(vkp.valid).toBe(false);
|
|
60
|
+
expect(vkp.warnings.length).toBe(0);
|
|
61
|
+
expect(vkp.errors.length).toBe(1);
|
|
62
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Found placeholder instead of real patch data at line 1 col 10");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('error: invalid hex data', () => {
|
|
66
|
+
let vkp = vkpParse(`AAAA: BB B`);
|
|
67
|
+
expect(vkp.valid).toBe(false);
|
|
68
|
+
expect(vkp.warnings.length).toBe(0);
|
|
69
|
+
expect(vkp.errors.length).toBe(1);
|
|
70
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Hex data (B) must be even length at line 1 col 10");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('error: old data is less than new data', () => {
|
|
74
|
+
let vkp = vkpParse(`AAAA: BB BBCC`);
|
|
75
|
+
expect(vkp.valid).toBe(false);
|
|
76
|
+
expect(vkp.warnings.length).toBe(0);
|
|
77
|
+
expect(vkp.errors.length).toBe(1);
|
|
78
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Old data (1 bytes) is less than new data (2 bytes) at line 1 col 7");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('error: comment tokens in string', () => {
|
|
82
|
+
let vkp = vkpParse(`
|
|
83
|
+
AAAA: AABBCCDDEE "//"
|
|
84
|
+
AAAA: AABBCCDDEE "/*"
|
|
85
|
+
AAAA: AABBCCDDEE "\\/\\/"
|
|
86
|
+
AAAA: AABBCCDDEE "\\/\\*"
|
|
87
|
+
`);
|
|
88
|
+
expect(vkp.valid).toBe(false);
|
|
89
|
+
expect(vkp.warnings.length).toBe(0);
|
|
90
|
+
expect(vkp.errors.length).toBe(2);
|
|
91
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Unescaped // is not allowed in string: \"//\" at line 2 col 20\nEscape these ambiguous characters like this: \\/* or \\/\\/.");
|
|
92
|
+
expect(vkp.errors[1]).toHaveProperty("message", "Unescaped /* is not allowed in string: \"/*\" at line 3 col 20\nEscape these ambiguous characters like this: \\/* or \\/\\/.");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('error: number ranges', () => {
|
|
96
|
+
let vkp = vkpParse(`
|
|
97
|
+
AAAA: AABBCCDDEE 0i0,0i999
|
|
98
|
+
AAAA: AABBCCDDEE 0i0,0i99999
|
|
99
|
+
AAAA: AABBCCDDEE 0i0,0i99999999
|
|
100
|
+
AAAA: AABBCCDDEE 0i0,0i9999999999
|
|
101
|
+
AAAA: AABBCCDDEE 0i0,0i9999999999999
|
|
102
|
+
AAAA: AABBCCDDEE 0i0,0i999999999999999
|
|
103
|
+
AAAA: AABBCCDDEE 0i0,0i99999999999999999
|
|
104
|
+
AAAA: AABBCCDDEE 0i0,0i99999999999999999999
|
|
105
|
+
|
|
106
|
+
AAAA: AABBCCDDEE 0i0,0i-999
|
|
107
|
+
AAAA: AABBCCDDEE 0i0,0i-99999
|
|
108
|
+
AAAA: AABBCCDDEE 0i0,0i-99999999
|
|
109
|
+
AAAA: AABBCCDDEE 0i0,0i-9999999999
|
|
110
|
+
AAAA: AABBCCDDEE 0i0,0i-9999999999999
|
|
111
|
+
AAAA: AABBCCDDEE 0i0,0i-999999999999999
|
|
112
|
+
AAAA: AABBCCDDEE 0i0,0i-99999999999999999
|
|
113
|
+
AAAA: AABBCCDDEE 0i0,0i-99999999999999999999
|
|
114
|
+
`);
|
|
115
|
+
expect(vkp.valid).toBe(false);
|
|
116
|
+
expect(vkp.warnings.length).toBe(0);
|
|
117
|
+
expect(vkp.errors.length).toBe(16);
|
|
118
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Number 0i999 exceeds allowed range 0 ... 255 at line 2 col 24");
|
|
119
|
+
expect(vkp.errors[1]).toHaveProperty("message", "Number 0i99999 exceeds allowed range 0 ... 65535 at line 3 col 24");
|
|
120
|
+
expect(vkp.errors[2]).toHaveProperty("message", "Number 0i99999999 exceeds allowed range 0 ... 16777215 at line 4 col 24");
|
|
121
|
+
expect(vkp.errors[3]).toHaveProperty("message", "Number 0i9999999999 exceeds allowed range 0 ... 4294967295 at line 5 col 24");
|
|
122
|
+
expect(vkp.errors[4]).toHaveProperty("message", "Number 0i9999999999999 exceeds allowed range 0 ... 1099511627775 at line 6 col 24");
|
|
123
|
+
expect(vkp.errors[5]).toHaveProperty("message", "Number 0i999999999999999 exceeds allowed range 0 ... 281474976710655 at line 7 col 24");
|
|
124
|
+
expect(vkp.errors[6]).toHaveProperty("message", "Number 0i99999999999999999 exceeds allowed range 0 ... 72057594037927935 at line 8 col 24");
|
|
125
|
+
expect(vkp.errors[7]).toHaveProperty("message", "Number 0i99999999999999999999 exceeds allowed range 0 ... 18446744073709551615 at line 9 col 24");
|
|
126
|
+
expect(vkp.errors[8]).toHaveProperty("message", "Number 0i-999 exceeds allowed range -127 ... +127 at line 11 col 24");
|
|
127
|
+
expect(vkp.errors[9]).toHaveProperty("message", "Number 0i-99999 exceeds allowed range -32767 ... +32767 at line 12 col 24");
|
|
128
|
+
expect(vkp.errors[10]).toHaveProperty("message", "Number 0i-99999999 exceeds allowed range -8388607 ... +8388607 at line 13 col 24");
|
|
129
|
+
expect(vkp.errors[11]).toHaveProperty("message", "Number 0i-9999999999 exceeds allowed range -2147483647 ... +2147483647 at line 14 col 24");
|
|
130
|
+
expect(vkp.errors[12]).toHaveProperty("message", "Number 0i-9999999999999 exceeds allowed range -549755813887 ... +549755813887 at line 15 col 24");
|
|
131
|
+
expect(vkp.errors[13]).toHaveProperty("message", "Number 0i-999999999999999 exceeds allowed range -140737488355327 ... +140737488355327 at line 16 col 24");
|
|
132
|
+
expect(vkp.errors[14]).toHaveProperty("message", "Number 0i-99999999999999999 exceeds allowed range -36028797018963967 ... +36028797018963967 at line 17 col 24");
|
|
133
|
+
expect(vkp.errors[15]).toHaveProperty("message", "Number 0i-99999999999999999999 exceeds allowed range -9223372036854775807 ... +9223372036854775807 at line 18 col 24");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('error: bad numbers', () => {
|
|
137
|
+
let vkp = vkpParse(`
|
|
138
|
+
AAAA: AABBCCDDEE 0i0,0n1234
|
|
139
|
+
AAAA: AABBCCDDEE 0i0,0n111111111111111111111111111111111
|
|
140
|
+
`);
|
|
141
|
+
expect(vkp.valid).toBe(false);
|
|
142
|
+
expect(vkp.warnings.length).toBe(0);
|
|
143
|
+
expect(vkp.errors.length).toBe(2);
|
|
144
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Syntax error at line 2 col 24");
|
|
145
|
+
expect(vkp.errors[1]).toHaveProperty("message", "Number 0n111111111111111111111111111111111 exceeds allowed range 0n0 ... 0n11111111111111111111111111111111 at line 3 col 24");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('error: bad decimal numbers', () => {
|
|
149
|
+
let vkp = vkpParse(`
|
|
150
|
+
00000000: FF,FF,FF 0i+000,0i+00,0i+0
|
|
151
|
+
00000000: FFFF 0i+0000
|
|
152
|
+
00000000: FFFFFF 0i+0000000
|
|
153
|
+
00000000: FFFFFFFF 0i+000000000
|
|
154
|
+
00000000: FFFFFFFFFF 0i+000000000000
|
|
155
|
+
00000000: FFFFFFFFFFFF 0i+00000000000000
|
|
156
|
+
00000000: FFFFFFFFFFFFFF 0i+0000000000000000
|
|
157
|
+
00000000: FFFFFFFFFFFFFFFF 0i+0000000000000000000
|
|
158
|
+
`);
|
|
159
|
+
expect(vkp.valid).toBe(false);
|
|
160
|
+
expect(vkp.warnings.length).toBe(0);
|
|
161
|
+
expect(vkp.errors.length).toBe(7);
|
|
162
|
+
const hint = "Must be: 3 (for BYTE), 5 (for WORD), 8 (for 3 BYTES), 10 (for DWORD), 13 (for 5 BYTES), 15 (for 6 BYTES), 17 (for 7 BYTES), " +
|
|
163
|
+
"20 (for 8 BYTES).Use leading zeroes to match the number of digits.";
|
|
164
|
+
expect(vkp.errors[0]).toHaveProperty("message", "The wrong number of digits in integer (0i+0000) at line 3 col 18\n" + hint);
|
|
165
|
+
expect(vkp.errors[1]).toHaveProperty("message", "The wrong number of digits in integer (0i+0000000) at line 4 col 20\n" + hint);
|
|
166
|
+
expect(vkp.errors[2]).toHaveProperty("message", "The wrong number of digits in integer (0i+000000000) at line 5 col 22\n" + hint);
|
|
167
|
+
expect(vkp.errors[3]).toHaveProperty("message", "The wrong number of digits in integer (0i+000000000000) at line 6 col 24\n" + hint);
|
|
168
|
+
expect(vkp.errors[4]).toHaveProperty("message", "The wrong number of digits in integer (0i+00000000000000) at line 7 col 26\n" + hint);
|
|
169
|
+
expect(vkp.errors[5]).toHaveProperty("message", "The wrong number of digits in integer (0i+0000000000000000) at line 8 col 28\n" + hint);
|
|
170
|
+
expect(vkp.errors[6]).toHaveProperty("message", "The wrong number of digits in integer (0i+0000000000000000000) at line 9 col 30\n" + hint);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('error: bad address & offset', () => {
|
|
174
|
+
let vkp = vkpParse(`
|
|
175
|
+
+AAAAAAAAA
|
|
176
|
+
AAAAAAAAA: AA BB
|
|
177
|
+
`);
|
|
178
|
+
expect(vkp.valid).toBe(false);
|
|
179
|
+
expect(vkp.warnings.length).toBe(0);
|
|
180
|
+
expect(vkp.errors.length).toBe(2);
|
|
181
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Offset +AAAAAAAAA exceeds allowed range 00000000 ... FFFFFFFF at line 2 col 3");
|
|
182
|
+
expect(vkp.errors[1]).toHaveProperty("message", "Address AAAAAAAAA: exceeds allowed range 00000000 ... FFFFFFFF at line 3 col 3");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('error: bad string', () => {
|
|
186
|
+
let vkp = vkpParse(`
|
|
187
|
+
AAAAAAAA: FFFFFFFFFFFFFFFF "\\xAA"
|
|
188
|
+
AAAAAAAA: FFFFFFFFFFFFFFFF "\\u1234"
|
|
189
|
+
AAAAAAAA: FFFFFFFFFFFFFFFF "\\777"
|
|
190
|
+
AAAAAAAA: FFFFFFFFFFFFFFFF "\\jam"
|
|
191
|
+
`);
|
|
192
|
+
expect(vkp.valid).toBe(false);
|
|
193
|
+
expect(vkp.warnings.length).toBe(0);
|
|
194
|
+
expect(vkp.errors.length).toBe(4);
|
|
195
|
+
expect(vkp.errors[0]).toHaveProperty("message", "Bad escape sequence (\\xAA) at line 2 col 31\nAllowed range: \\x00-\\x7F.");
|
|
196
|
+
expect(vkp.errors[1]).toHaveProperty("message", "Unknown escape sequence (\\u1234) at line 3 col 31");
|
|
197
|
+
expect(vkp.errors[2]).toHaveProperty("message", "Unknown escape sequence (\\777) at line 4 col 31");
|
|
198
|
+
expect(vkp.errors[3]).toHaveProperty("message", "Unknown escape sequence (\\j) at line 5 col 31");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('data: valid address & offset', () => {
|
|
202
|
+
let vkp = vkpParse(`
|
|
203
|
+
-123450
|
|
204
|
+
A8123456: AA BB
|
|
205
|
+
+0
|
|
206
|
+
`);
|
|
207
|
+
expect(vkp.valid).toBe(true);
|
|
208
|
+
expect(vkp.warnings.length).toBe(0);
|
|
209
|
+
expect(vkp.errors.length).toBe(0);
|
|
210
|
+
expect(vkp.writes.length).toBe(1);
|
|
211
|
+
expect(vkp.writes[0].addr).toBe(0xA8000006);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('data: HEX bytes', () => {
|
|
215
|
+
let vkp = vkpParse(`00000000: FFFFFFFFFFFFFFFF DEAD926E,DE,AD,92,6E`);
|
|
216
|
+
expect(vkp.valid).toBe(true);
|
|
217
|
+
expect(vkp.warnings.length).toBe(0);
|
|
218
|
+
expect(vkp.errors.length).toBe(0);
|
|
219
|
+
expect(vkp.writes.length).toBe(1);
|
|
220
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('dead926edead926e');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('data: HEX numbers', () => {
|
|
224
|
+
let vkp = vkpParse(`00000000: FFFFFFFFFFFFFFFFFFFFFFFF 0xDEAD926E,0xDEAD,0x92,0x6E,0x1,0x2,0x123`);
|
|
225
|
+
expect(vkp.valid).toBe(true);
|
|
226
|
+
expect(vkp.warnings.length).toBe(0);
|
|
227
|
+
expect(vkp.errors.length).toBe(0);
|
|
228
|
+
expect(vkp.writes.length).toBe(1);
|
|
229
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('6e92addeadde926e01022301');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('data: binary numbers', () => {
|
|
233
|
+
let vkp = vkpParse(`00000000: FFFFFFFFFFFFFFFFFFFFFF 0n11011110101011011011111011101111,0n11011110,0n1101111010101101,0n100100011010001010110`);
|
|
234
|
+
expect(vkp.valid).toBe(true);
|
|
235
|
+
expect(vkp.warnings.length).toBe(0);
|
|
236
|
+
expect(vkp.errors.length).toBe(0);
|
|
237
|
+
expect(vkp.writes.length).toBe(1);
|
|
238
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('efbeaddedeadde563412');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('data: unsigned decimal numbers', () => {
|
|
242
|
+
let vkp = vkpParse(`
|
|
243
|
+
00000000: FF 0i18
|
|
244
|
+
00000000: FFFF 0i04660
|
|
245
|
+
00000000: FFFFFF 0i01193046
|
|
246
|
+
00000000: FFFFFFFF 0i0305419896
|
|
247
|
+
00000000: FFFFFFFFFF 0i0078187493530
|
|
248
|
+
00000000: FFFFFFFFFFFF 0i020015998343868
|
|
249
|
+
00000000: FFFFFFFFFFFFFF 0i05124095576030430
|
|
250
|
+
00000000: FFFFFFFFFFFFFFFF 0i01311768467463790320
|
|
251
|
+
`);
|
|
252
|
+
expect(vkp.valid).toBe(true);
|
|
253
|
+
expect(vkp.warnings.length).toBe(0);
|
|
254
|
+
expect(vkp.errors.length).toBe(0);
|
|
255
|
+
expect(vkp.writes.length).toBe(8);
|
|
256
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('12');
|
|
257
|
+
expect(vkp.writes[1].new.toString('hex')).toBe('3412');
|
|
258
|
+
expect(vkp.writes[2].new.toString('hex')).toBe('563412');
|
|
259
|
+
expect(vkp.writes[3].new.toString('hex')).toBe('78563412');
|
|
260
|
+
expect(vkp.writes[4].new.toString('hex')).toBe('9a78563412');
|
|
261
|
+
expect(vkp.writes[5].new.toString('hex')).toBe('bc9a78563412');
|
|
262
|
+
expect(vkp.writes[6].new.toString('hex')).toBe('debc9a78563412');
|
|
263
|
+
expect(vkp.writes[7].new.toString('hex')).toBe('f0debc9a78563412');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('data: positive decimal numbers', () => {
|
|
267
|
+
let vkp = vkpParse(`
|
|
268
|
+
; middle value
|
|
269
|
+
00000000: FF 0i+18
|
|
270
|
+
00000000: FFFF 0i+04660
|
|
271
|
+
00000000: FFFFFF 0i+01193046
|
|
272
|
+
00000000: FFFFFFFF 0i+0305419896
|
|
273
|
+
00000000: FFFFFFFFFF 0i+0078187493530
|
|
274
|
+
00000000: FFFFFFFFFFFF 0i+020015998343868
|
|
275
|
+
00000000: FFFFFFFFFFFFFF 0i+05124095576030430
|
|
276
|
+
00000000: FFFFFFFFFFFFFFFF 0i+01311768467463790320
|
|
277
|
+
|
|
278
|
+
; max value
|
|
279
|
+
00000000: FF 0i+127
|
|
280
|
+
00000000: FFFF 0i+32767
|
|
281
|
+
00000000: FFFFFF 0i+08388607
|
|
282
|
+
00000000: FFFFFFFF 0i+2147483647
|
|
283
|
+
00000000: FFFFFFFFFF 0i+0549755813887
|
|
284
|
+
00000000: FFFFFFFFFFFF 0i+140737488355327
|
|
285
|
+
00000000: FFFFFFFFFFFFFF 0i+36028797018963967
|
|
286
|
+
00000000: FFFFFFFFFFFFFFFF 0i+09223372036854775807
|
|
287
|
+
|
|
288
|
+
; min value
|
|
289
|
+
00000000: FF 0i+000
|
|
290
|
+
00000000: FFFF 0i+00000
|
|
291
|
+
00000000: FFFFFF 0i+00000000
|
|
292
|
+
00000000: FFFFFFFF 0i+0000000000
|
|
293
|
+
00000000: FFFFFFFFFF 0i+0000000000000
|
|
294
|
+
00000000: FFFFFFFFFFFF 0i+000000000000000
|
|
295
|
+
00000000: FFFFFFFFFFFFFF 0i+00000000000000000
|
|
296
|
+
00000000: FFFFFFFFFFFFFFFF 0i+00000000000000000000
|
|
297
|
+
`);
|
|
298
|
+
expect(vkp.valid).toBe(true);
|
|
299
|
+
expect(vkp.warnings.length).toBe(0);
|
|
300
|
+
expect(vkp.errors.length).toBe(0);
|
|
301
|
+
expect(vkp.writes.length).toBe(24);
|
|
302
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('12');
|
|
303
|
+
expect(vkp.writes[1].new.toString('hex')).toBe('3412');
|
|
304
|
+
expect(vkp.writes[2].new.toString('hex')).toBe('563412');
|
|
305
|
+
expect(vkp.writes[3].new.toString('hex')).toBe('78563412');
|
|
306
|
+
expect(vkp.writes[4].new.toString('hex')).toBe('9a78563412');
|
|
307
|
+
expect(vkp.writes[5].new.toString('hex')).toBe('bc9a78563412');
|
|
308
|
+
expect(vkp.writes[6].new.toString('hex')).toBe('debc9a78563412');
|
|
309
|
+
expect(vkp.writes[7].new.toString('hex')).toBe('f0debc9a78563412');
|
|
310
|
+
expect(vkp.writes[8].new.toString('hex')).toBe('7f');
|
|
311
|
+
expect(vkp.writes[9].new.toString('hex')).toBe('ff7f');
|
|
312
|
+
expect(vkp.writes[10].new.toString('hex')).toBe('ffff7f');
|
|
313
|
+
expect(vkp.writes[11].new.toString('hex')).toBe('ffffff7f');
|
|
314
|
+
expect(vkp.writes[12].new.toString('hex')).toBe('ffffffff7f');
|
|
315
|
+
expect(vkp.writes[13].new.toString('hex')).toBe('ffffffffff7f');
|
|
316
|
+
expect(vkp.writes[14].new.toString('hex')).toBe('ffffffffffff7f');
|
|
317
|
+
expect(vkp.writes[15].new.toString('hex')).toBe('ffffffffffffff7f');
|
|
318
|
+
expect(vkp.writes[16].new.toString('hex')).toBe('00');
|
|
319
|
+
expect(vkp.writes[17].new.toString('hex')).toBe('0000');
|
|
320
|
+
expect(vkp.writes[18].new.toString('hex')).toBe('000000');
|
|
321
|
+
expect(vkp.writes[19].new.toString('hex')).toBe('00000000');
|
|
322
|
+
expect(vkp.writes[20].new.toString('hex')).toBe('0000000000');
|
|
323
|
+
expect(vkp.writes[21].new.toString('hex')).toBe('000000000000');
|
|
324
|
+
expect(vkp.writes[22].new.toString('hex')).toBe('00000000000000');
|
|
325
|
+
expect(vkp.writes[23].new.toString('hex')).toBe('0000000000000000');
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('data: negative decimal numbers', () => {
|
|
329
|
+
let vkp = vkpParse(`
|
|
330
|
+
; middle value
|
|
331
|
+
00000000: FF 0i-18
|
|
332
|
+
00000000: FFFF 0i-04660
|
|
333
|
+
00000000: FFFFFF 0i-01193046
|
|
334
|
+
00000000: FFFFFFFF 0i-0305419896
|
|
335
|
+
00000000: FFFFFFFFFF 0i-0078187493530
|
|
336
|
+
00000000: FFFFFFFFFFFF 0i-020015998343868
|
|
337
|
+
00000000: FFFFFFFFFFFFFF 0i-05124095576030430
|
|
338
|
+
00000000: FFFFFFFFFFFFFFFF 0i-01311768467463790320
|
|
339
|
+
|
|
340
|
+
; min value
|
|
341
|
+
00000000: FF 0i-127
|
|
342
|
+
00000000: FFFF 0i-32767
|
|
343
|
+
00000000: FFFFFF 0i-08388607
|
|
344
|
+
00000000: FFFFFFFF 0i-2147483647
|
|
345
|
+
00000000: FFFFFFFFFF 0i-0549755813887
|
|
346
|
+
00000000: FFFFFFFFFFFF 0i-140737488355327
|
|
347
|
+
00000000: FFFFFFFFFFFFFF 0i-36028797018963967
|
|
348
|
+
00000000: FFFFFFFFFFFFFFFF 0i-09223372036854775807
|
|
349
|
+
|
|
350
|
+
; max value
|
|
351
|
+
00000000: FF 0i-001
|
|
352
|
+
00000000: FFFF 0i-00001
|
|
353
|
+
00000000: FFFFFF 0i-00000001
|
|
354
|
+
00000000: FFFFFFFF 0i-0000000001
|
|
355
|
+
00000000: FFFFFFFFFF 0i-0000000000001
|
|
356
|
+
00000000: FFFFFFFFFFFF 0i-000000000000001
|
|
357
|
+
00000000: FFFFFFFFFFFFFF 0i-00000000000000001
|
|
358
|
+
00000000: FFFFFFFFFFFFFFFF 0i-00000000000000000001
|
|
359
|
+
`);
|
|
360
|
+
expect(vkp.valid).toBe(true);
|
|
361
|
+
expect(vkp.warnings.length).toBe(0);
|
|
362
|
+
expect(vkp.errors.length).toBe(0);
|
|
363
|
+
expect(vkp.writes.length).toBe(24);
|
|
364
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('ee');
|
|
365
|
+
expect(vkp.writes[1].new.toString('hex')).toBe('cced');
|
|
366
|
+
expect(vkp.writes[2].new.toString('hex')).toBe('aacbed');
|
|
367
|
+
expect(vkp.writes[3].new.toString('hex')).toBe('88a9cbed');
|
|
368
|
+
expect(vkp.writes[4].new.toString('hex')).toBe('6687a9cbed');
|
|
369
|
+
expect(vkp.writes[5].new.toString('hex')).toBe('446587a9cbed');
|
|
370
|
+
expect(vkp.writes[6].new.toString('hex')).toBe('22436587a9cbed');
|
|
371
|
+
expect(vkp.writes[7].new.toString('hex')).toBe('1021436587a9cbed');
|
|
372
|
+
expect(vkp.writes[8].new.toString('hex')).toBe('81');
|
|
373
|
+
expect(vkp.writes[9].new.toString('hex')).toBe('0180');
|
|
374
|
+
expect(vkp.writes[10].new.toString('hex')).toBe('010080');
|
|
375
|
+
expect(vkp.writes[11].new.toString('hex')).toBe('01000080');
|
|
376
|
+
expect(vkp.writes[12].new.toString('hex')).toBe('0100000080');
|
|
377
|
+
expect(vkp.writes[13].new.toString('hex')).toBe('010000000080');
|
|
378
|
+
expect(vkp.writes[14].new.toString('hex')).toBe('01000000000080');
|
|
379
|
+
expect(vkp.writes[15].new.toString('hex')).toBe('0100000000000080');
|
|
380
|
+
expect(vkp.writes[16].new.toString('hex')).toBe('ff');
|
|
381
|
+
expect(vkp.writes[17].new.toString('hex')).toBe('ffff');
|
|
382
|
+
expect(vkp.writes[18].new.toString('hex')).toBe('ffffff');
|
|
383
|
+
expect(vkp.writes[19].new.toString('hex')).toBe('ffffffff');
|
|
384
|
+
expect(vkp.writes[20].new.toString('hex')).toBe('ffffffffff');
|
|
385
|
+
expect(vkp.writes[21].new.toString('hex')).toBe('ffffffffffff');
|
|
386
|
+
expect(vkp.writes[22].new.toString('hex')).toBe('ffffffffffffff');
|
|
387
|
+
expect(vkp.writes[23].new.toString('hex')).toBe('ffffffffffffffff');
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('data: string', () => {
|
|
391
|
+
let vkp = vkpParse(`
|
|
392
|
+
00000000: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF "ололо\\
|
|
393
|
+
\\0\\177\\100test\\x50\\x20\\a\\b\\t\\r\\n\\v\\f\\e\\\\\\/"
|
|
394
|
+
00000000: FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 'ололо\\
|
|
395
|
+
\\0\\177\\100\\uABCDtest\\xAB\\xCD\\a\\b\\t\\r\\n\\v\\f\\e\\\\\\/'
|
|
396
|
+
`);
|
|
397
|
+
expect(vkp.valid).toBe(true);
|
|
398
|
+
expect(vkp.warnings.length).toBe(0);
|
|
399
|
+
expect(vkp.errors.length).toBe(0);
|
|
400
|
+
expect(vkp.writes.length).toBe(2);
|
|
401
|
+
expect(vkp.writes[0].new.toString('hex')).toBe('eeebeeebee007f407465737450200708090d0a0b0c1b5c2f');
|
|
402
|
+
expect(vkp.writes[1].new.toString('hex')).toBe('3e043b043e043b043e0400007f004000cdab7400650073007400ab00cd000700080009000d000a000b000c001b005c002f00');
|
|
403
|
+
});
|
package/src/lexer.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import moo from 'moo';
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
|
+
import { VkpParseError } from './VkpParseError.js';
|
|
4
|
+
|
|
5
|
+
const RE_PLACEHOLDERS = [
|
|
6
|
+
/(?:0x)?(?:[a-fA-F0-9]*(?:XX|xx|YY|yy|ZZ|zz|HH|hh|nn|NN|Nn|MS|ML|\?\?)[a-fA-F0-9]*)+(?!\w)/,
|
|
7
|
+
/(?:0i[+-]?)(?:[0-9]*(?:[xyz?]+)[0-9]*)+(?!\w)/
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export const TOKEN = {
|
|
11
|
+
WHITESPACE: 1,
|
|
12
|
+
PRAGMA: 2,
|
|
13
|
+
COMMENT: 3,
|
|
14
|
+
OFFSET: 4,
|
|
15
|
+
ADDRESS: 5,
|
|
16
|
+
NUMBER: 6,
|
|
17
|
+
DATA: 7,
|
|
18
|
+
PLACEHOLDER: 8,
|
|
19
|
+
STRING: 9,
|
|
20
|
+
COMMA: 10,
|
|
21
|
+
LINE_ESCAPE: 11,
|
|
22
|
+
MULTILINE_COMMENT: 12,
|
|
23
|
+
UNFINISHED_COMMENT: 13,
|
|
24
|
+
TRAILING_COMMENT_END: 14,
|
|
25
|
+
NEWLINE: 15,
|
|
26
|
+
ERROR: 16,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const LEXER = moo.compile([
|
|
30
|
+
{ type: TOKEN.WHITESPACE, match: /[ \t]+/, lineBreaks: false },
|
|
31
|
+
{ type: TOKEN.PRAGMA, match: /#pragma[ \t\w]+/, lineBreaks: false },
|
|
32
|
+
{ type: TOKEN.COMMENT, match: /(?:\/\/|;|#).*?$/, lineBreaks: false },
|
|
33
|
+
{ type: TOKEN.OFFSET, match: /[+-](?:0[xX])?[a-fA-F0-9]+/, lineBreaks: false },
|
|
34
|
+
{ type: TOKEN.ADDRESS, match: /(?:0[xX])?[a-fA-F0-9]+:/, lineBreaks: false },
|
|
35
|
+
{ type: TOKEN.NUMBER, match: [/0x[a-fA-F0-9]+(?:\b|$)/, /0n[10]+(?:\b|$)/, /0i[+-]?[0-9]+(?!\w)/], lineBreaks: false },
|
|
36
|
+
{ type: TOKEN.DATA, match: /[a-fA-F0-9]+\b/, lineBreaks: false },
|
|
37
|
+
{ type: TOKEN.PLACEHOLDER, match: RE_PLACEHOLDERS, lineBreaks: false },
|
|
38
|
+
{ type: TOKEN.STRING, match: /(?:"(?:\\[^]|[^"\\])*?"|'(?:\\[^]|[^"\\])*?')/, lineBreaks: true },
|
|
39
|
+
{ type: TOKEN.COMMA, match: /,/, lineBreaks: false },
|
|
40
|
+
{ type: TOKEN.LINE_ESCAPE, match: /\\(?:\r\n|\n)/, lineBreaks: true },
|
|
41
|
+
{ type: TOKEN.MULTILINE_COMMENT, match: /\/\*[^]*?\*\//, lineBreaks: true },
|
|
42
|
+
{ type: TOKEN.UNFINISHED_COMMENT, match: /\/\*[^]*$/, lineBreaks: true },
|
|
43
|
+
{ type: TOKEN.TRAILING_COMMENT_END, match: /\*\//, lineBreaks: false },
|
|
44
|
+
{ type: TOKEN.NEWLINE, match: /(?:\r\n|\n)/, lineBreaks: true },
|
|
45
|
+
{ type: TOKEN.ERROR, match: /.+?$/, lineBreaks: false },
|
|
46
|
+
]);
|
package/src/parser.js
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import iconv from 'iconv-lite';
|
|
2
|
+
import { LEXER, TOKEN } from './lexer.js';
|
|
3
|
+
import { VkpParseError } from './VkpParseError.js';
|
|
4
|
+
|
|
5
|
+
const UINT_PARSER_DATA = [
|
|
6
|
+
[3, 0xFF, 8], // 1b
|
|
7
|
+
[5, 0xFFFF, 16], // 2b
|
|
8
|
+
[8, 0xFFFFFF, 24], // 3b
|
|
9
|
+
[10, 0xFFFFFFFF, 32], // 4b
|
|
10
|
+
[13, 0xFFFFFFFFFF, 40], // 5b
|
|
11
|
+
[15, 0xFFFFFFFFFFFF, 48], // 6b
|
|
12
|
+
[17, 0xFFFFFFFFFFFFFFn, 56], // 7b
|
|
13
|
+
[20, 0xFFFFFFFFFFFFFFFFn, 64], // 8b
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const SINT_PARSER_DATA = [
|
|
17
|
+
[3, 0x7F, 8], // 1b
|
|
18
|
+
[5, 0x7FFF, 16], // 2b
|
|
19
|
+
[8, 0x7FFFFF, 24], // 3b
|
|
20
|
+
[10, 0x7FFFFFFF, 32], // 4b
|
|
21
|
+
[13, 0x7FFFFFFFFF, 40], // 5b
|
|
22
|
+
[15, 0x7FFFFFFFFFFF, 48], // 6b
|
|
23
|
+
[17, 0x7FFFFFFFFFFFFFn, 56], // 7b
|
|
24
|
+
[20, 0x7FFFFFFFFFFFFFFFn, 64], // 8b
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const STR_ESCAPE_TABLE = {
|
|
28
|
+
"a": "\x07",
|
|
29
|
+
"b": "\b",
|
|
30
|
+
"t": "\t",
|
|
31
|
+
"r": "\r",
|
|
32
|
+
"n": "\n",
|
|
33
|
+
"v": "\v",
|
|
34
|
+
"f": "\f",
|
|
35
|
+
"e": "\x1B",
|
|
36
|
+
"\\": "\\",
|
|
37
|
+
"/": "/",
|
|
38
|
+
"*": "*",
|
|
39
|
+
"\"": "\"",
|
|
40
|
+
"'": "'",
|
|
41
|
+
"`": "`",
|
|
42
|
+
" ": " ",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let state;
|
|
46
|
+
|
|
47
|
+
function noop() {
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function vkpRawParser(text, options = {}) {
|
|
52
|
+
options = {
|
|
53
|
+
onPragma: noop,
|
|
54
|
+
onPatchData: noop,
|
|
55
|
+
onOffset: noop,
|
|
56
|
+
onComments: noop,
|
|
57
|
+
onWarning: noop,
|
|
58
|
+
onError: noop,
|
|
59
|
+
...options
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
state = {
|
|
63
|
+
token: null,
|
|
64
|
+
prevToken: null,
|
|
65
|
+
warnings: [],
|
|
66
|
+
onPragma: options.onPragma,
|
|
67
|
+
onPatchData: options.onPatchData,
|
|
68
|
+
onOffset: options.onOffset,
|
|
69
|
+
onComments: options.onComments,
|
|
70
|
+
onWarning: options.onWarning,
|
|
71
|
+
onError: options.onError,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
LEXER.reset(text);
|
|
75
|
+
|
|
76
|
+
let token;
|
|
77
|
+
while ((token = peekToken())) {
|
|
78
|
+
try {
|
|
79
|
+
if (token.type == TOKEN.ADDRESS) {
|
|
80
|
+
let loc = getLocation();
|
|
81
|
+
state.onPatchData(parsePatchRecord(), loc);
|
|
82
|
+
} else if (token.type == TOKEN.PRAGMA) {
|
|
83
|
+
let loc = getLocation();
|
|
84
|
+
state.onPragma(parsePatchPragma(), loc);
|
|
85
|
+
} else if (token.type == TOKEN.OFFSET) {
|
|
86
|
+
let loc = getLocation();
|
|
87
|
+
state.onOffset(parsePatchOffsetCorrector(), loc);
|
|
88
|
+
} else if (token.type == TOKEN.NEWLINE || token.type == TOKEN.WHITESPACE) {
|
|
89
|
+
nextToken();
|
|
90
|
+
} else if (token.type == TOKEN.COMMENT || token.type == TOKEN.MULTILINE_COMMENT || token.type == TOKEN.UNFINISHED_COMMENT) {
|
|
91
|
+
let loc = getLocation();
|
|
92
|
+
state.onComments(parseComments(), loc);
|
|
93
|
+
} else if (token.type == TOKEN.TRAILING_COMMENT_END) {
|
|
94
|
+
state.onWarning(new VkpParseError(`Trailing multiline comment end`, getLocation()));
|
|
95
|
+
nextToken();
|
|
96
|
+
} else {
|
|
97
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
let loc = getLocation();
|
|
101
|
+
let token;
|
|
102
|
+
while ((token = nextToken())) {
|
|
103
|
+
if (token.type == TOKEN.NEWLINE)
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
state.onError(e, loc);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
state = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseComments() {
|
|
114
|
+
let comments = [];
|
|
115
|
+
let token;
|
|
116
|
+
while ((token = peekToken())) {
|
|
117
|
+
if (token.type == TOKEN.NEWLINE) {
|
|
118
|
+
nextToken();
|
|
119
|
+
break;
|
|
120
|
+
} else if (token.type == TOKEN.WHITESPACE) {
|
|
121
|
+
nextToken();
|
|
122
|
+
} else if (token.type == TOKEN.COMMENT || token.type == TOKEN.MULTILINE_COMMENT || token.type == TOKEN.UNFINISHED_COMMENT) {
|
|
123
|
+
if (token.type == TOKEN.UNFINISHED_COMMENT)
|
|
124
|
+
state.onWarning(new VkpParseError(`Unfinished multiline comment`, getLocation()));
|
|
125
|
+
comments.push(nextToken());
|
|
126
|
+
} else {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return comments;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parsePatchPragma() {
|
|
134
|
+
let pragma = parsePragmaValue(peekToken().value);
|
|
135
|
+
nextToken();
|
|
136
|
+
let comments = parseCommentsAfterExpr();
|
|
137
|
+
return { pragma, comments };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parsePatchOffsetCorrector() {
|
|
141
|
+
let text = peekToken().value;
|
|
142
|
+
let offset = parseOffsetValue(text);
|
|
143
|
+
nextToken();
|
|
144
|
+
let comments = parseCommentsAfterExpr();
|
|
145
|
+
return { text, offset, comments };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parsePatchRecord() {
|
|
149
|
+
let address = parsePatchRecordAddress();
|
|
150
|
+
|
|
151
|
+
let data = [];
|
|
152
|
+
for (let i = 0; i < 2; i++) {
|
|
153
|
+
if (!parsePatchRecordSeparator())
|
|
154
|
+
break;
|
|
155
|
+
let loc = getLocation();
|
|
156
|
+
let [buffer, placeholders] = parsePatchData();
|
|
157
|
+
data.push({ loc, buffer: mergeBuffers(buffer), placeholders });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!data.length)
|
|
161
|
+
throw new VkpParseError(`Empty patch data record!`, getLocation());
|
|
162
|
+
|
|
163
|
+
let comments = parseCommentsAfterExpr();
|
|
164
|
+
return {
|
|
165
|
+
address,
|
|
166
|
+
comments,
|
|
167
|
+
old: data.length == 2 ? data[0] : null,
|
|
168
|
+
new: data.length == 2 ? data[1] : data[0],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function mergeBuffers(buffers) {
|
|
173
|
+
return buffers.length > 1 ? Buffer.concat(buffers) : buffers[0];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parsePatchRecordAddress() {
|
|
177
|
+
let value = parseAddressValue(peekToken().value);
|
|
178
|
+
nextToken();
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function parsePatchData() {
|
|
183
|
+
let data = [];
|
|
184
|
+
let token;
|
|
185
|
+
let placeholders = 0;
|
|
186
|
+
while ((token = peekToken())) {
|
|
187
|
+
if (token.type == TOKEN.COMMA) {
|
|
188
|
+
nextToken();
|
|
189
|
+
} else if (token.type == TOKEN.DATA) {
|
|
190
|
+
data.push(parseHexDataValue(peekToken().value));
|
|
191
|
+
nextToken();
|
|
192
|
+
} else if (token.type == TOKEN.PLACEHOLDER) {
|
|
193
|
+
data.push(parsePlaceholderValue(peekToken().value));
|
|
194
|
+
nextToken();
|
|
195
|
+
placeholders++;
|
|
196
|
+
} else if (token.type == TOKEN.NUMBER) {
|
|
197
|
+
data.push(parseAnyNumberValue(peekToken().value));
|
|
198
|
+
nextToken();
|
|
199
|
+
} else if (token.type == TOKEN.STRING) {
|
|
200
|
+
data.push(parseStringValue(peekToken().value));
|
|
201
|
+
nextToken();
|
|
202
|
+
} else if (token.type == TOKEN.LINE_ESCAPE) {
|
|
203
|
+
nextToken();
|
|
204
|
+
} else if (token.type == TOKEN.WHITESPACE || token.type == TOKEN.NEWLINE) {
|
|
205
|
+
break;
|
|
206
|
+
} else if (token.type == TOKEN.COMMENT || token.type == TOKEN.MULTILINE_COMMENT || token.type == TOKEN.UNFINISHED_COMMENT) {
|
|
207
|
+
if (prevToken().type == TOKEN.NUMBER)
|
|
208
|
+
throw new VkpParseError(`No whitespace between number and comment`, getLocation());
|
|
209
|
+
break;
|
|
210
|
+
} else {
|
|
211
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return [data, placeholders];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parsePatchRecordSeparator() {
|
|
218
|
+
let token;
|
|
219
|
+
while ((token = peekToken())) {
|
|
220
|
+
if (token.type == TOKEN.NEWLINE) {
|
|
221
|
+
return false;
|
|
222
|
+
} else if (token.type == TOKEN.DATA || token.type == TOKEN.PLACEHOLDER || token.type == TOKEN.NUMBER || token.type == TOKEN.STRING) {
|
|
223
|
+
return true;
|
|
224
|
+
} else if (token.type == TOKEN.COMMENT || token.type == TOKEN.MULTILINE_COMMENT || token.type == TOKEN.UNFINISHED_COMMENT) {
|
|
225
|
+
return false;
|
|
226
|
+
} else if (token.type == TOKEN.WHITESPACE || token.type == TOKEN.LINE_ESCAPE) {
|
|
227
|
+
nextToken();
|
|
228
|
+
} else {
|
|
229
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseCommentsAfterExpr() {
|
|
236
|
+
let comments = [];
|
|
237
|
+
let token;
|
|
238
|
+
while ((token = peekToken())) {
|
|
239
|
+
if (token.type == TOKEN.NEWLINE) {
|
|
240
|
+
nextToken();
|
|
241
|
+
break;
|
|
242
|
+
} else if (token.type == TOKEN.WHITESPACE) {
|
|
243
|
+
nextToken();
|
|
244
|
+
} else if (token.type == TOKEN.COMMENT || token.type == TOKEN.MULTILINE_COMMENT || token.type == TOKEN.UNFINISHED_COMMENT) {
|
|
245
|
+
if (token.type == TOKEN.UNFINISHED_COMMENT)
|
|
246
|
+
state.onWarning(new VkpParseError(`Unfinished multiline comment`, getLocation()));
|
|
247
|
+
comments.push(nextToken());
|
|
248
|
+
} else {
|
|
249
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return comments;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function nextToken() {
|
|
256
|
+
state.prevToken = state.token;
|
|
257
|
+
let token = state.token ? state.token : LEXER.next();
|
|
258
|
+
state.token = LEXER.next();
|
|
259
|
+
return token;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function peekToken() {
|
|
263
|
+
if (state.token == null)
|
|
264
|
+
state.token = LEXER.next();
|
|
265
|
+
return state.token;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function prevToken() {
|
|
269
|
+
return state.prevToken;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Token value parsers
|
|
274
|
+
* */
|
|
275
|
+
function parseAnyNumberValue(v) {
|
|
276
|
+
let m;
|
|
277
|
+
let tmpBuffer = Buffer.allocUnsafe(8);
|
|
278
|
+
|
|
279
|
+
if ((m = v.match(/^0i([+-]\d+)$/i))) { // dec signed
|
|
280
|
+
let num = m[1];
|
|
281
|
+
for (let d of SINT_PARSER_DATA) {
|
|
282
|
+
if ((num.length - 1) <= d[0]) {
|
|
283
|
+
let parsedNum = BigInt(num);
|
|
284
|
+
if (parsedNum < -d[1] || parsedNum > d[1])
|
|
285
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range -${d[1].toString(10)} ... +${d[1].toString(10)}`, getLocation());
|
|
286
|
+
if ((num.length - 1) < d[0] && d[0] > 3) {
|
|
287
|
+
throw new VkpParseError(`The wrong number of digits in integer (${v})`, getLocation(),
|
|
288
|
+
"Must be: 3 (for BYTE), 5 (for WORD), 8 (for 3 BYTES), 10 (for DWORD), 13 (for 5 BYTES), 15 (for 6 BYTES), 17 (for 7 BYTES), 20 (for 8 BYTES)." +
|
|
289
|
+
"Use leading zeroes to match the number of digits.");
|
|
290
|
+
}
|
|
291
|
+
tmpBuffer.writeBigUInt64LE(BigInt.asUintN(d[2], parsedNum), 0);
|
|
292
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
} else if ((m = v.match(/^0i(\d+)$/i))) { // dec unsigned
|
|
296
|
+
let num = m[1];
|
|
297
|
+
for (let d of UINT_PARSER_DATA) {
|
|
298
|
+
if (num.length <= d[0]) {
|
|
299
|
+
let parsedNum = d[2] <= 32 ? parseInt(num, 10) : BigInt(num);
|
|
300
|
+
if (parsedNum < 0 || parsedNum > d[1])
|
|
301
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0 ... ${d[1].toString(10)}`, getLocation());
|
|
302
|
+
if (num.length < d[0] && d[0] > 3) {
|
|
303
|
+
throw new VkpParseError(`The wrong number of digits in integer (${v})`, getLocation(),
|
|
304
|
+
"Must be: 3 (for BYTE), 5 (for WORD), 8 (for 3 BYTES), 10 (for DWORD), 13 (for 5 BYTES), 15 (for 6 BYTES), 17 (for 7 BYTES), 20 (for 8 BYTES)." +
|
|
305
|
+
"Use leading zeroes to match the number of digits.");
|
|
306
|
+
}
|
|
307
|
+
if (d[2] <= 32) {
|
|
308
|
+
tmpBuffer.writeUInt32LE(parsedNum, 0);
|
|
309
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
310
|
+
} else {
|
|
311
|
+
tmpBuffer.writeBigUInt64LE(parsedNum, 0);
|
|
312
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} else if ((m = v.match(/^0x([a-f0-9]+)$/i))) { // hex unsigned
|
|
317
|
+
let hexnum = m[1];
|
|
318
|
+
if (hexnum.length % 2)
|
|
319
|
+
hexnum = "0" + hexnum;
|
|
320
|
+
if (hexnum.length > 8)
|
|
321
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0x00000000 ... 0xFFFFFFFF`, getLocation());
|
|
322
|
+
let number = parseInt(`0x${hexnum}`, 16);
|
|
323
|
+
tmpBuffer.writeUInt32LE(number, 0);
|
|
324
|
+
return tmpBuffer.subarray(0, Math.ceil(hexnum.length / 2));
|
|
325
|
+
} else if ((m = v.match(/^0n([10]+)$/i))) { // binary unsigned
|
|
326
|
+
if (m[1].length > 32)
|
|
327
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0n0 ... 0n11111111111111111111111111111111`, getLocation());
|
|
328
|
+
let number = parseInt(m[1], 2);
|
|
329
|
+
tmpBuffer.writeUInt32LE(number, 0);
|
|
330
|
+
return tmpBuffer.subarray(0, Math.ceil(m[1].length / 8));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
throw new VkpParseError(`Invalid number: ${v}`, getLocation());
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function parsePlaceholderValue(value) {
|
|
337
|
+
let m;
|
|
338
|
+
if ((m = value.match(/^(0i|0x|0n)(.*?)$/i))) {
|
|
339
|
+
return parseAnyNumberValue(m[1] + m[2].replace(/[^0-9a-f]/gi, '0'));
|
|
340
|
+
} else {
|
|
341
|
+
return parseHexDataValue(value.replace(/[^0-9a-f]/gi, '0'));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function parseStringValue(value) {
|
|
346
|
+
let m;
|
|
347
|
+
if ((m = value.match(/(\/\*|\*\/|\/\/)/))) {
|
|
348
|
+
throw new VkpParseError(`Unescaped ${m[1]} is not allowed in string: ${value}`, getLocation(),
|
|
349
|
+
`Escape these ambiguous characters like this: \\/* or \\/\\/.`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
let text = value.slice(1, -1);
|
|
353
|
+
|
|
354
|
+
let offset = 0;
|
|
355
|
+
let parts = [];
|
|
356
|
+
let tmp = "";
|
|
357
|
+
let unicode = (value.charAt(0) == "'");
|
|
358
|
+
let escape = false;
|
|
359
|
+
|
|
360
|
+
/*
|
|
361
|
+
if (!unicode && value.match(/[^\u0000-\u007F]/)) {
|
|
362
|
+
throw new VkpParseError(`ASCII string with non-ASCII characters`, getLocation(),
|
|
363
|
+
`Please use only ASCII-safe characters from the range U+0000-U+007F or \\xNN escape sequences.`);
|
|
364
|
+
}
|
|
365
|
+
*/
|
|
366
|
+
|
|
367
|
+
let breakpoint = () => {
|
|
368
|
+
if (tmp.length) {
|
|
369
|
+
parts.push(tmp);
|
|
370
|
+
tmp = "";
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
let getStrLocation = (i) => {
|
|
375
|
+
let loc = getLocation();
|
|
376
|
+
loc.column += i;
|
|
377
|
+
return loc;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < text.length; i++) {
|
|
381
|
+
let c = text.charAt(i);
|
|
382
|
+
if (escape) {
|
|
383
|
+
if (c == "\r") {
|
|
384
|
+
if (text.charAt(i + 1) == "\n")
|
|
385
|
+
i++;
|
|
386
|
+
} else if (c == "\n") {
|
|
387
|
+
// Ignore
|
|
388
|
+
} else if (c == "x") {
|
|
389
|
+
let hex = text.substr(i + 1, 2);
|
|
390
|
+
if (hex.length == 2) {
|
|
391
|
+
breakpoint();
|
|
392
|
+
let hexnum = parseInt(`0x${hex}`);
|
|
393
|
+
if (unicode) {
|
|
394
|
+
parts.push(Buffer.from([ hexnum, 0x00 ]));
|
|
395
|
+
} else {
|
|
396
|
+
if (hexnum >= 0x7F && !unicode)
|
|
397
|
+
throw new VkpParseError(`Bad escape sequence (\\x${hex})`, getStrLocation(i), `Allowed range: \\x00-\\x7F.`);
|
|
398
|
+
parts.push(Buffer.from([ hexnum ]));
|
|
399
|
+
}
|
|
400
|
+
i += 2;
|
|
401
|
+
} else {
|
|
402
|
+
throw new VkpParseError(`Unknown escape sequence (\\x${hex})`, getStrLocation(i));
|
|
403
|
+
}
|
|
404
|
+
} else if (c == "u") {
|
|
405
|
+
let hex = text.substr(i + 1, 4);
|
|
406
|
+
if (hex.length == 4) {
|
|
407
|
+
breakpoint();
|
|
408
|
+
let hexnum = parseInt(`0x${hex}`);
|
|
409
|
+
if (unicode) {
|
|
410
|
+
parts.push(Buffer.from([ hexnum & 0xFF, (hexnum >> 8) & 0xFF ]));
|
|
411
|
+
} else {
|
|
412
|
+
throw new VkpParseError(`Unknown escape sequence (\\u${hex})`, getStrLocation(i));
|
|
413
|
+
}
|
|
414
|
+
i += 4;
|
|
415
|
+
} else {
|
|
416
|
+
throw new VkpParseError(`Unknown escape sequence (\\u${hex})`, getStrLocation(i));
|
|
417
|
+
}
|
|
418
|
+
} else if (c.match(/[0-7]/)) {
|
|
419
|
+
let ocatlLen = 1;
|
|
420
|
+
for (let j = 1; j < 3; j++) {
|
|
421
|
+
if (!text.charAt(i + j).match(/[0-7]/))
|
|
422
|
+
break;
|
|
423
|
+
ocatlLen++;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let oct = parseInt(text.substr(i, ocatlLen), 8);
|
|
427
|
+
if (oct > 0xFF)
|
|
428
|
+
throw new VkpParseError(`Unknown escape sequence (\\${text.substr(i, ocatlLen)})`, getStrLocation(i));
|
|
429
|
+
|
|
430
|
+
breakpoint();
|
|
431
|
+
if (unicode) {
|
|
432
|
+
parts.push(Buffer.from([ oct, 0x00 ]));
|
|
433
|
+
} else {
|
|
434
|
+
parts.push(Buffer.from([ oct ]));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
i += ocatlLen - 1;
|
|
438
|
+
} else if ((c in STR_ESCAPE_TABLE)) {
|
|
439
|
+
tmp += STR_ESCAPE_TABLE[c];
|
|
440
|
+
} else {
|
|
441
|
+
throw new VkpParseError(`Unknown escape sequence (\\${c})`, getStrLocation(i));
|
|
442
|
+
}
|
|
443
|
+
escape = false;
|
|
444
|
+
} else {
|
|
445
|
+
if (c == '\\') {
|
|
446
|
+
escape = true;
|
|
447
|
+
} else {
|
|
448
|
+
tmp += c;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
breakpoint();
|
|
454
|
+
|
|
455
|
+
if (unicode) {
|
|
456
|
+
return Buffer.concat(parts.map((p) => typeof p === "string" ? iconv.encode(p, 'utf-16', { addBOM: false }) : p));
|
|
457
|
+
} else {
|
|
458
|
+
return Buffer.concat(parts.map((p) => typeof p === "string" ? iconv.encode(p, 'windows-1251') : p));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function parseOffsetValue(v) {
|
|
463
|
+
let result = parseInt(v.replace(/^([+-])(0x)?/i, '$10x'), 16);
|
|
464
|
+
if (isNaN(result))
|
|
465
|
+
throw new VkpParseError(`Invalid offset: ${v}`, getLocation());
|
|
466
|
+
if (result > 0xFFFFFFFF)
|
|
467
|
+
throw new VkpParseError(`Offset ${v} exceeds allowed range 00000000 ... FFFFFFFF`, getLocation());
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function parseAddressValue(v) {
|
|
472
|
+
let result = parseInt(v.replace(/^(0x)?(.*?):$/i, '0x$2'), 16);
|
|
473
|
+
if (isNaN(result))
|
|
474
|
+
throw new VkpParseError(`Invalid address: ${v}`, getLocation());
|
|
475
|
+
if (result > 0xFFFFFFFF)
|
|
476
|
+
throw new VkpParseError(`Address ${v} exceeds allowed range 00000000 ... FFFFFFFF`, getLocation());
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function parseHexDataValue(v) {
|
|
481
|
+
if (v.length % 2 != 0)
|
|
482
|
+
throw new VkpParseError(`Hex data (${v}) must be even length`, getLocation());
|
|
483
|
+
return Buffer.from(v, "hex");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function parsePragmaValue(v) {
|
|
487
|
+
let m;
|
|
488
|
+
if (!(m = v.trim().match(/^#pragma\s+(enable|disable)\s+(warn_no_old_on_apply|warn_if_new_exist_on_apply|warn_if_old_exist_on_undo|undo|old_equal_ff)$/)))
|
|
489
|
+
throw new VkpParseError(`Invalid PRAGMA: ${v}`, getLocation());
|
|
490
|
+
return { name: m[2], action: m[1] };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function getLocation() {
|
|
494
|
+
return state.token ? { line: state.token.line, column: state.token.col } : { line: 1, column: 1 };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export { vkpRawParser };
|