adofai 2.13.2 → 2.14.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 CHANGED
@@ -1,112 +1,112 @@
1
- # ADOFAI
2
-
3
- A Javascript library for ADOFAI levels.
4
-
5
- ## Usage
6
- Preview / Edit the `.adofai` file.
7
-
8
- Re_ADOJAS(A Level Player of ADOFAI) uses `adofai` to parse ADOFAI Level file.
9
-
10
- ## Installation
11
-
12
- ```bash
13
- npm install adofai
14
- # or
15
- yarn add adofai
16
- # or
17
- pnpm install adofai
18
- ```
19
-
20
- if you want to display highlight of adofai file, you can use `Rhythm Game Syntax Highlighter` vscode extension.
21
-
22
- ## Got Started
23
-
24
- ### Import
25
-
26
- For Commonjs:
27
- ```ts
28
- const adofai = require('adofai');
29
- ```
30
-
31
- For ES6 Modules:
32
- ```ts
33
- import * as adofai from 'adofai';
34
- ```
35
-
36
- ### Create a Level
37
-
38
- ```ts
39
- const file = new adofai.Level(adofaiFileContent);
40
-
41
- //or
42
-
43
- const parser = new adofai.Parsers.StringParser();
44
- const file = new adofai.Level(adofaiFileContent,parser);
45
-
46
- //The advantage of the latter over the former is that it pre-initializes the Parser, avoiding multiple instantiations.
47
- ```
48
-
49
- Format:
50
- ```ts
51
- class Level {
52
- constructor(opt: string | LevelOptions, provider?: ParseProvider)
53
- }
54
-
55
- ```
56
- Available ParseProviders:
57
- `StringParser` `ArrayBufferParser` `BufferParser`
58
-
59
-
60
- Usually,only `StringParser` is needed.
61
- but you can use `BufferParser` to parse ADOFAI files in Node environment.
62
-
63
- On browser, you can also use `ArrayBuffer` to parse ADOFAI files.
64
- (`BufferParser` is not available in browser,but you can use browserify `Buffer` to polyfill)
65
-
66
- ### Load Level
67
- ```ts
68
- file.on('load'() => {
69
- //logic...
70
- })
71
- file.load()
72
- ```
73
-
74
- or you can use `then()`
75
- ```ts
76
- file.load().then(() => {
77
-
78
- })
79
- ```
80
-
81
- ### Export Level
82
- ```ts
83
- type FileType = 'string'|'object'
84
-
85
- file.export(type: FileType = 'string',indent?:number,useAdofaiStyle:boolean = true)
86
- ```
87
-
88
- method `export()` returns a Object or String.
89
-
90
- Object: return ADOFAI Object.
91
- String: return ADOFAI String.
92
-
93
- ```ts
94
- import fs from 'fs'
95
- type FileType = 'string'|'object'
96
-
97
- const content = file.export('string',null,true);
98
- fs.writeFileSync('output.adofai',content)
99
- ```
100
-
101
-
102
- ## Data Operation
103
-
104
- See interfaces to see all data.
105
-
106
- ```ts
107
- //Get AngleDatas:
108
- const angleDatas = file.angleData;
109
-
110
-
111
-
1
+ # ADOFAI
2
+
3
+ A Javascript library for ADOFAI levels.
4
+
5
+ ## Usage
6
+ Preview / Edit the `.adofai` file.
7
+
8
+ Re_ADOJAS(A Level Player of ADOFAI) uses `adofai` to parse ADOFAI Level file.
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install adofai
14
+ # or
15
+ yarn add adofai
16
+ # or
17
+ pnpm install adofai
18
+ ```
19
+
20
+ if you want to display highlight of adofai file, you can use `Rhythm Game Syntax Highlighter` vscode extension.
21
+
22
+ ## Got Started
23
+
24
+ ### Import
25
+
26
+ For Commonjs:
27
+ ```ts
28
+ const adofai = require('adofai');
29
+ ```
30
+
31
+ For ES6 Modules:
32
+ ```ts
33
+ import * as adofai from 'adofai';
34
+ ```
35
+
36
+ ### Create a Level
37
+
38
+ ```ts
39
+ const file = new adofai.Level(adofaiFileContent);
40
+
41
+ //or
42
+
43
+ const parser = new adofai.Parsers.StringParser();
44
+ const file = new adofai.Level(adofaiFileContent,parser);
45
+
46
+ //The advantage of the latter over the former is that it pre-initializes the Parser, avoiding multiple instantiations.
47
+ ```
48
+
49
+ Format:
50
+ ```ts
51
+ class Level {
52
+ constructor(opt: string | LevelOptions, provider?: ParseProvider)
53
+ }
54
+
55
+ ```
56
+ Available ParseProviders:
57
+ `StringParser` `ArrayBufferParser` `BufferParser`
58
+
59
+
60
+ Usually,only `StringParser` is needed.
61
+ but you can use `BufferParser` to parse ADOFAI files in Node environment.
62
+
63
+ On browser, you can also use `ArrayBuffer` to parse ADOFAI files.
64
+ (`BufferParser` is not available in browser,but you can use browserify `Buffer` to polyfill)
65
+
66
+ ### Load Level
67
+ ```ts
68
+ file.on('load'() => {
69
+ //logic...
70
+ })
71
+ file.load()
72
+ ```
73
+
74
+ or you can use `then()`
75
+ ```ts
76
+ file.load().then(() => {
77
+
78
+ })
79
+ ```
80
+
81
+ ### Export Level
82
+ ```ts
83
+ type FileType = 'string'|'object'
84
+
85
+ file.export(type: FileType = 'string',indent?:number,useAdofaiStyle:boolean = true)
86
+ ```
87
+
88
+ method `export()` returns a Object or String.
89
+
90
+ Object: return ADOFAI Object.
91
+ String: return ADOFAI String.
92
+
93
+ ```ts
94
+ import fs from 'fs'
95
+ type FileType = 'string'|'object'
96
+
97
+ const content = file.export('string',null,true);
98
+ fs.writeFileSync('output.adofai',content)
99
+ ```
100
+
101
+
102
+ ## Data Operation
103
+
104
+ See interfaces to see all data.
105
+
106
+ ```ts
107
+ //Get AngleDatas:
108
+ const angleDatas = file.angleData;
109
+
110
+
111
+
112
112
  ```
@@ -1,9 +1,11 @@
1
1
  import Parser from "./Parser";
2
- export declare class BufferParser extends Parser<Buffer | string, any> {
3
- parse(input: Buffer | string): any;
2
+ /**
3
+ * 字节流原生 JSON 解析器
4
+ * 直接在 Uint8Array 上运行状态机,支持非标换行,且无需预转字符串。
5
+ */
6
+ export declare class BufferParser extends Parser<Uint8Array | Buffer | string, any> {
7
+ parse(input: Uint8Array | Buffer | string): any;
4
8
  stringify(obj: any): string;
5
9
  }
6
- export declare function stripBOM(buffer: Buffer): Buffer;
7
- export declare function normalizeJsonBuffer(text: Buffer): Buffer;
8
- export declare function decodeStringFromUTF8BOM(buffer: Buffer): string;
10
+ export declare function stripBOM(buffer: Uint8Array | Buffer): Uint8Array;
9
11
  export default BufferParser;
@@ -1,196 +1,215 @@
1
1
  import Parser from "./Parser";
2
- import StringParser from "./StringParser";
3
- let BOM;
4
- let COMMA;
5
- let BufferAvailable = true;
6
- try {
7
- BOM = Buffer.of(0xef, 0xbb, 0xbf);
8
- COMMA = Buffer.from(",");
9
- }
10
- catch (e) {
11
- BufferAvailable = false;
12
- console.warn('Buffer is not available in current environment, try to use ArrayBufferParser');
13
- // minimal shims to keep types happy; won't be used when BufferUnavailable
14
- BOM = { equals: () => false, subarray: () => null };
15
- COMMA = { equals: () => false, subarray: () => null };
16
- }
17
2
  /**
18
- * Inspect buffer to detect an unescaped raw newline inside a quoted string.
19
- * If found, we must fallback to the more tolerant StringParser.
3
+ * 字节流原生 JSON 解析器
4
+ * 直接在 Uint8Array 上运行状态机,支持非标换行,且无需预转字符串。
20
5
  */
21
- function hasRawNewlineInStringBuffer(buf) {
22
- let last = "other";
23
- for (let i = 0; i < buf.length; i++) {
24
- const c = buf[i];
25
- if (last === "escape") {
26
- last = "string";
27
- continue;
6
+ export class BufferParser extends Parser {
7
+ parse(input) {
8
+ let u8;
9
+ if (typeof input === "string") {
10
+ // 如果输入是字符串,转为字节流处理以保持逻辑统一
11
+ u8 = new TextEncoder().encode(input);
28
12
  }
29
- switch (c) {
30
- case 34: // "
31
- if (last === "string") {
32
- last = "other";
33
- }
34
- else {
35
- if (last === "comma") {
36
- // entering string after comma
37
- }
38
- last = "string";
39
- }
40
- break;
41
- case 92: // \
42
- if (last === "string")
43
- last = "escape";
44
- break;
45
- case 44: // ,
46
- if (last === "other")
47
- last = "comma";
48
- break;
49
- case 93: // ]
50
- case 125: // }
51
- if (last === "comma")
52
- last = "other";
53
- break;
54
- // whitespace bytes
55
- case 9:
56
- case 10:
57
- case 11:
58
- case 12:
59
- case 13:
60
- case 32:
61
- // If we see newline (10) or carriage (13) while inside a string AND not escaped,
62
- // that's a raw newline in string -> not valid strict JSON; detect and return true.
63
- if ((c === 10 || c === 13) && last === "string") {
64
- return true;
65
- }
66
- break;
67
- default:
68
- if (last === "comma") {
69
- last = "other";
70
- }
71
- break;
13
+ else {
14
+ u8 = stripBOM(input);
72
15
  }
16
+ const engine = new BufferParserEngine(u8);
17
+ return engine.parseValue();
18
+ }
19
+ stringify(obj) {
20
+ return JSON.stringify(obj);
73
21
  }
74
- return false;
75
22
  }
76
- /**
77
- * Normalize buffer by removing insignificant whitespace outside strings.
78
- * Works on Uint8Array and returns Buffer (for node) or Uint8Array (for arraybuffer flow).
79
- * This function keeps contents inside strings untouched.
80
- */
81
- function normalizeJsonUint8(buf) {
82
- const builder = [];
83
- let last = "other";
84
- let from = 0;
85
- for (let i = 0; i < buf.length; i++) {
86
- const charCode = buf[i];
87
- if (last === "escape") {
88
- last = "string";
89
- continue;
23
+ class BufferParserEngine {
24
+ constructor(data) {
25
+ this.data = data;
26
+ this.pos = 0;
27
+ this.decoder = new TextDecoder("utf-8");
28
+ this.length = data.length;
29
+ }
30
+ parseValue() {
31
+ this.eatWhitespace();
32
+ const byte = this.peek();
33
+ if (byte === -1)
34
+ return null;
35
+ switch (byte) {
36
+ case 123: return this.parseObject(); // {
37
+ case 91: return this.parseArray(); // [
38
+ case 34: return this.parseString(); // "
39
+ case 116: return this.parseKeyword(true); // t(rue)
40
+ case 102: return this.parseKeyword(false); // f(alse)
41
+ case 110: return this.parseKeyword(null); // n(ull)
42
+ case 45: // -
43
+ case 48:
44
+ case 49:
45
+ case 50:
46
+ case 51:
47
+ case 52:
48
+ case 53:
49
+ case 54:
50
+ case 55:
51
+ case 56:
52
+ case 57:
53
+ return this.parseNumber();
54
+ default:
55
+ this.pos++;
56
+ return null;
90
57
  }
91
- else {
92
- switch (charCode) {
93
- case 34: // "
94
- switch (last) {
95
- case "string":
96
- last = "other";
97
- break;
98
- case "comma":
99
- // when we hit a quote after a comma we keep the comma boundary
100
- builder.push(COMMA ? COMMA : new Uint8Array([44]));
101
- default:
102
- last = "string";
103
- break;
104
- }
105
- break;
106
- case 92: // \
107
- if (last === "string")
108
- last = "escape";
109
- break;
110
- case 44: // ,
111
- builder.push(buf.subarray(from, i));
112
- from = i + 1;
113
- if (last === "other")
114
- last = "comma";
115
- break;
116
- case 93:
117
- case 125:
118
- if (last === "comma")
119
- last = "other";
120
- break;
121
- case 9:
122
- case 10:
123
- case 11:
124
- case 12:
125
- case 13:
126
- case 32:
127
- // ignore whitespace outside strings
128
- break;
129
- default:
130
- if (last === "comma") {
131
- builder.push(COMMA ? COMMA : new Uint8Array([44]));
132
- last = "other";
133
- }
134
- break;
58
+ }
59
+ parseObject() {
60
+ const obj = {};
61
+ this.pos++; // skip {
62
+ while (true) {
63
+ this.eatWhitespace();
64
+ if (this.peek() === 125) { // }
65
+ this.pos++;
66
+ break;
67
+ }
68
+ const key = this.parseString();
69
+ this.eatWhitespace();
70
+ if (this.peek() !== 58)
71
+ break; // :
72
+ this.pos++;
73
+ obj[key] = this.parseValue();
74
+ this.eatWhitespace();
75
+ if (this.peek() === 44) { // ,
76
+ this.pos++;
135
77
  }
136
78
  }
79
+ return obj;
137
80
  }
138
- builder.push(buf.subarray(from));
139
- // concat
140
- let total = 0;
141
- for (const piece of builder)
142
- total += piece.length;
143
- const out = new Uint8Array(total);
144
- let offset = 0;
145
- for (const piece of builder) {
146
- out.set(piece, offset);
147
- offset += piece.length;
81
+ parseArray() {
82
+ const arr = [];
83
+ this.pos++; // skip [
84
+ while (true) {
85
+ this.eatWhitespace();
86
+ if (this.peek() === 93) { // ]
87
+ this.pos++;
88
+ break;
89
+ }
90
+ arr.push(this.parseValue());
91
+ this.eatWhitespace();
92
+ if (this.peek() === 44) { // ,
93
+ this.pos++;
94
+ }
95
+ }
96
+ return arr;
148
97
  }
149
- return out;
150
- }
151
- export class BufferParser extends Parser {
152
- parse(input) {
153
- // string input: delegate to StringParser (keeps current behavior)
154
- if (typeof input === "string") {
155
- return StringParser.prototype.parse.call(StringParser.prototype, input);
98
+ /**
99
+ * 核心修改:支持字符串内原始换行 (10, 13)
100
+ */
101
+ parseString() {
102
+ this.pos++; // skip "
103
+ const start = this.pos;
104
+ let hasEscapes = false;
105
+ while (this.pos < this.length) {
106
+ const b = this.data[this.pos];
107
+ if (b === 34)
108
+ break; // "
109
+ if (b === 92) { // \
110
+ hasEscapes = true;
111
+ this.pos += 2; // 简单跳过转义序列
112
+ }
113
+ else {
114
+ this.pos++;
115
+ }
156
116
  }
157
- // Node Buffer path
158
- const nodeBuf = input;
159
- // strip BOM quickly
160
- const stripped = stripBOM(nodeBuf);
161
- const u8 = new Uint8Array(stripped);
162
- // If there is any raw newline inside a quoted string, fallback to StringParser
163
- if (hasRawNewlineInStringBuffer(u8)) {
164
- // decode exactly and let StringParser tolerant-parse it
165
- return StringParser.prototype.parse.call(StringParser.prototype, decodeStringFromUTF8BOM(stripped));
117
+ const end = this.pos;
118
+ this.pos++; // skip closing "
119
+ const raw = this.data.subarray(start, end);
120
+ if (!hasEscapes) {
121
+ return this.decoder.decode(raw);
166
122
  }
167
- // Fast path: normalize then try JSON.parse (much faster than StringParser).
168
- try {
169
- const normalized = normalizeJsonUint8(u8);
170
- const text = decodeStringFromUTF8BOM(Buffer.from(normalized));
171
- return JSON.parse(text);
123
+ else {
124
+ // 处理转义字符逻辑
125
+ return this.processEscapes(raw);
172
126
  }
173
- catch (e) {
174
- // Fallback to StringParser if anything unexpected occurs
175
- return StringParser.prototype.parse.call(StringParser.prototype, decodeStringFromUTF8BOM(stripped));
127
+ }
128
+ processEscapes(raw) {
129
+ // 降级处理带有转义的字符串:转成文本后用类似 StringParser 的逻辑处理
130
+ const str = this.decoder.decode(raw);
131
+ let result = "";
132
+ for (let i = 0; i < str.length; i++) {
133
+ if (str[i] === "\\" && i + 1 < str.length) {
134
+ const next = str[++i];
135
+ switch (next) {
136
+ case '"':
137
+ case '\\':
138
+ case '/':
139
+ result += next;
140
+ break;
141
+ case 'b':
142
+ result += '\b';
143
+ break;
144
+ case 'f':
145
+ result += '\f';
146
+ break;
147
+ case 'n':
148
+ result += '\n';
149
+ break;
150
+ case 'r':
151
+ result += '\r';
152
+ break;
153
+ case 't':
154
+ result += '\t';
155
+ break;
156
+ case 'u':
157
+ result += String.fromCharCode(parseInt(str.substr(i + 1, 4), 16));
158
+ i += 4;
159
+ break;
160
+ }
161
+ }
162
+ else {
163
+ result += str[i];
164
+ }
176
165
  }
166
+ return result;
177
167
  }
178
- stringify(obj) {
179
- return JSON.stringify(obj);
168
+ parseNumber() {
169
+ const start = this.pos;
170
+ while (this.pos < this.length) {
171
+ const b = this.data[this.pos];
172
+ // 0-9 . e E + -
173
+ if ((b >= 48 && b <= 57) || b === 46 || b === 101 || b === 69 || b === 43 || b === 45) {
174
+ this.pos++;
175
+ }
176
+ else {
177
+ break;
178
+ }
179
+ }
180
+ const s = this.decoder.decode(this.data.subarray(start, this.pos));
181
+ return parseFloat(s);
182
+ }
183
+ parseKeyword(value) {
184
+ // 简单地跳过 true, false, null 的长度
185
+ const b = this.data[this.pos];
186
+ if (b === 116)
187
+ this.pos += 4; // true
188
+ else if (b === 102)
189
+ this.pos += 5; // false
190
+ else if (b === 110)
191
+ this.pos += 4; // null
192
+ return value;
193
+ }
194
+ eatWhitespace() {
195
+ while (this.pos < this.length) {
196
+ const b = this.data[this.pos];
197
+ if (b === 32 || b === 10 || b === 13 || b === 9) {
198
+ this.pos++;
199
+ }
200
+ else {
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ peek() {
206
+ return this.pos < this.length ? this.data[this.pos] : -1;
180
207
  }
181
208
  }
182
209
  export function stripBOM(buffer) {
183
- if (buffer.length >= 3 && BOM.equals(buffer.subarray(0, 3))) {
210
+ if (buffer.length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
184
211
  return buffer.subarray(3);
185
212
  }
186
213
  return buffer;
187
214
  }
188
- export function normalizeJsonBuffer(text) {
189
- // keep backward compatibility: use the Uint8 normalizer and return a Buffer
190
- const u8 = normalizeJsonUint8(new Uint8Array(text));
191
- return Buffer.from(u8);
192
- }
193
- export function decodeStringFromUTF8BOM(buffer) {
194
- return stripBOM(buffer).toString("utf-8");
195
- }
196
215
  export default BufferParser;