functionalscript 0.0.323 → 0.0.327
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/commonjs/build/test.js +23 -9
- package/json/tokenizer/index.js +433 -0
- package/json/tokenizer/test.js +307 -0
- package/package.json +1 -1
- package/test.js +1 -0
package/commonjs/build/test.js
CHANGED
|
@@ -10,13 +10,13 @@ const compileMap = {
|
|
|
10
10
|
':index.js': [
|
|
11
11
|
'ok',
|
|
12
12
|
require_ => m0 => {
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
return [['ok', ':index.js'],
|
|
13
|
+
let [r, m] = require_('./b')(m0);
|
|
14
|
+
if (r[0] === 'error') { throw r }
|
|
15
|
+
[r, m] = require_('./a/')(m);
|
|
16
|
+
if (r[0] === 'error') { throw r }
|
|
17
|
+
[r, m] = require_('x/r')(m);
|
|
18
|
+
if (r[0] === 'error') { throw r }
|
|
19
|
+
return [['ok', ':index.js'], m]
|
|
20
20
|
}],
|
|
21
21
|
':b.js': [
|
|
22
22
|
'ok',
|
|
@@ -32,7 +32,9 @@ const compileMap = {
|
|
|
32
32
|
/** @type {function_.Compile} */
|
|
33
33
|
const compile = source => compileMap[source] ?? ['error', 'invalid source']
|
|
34
34
|
|
|
35
|
-
/** @
|
|
35
|
+
/** @typedef {{ readonly [k in string]?: string }} StringMap */
|
|
36
|
+
|
|
37
|
+
/** @type {{ readonly [k in string]?: { readonly dependencies: StringMap, files: StringMap }}} */
|
|
36
38
|
const packageMap = {
|
|
37
39
|
'': {
|
|
38
40
|
dependencies: {
|
|
@@ -68,10 +70,22 @@ const getOrBuild = _.getOrBuild
|
|
|
68
70
|
(/** @type {module_.MapInterface<map.Map<module_.State>>} */(map))
|
|
69
71
|
|
|
70
72
|
{
|
|
71
|
-
|
|
73
|
+
let [r, m] = getOrBuild({ package: '', path: ['index.js'] })(undefined)
|
|
72
74
|
if (JSON.stringify(r) !==
|
|
73
75
|
'["ok",{"exports":":index.js","requireMap":{"./a/":"/a/index.js","./b":"/b.js","x/r":"/node_modules/x/r.js"}}]'
|
|
74
76
|
) {
|
|
75
77
|
throw r
|
|
76
78
|
}
|
|
79
|
+
[r, m] = getOrBuild({ package: '', path: ['b.js'] })(m)
|
|
80
|
+
if (JSON.stringify(r) !==
|
|
81
|
+
'["ok",{"exports":":b.js","requireMap":{}}]'
|
|
82
|
+
) {
|
|
83
|
+
throw r
|
|
84
|
+
}
|
|
85
|
+
[r, m] = getOrBuild({ package: '', path: ['c.js']})(m)
|
|
86
|
+
if (JSON.stringify(r) !==
|
|
87
|
+
'["error",["file not found"]]'
|
|
88
|
+
) {
|
|
89
|
+
throw r
|
|
90
|
+
}
|
|
77
91
|
}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
const { todo } = require('../../dev')
|
|
2
|
+
const operator = require('../../types/function/operator')
|
|
3
|
+
const { concat } = require('../../types/list')
|
|
4
|
+
const list = require('../../types/list')
|
|
5
|
+
|
|
6
|
+
/** @typedef {{readonly kind: '{'}} LeftBraceToken */
|
|
7
|
+
|
|
8
|
+
/** @typedef {{readonly kind: '}'}} RightBraceToken */
|
|
9
|
+
|
|
10
|
+
/** @typedef {{readonly kind: ':'}} ColonToken */
|
|
11
|
+
|
|
12
|
+
/** @typedef {{readonly kind: ','}} CommaToken */
|
|
13
|
+
|
|
14
|
+
/** @typedef {{readonly kind: '['}} LeftBracketToken */
|
|
15
|
+
|
|
16
|
+
/** @typedef {{readonly kind: ']'}} RightBracketToken */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* readonly kind: 'string'
|
|
21
|
+
* readonly value: string
|
|
22
|
+
* }} StringToken
|
|
23
|
+
* */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {{
|
|
27
|
+
* readonly kind: 'number'
|
|
28
|
+
* readonly value: string
|
|
29
|
+
* }} NumberToken
|
|
30
|
+
* */
|
|
31
|
+
|
|
32
|
+
/** @typedef {{readonly kind: 'true'}} TrueToken */
|
|
33
|
+
|
|
34
|
+
/** @typedef {{readonly kind: 'false'}} FalseToken */
|
|
35
|
+
|
|
36
|
+
/** @typedef {{readonly kind: 'null'}} NullToken */
|
|
37
|
+
|
|
38
|
+
/** @typedef {{readonly kind: 'error', message: ErrorMessage}} ErrorToken */
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {|
|
|
42
|
+
* LeftBraceToken |
|
|
43
|
+
* RightBraceToken |
|
|
44
|
+
* ColonToken |
|
|
45
|
+
* CommaToken |
|
|
46
|
+
* LeftBracketToken |
|
|
47
|
+
* RightBracketToken |
|
|
48
|
+
* StringToken |
|
|
49
|
+
* NumberToken |
|
|
50
|
+
* TrueToken |
|
|
51
|
+
* FalseToken |
|
|
52
|
+
* NullToken |
|
|
53
|
+
* ErrorToken
|
|
54
|
+
* } JsonToken
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
const leftBrace = 0x7b
|
|
58
|
+
const rightBrace = 0x7d
|
|
59
|
+
const colon = 0x3a
|
|
60
|
+
const comma = 0x2c
|
|
61
|
+
const leftBracket = 0x5b
|
|
62
|
+
const rightBracket = 0x5d
|
|
63
|
+
|
|
64
|
+
const quotationMark = 0x22
|
|
65
|
+
const digit0 = 0x30
|
|
66
|
+
const digit1 = 0x31
|
|
67
|
+
const digit9 = 0x39
|
|
68
|
+
const signPlus = 0x2b
|
|
69
|
+
const signMinus = 0x2d
|
|
70
|
+
const decimalPoint = 0x2e
|
|
71
|
+
|
|
72
|
+
const horizontalTab = 0x09
|
|
73
|
+
const newLine = 0x0a
|
|
74
|
+
const carriageReturn = 0x0d
|
|
75
|
+
const space = 0x20
|
|
76
|
+
|
|
77
|
+
const backslach = 0x5c
|
|
78
|
+
const slash = 0x2f
|
|
79
|
+
const backspace = 0x08
|
|
80
|
+
const formfeed = 0x0c
|
|
81
|
+
|
|
82
|
+
const capitalLetterA = 0x41
|
|
83
|
+
const capitalLetterE = 0x45
|
|
84
|
+
const capitalLetterF = 0x46
|
|
85
|
+
|
|
86
|
+
const letterA = 0x61
|
|
87
|
+
const letterB = 0x62
|
|
88
|
+
const letterE = 0x65
|
|
89
|
+
const letterF = 0x66
|
|
90
|
+
const letterN = 0x6e
|
|
91
|
+
const letterR = 0x72
|
|
92
|
+
const letterT = 0x74
|
|
93
|
+
const letterU = 0x75
|
|
94
|
+
const letterZ = 0x7a
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @typedef {|
|
|
98
|
+
* InitialState |
|
|
99
|
+
* ParseKeywordState |
|
|
100
|
+
* ParseStringState |
|
|
101
|
+
* ParseEscapeCharState |
|
|
102
|
+
* ParseUnicodeCharState |
|
|
103
|
+
* ParseNumberState |
|
|
104
|
+
* InvalidNumberState |
|
|
105
|
+
* EofState
|
|
106
|
+
* } TokenizerState
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @typedef {|
|
|
111
|
+
* 'invalid keyword' |
|
|
112
|
+
* '" are missing' |
|
|
113
|
+
* 'unescaped character' |
|
|
114
|
+
* 'invalid hex value' |
|
|
115
|
+
* 'unexpected character' |
|
|
116
|
+
* 'invalid number' |
|
|
117
|
+
* 'eof'
|
|
118
|
+
* } ErrorMessage
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/** @typedef {{ readonly kind: 'initial'}} InitialState */
|
|
122
|
+
|
|
123
|
+
/** @typedef {{ readonly kind: 'keyword', readonly value: string}} ParseKeywordState */
|
|
124
|
+
|
|
125
|
+
/** @typedef {{ readonly kind: 'string', readonly value: string}} ParseStringState */
|
|
126
|
+
|
|
127
|
+
/** @typedef {{ readonly kind: 'escapeChar', readonly value: string}} ParseEscapeCharState */
|
|
128
|
+
|
|
129
|
+
/** @typedef {{ readonly kind: 'unicodeChar', readonly value: string, readonly unicode: number, readonly hexIndex: number}} ParseUnicodeCharState */
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @typedef {{
|
|
133
|
+
* readonly kind: 'number',
|
|
134
|
+
* readonly numberKind: '0' | '-' | 'int' | '.' | 'fractional' | 'e' | 'e+' | 'e-' | 'expDigits'
|
|
135
|
+
* readonly value: string
|
|
136
|
+
* }} ParseNumberState
|
|
137
|
+
* */
|
|
138
|
+
|
|
139
|
+
/** @typedef {{ readonly kind: 'invalidNumber'}} InvalidNumberState */
|
|
140
|
+
|
|
141
|
+
/** @typedef {{ readonly kind: 'eof'}} EofState */
|
|
142
|
+
|
|
143
|
+
/** @typedef {number|undefined} JsonCharacter */
|
|
144
|
+
|
|
145
|
+
/** @type {(old: string) => (input: JsonCharacter) => string} */
|
|
146
|
+
const appendChar = old => input => input === undefined ? old : operator.concat(charToString(input))(old)
|
|
147
|
+
|
|
148
|
+
/** @type {(input: JsonCharacter) => string} */
|
|
149
|
+
const charToString = input => input === undefined ? '' : String.fromCharCode(input)
|
|
150
|
+
|
|
151
|
+
/** @type {(state: InitialState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
152
|
+
const initialStateOp = initialState => input =>
|
|
153
|
+
{
|
|
154
|
+
if (input === undefined)
|
|
155
|
+
{
|
|
156
|
+
return[undefined, {kind: 'eof'}]
|
|
157
|
+
}
|
|
158
|
+
if (input >= digit1 && input <= digit9)
|
|
159
|
+
{
|
|
160
|
+
return [undefined, { kind: 'number', value: charToString(input), numberKind: 'int'}]
|
|
161
|
+
}
|
|
162
|
+
if (input >= letterA && input <= letterZ)
|
|
163
|
+
{
|
|
164
|
+
return [undefined, { kind: 'keyword', value: charToString(input)}]
|
|
165
|
+
}
|
|
166
|
+
switch(input)
|
|
167
|
+
{
|
|
168
|
+
case leftBrace: return [[{kind: '{'}], initialState]
|
|
169
|
+
case rightBrace: return [[{kind: '}'}], initialState]
|
|
170
|
+
case colon: return [[{kind: ':'}], initialState]
|
|
171
|
+
case comma: return [[{kind: ','}], initialState]
|
|
172
|
+
case leftBracket: return [[{kind: '['}], initialState]
|
|
173
|
+
case rightBracket: return [[{kind: ']'}], initialState]
|
|
174
|
+
case quotationMark: return[undefined, {kind: 'string', value: ''}]
|
|
175
|
+
case digit0: return [undefined, { kind: 'number', value: charToString(input), numberKind: '0'}]
|
|
176
|
+
case signMinus: return [undefined, { kind: 'number', value: charToString(input), numberKind: '-'}]
|
|
177
|
+
case horizontalTab:
|
|
178
|
+
case newLine:
|
|
179
|
+
case carriageReturn:
|
|
180
|
+
case space: return[undefined, initialState]
|
|
181
|
+
default: return [[{kind: 'error', message: 'unexpected character'}], initialState]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** @type {(state: ParseNumberState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
186
|
+
const parseNumberStateOp = state => input =>
|
|
187
|
+
{
|
|
188
|
+
if (input === undefined)
|
|
189
|
+
{
|
|
190
|
+
switch (state.numberKind)
|
|
191
|
+
{
|
|
192
|
+
case '-':
|
|
193
|
+
case '.':
|
|
194
|
+
case 'e':
|
|
195
|
+
case 'e+':
|
|
196
|
+
case 'e-': return [[{kind: 'error', message: 'invalid number'}], {kind: 'invalidNumber', }]
|
|
197
|
+
default: return [[{kind: 'number', value: state.value}], {kind: 'eof'}]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (input === decimalPoint)
|
|
201
|
+
{
|
|
202
|
+
switch (state.numberKind)
|
|
203
|
+
{
|
|
204
|
+
case '0':
|
|
205
|
+
case 'int': return [undefined, {kind: 'number', value: appendChar(state.value)(input), numberKind: '.'}]
|
|
206
|
+
default: return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (input === digit0)
|
|
210
|
+
{
|
|
211
|
+
switch (state.numberKind)
|
|
212
|
+
{
|
|
213
|
+
case '0': return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
214
|
+
case '-': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: '0'}]
|
|
215
|
+
case '.': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'fractional'}]
|
|
216
|
+
case 'e':
|
|
217
|
+
case 'e+':
|
|
218
|
+
case 'e-': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'expDigits'}]
|
|
219
|
+
default: return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: state.numberKind}]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (input >= digit1 && input <= digit9)
|
|
223
|
+
{
|
|
224
|
+
switch (state.numberKind)
|
|
225
|
+
{
|
|
226
|
+
case '0': return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
227
|
+
case '-': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'int'}]
|
|
228
|
+
case '.': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'fractional'}]
|
|
229
|
+
case 'e':
|
|
230
|
+
case 'e+':
|
|
231
|
+
case 'e-': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'expDigits'}]
|
|
232
|
+
default: return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: state.numberKind}]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (input === letterE || input === capitalLetterE)
|
|
236
|
+
{
|
|
237
|
+
switch (state.numberKind)
|
|
238
|
+
{
|
|
239
|
+
case '0':
|
|
240
|
+
case 'int':
|
|
241
|
+
case 'fractional': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'e'}]
|
|
242
|
+
default: return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (input === signMinus)
|
|
246
|
+
{
|
|
247
|
+
switch (state.numberKind)
|
|
248
|
+
{
|
|
249
|
+
case 'e': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'e-'}]
|
|
250
|
+
default: return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (input === signPlus)
|
|
254
|
+
{
|
|
255
|
+
switch (state.numberKind)
|
|
256
|
+
{
|
|
257
|
+
case 'e': return [undefined, {kind:'number', value: appendChar(state.value)(input), numberKind: 'e+'}]
|
|
258
|
+
default: return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (isTerminalForNumber(input))
|
|
262
|
+
{
|
|
263
|
+
switch (state.numberKind)
|
|
264
|
+
{
|
|
265
|
+
case '-':
|
|
266
|
+
case '.':
|
|
267
|
+
case 'e':
|
|
268
|
+
case 'e+':
|
|
269
|
+
case 'e-':
|
|
270
|
+
{
|
|
271
|
+
const next = tokenizeOp({kind: 'initial'})(input)
|
|
272
|
+
return [{first: {kind: 'error', message: 'invalid number'}, tail: next[0]}, next[1]]
|
|
273
|
+
}
|
|
274
|
+
default:
|
|
275
|
+
{
|
|
276
|
+
const next = tokenizeOp({kind: 'initial'})(input)
|
|
277
|
+
return [{first: {kind: 'number', value: state.value}, tail: next[0]}, next[1]]
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return tokenizeOp({kind: 'invalidNumber'})(input)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** @type {(char: number) => boolean} */
|
|
285
|
+
const isTerminalForNumber = char =>
|
|
286
|
+
{
|
|
287
|
+
switch (char)
|
|
288
|
+
{
|
|
289
|
+
case quotationMark:
|
|
290
|
+
case comma:
|
|
291
|
+
case leftBrace:
|
|
292
|
+
case rightBrace:
|
|
293
|
+
case leftBracket:
|
|
294
|
+
case rightBracket:
|
|
295
|
+
case colon: return true
|
|
296
|
+
default: return false
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** @type {(state: InvalidNumberState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
301
|
+
const invalidNumberStateOp = state => input =>
|
|
302
|
+
{
|
|
303
|
+
if (input === undefined)
|
|
304
|
+
{
|
|
305
|
+
return [[{kind: 'error', message: 'invalid number'}], {kind: 'eof'}]
|
|
306
|
+
}
|
|
307
|
+
if (isTerminalForNumber(input))
|
|
308
|
+
{
|
|
309
|
+
const next = tokenizeOp({kind: 'initial'})(input)
|
|
310
|
+
return [{first: {kind: 'error', message: 'invalid number'}, tail: next[0]}, next[1]]
|
|
311
|
+
}
|
|
312
|
+
return [undefined, {kind: 'invalidNumber'}]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @type {(state: ParseStringState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
316
|
+
const parseStringStateOp = state => input =>
|
|
317
|
+
{
|
|
318
|
+
switch(input)
|
|
319
|
+
{
|
|
320
|
+
case quotationMark: return[[{kind: 'string', value: state.value}], {kind: 'initial'}]
|
|
321
|
+
case backslach: return [undefined, {kind:'escapeChar', value: state.value}]
|
|
322
|
+
case undefined: return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
323
|
+
default: return [undefined, {kind:'string', value: appendChar(state.value)(input)}]
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** @type {(state: ParseEscapeCharState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
328
|
+
const parseEscapeCharStateOp = state => input =>
|
|
329
|
+
{
|
|
330
|
+
switch(input)
|
|
331
|
+
{
|
|
332
|
+
case quotationMark:
|
|
333
|
+
case backslach:
|
|
334
|
+
case slash: return [undefined, {kind: 'string', value: appendChar(state.value)(input)}]
|
|
335
|
+
case letterB: return [undefined, {kind: 'string', value: appendChar(state.value)(backspace)}]
|
|
336
|
+
case letterF: return [undefined, {kind: 'string', value: appendChar(state.value)(formfeed)}]
|
|
337
|
+
case letterN: return [undefined, {kind: 'string', value: appendChar(state.value)(newLine)}]
|
|
338
|
+
case letterR: return [undefined, {kind: 'string', value: appendChar(state.value)(carriageReturn)}]
|
|
339
|
+
case letterT: return [undefined, {kind: 'string', value: appendChar(state.value)(horizontalTab)}]
|
|
340
|
+
case letterU: return [undefined, {kind: 'unicodeChar', value: state.value, unicode: 0, hexIndex: 0}]
|
|
341
|
+
case undefined: return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
342
|
+
default: {
|
|
343
|
+
const next = tokenizeOp({kind: 'string', value: state.value})(input)
|
|
344
|
+
return [{first: {kind: 'error', message: 'unescaped character'}, tail: next[0]}, next[1]]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** @type {(hex: number) => number|undefined} */
|
|
350
|
+
const hexDigitToNumber = hex =>
|
|
351
|
+
{
|
|
352
|
+
if (hex >= digit0 && hex <= digit9) { return hex - digit0 }
|
|
353
|
+
if (hex >= capitalLetterA && hex <= capitalLetterF) { return hex - capitalLetterA + 10 }
|
|
354
|
+
if (hex >= letterA && hex <= letterF) { return hex - letterA + 10 }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** @type {(state: ParseUnicodeCharState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
358
|
+
const parseUnicodeCharStateOp = state => input =>
|
|
359
|
+
{
|
|
360
|
+
if (input === undefined)
|
|
361
|
+
{
|
|
362
|
+
return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
363
|
+
}
|
|
364
|
+
const hexValue = hexDigitToNumber(input)
|
|
365
|
+
if (hexValue === undefined)
|
|
366
|
+
{
|
|
367
|
+
const next = tokenizeOp({kind: 'string', value: state.value})(input)
|
|
368
|
+
return [{first: {kind: 'error', message: 'invalid hex value'}, tail: next[0]}, next[1]]
|
|
369
|
+
}
|
|
370
|
+
const newUnicode = state.unicode | (hexValue << (3 - state.hexIndex) * 4)
|
|
371
|
+
return [undefined, state.hexIndex === 3 ?
|
|
372
|
+
{kind: 'string', value: appendChar(state.value)(newUnicode)} :
|
|
373
|
+
{kind: 'unicodeChar', value: state.value, unicode: newUnicode, hexIndex: state.hexIndex + 1}]
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @type {(s: string) => JsonToken} */
|
|
377
|
+
const stringToKeywordToken = s =>
|
|
378
|
+
{
|
|
379
|
+
switch(s)
|
|
380
|
+
{
|
|
381
|
+
case 'true': return {kind: 'true'}
|
|
382
|
+
case 'false': return {kind: 'false'}
|
|
383
|
+
case 'null': return {kind: 'null'}
|
|
384
|
+
default: return {kind: 'error', message: 'invalid keyword'}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** @type {(state: ParseKeywordState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
389
|
+
const parseKeyWordStateOp = state => input =>
|
|
390
|
+
{
|
|
391
|
+
if (input === undefined)
|
|
392
|
+
{
|
|
393
|
+
const keyWordToken = stringToKeywordToken(state.value)
|
|
394
|
+
return [[keyWordToken], {kind: 'eof'}]
|
|
395
|
+
}
|
|
396
|
+
if (input >= letterA && input <= letterZ)
|
|
397
|
+
{
|
|
398
|
+
return [undefined, {kind: 'keyword', value: appendChar(state.value)(input)}]
|
|
399
|
+
}
|
|
400
|
+
const keyWordToken = stringToKeywordToken(state.value)
|
|
401
|
+
const next = tokenizeOp({kind: 'initial'})(input)
|
|
402
|
+
return [{first: keyWordToken, tail: next[0]}, next[1]]
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** @type {(state: EofState) => (input: JsonCharacter) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
406
|
+
const eofStateOp = state => input => [[{kind: 'error', message: 'eof'}], state]
|
|
407
|
+
|
|
408
|
+
/** @type {operator.StateScan<JsonCharacter, TokenizerState, list.List<JsonToken>>} */
|
|
409
|
+
const tokenizeOp = state => input =>
|
|
410
|
+
{
|
|
411
|
+
const f = () => {
|
|
412
|
+
switch(state.kind)
|
|
413
|
+
{
|
|
414
|
+
case 'initial': return initialStateOp(state)
|
|
415
|
+
case 'keyword': return parseKeyWordStateOp(state)
|
|
416
|
+
case 'string': return parseStringStateOp(state)
|
|
417
|
+
case 'escapeChar': return parseEscapeCharStateOp(state)
|
|
418
|
+
case 'unicodeChar': return parseUnicodeCharStateOp(state)
|
|
419
|
+
case 'invalidNumber': return invalidNumberStateOp(state)
|
|
420
|
+
case 'number': return parseNumberStateOp(state)
|
|
421
|
+
case 'eof': return eofStateOp(state)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return f()(input)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** @type {(input: list.List<JsonCharacter>) => list.List<JsonToken>} */
|
|
428
|
+
const tokenize = input => list.flat(list.stateScan(tokenizeOp)({kind: 'initial'})(input))
|
|
429
|
+
|
|
430
|
+
module.exports = {
|
|
431
|
+
/** @readonly */
|
|
432
|
+
tokenize,
|
|
433
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const tokenizer = require('.')
|
|
2
|
+
const list = require('../../types/list')
|
|
3
|
+
const json = require('..')
|
|
4
|
+
const { sort } = require('../../types/object')
|
|
5
|
+
|
|
6
|
+
/** @type {(s: string) => list.List<tokenizer.JsonCharacter>} */
|
|
7
|
+
const toCharacters = s =>
|
|
8
|
+
{
|
|
9
|
+
/** @type {list.List<tokenizer.JsonCharacter>} */
|
|
10
|
+
const charCodes = list.toCharCodes(s)
|
|
11
|
+
return list.concat(charCodes)([undefined])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @type {(s: string) => readonly tokenizer.JsonToken[]} */
|
|
15
|
+
const tokenizeString = s =>
|
|
16
|
+
{
|
|
17
|
+
const characters = toCharacters(s)
|
|
18
|
+
return list.toArray(tokenizer.tokenize(characters))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @type {(a: readonly tokenizer.JsonToken[]) => string} */
|
|
22
|
+
const stringify = a => json.stringify(sort)(a)
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
const result = tokenizeString('')
|
|
26
|
+
if (result.length !== 0) { throw result }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
const result = stringify(tokenizeString('{'))
|
|
31
|
+
if (result !== '[{"kind":"{"}]') { throw result }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
{
|
|
35
|
+
const result = stringify(tokenizeString('}'))
|
|
36
|
+
if (result !== '[{"kind":"}"}]') { throw result }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
const result = stringify(tokenizeString(':'))
|
|
41
|
+
if (result !== '[{"kind":":"}]') { throw result }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
const result = stringify(tokenizeString(','))
|
|
46
|
+
if (result !== '[{"kind":","}]') { throw result }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
const result = stringify(tokenizeString('['))
|
|
51
|
+
if (result !== '[{"kind":"["}]') { throw result }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
const result = stringify(tokenizeString(']'))
|
|
56
|
+
if (result !== '[{"kind":"]"}]') { throw result }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
const result = stringify(tokenizeString('ᄑ'))
|
|
61
|
+
if (result !== '[{"kind":"error","message":"unexpected character"}]') { throw result }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
{
|
|
65
|
+
const result = stringify(tokenizeString('err'))
|
|
66
|
+
if (result !== '[{"kind":"error","message":"invalid keyword"}]') { throw result }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
const result = stringify(tokenizeString('{e}'))
|
|
71
|
+
if (result !== '[{"kind":"{"},{"kind":"error","message":"invalid keyword"},{"kind":"}"}]') { throw result }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
const result = stringify(tokenizeString('{ \t\n\r}'))
|
|
76
|
+
if (result !== '[{"kind":"{"},{"kind":"}"}]') { throw result }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
const result = stringify(tokenizeString('true'))
|
|
81
|
+
if (result !== '[{"kind":"true"}]') { throw result }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
const result = stringify(tokenizeString('tru'))
|
|
86
|
+
if (result !== '[{"kind":"error","message":"invalid keyword"}]') { throw result }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
const result = stringify(tokenizeString('false'))
|
|
91
|
+
if (result !== '[{"kind":"false"}]') { throw result }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
const result = stringify(tokenizeString('null'))
|
|
96
|
+
if (result !== '[{"kind":"null"}]') { throw result }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
{
|
|
100
|
+
const result = stringify(tokenizeString('[null]'))
|
|
101
|
+
if (result !== '[{"kind":"["},{"kind":"null"},{"kind":"]"}]') { throw result }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
const result = stringify(tokenizeString('""'))
|
|
106
|
+
if (result !== '[{"kind":"string","value":""}]') { throw result }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
{
|
|
110
|
+
const result = stringify(tokenizeString('"value"'))
|
|
111
|
+
if (result !== '[{"kind":"string","value":"value"}]') { throw result }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
const result = stringify(tokenizeString('"value'))
|
|
116
|
+
if (result !== '[{"kind":"error","message":"\\" are missing"}]') { throw result }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
const result = stringify(tokenizeString('"value1" "value2"'))
|
|
121
|
+
if (result !== '[{"kind":"string","value":"value1"},{"kind":"string","value":"value2"}]') { throw result }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
const result = stringify(tokenizeString('"'))
|
|
126
|
+
if (result !== '[{"kind":"error","message":"\\" are missing"}]') { throw result }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
const result = stringify(tokenizeString('"\\\\"'))
|
|
131
|
+
if (result !== '[{"kind":"string","value":"\\\\"}]') { throw result }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
const result = stringify(tokenizeString('"\\""'))
|
|
136
|
+
if (result !== '[{"kind":"string","value":"\\""}]') { throw result }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
const result = stringify(tokenizeString('"\\/"'))
|
|
141
|
+
if (result !== '[{"kind":"string","value":"/"}]') { throw result }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
const result = stringify(tokenizeString('"\\x"'))
|
|
146
|
+
if (result !== '[{"kind":"error","message":"unescaped character"},{"kind":"string","value":"x"}]') { throw result }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
{
|
|
150
|
+
const result = stringify(tokenizeString('"\\'))
|
|
151
|
+
if (result !== '[{"kind":"error","message":"\\" are missing"}]') { throw result }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
{
|
|
155
|
+
const result = stringify(tokenizeString('"\\b\\f\\n\\r\\t"'))
|
|
156
|
+
if (result !== '[{"kind":"string","value":"\\b\\f\\n\\r\\t"}]') { throw result }
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
{
|
|
160
|
+
const result = stringify(tokenizeString('"\\u1234"'))
|
|
161
|
+
if (result !== '[{"kind":"string","value":"ሴ"}]') { throw result }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
{
|
|
165
|
+
const result = stringify(tokenizeString('"\\uaBcDEeFf"'))
|
|
166
|
+
if (result !== '[{"kind":"string","value":"ꯍEeFf"}]') { throw result }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
const result = stringify(tokenizeString('"\\uEeFg"'))
|
|
171
|
+
if (result !== '[{"kind":"error","message":"invalid hex value"},{"kind":"string","value":"g"}]') { throw result }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
const result = stringify(tokenizeString('0'))
|
|
176
|
+
if (result !== '[{"kind":"number","value":"0"}]') { throw result }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
{
|
|
180
|
+
const result = stringify(tokenizeString('[0]'))
|
|
181
|
+
if (result !== '[{"kind":"["},{"kind":"number","value":"0"},{"kind":"]"}]') { throw result }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
{
|
|
185
|
+
const result = stringify(tokenizeString('00'))
|
|
186
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
const result = stringify(tokenizeString('0abc,'))
|
|
191
|
+
if (result !== '[{"kind":"error","message":"invalid number"},{"kind":","}]') { throw result }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
const result = stringify(tokenizeString('1234567890'))
|
|
196
|
+
if (result !== '[{"kind":"number","value":"1234567890"}]') { throw result }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
{
|
|
200
|
+
const result = stringify(tokenizeString('{90}'))
|
|
201
|
+
if (result !== '[{"kind":"{"},{"kind":"number","value":"90"},{"kind":"}"}]') { throw result }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
{
|
|
205
|
+
const result = stringify(tokenizeString('10-0'))
|
|
206
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
{
|
|
210
|
+
const result = stringify(tokenizeString('9a:'))
|
|
211
|
+
if (result !== '[{"kind":"error","message":"invalid number"},{"kind":":"}]') { throw result }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
{
|
|
215
|
+
const result = stringify(tokenizeString('-10'))
|
|
216
|
+
if (result !== '[{"kind":"number","value":"-10"}]') { throw result }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
{
|
|
220
|
+
const result = stringify(tokenizeString('-0'))
|
|
221
|
+
if (result !== '[{"kind":"number","value":"-0"}]') { throw result }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
{
|
|
225
|
+
const result = stringify(tokenizeString('-00'))
|
|
226
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
{
|
|
230
|
+
const result = stringify(tokenizeString('-.123'))
|
|
231
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
{
|
|
235
|
+
const result = stringify(tokenizeString('0.01'))
|
|
236
|
+
if (result !== '[{"kind":"number","value":"0.01"}]') { throw result }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
{
|
|
240
|
+
const result = stringify(tokenizeString('-0.9'))
|
|
241
|
+
if (result !== '[{"kind":"number","value":"-0.9"}]') { throw result }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
const result = stringify(tokenizeString('-0.'))
|
|
246
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
{
|
|
250
|
+
const result = stringify(tokenizeString('-0.]'))
|
|
251
|
+
if (result !== '[{"kind":"error","message":"invalid number"},{"kind":"]"}]') { throw result }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
{
|
|
255
|
+
const result = stringify(tokenizeString('12.34'))
|
|
256
|
+
if (result !== '[{"kind":"number","value":"12.34"}]') { throw result }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
{
|
|
260
|
+
const result = stringify(tokenizeString('-12.00'))
|
|
261
|
+
if (result !== '[{"kind":"number","value":"-12.00"}]') { throw result }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
{
|
|
265
|
+
const result = stringify(tokenizeString('-12.'))
|
|
266
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
{
|
|
270
|
+
const result = stringify(tokenizeString('12.]'))
|
|
271
|
+
if (result !== '[{"kind":"error","message":"invalid number"},{"kind":"]"}]') { throw result }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
{
|
|
275
|
+
const result = stringify(tokenizeString('0e1'))
|
|
276
|
+
if (result !== '[{"kind":"number","value":"0e1"}]') { throw result }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
{
|
|
280
|
+
const result = stringify(tokenizeString('0e+2'))
|
|
281
|
+
if (result !== '[{"kind":"number","value":"0e+2"}]') { throw result }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
const result = stringify(tokenizeString('0e-0'))
|
|
286
|
+
if (result !== '[{"kind":"number","value":"0e-0"}]') { throw result }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
{
|
|
290
|
+
const result = stringify(tokenizeString('12e0000'))
|
|
291
|
+
if (result !== '[{"kind":"number","value":"12e0000"}]') { throw result }
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
{
|
|
295
|
+
const result = stringify(tokenizeString('-12e-0001'))
|
|
296
|
+
if (result !== '[{"kind":"number","value":"-12e-0001"}]') { throw result }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
{
|
|
300
|
+
const result = stringify(tokenizeString('0e'))
|
|
301
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
const result = stringify(tokenizeString('0e-'))
|
|
306
|
+
if (result !== '[{"kind":"error","message":"invalid number"}]') { throw result }
|
|
307
|
+
}
|
package/package.json
CHANGED
package/test.js
CHANGED