functionalscript 0.0.327 → 0.0.331
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 +2 -0
- package/json/tokenizer/index.js +57 -89
- package/json/tokenizer/test.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,8 @@ FunctionalScript is a purely functional programming language and a strict subset
|
|
|
11
11
|
|
|
12
12
|
Create a new FunctionalScript repository on GitHub [here](https://github.com/functionalscript/template/generate).
|
|
13
13
|
|
|
14
|
+
Learn more about [FunctionalScript](https://medium.com/@sergeyshandar/purely-functional-programming-in-javascript-91114b1b2dff).
|
|
15
|
+
|
|
14
16
|
## Design Principles
|
|
15
17
|
|
|
16
18
|
In FunctionalScript:
|
package/json/tokenizer/index.js
CHANGED
|
@@ -3,18 +3,6 @@ const operator = require('../../types/function/operator')
|
|
|
3
3
|
const { concat } = require('../../types/list')
|
|
4
4
|
const list = require('../../types/list')
|
|
5
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
6
|
/**
|
|
19
7
|
* @typedef {{
|
|
20
8
|
* readonly kind: 'string'
|
|
@@ -29,27 +17,15 @@ const list = require('../../types/list')
|
|
|
29
17
|
* }} NumberToken
|
|
30
18
|
* */
|
|
31
19
|
|
|
32
|
-
/** @typedef {{readonly kind: 'true'}} TrueToken */
|
|
33
|
-
|
|
34
|
-
/** @typedef {{readonly kind: 'false'}} FalseToken */
|
|
35
|
-
|
|
36
|
-
/** @typedef {{readonly kind: 'null'}} NullToken */
|
|
37
|
-
|
|
38
20
|
/** @typedef {{readonly kind: 'error', message: ErrorMessage}} ErrorToken */
|
|
39
21
|
|
|
22
|
+
/** @typedef {{readonly kind: '{' | '}' | ':' | ',' | '[' | ']' | 'true' | 'false' | 'null'}} SimpleToken */
|
|
23
|
+
|
|
40
24
|
/**
|
|
41
25
|
* @typedef {|
|
|
42
|
-
*
|
|
43
|
-
* RightBraceToken |
|
|
44
|
-
* ColonToken |
|
|
45
|
-
* CommaToken |
|
|
46
|
-
* LeftBracketToken |
|
|
47
|
-
* RightBracketToken |
|
|
26
|
+
* SimpleToken |
|
|
48
27
|
* StringToken |
|
|
49
28
|
* NumberToken |
|
|
50
|
-
* TrueToken |
|
|
51
|
-
* FalseToken |
|
|
52
|
-
* NullToken |
|
|
53
29
|
* ErrorToken
|
|
54
30
|
* } JsonToken
|
|
55
31
|
*/
|
|
@@ -74,7 +50,7 @@ const newLine = 0x0a
|
|
|
74
50
|
const carriageReturn = 0x0d
|
|
75
51
|
const space = 0x20
|
|
76
52
|
|
|
77
|
-
const
|
|
53
|
+
const backslash = 0x5c
|
|
78
54
|
const slash = 0x2f
|
|
79
55
|
const backspace = 0x08
|
|
80
56
|
const formfeed = 0x0c
|
|
@@ -140,21 +116,17 @@ const letterZ = 0x7a
|
|
|
140
116
|
|
|
141
117
|
/** @typedef {{ readonly kind: 'eof'}} EofState */
|
|
142
118
|
|
|
143
|
-
/** @typedef {number|undefined}
|
|
119
|
+
/** @typedef {number|undefined} CharCodeOrEof */
|
|
144
120
|
|
|
145
|
-
/** @type {(old: string) => (input:
|
|
121
|
+
/** @type {(old: string) => (input: CharCodeOrEof) => string} */
|
|
146
122
|
const appendChar = old => input => input === undefined ? old : operator.concat(charToString(input))(old)
|
|
147
123
|
|
|
148
|
-
/** @type {(input:
|
|
124
|
+
/** @type {(input: CharCodeOrEof) => string} */
|
|
149
125
|
const charToString = input => input === undefined ? '' : String.fromCharCode(input)
|
|
150
126
|
|
|
151
|
-
/** @type {(state: InitialState) => (input:
|
|
127
|
+
/** @type {(state: InitialState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
152
128
|
const initialStateOp = initialState => input =>
|
|
153
129
|
{
|
|
154
|
-
if (input === undefined)
|
|
155
|
-
{
|
|
156
|
-
return[undefined, {kind: 'eof'}]
|
|
157
|
-
}
|
|
158
130
|
if (input >= digit1 && input <= digit9)
|
|
159
131
|
{
|
|
160
132
|
return [undefined, { kind: 'number', value: charToString(input), numberKind: 'int'}]
|
|
@@ -182,21 +154,9 @@ const initialStateOp = initialState => input =>
|
|
|
182
154
|
}
|
|
183
155
|
}
|
|
184
156
|
|
|
185
|
-
/** @type {(state: ParseNumberState) => (input:
|
|
157
|
+
/** @type {(state: ParseNumberState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
186
158
|
const parseNumberStateOp = state => input =>
|
|
187
159
|
{
|
|
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
160
|
if (input === decimalPoint)
|
|
201
161
|
{
|
|
202
162
|
switch (state.numberKind)
|
|
@@ -297,13 +257,9 @@ const isTerminalForNumber = char =>
|
|
|
297
257
|
}
|
|
298
258
|
}
|
|
299
259
|
|
|
300
|
-
/** @type {(state: InvalidNumberState) => (input:
|
|
260
|
+
/** @type {(state: InvalidNumberState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
301
261
|
const invalidNumberStateOp = state => input =>
|
|
302
262
|
{
|
|
303
|
-
if (input === undefined)
|
|
304
|
-
{
|
|
305
|
-
return [[{kind: 'error', message: 'invalid number'}], {kind: 'eof'}]
|
|
306
|
-
}
|
|
307
263
|
if (isTerminalForNumber(input))
|
|
308
264
|
{
|
|
309
265
|
const next = tokenizeOp({kind: 'initial'})(input)
|
|
@@ -312,25 +268,24 @@ const invalidNumberStateOp = state => input =>
|
|
|
312
268
|
return [undefined, {kind: 'invalidNumber'}]
|
|
313
269
|
}
|
|
314
270
|
|
|
315
|
-
/** @type {(state: ParseStringState) => (input:
|
|
271
|
+
/** @type {(state: ParseStringState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
316
272
|
const parseStringStateOp = state => input =>
|
|
317
273
|
{
|
|
318
274
|
switch(input)
|
|
319
275
|
{
|
|
320
276
|
case quotationMark: return[[{kind: 'string', value: state.value}], {kind: 'initial'}]
|
|
321
|
-
case
|
|
322
|
-
case undefined: return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
277
|
+
case backslash: return [undefined, {kind:'escapeChar', value: state.value}]
|
|
323
278
|
default: return [undefined, {kind:'string', value: appendChar(state.value)(input)}]
|
|
324
279
|
}
|
|
325
280
|
}
|
|
326
281
|
|
|
327
|
-
/** @type {(state: ParseEscapeCharState) => (input:
|
|
282
|
+
/** @type {(state: ParseEscapeCharState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
328
283
|
const parseEscapeCharStateOp = state => input =>
|
|
329
284
|
{
|
|
330
285
|
switch(input)
|
|
331
286
|
{
|
|
332
287
|
case quotationMark:
|
|
333
|
-
case
|
|
288
|
+
case backslash:
|
|
334
289
|
case slash: return [undefined, {kind: 'string', value: appendChar(state.value)(input)}]
|
|
335
290
|
case letterB: return [undefined, {kind: 'string', value: appendChar(state.value)(backspace)}]
|
|
336
291
|
case letterF: return [undefined, {kind: 'string', value: appendChar(state.value)(formfeed)}]
|
|
@@ -338,7 +293,6 @@ const parseEscapeCharStateOp = state => input =>
|
|
|
338
293
|
case letterR: return [undefined, {kind: 'string', value: appendChar(state.value)(carriageReturn)}]
|
|
339
294
|
case letterT: return [undefined, {kind: 'string', value: appendChar(state.value)(horizontalTab)}]
|
|
340
295
|
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
296
|
default: {
|
|
343
297
|
const next = tokenizeOp({kind: 'string', value: state.value})(input)
|
|
344
298
|
return [{first: {kind: 'error', message: 'unescaped character'}, tail: next[0]}, next[1]]
|
|
@@ -354,13 +308,9 @@ const hexDigitToNumber = hex =>
|
|
|
354
308
|
if (hex >= letterA && hex <= letterF) { return hex - letterA + 10 }
|
|
355
309
|
}
|
|
356
310
|
|
|
357
|
-
/** @type {(state: ParseUnicodeCharState) => (input:
|
|
311
|
+
/** @type {(state: ParseUnicodeCharState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
358
312
|
const parseUnicodeCharStateOp = state => input =>
|
|
359
313
|
{
|
|
360
|
-
if (input === undefined)
|
|
361
|
-
{
|
|
362
|
-
return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
363
|
-
}
|
|
364
314
|
const hexValue = hexDigitToNumber(input)
|
|
365
315
|
if (hexValue === undefined)
|
|
366
316
|
{
|
|
@@ -385,14 +335,9 @@ const stringToKeywordToken = s =>
|
|
|
385
335
|
}
|
|
386
336
|
}
|
|
387
337
|
|
|
388
|
-
/** @type {(state: ParseKeywordState) => (input:
|
|
338
|
+
/** @type {(state: ParseKeywordState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
389
339
|
const parseKeyWordStateOp = state => input =>
|
|
390
340
|
{
|
|
391
|
-
if (input === undefined)
|
|
392
|
-
{
|
|
393
|
-
const keyWordToken = stringToKeywordToken(state.value)
|
|
394
|
-
return [[keyWordToken], {kind: 'eof'}]
|
|
395
|
-
}
|
|
396
341
|
if (input >= letterA && input <= letterZ)
|
|
397
342
|
{
|
|
398
343
|
return [undefined, {kind: 'keyword', value: appendChar(state.value)(input)}]
|
|
@@ -402,29 +347,52 @@ const parseKeyWordStateOp = state => input =>
|
|
|
402
347
|
return [{first: keyWordToken, tail: next[0]}, next[1]]
|
|
403
348
|
}
|
|
404
349
|
|
|
405
|
-
/** @type {(state: EofState) => (input:
|
|
350
|
+
/** @type {(state: EofState) => (input: number) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
406
351
|
const eofStateOp = state => input => [[{kind: 'error', message: 'eof'}], state]
|
|
407
352
|
|
|
408
|
-
/** @type {operator.StateScan<
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
case 'number': return parseNumberStateOp(state)
|
|
421
|
-
case 'eof': return eofStateOp(state)
|
|
422
|
-
}
|
|
353
|
+
/** @type {operator.StateScan<number, TokenizerState, list.List<JsonToken>>} */
|
|
354
|
+
const tokenizeCharCodeOp = state => {
|
|
355
|
+
switch(state.kind)
|
|
356
|
+
{
|
|
357
|
+
case 'initial': return initialStateOp(state)
|
|
358
|
+
case 'keyword': return parseKeyWordStateOp(state)
|
|
359
|
+
case 'string': return parseStringStateOp(state)
|
|
360
|
+
case 'escapeChar': return parseEscapeCharStateOp(state)
|
|
361
|
+
case 'unicodeChar': return parseUnicodeCharStateOp(state)
|
|
362
|
+
case 'invalidNumber': return invalidNumberStateOp(state)
|
|
363
|
+
case 'number': return parseNumberStateOp(state)
|
|
364
|
+
case 'eof': return eofStateOp(state)
|
|
423
365
|
}
|
|
424
|
-
return f()(input)
|
|
425
366
|
}
|
|
426
367
|
|
|
427
|
-
/** @type {(
|
|
368
|
+
/** @type {(state: TokenizerState) => readonly[list.List<JsonToken>, TokenizerState]} */
|
|
369
|
+
const tokenizeEofOp = state => {
|
|
370
|
+
switch(state.kind)
|
|
371
|
+
{
|
|
372
|
+
case 'initial': return[undefined, {kind: 'eof'}]
|
|
373
|
+
case 'keyword': return [[stringToKeywordToken(state.value)], {kind: 'eof'}]
|
|
374
|
+
case 'string':
|
|
375
|
+
case 'escapeChar':
|
|
376
|
+
case 'unicodeChar': return [[{kind: 'error', message: '" are missing'}], {kind: 'eof'}]
|
|
377
|
+
case 'invalidNumber': return [[{kind: 'error', message: 'invalid number'}], {kind: 'eof'}]
|
|
378
|
+
case 'number':
|
|
379
|
+
switch (state.numberKind)
|
|
380
|
+
{
|
|
381
|
+
case '-':
|
|
382
|
+
case '.':
|
|
383
|
+
case 'e':
|
|
384
|
+
case 'e+':
|
|
385
|
+
case 'e-': return [[{kind: 'error', message: 'invalid number'}], {kind: 'invalidNumber', }]
|
|
386
|
+
default: return [[{kind: 'number', value: state.value}], {kind: 'eof'}]
|
|
387
|
+
}
|
|
388
|
+
case 'eof': return [[{kind: 'error', message: 'eof'}], state]
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** @type {operator.StateScan<CharCodeOrEof, TokenizerState, list.List<JsonToken>>} */
|
|
393
|
+
const tokenizeOp = state => input => input === undefined ? tokenizeEofOp(state) : tokenizeCharCodeOp(state)(input)
|
|
394
|
+
|
|
395
|
+
/** @type {(input: list.List<CharCodeOrEof>) => list.List<JsonToken>} */
|
|
428
396
|
const tokenize = input => list.flat(list.stateScan(tokenizeOp)({kind: 'initial'})(input))
|
|
429
397
|
|
|
430
398
|
module.exports = {
|
package/json/tokenizer/test.js
CHANGED
|
@@ -3,17 +3,17 @@ const list = require('../../types/list')
|
|
|
3
3
|
const json = require('..')
|
|
4
4
|
const { sort } = require('../../types/object')
|
|
5
5
|
|
|
6
|
-
/** @type {(s: string) => list.List<tokenizer.
|
|
6
|
+
/** @type {(s: string) => list.List<tokenizer.CharCodeOrEof>} */
|
|
7
7
|
const toCharacters = s =>
|
|
8
8
|
{
|
|
9
|
-
/** @type {list.List<tokenizer.
|
|
9
|
+
/** @type {list.List<tokenizer.CharCodeOrEof>} */
|
|
10
10
|
const charCodes = list.toCharCodes(s)
|
|
11
11
|
return list.concat(charCodes)([undefined])
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
13
|
|
|
14
14
|
/** @type {(s: string) => readonly tokenizer.JsonToken[]} */
|
|
15
15
|
const tokenizeString = s =>
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
17
|
const characters = toCharacters(s)
|
|
18
18
|
return list.toArray(tokenizer.tokenize(characters))
|
|
19
19
|
}
|