@sie-js/vkp 1.0.6 → 2.0.1
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 +4 -4
- package/dist/src/VkpParseError.d.ts +12 -0
- package/dist/src/VkpParseError.js +65 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +156 -0
- package/dist/src/index.test.d.ts +1 -0
- package/dist/src/index.test.js +381 -0
- package/dist/src/lexer.d.ts +2 -0
- package/dist/src/lexer.js +23 -0
- package/dist/src/parser.d.ts +37 -0
- package/dist/src/parser.js +510 -0
- package/package.json +34 -25
- package/.editorconfig +0 -6
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/ci.yml +0 -18
- package/.prettierrc.json +0 -21
- package/examples/get-bad-patches.js +0 -122
- package/jest.config.js +0 -4
- package/src/VkpParseError.js +0 -68
- package/src/index.js +0 -171
- package/src/index.test.js +0 -403
- package/src/lexer.js +0 -46
- package/src/parser.js +0 -515
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export type VkpTokenType = 'WHITESPACE' | 'PRAGMA' | 'COMMENT' | 'OFFSET' | 'ADDRESS' | 'NUMBER' | 'DATA' | 'PLACEHOLDER' | 'STRING' | 'COMMA' | 'LINE_ESCAPE' | 'MULTILINE_COMMENT' | 'UNFINISHED_COMMENT' | 'TRAILING_COMMENT_END' | 'NEWLINE' | 'ERROR';
|
|
2
|
+
export declare const VkpLexer: import("moo").Lexer;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import moo from "moo";
|
|
2
|
+
const RE_PLACEHOLDERS = [
|
|
3
|
+
/(?:0x)?(?:[a-fA-F0-9]*(?:XX|xx|YY|yy|ZZ|zz|HH|hh|nn|NN|Nn|MS|ML|\?\?)[a-fA-F0-9]*)+(?!\w)/,
|
|
4
|
+
/0i[+-]?(?:[0-9]*[xyz?]+[0-9]*)+(?!\w)/
|
|
5
|
+
];
|
|
6
|
+
export const VkpLexer = moo.compile({
|
|
7
|
+
WHITESPACE: { match: /[ \t]+/, lineBreaks: false },
|
|
8
|
+
PRAGMA: { match: /#pragma[ \t\w]+/, lineBreaks: false },
|
|
9
|
+
COMMENT: { match: /(?:\/\/|;|#).*?$/, lineBreaks: false },
|
|
10
|
+
OFFSET: { match: /[+-](?:0[xX])?[a-fA-F0-9]+/, lineBreaks: false },
|
|
11
|
+
ADDRESS: { match: /(?:0[xX])?[a-fA-F0-9]+:/, lineBreaks: false },
|
|
12
|
+
NUMBER: { match: [/0x[a-fA-F0-9]+(?:\b|$)/, /0n[10]+(?:\b|$)/, /0i[+-]?[0-9]+(?!\w)/], lineBreaks: false },
|
|
13
|
+
DATA: { match: /[a-fA-F0-9]+\b/, lineBreaks: false },
|
|
14
|
+
PLACEHOLDER: { match: RE_PLACEHOLDERS, lineBreaks: false },
|
|
15
|
+
STRING: { match: /"(?:\\[^]|[^"\\])*?"|'(?:\\[^]|[^"\\])*?'/, lineBreaks: true },
|
|
16
|
+
COMMA: { match: /,/, lineBreaks: false },
|
|
17
|
+
LINE_ESCAPE: { match: /\\(?:\r\n|\n)/, lineBreaks: true },
|
|
18
|
+
MULTILINE_COMMENT: { match: /\/\*[^]*?\*\//, lineBreaks: true },
|
|
19
|
+
UNFINISHED_COMMENT: { match: /\/\*[^]*$/, lineBreaks: true },
|
|
20
|
+
TRAILING_COMMENT_END: { match: /\*\//, lineBreaks: false },
|
|
21
|
+
NEWLINE: { match: /\r\n|\n/, lineBreaks: true },
|
|
22
|
+
ERROR: { match: /.+?$/, lineBreaks: false },
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { VkpLocation, VkpParseError } from "./VkpParseError.js";
|
|
2
|
+
export type VkpPragmaName = 'warn_no_old_on_apply' | 'warn_if_new_exist_on_apply' | 'warn_if_old_exist_on_undo' | 'undo' | 'old_equal_ff';
|
|
3
|
+
export type VkpPragmaAction = 'enable' | 'disable';
|
|
4
|
+
export interface VkpPragma {
|
|
5
|
+
name: VkpPragmaName;
|
|
6
|
+
action: VkpPragmaAction;
|
|
7
|
+
}
|
|
8
|
+
export interface VkpPragmaNode {
|
|
9
|
+
pragma: VkpPragma;
|
|
10
|
+
comment: string;
|
|
11
|
+
}
|
|
12
|
+
export interface VkpOffsetNode {
|
|
13
|
+
text: string;
|
|
14
|
+
offset: number;
|
|
15
|
+
comment: string;
|
|
16
|
+
}
|
|
17
|
+
export interface VkpPatchData {
|
|
18
|
+
loc: VkpLocation;
|
|
19
|
+
buffer: Buffer;
|
|
20
|
+
placeholders: number;
|
|
21
|
+
}
|
|
22
|
+
export interface VkpPatchDataNode {
|
|
23
|
+
address: number;
|
|
24
|
+
comment: string;
|
|
25
|
+
old?: VkpPatchData;
|
|
26
|
+
new: VkpPatchData;
|
|
27
|
+
}
|
|
28
|
+
export interface VkpParserOptions {
|
|
29
|
+
onPragma?: (value: VkpPragmaNode, loc: VkpLocation) => void;
|
|
30
|
+
onPatchData?: (data: VkpPatchDataNode, loc: VkpLocation) => void;
|
|
31
|
+
onOffset?: (value: VkpOffsetNode, loc: VkpLocation) => void;
|
|
32
|
+
onComments?: (comments: string[], loc: VkpLocation) => void;
|
|
33
|
+
onWarning?: (warning: VkpParseError) => void;
|
|
34
|
+
onError?: (error: VkpParseError, loc?: VkpLocation) => void;
|
|
35
|
+
}
|
|
36
|
+
declare function vkpRawParser(text: string, options?: VkpParserOptions): void;
|
|
37
|
+
export { vkpRawParser };
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import iconv from "iconv-lite";
|
|
2
|
+
import { VkpLexer } from "./lexer.js";
|
|
3
|
+
import { VkpParseError } from "./VkpParseError.js";
|
|
4
|
+
const UINT_PARSER_DATA = [
|
|
5
|
+
[3, 0xFF, 8], // 1b
|
|
6
|
+
[5, 0xFFFF, 16], // 2b
|
|
7
|
+
[8, 0xFFFFFF, 24], // 3b
|
|
8
|
+
[10, 0xFFFFFFFF, 32], // 4b
|
|
9
|
+
[13, 0xFFFFFFFFFF, 40], // 5b
|
|
10
|
+
[15, 0xFFFFFFFFFFFF, 48], // 6b
|
|
11
|
+
[17, 0xffffffffffffffn, 56], // 7b
|
|
12
|
+
[20, 0xffffffffffffffffn, 64], // 8b
|
|
13
|
+
];
|
|
14
|
+
const SINT_PARSER_DATA = [
|
|
15
|
+
[3, 0x7F, 8], // 1b
|
|
16
|
+
[5, 0x7FFF, 16], // 2b
|
|
17
|
+
[8, 0x7FFFFF, 24], // 3b
|
|
18
|
+
[10, 0x7FFFFFFF, 32], // 4b
|
|
19
|
+
[13, 0x7FFFFFFFFF, 40], // 5b
|
|
20
|
+
[15, 0x7FFFFFFFFFFF, 48], // 6b
|
|
21
|
+
[17, 0x7fffffffffffffn, 56], // 7b
|
|
22
|
+
[20, 0x7fffffffffffffffn, 64], // 8b
|
|
23
|
+
];
|
|
24
|
+
const STR_ESCAPE_TABLE = {
|
|
25
|
+
"a": "\x07",
|
|
26
|
+
"b": "\b",
|
|
27
|
+
"t": "\t",
|
|
28
|
+
"r": "\r",
|
|
29
|
+
"n": "\n",
|
|
30
|
+
"v": "\v",
|
|
31
|
+
"f": "\f",
|
|
32
|
+
"e": "\x1B",
|
|
33
|
+
"\\": "\\",
|
|
34
|
+
"/": "/",
|
|
35
|
+
"*": "*",
|
|
36
|
+
"\"": "\"",
|
|
37
|
+
"'": "'",
|
|
38
|
+
"`": "`",
|
|
39
|
+
" ": " ",
|
|
40
|
+
};
|
|
41
|
+
let state;
|
|
42
|
+
function noop() {
|
|
43
|
+
// Really nothing!
|
|
44
|
+
}
|
|
45
|
+
function vkpRawParser(text, options = {}) {
|
|
46
|
+
const validOptions = {
|
|
47
|
+
onPragma: noop,
|
|
48
|
+
onPatchData: noop,
|
|
49
|
+
onOffset: noop,
|
|
50
|
+
onComments: noop,
|
|
51
|
+
onWarning: noop,
|
|
52
|
+
onError: noop,
|
|
53
|
+
...options
|
|
54
|
+
};
|
|
55
|
+
state = {
|
|
56
|
+
token: undefined,
|
|
57
|
+
prevToken: undefined,
|
|
58
|
+
warnings: [],
|
|
59
|
+
onPragma: validOptions.onPragma,
|
|
60
|
+
onPatchData: validOptions.onPatchData,
|
|
61
|
+
onOffset: validOptions.onOffset,
|
|
62
|
+
onComments: validOptions.onComments,
|
|
63
|
+
onWarning: validOptions.onWarning,
|
|
64
|
+
onError: validOptions.onError,
|
|
65
|
+
};
|
|
66
|
+
VkpLexer.reset(text);
|
|
67
|
+
let token;
|
|
68
|
+
while ((token = peekToken())) {
|
|
69
|
+
try {
|
|
70
|
+
if (token.type == 'ADDRESS') {
|
|
71
|
+
const loc = getLocation();
|
|
72
|
+
state.onPatchData(parsePatchRecord(), loc);
|
|
73
|
+
}
|
|
74
|
+
else if (token.type == 'PRAGMA') {
|
|
75
|
+
const loc = getLocation();
|
|
76
|
+
state.onPragma(parsePatchPragma(), loc);
|
|
77
|
+
}
|
|
78
|
+
else if (token.type == 'OFFSET') {
|
|
79
|
+
const loc = getLocation();
|
|
80
|
+
state.onOffset(parsePatchOffsetCorrector(), loc);
|
|
81
|
+
}
|
|
82
|
+
else if (token.type == 'NEWLINE' || token.type == 'WHITESPACE') {
|
|
83
|
+
nextToken();
|
|
84
|
+
}
|
|
85
|
+
else if (token.type == 'COMMENT' || token.type == 'MULTILINE_COMMENT' || token.type == 'UNFINISHED_COMMENT') {
|
|
86
|
+
const loc = getLocation();
|
|
87
|
+
state.onComments(parseComments(), loc);
|
|
88
|
+
}
|
|
89
|
+
else if (token.type == 'TRAILING_COMMENT_END') {
|
|
90
|
+
state.onWarning(new VkpParseError(`Trailing multiline comment end`, getLocation()));
|
|
91
|
+
nextToken();
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
if (!(e instanceof VkpParseError))
|
|
99
|
+
throw e;
|
|
100
|
+
const loc = getLocation();
|
|
101
|
+
let token;
|
|
102
|
+
while ((token = nextToken())) {
|
|
103
|
+
if (token.type == 'NEWLINE')
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
state.onError(e, loc);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
state = undefined;
|
|
110
|
+
}
|
|
111
|
+
function parseComments() {
|
|
112
|
+
const comments = [];
|
|
113
|
+
let token;
|
|
114
|
+
while ((token = peekToken())) {
|
|
115
|
+
if (token.type == 'NEWLINE') {
|
|
116
|
+
nextToken();
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
else if (token.type == 'WHITESPACE') {
|
|
120
|
+
nextToken();
|
|
121
|
+
}
|
|
122
|
+
else if (token.type == 'COMMENT' || token.type == 'MULTILINE_COMMENT' || token.type == 'UNFINISHED_COMMENT') {
|
|
123
|
+
if (token.type == 'UNFINISHED_COMMENT')
|
|
124
|
+
state.onWarning(new VkpParseError(`Unfinished multiline comment`, getLocation()));
|
|
125
|
+
comments.push(parseCommentValue(token.value));
|
|
126
|
+
nextToken();
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return comments;
|
|
133
|
+
}
|
|
134
|
+
function parsePatchPragma() {
|
|
135
|
+
const pragma = parsePragmaValue(peekToken().value);
|
|
136
|
+
nextToken();
|
|
137
|
+
const comment = parseCommentsAfterExpr();
|
|
138
|
+
return { pragma, comment };
|
|
139
|
+
}
|
|
140
|
+
function parsePatchOffsetCorrector() {
|
|
141
|
+
const text = peekToken().value;
|
|
142
|
+
const offset = parseOffsetValue(text);
|
|
143
|
+
nextToken();
|
|
144
|
+
const comment = parseCommentsAfterExpr();
|
|
145
|
+
return { text, offset, comment };
|
|
146
|
+
}
|
|
147
|
+
function parsePatchRecord() {
|
|
148
|
+
const address = parsePatchRecordAddress();
|
|
149
|
+
const data = [];
|
|
150
|
+
for (let i = 0; i < 2; i++) {
|
|
151
|
+
if (!parsePatchRecordSeparator())
|
|
152
|
+
break;
|
|
153
|
+
const loc = getLocation();
|
|
154
|
+
const [buffer, placeholders] = parsePatchData();
|
|
155
|
+
data.push({ loc, buffer: mergeBuffers(buffer), placeholders });
|
|
156
|
+
}
|
|
157
|
+
if (!data.length)
|
|
158
|
+
throw new VkpParseError(`Empty patch data record!`, getLocation());
|
|
159
|
+
const comment = parseCommentsAfterExpr();
|
|
160
|
+
return {
|
|
161
|
+
address,
|
|
162
|
+
comment,
|
|
163
|
+
old: data.length == 2 ? data[0] : undefined,
|
|
164
|
+
new: data.length == 2 ? data[1] : data[0],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function mergeBuffers(buffers) {
|
|
168
|
+
return buffers.length > 1 ? Buffer.concat(buffers) : buffers[0];
|
|
169
|
+
}
|
|
170
|
+
function parsePatchRecordAddress() {
|
|
171
|
+
const value = parseAddressValue(peekToken().value);
|
|
172
|
+
nextToken();
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function parsePatchData() {
|
|
176
|
+
const data = [];
|
|
177
|
+
let token;
|
|
178
|
+
let placeholders = 0;
|
|
179
|
+
while ((token = peekToken())) {
|
|
180
|
+
if (token.type == 'COMMA') {
|
|
181
|
+
nextToken();
|
|
182
|
+
}
|
|
183
|
+
else if (token.type == 'DATA') {
|
|
184
|
+
data.push(parseHexDataValue(peekToken().value));
|
|
185
|
+
nextToken();
|
|
186
|
+
}
|
|
187
|
+
else if (token.type == 'PLACEHOLDER') {
|
|
188
|
+
data.push(parsePlaceholderValue(peekToken().value));
|
|
189
|
+
nextToken();
|
|
190
|
+
placeholders++;
|
|
191
|
+
}
|
|
192
|
+
else if (token.type == 'NUMBER') {
|
|
193
|
+
data.push(parseAnyNumberValue(peekToken().value));
|
|
194
|
+
nextToken();
|
|
195
|
+
}
|
|
196
|
+
else if (token.type == 'STRING') {
|
|
197
|
+
data.push(parseStringValue(peekToken().value));
|
|
198
|
+
nextToken();
|
|
199
|
+
}
|
|
200
|
+
else if (token.type == 'LINE_ESCAPE') {
|
|
201
|
+
nextToken();
|
|
202
|
+
}
|
|
203
|
+
else if (token.type == 'WHITESPACE' || token.type == 'NEWLINE') {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
else if (token.type == 'COMMENT' || token.type == 'MULTILINE_COMMENT' || token.type == 'UNFINISHED_COMMENT') {
|
|
207
|
+
if (prevToken().type == 'NUMBER')
|
|
208
|
+
throw new VkpParseError(`No whitespace between number and comment`, getLocation());
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return [data, placeholders];
|
|
216
|
+
}
|
|
217
|
+
function parsePatchRecordSeparator() {
|
|
218
|
+
let token;
|
|
219
|
+
while ((token = peekToken())) {
|
|
220
|
+
if (token.type == 'NEWLINE') {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
else if (token.type == 'DATA' || token.type == 'PLACEHOLDER' || token.type == 'NUMBER' || token.type == 'STRING') {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
else if (token.type == 'COMMENT' || token.type == 'MULTILINE_COMMENT' || token.type == 'UNFINISHED_COMMENT') {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
else if (token.type == 'WHITESPACE' || token.type == 'LINE_ESCAPE') {
|
|
230
|
+
nextToken();
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
function parseCommentsAfterExpr() {
|
|
239
|
+
const comments = [];
|
|
240
|
+
let token;
|
|
241
|
+
while ((token = peekToken())) {
|
|
242
|
+
if (token.type == 'NEWLINE') {
|
|
243
|
+
nextToken();
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
else if (token.type == 'WHITESPACE') {
|
|
247
|
+
nextToken();
|
|
248
|
+
}
|
|
249
|
+
else if (token.type == 'COMMENT' || token.type == 'MULTILINE_COMMENT' || token.type == 'UNFINISHED_COMMENT') {
|
|
250
|
+
if (token.type == 'UNFINISHED_COMMENT')
|
|
251
|
+
state.onWarning(new VkpParseError(`Unfinished multiline comment`, getLocation()));
|
|
252
|
+
comments.push(parseCommentValue(token.value));
|
|
253
|
+
nextToken();
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
throw new VkpParseError("Syntax error", getLocation());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return comments.join(" ");
|
|
260
|
+
}
|
|
261
|
+
function nextToken() {
|
|
262
|
+
state.prevToken = state.token;
|
|
263
|
+
const token = state.token ? state.token : VkpLexer.next();
|
|
264
|
+
state.token = VkpLexer.next();
|
|
265
|
+
return token;
|
|
266
|
+
}
|
|
267
|
+
function peekToken() {
|
|
268
|
+
if (state.token == null)
|
|
269
|
+
state.token = VkpLexer.next();
|
|
270
|
+
return state.token;
|
|
271
|
+
}
|
|
272
|
+
function prevToken() {
|
|
273
|
+
return state.prevToken;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* VkpToken value parsers
|
|
277
|
+
* */
|
|
278
|
+
function parseCommentValue(v) {
|
|
279
|
+
if (v.startsWith(';')) {
|
|
280
|
+
return v.substring(1);
|
|
281
|
+
}
|
|
282
|
+
else if (v.startsWith('#')) {
|
|
283
|
+
return v.substring(1);
|
|
284
|
+
}
|
|
285
|
+
else if (v.startsWith('//')) {
|
|
286
|
+
return v.substring(2);
|
|
287
|
+
}
|
|
288
|
+
else if (v.startsWith('/*')) {
|
|
289
|
+
return v.slice(2, -2);
|
|
290
|
+
}
|
|
291
|
+
throw new VkpParseError(`Invalid comment type`, getLocation());
|
|
292
|
+
}
|
|
293
|
+
function parseAnyNumberValue(v) {
|
|
294
|
+
let m;
|
|
295
|
+
const tmpBuffer = Buffer.allocUnsafe(8);
|
|
296
|
+
if ((m = v.match(/^0i([+-]\d+)$/i))) { // dec signed
|
|
297
|
+
const num = m[1];
|
|
298
|
+
for (const d of SINT_PARSER_DATA) {
|
|
299
|
+
if ((num.length - 1) <= d[0]) {
|
|
300
|
+
const parsedNum = BigInt(num);
|
|
301
|
+
if (parsedNum < -BigInt(d[1]) || parsedNum > BigInt(d[1])) {
|
|
302
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range -${d[1].toString(10)} ... +${d[1].toString(10)}`, getLocation());
|
|
303
|
+
}
|
|
304
|
+
if ((num.length - 1) < d[0] && d[0] > 3) {
|
|
305
|
+
throw new VkpParseError(`The wrong number of digits in integer (${v})`, getLocation(), "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)." +
|
|
306
|
+
"Use leading zeroes to match the number of digits.");
|
|
307
|
+
}
|
|
308
|
+
tmpBuffer.writeBigUInt64LE(BigInt.asUintN(d[2], parsedNum), 0);
|
|
309
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if ((m = v.match(/^0i(\d+)$/i))) { // dec unsigned
|
|
314
|
+
const num = m[1];
|
|
315
|
+
for (const d of UINT_PARSER_DATA) {
|
|
316
|
+
if (num.length <= d[0]) {
|
|
317
|
+
const parsedNum = d[2] <= 32 ? parseInt(num, 10) : BigInt(num);
|
|
318
|
+
if (parsedNum < 0 || parsedNum > d[1])
|
|
319
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0 ... ${d[1].toString(10)}`, getLocation());
|
|
320
|
+
if (num.length < d[0] && d[0] > 3) {
|
|
321
|
+
throw new VkpParseError(`The wrong number of digits in integer (${v})`, getLocation(), "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)." +
|
|
322
|
+
"Use leading zeroes to match the number of digits.");
|
|
323
|
+
}
|
|
324
|
+
if (d[2] <= 32) {
|
|
325
|
+
tmpBuffer.writeUInt32LE(Number(parsedNum), 0);
|
|
326
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
tmpBuffer.writeBigUInt64LE(BigInt(parsedNum), 0);
|
|
330
|
+
return tmpBuffer.subarray(0, d[2] / 8);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
else if ((m = v.match(/^0x([a-f0-9]+)$/i))) { // hex unsigned
|
|
336
|
+
let hexnum = m[1];
|
|
337
|
+
if (hexnum.length % 2)
|
|
338
|
+
hexnum = "0" + hexnum;
|
|
339
|
+
if (hexnum.length > 8)
|
|
340
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0x00000000 ... 0xFFFFFFFF`, getLocation());
|
|
341
|
+
let number = parseInt(`0x${hexnum}`, 16);
|
|
342
|
+
tmpBuffer.writeUInt32LE(number, 0);
|
|
343
|
+
return tmpBuffer.subarray(0, Math.ceil(hexnum.length / 2));
|
|
344
|
+
}
|
|
345
|
+
else if ((m = v.match(/^0n([10]+)$/i))) { // binary unsigned
|
|
346
|
+
if (m[1].length > 32)
|
|
347
|
+
throw new VkpParseError(`Number ${v} exceeds allowed range 0n0 ... 0n11111111111111111111111111111111`, getLocation());
|
|
348
|
+
let number = parseInt(m[1], 2);
|
|
349
|
+
tmpBuffer.writeUInt32LE(number, 0);
|
|
350
|
+
return tmpBuffer.subarray(0, Math.ceil(m[1].length / 8));
|
|
351
|
+
}
|
|
352
|
+
throw new VkpParseError(`Invalid number: ${v}`, getLocation());
|
|
353
|
+
}
|
|
354
|
+
function parsePlaceholderValue(value) {
|
|
355
|
+
let m;
|
|
356
|
+
if ((m = value.match(/^(0i|0x|0n)(.*?)$/i))) {
|
|
357
|
+
return parseAnyNumberValue(m[1] + m[2].replace(/[^0-9a-f]/gi, '0'));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
return parseHexDataValue(value.replace(/[^0-9a-f]/gi, '0'));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function parseStringValue(value) {
|
|
364
|
+
let m;
|
|
365
|
+
if ((m = value.match(/(\/\*|\*\/|\/\/)/))) {
|
|
366
|
+
throw new VkpParseError(`Unescaped ${m[1]} is not allowed in string: ${value}`, getLocation(), `Escape these ambiguous characters like this: \\/* or \\/\\/.`);
|
|
367
|
+
}
|
|
368
|
+
const text = value.slice(1, -1);
|
|
369
|
+
const parts = [];
|
|
370
|
+
let tmp = "";
|
|
371
|
+
const unicode = (value.charAt(0) == "'");
|
|
372
|
+
let escape = false;
|
|
373
|
+
/*
|
|
374
|
+
if (!unicode && value.match(/[^\u0000-\u007F]/)) {
|
|
375
|
+
throw new VkpParseError(`ASCII string with non-ASCII characters`, getLocation(),
|
|
376
|
+
`Please use only ASCII-safe characters from the range U+0000-U+007F or \\xNN escape sequences.`);
|
|
377
|
+
}
|
|
378
|
+
*/
|
|
379
|
+
const breakpoint = () => {
|
|
380
|
+
if (tmp.length) {
|
|
381
|
+
parts.push(tmp);
|
|
382
|
+
tmp = "";
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const getStrLocation = (i) => {
|
|
386
|
+
const loc = getLocation();
|
|
387
|
+
loc.column += i;
|
|
388
|
+
return loc;
|
|
389
|
+
};
|
|
390
|
+
for (let i = 0; i < text.length; i++) {
|
|
391
|
+
const c = text.charAt(i);
|
|
392
|
+
if (escape) {
|
|
393
|
+
if (c == "\r") {
|
|
394
|
+
if (text.charAt(i + 1) == "\n")
|
|
395
|
+
i++;
|
|
396
|
+
}
|
|
397
|
+
else if (c == "\n") {
|
|
398
|
+
// Ignore
|
|
399
|
+
}
|
|
400
|
+
else if (c == "x") {
|
|
401
|
+
const hex = text.substring(i + 1, i + 1 + 2);
|
|
402
|
+
if (hex.length == 2) {
|
|
403
|
+
breakpoint();
|
|
404
|
+
const hexnum = parseInt(`0x${hex}`);
|
|
405
|
+
if (unicode) {
|
|
406
|
+
parts.push(Buffer.from([hexnum, 0x00]));
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
if (hexnum >= 0x7F && !unicode)
|
|
410
|
+
throw new VkpParseError(`Bad escape sequence (\\x${hex})`, getStrLocation(i), `Allowed range: \\x00-\\x7F.`);
|
|
411
|
+
parts.push(Buffer.from([hexnum]));
|
|
412
|
+
}
|
|
413
|
+
i += 2;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
throw new VkpParseError(`Unknown escape sequence (\\x${hex})`, getStrLocation(i));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else if (c == "u") {
|
|
420
|
+
const hex = text.substring(i + 1, i + 1 + 4);
|
|
421
|
+
if (hex.length == 4) {
|
|
422
|
+
breakpoint();
|
|
423
|
+
const hexnum = parseInt(`0x${hex}`);
|
|
424
|
+
if (unicode) {
|
|
425
|
+
parts.push(Buffer.from([hexnum & 0xFF, (hexnum >> 8) & 0xFF]));
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
throw new VkpParseError(`Unknown escape sequence (\\u${hex})`, getStrLocation(i));
|
|
429
|
+
}
|
|
430
|
+
i += 4;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
throw new VkpParseError(`Unknown escape sequence (\\u${hex})`, getStrLocation(i));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else if (c.match(/[0-7]/)) {
|
|
437
|
+
let octalLen = 1;
|
|
438
|
+
for (let j = 1; j < 3; j++) {
|
|
439
|
+
if (!text.charAt(i + j).match(/[0-7]/))
|
|
440
|
+
break;
|
|
441
|
+
octalLen++;
|
|
442
|
+
}
|
|
443
|
+
const oct = parseInt(text.substring(i, i + octalLen), 8);
|
|
444
|
+
if (oct > 0xFF)
|
|
445
|
+
throw new VkpParseError(`Unknown escape sequence (\\${text.substring(i, i + octalLen)})`, getStrLocation(i));
|
|
446
|
+
breakpoint();
|
|
447
|
+
if (unicode) {
|
|
448
|
+
parts.push(Buffer.from([oct, 0x00]));
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
parts.push(Buffer.from([oct]));
|
|
452
|
+
}
|
|
453
|
+
i += octalLen - 1;
|
|
454
|
+
}
|
|
455
|
+
else if ((c in STR_ESCAPE_TABLE)) {
|
|
456
|
+
tmp += STR_ESCAPE_TABLE[c];
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
throw new VkpParseError(`Unknown escape sequence (\\${c})`, getStrLocation(i));
|
|
460
|
+
}
|
|
461
|
+
escape = false;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
if (c == '\\') {
|
|
465
|
+
escape = true;
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
tmp += c;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
breakpoint();
|
|
473
|
+
if (unicode) {
|
|
474
|
+
return Buffer.concat(parts.map((p) => typeof p === "string" ? iconv.encode(p, 'utf-16', { addBOM: false }) : p));
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
return Buffer.concat(parts.map((p) => typeof p === "string" ? iconv.encode(p, 'windows-1251') : p));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function parseOffsetValue(v) {
|
|
481
|
+
let result = parseInt(v.replace(/^([+-])(0x)?/i, '$10x'), 16);
|
|
482
|
+
if (isNaN(result))
|
|
483
|
+
throw new VkpParseError(`Invalid offset: ${v}`, getLocation());
|
|
484
|
+
if (result > 0xFFFFFFFF)
|
|
485
|
+
throw new VkpParseError(`Offset ${v} exceeds allowed range 00000000 ... FFFFFFFF`, getLocation());
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
function parseAddressValue(v) {
|
|
489
|
+
let result = parseInt(v.replace(/^(0x)?(.*?):$/i, '0x$2'), 16);
|
|
490
|
+
if (isNaN(result))
|
|
491
|
+
throw new VkpParseError(`Invalid address: ${v}`, getLocation());
|
|
492
|
+
if (result > 0xFFFFFFFF)
|
|
493
|
+
throw new VkpParseError(`Address ${v} exceeds allowed range 00000000 ... FFFFFFFF`, getLocation());
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
496
|
+
function parseHexDataValue(v) {
|
|
497
|
+
if (v.length % 2 != 0)
|
|
498
|
+
throw new VkpParseError(`Hex data (${v}) must be even length`, getLocation());
|
|
499
|
+
return Buffer.from(v, "hex");
|
|
500
|
+
}
|
|
501
|
+
function parsePragmaValue(v) {
|
|
502
|
+
let m;
|
|
503
|
+
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)$/)))
|
|
504
|
+
throw new VkpParseError(`Invalid PRAGMA: ${v}`, getLocation());
|
|
505
|
+
return { name: m[2], action: m[1] };
|
|
506
|
+
}
|
|
507
|
+
function getLocation() {
|
|
508
|
+
return state.token ? { line: state.token.line, column: state.token.col } : { line: 1, column: 1 };
|
|
509
|
+
}
|
|
510
|
+
export { vkpRawParser };
|
package/package.json
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
2
|
+
"name": "@sie-js/vkp",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Parser and utils for the .VKP files format which is used in the V-Klay.",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"./dist/src/*"
|
|
10
|
+
],
|
|
11
|
+
"author": "kirill.zhumarin@gmail.com",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"iconv-lite": "^0.6.3",
|
|
15
|
+
"moo": "^0.5.2",
|
|
16
|
+
"rtf-parser": "^1.3.3",
|
|
17
|
+
"sprintf-js": "^1.1.3"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/jest": "^29.5.14",
|
|
21
|
+
"@types/node": "^22.14.1",
|
|
22
|
+
"@types/sprintf-js": "^1.1.4",
|
|
23
|
+
"glob": "^10.3.12",
|
|
24
|
+
"vitest": "^3.1.1"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"url": "git+https://github.com/siemens-mobile-hacks/node-sie-vkp.git"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsc --watch"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/.editorconfig
DELETED
package/.github/dependabot.yml
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
-
# package ecosystems to update and where the package manifests are located.
|
|
3
|
-
# Please see the documentation for all configuration options:
|
|
4
|
-
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
-
|
|
6
|
-
version: 2
|
|
7
|
-
updates:
|
|
8
|
-
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
-
directory: "/" # Location of package manifests
|
|
10
|
-
schedule:
|
|
11
|
-
interval: "weekly"
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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 }}
|