1 | 'use strict'
|
2 |
|
3 | module.exports = makeParserClass(require('./parser.js'))
|
4 | module.exports.makeParserClass = makeParserClass
|
5 |
|
6 | class TomlError extends Error {
|
7 | constructor (msg) {
|
8 | super(msg)
|
9 |
|
10 | if (Error.captureStackTrace) Error.captureStackTrace(this, TomlError)
|
11 | this.fromTOML = true
|
12 | this.wrapped = null
|
13 | }
|
14 | }
|
15 | TomlError.wrap = err => {
|
16 | const terr = new TomlError(err.message)
|
17 | terr.code = err.code
|
18 | terr.wrapped = err
|
19 | return terr
|
20 | }
|
21 | module.exports.TomlError = TomlError
|
22 |
|
23 | const createDateTime = require('./create-datetime.js')
|
24 |
|
25 | const escapes = {
|
26 | 'b': '\x08',
|
27 | 't': '\x09',
|
28 | 'n': '\x0a',
|
29 | 'f': '\x0c',
|
30 | 'r': '\x0d',
|
31 | '"': '\x22',
|
32 | '\\': '\x5c'
|
33 | }
|
34 |
|
35 | function isDigit (char) {
|
36 | return char === '0' || char === '1' || char === '2' || char === '3' ||
|
37 | char === '4' || char === '5' || char === '6' || char === '7' ||
|
38 | char === '8' || char === '9'
|
39 | }
|
40 | function isHexit (char) {
|
41 |
|
42 | if (typeof char !== 'string') return false
|
43 | const cp = char.codePointAt(0)
|
44 | return (cp >= 65 && cp <= 70) || (cp >= 97 && cp <= 102) || (cp >= 48 && cp <= 57)
|
45 | }
|
46 | function isAlphaNumQuoteHyphen (char) {
|
47 |
|
48 | if (typeof char !== 'string') return false
|
49 | const cp = char.codePointAt(0)
|
50 |
|
51 | return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122) || (cp >= 48 && cp <= 57) || cp === 39 || cp === 34 || cp === 95 || cp === 45
|
52 | }
|
53 | function isAlphaNumHyphen (char) {
|
54 |
|
55 | if (typeof char !== 'string') return false
|
56 | const cp = char.codePointAt(0)
|
57 |
|
58 | return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122) || (cp >= 48 && cp <= 57) || cp === 95 || cp === 45
|
59 | }
|
60 | const _type = Symbol('type')
|
61 | const _declared = Symbol('declared')
|
62 |
|
63 | const INLINE_TABLE = Symbol('inline-table')
|
64 | function InlineTable () {
|
65 | return Object.defineProperties({}, {
|
66 | [_type]: {value: INLINE_TABLE}
|
67 | })
|
68 | }
|
69 | function isInlineTable (obj) {
|
70 | if (obj === null || typeof (obj) !== 'object') return false
|
71 | return obj[_type] === INLINE_TABLE
|
72 | }
|
73 |
|
74 | const TABLE = Symbol('table')
|
75 | function Table () {
|
76 | return Object.defineProperties({}, {
|
77 | [_type]: {value: TABLE},
|
78 | [_declared]: {value: false, writable: true}
|
79 | })
|
80 | }
|
81 | function isTable (obj) {
|
82 | if (obj === null || typeof (obj) !== 'object') return false
|
83 | return obj[_type] === TABLE
|
84 | }
|
85 |
|
86 | const _contentType = Symbol('content-type')
|
87 | const INLINE_LIST = Symbol('inline-list')
|
88 | function InlineList (type) {
|
89 | return Object.defineProperties([], {
|
90 | [_type]: {value: INLINE_LIST},
|
91 | [_contentType]: {value: type}
|
92 | })
|
93 | }
|
94 | function isInlineList (obj) {
|
95 | if (obj === null || typeof (obj) !== 'object') return false
|
96 | return obj[_type] === INLINE_LIST
|
97 | }
|
98 |
|
99 | const LIST = Symbol('list')
|
100 | function List () {
|
101 | return Object.defineProperties([], {
|
102 | [_type]: {value: LIST}
|
103 | })
|
104 | }
|
105 | function isList (obj) {
|
106 | if (obj === null || typeof (obj) !== 'object') return false
|
107 | return obj[_type] === LIST
|
108 | }
|
109 |
|
110 |
|
111 | const utilInspect = eval(`require('util').inspect`)
|
112 |
|
113 | const _inspect = (utilInspect && utilInspect.custom) || 'inspect'
|
114 |
|
115 | const INTEGER = Symbol('integer')
|
116 | function Integer (value) {
|
117 | return Object.defineProperties(new Number(value), {
|
118 | [_type]: {value: INTEGER},
|
119 | [_inspect]: {value: () => `[Integer: ${value}]`}
|
120 | })
|
121 | }
|
122 | function isInteger (obj) {
|
123 | if (obj === null || typeof (obj) !== 'object') return false
|
124 | return obj[_type] === INTEGER
|
125 | }
|
126 |
|
127 | const FLOAT = Symbol('float')
|
128 | function Float (value) {
|
129 | return Object.defineProperties(new Number(value), {
|
130 | [_type]: {value: FLOAT},
|
131 | [_inspect]: {value: () => `[Float: ${value}]`}
|
132 | })
|
133 | }
|
134 | function isFloat (obj) {
|
135 | if (obj === null || typeof (obj) !== 'object') return false
|
136 | return obj[_type] === FLOAT
|
137 | }
|
138 |
|
139 | function tomlType (value) {
|
140 | const type = typeof value
|
141 | if (type === 'object') {
|
142 |
|
143 | if (value === null) return 'null'
|
144 | if (value instanceof Date) return 'datetime'
|
145 |
|
146 | if (_type in value) {
|
147 | switch (value[_type]) {
|
148 | case INLINE_TABLE: return 'inline-table'
|
149 | case INLINE_LIST: return 'inline-list'
|
150 |
|
151 | case TABLE: return 'table'
|
152 |
|
153 | case LIST: return 'list'
|
154 | case FLOAT: return 'float'
|
155 | case INTEGER: return 'integer'
|
156 | }
|
157 | }
|
158 | }
|
159 | return type
|
160 | }
|
161 |
|
162 | function makeParserClass (Parser) {
|
163 | class TOMLParser extends Parser {
|
164 | constructor () {
|
165 | super()
|
166 | this.ctx = this.obj = Table()
|
167 | }
|
168 |
|
169 |
|
170 | atEndOfWord () {
|
171 | return this.char === '#' || this.char === '\t' || this.char === ' ' || this.atEndOfLine()
|
172 | }
|
173 | atEndOfLine () {
|
174 | return this.char === Parser.END || this.char === '\n' || this.char === '\r'
|
175 | }
|
176 |
|
177 | parseStart () {
|
178 | if (this.char === Parser.END) {
|
179 | return null
|
180 | } else if (this.char === '[') {
|
181 | return this.call(this.parseTableOrList)
|
182 | } else if (this.char === '#') {
|
183 | return this.call(this.parseComment)
|
184 | } else if (this.char === '\n' || this.char === ' ' || this.char === '\t' || this.char === '\r') {
|
185 | return null
|
186 | } else if (isAlphaNumQuoteHyphen(this.char)) {
|
187 | return this.callNow(this.parseAssignStatement)
|
188 | } else {
|
189 | throw this.error(new TomlError(`Unknown character "${this.char}"`))
|
190 | }
|
191 | }
|
192 |
|
193 |
|
194 |
|
195 | parseWhitespaceToEOL () {
|
196 | if (this.char === ' ' || this.char === '\t' || this.char === '\r') {
|
197 | return null
|
198 | } else if (this.char === '#') {
|
199 | return this.goto(this.parseComment)
|
200 | } else if (this.char === Parser.END || this.char === '\n') {
|
201 | return this.return()
|
202 | } else {
|
203 | throw this.error(new TomlError('Unexpected character, expected only whitespace or comments till end of line'))
|
204 | }
|
205 | }
|
206 |
|
207 |
|
208 | parseAssignStatement () {
|
209 | return this.callNow(this.parseAssign, this.recordAssignStatement)
|
210 | }
|
211 | recordAssignStatement (kv) {
|
212 | if (kv.key in this.ctx) {
|
213 | throw this.error(new TomlError("Can't redefine existing key"))
|
214 | }
|
215 |
|
216 | if (isInteger(kv.value) || isFloat(kv.value)) {
|
217 | this.ctx[kv.key] = kv.value.valueOf()
|
218 | } else {
|
219 | this.ctx[kv.key] = kv.value
|
220 | }
|
221 | return this.goto(this.parseWhitespaceToEOL)
|
222 | }
|
223 |
|
224 |
|
225 | parseAssign () {
|
226 | return this.callNow(this.parseKeyword, this.recordAssignKeyword)
|
227 | }
|
228 | recordAssignKeyword (key) {
|
229 | this.state.result = key
|
230 | return this.goto(this.parseAssignEqual)
|
231 | }
|
232 | parseAssignEqual () {
|
233 | if (this.char === ' ' || this.char === '\t') {
|
234 | return null
|
235 | } else if (this.char === '=') {
|
236 | return this.next(this.parseAssignPreValue)
|
237 | } else {
|
238 | throw this.error(new TomlError('Invalid character, expected "="'))
|
239 | }
|
240 | }
|
241 | parseAssignPreValue () {
|
242 | if (this.char === ' ' || this.char === '\t') {
|
243 | return null
|
244 | } else {
|
245 | return this.callNow(this.parseValue, this.recordAssignValue)
|
246 | }
|
247 | }
|
248 | recordAssignValue (value) {
|
249 | return this.returnNow({key: this.state.result, value: value})
|
250 | }
|
251 |
|
252 |
|
253 | parseComment () {
|
254 | do {
|
255 | if (this.char === Parser.END || this.char === '\n') {
|
256 | return this.return()
|
257 | }
|
258 | } while (this.nextChar())
|
259 | }
|
260 |
|
261 |
|
262 | parseTableOrList () {
|
263 | if (this.char === '[') {
|
264 | this.next(this.parseList)
|
265 | } else {
|
266 | return this.goto(this.parseTable)
|
267 | }
|
268 | }
|
269 |
|
270 |
|
271 | parseTable () {
|
272 | this.ctx = this.obj
|
273 | return this.goto(this.parseTableNext)
|
274 | }
|
275 | parseTableNext () {
|
276 | if (this.char === ' ' || this.char === '\t') {
|
277 | return null
|
278 | } else {
|
279 | return this.callNow(this.parseKeyword, this.parseTableMore)
|
280 | }
|
281 | }
|
282 | parseTableMore (keyword) {
|
283 | if (this.char === ' ' || this.char === '\t') {
|
284 | return null
|
285 | } else if (this.char === ']') {
|
286 | if (keyword in this.ctx && isTable(this.ctx[keyword]) && this.ctx[keyword][_declared]) {
|
287 | throw this.error(new TomlError("Can't redefine existing key"))
|
288 | } else {
|
289 | this.ctx = this.ctx[keyword] = this.ctx[keyword] || Table()
|
290 | this.ctx[_declared] = true
|
291 | }
|
292 | return this.next(this.parseWhitespaceToEOL)
|
293 | } else if (this.char === '.') {
|
294 | if (!(keyword in this.ctx)) {
|
295 | this.ctx = this.ctx[keyword] = Table()
|
296 | } else if (isTable(this.ctx[keyword])) {
|
297 | this.ctx = this.ctx[keyword]
|
298 | } else if (isList(this.ctx[keyword])) {
|
299 | this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1]
|
300 | } else {
|
301 | throw this.error(new TomlError("Can't redefine existing key"))
|
302 | }
|
303 | return this.next(this.parseTableNext)
|
304 | } else {
|
305 | throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
|
306 | }
|
307 | }
|
308 |
|
309 |
|
310 | parseList () {
|
311 | this.ctx = this.obj
|
312 | return this.goto(this.parseListNext)
|
313 | }
|
314 | parseListNext () {
|
315 | if (this.char === ' ' || this.char === '\t') {
|
316 | return null
|
317 | } else {
|
318 | return this.callNow(this.parseKeyword, this.parseListMore)
|
319 | }
|
320 | }
|
321 | parseListMore (keyword) {
|
322 | if (this.char === ' ' || this.char === '\t') {
|
323 | return null
|
324 | } else if (this.char === ']') {
|
325 | if (!(keyword in this.ctx)) {
|
326 | this.ctx[keyword] = List()
|
327 | }
|
328 | if (isInlineList(this.ctx[keyword])) {
|
329 | throw this.error(new TomlError("Can't extend an inline array"))
|
330 | } else if (isList(this.ctx[keyword])) {
|
331 | const next = Table()
|
332 | this.ctx[keyword].push(next)
|
333 | this.ctx = next
|
334 | } else {
|
335 | throw this.error(new TomlError("Can't redefine an existing key"))
|
336 | }
|
337 | return this.next(this.parseListEnd)
|
338 | } else if (this.char === '.') {
|
339 | if (!(keyword in this.ctx)) {
|
340 | this.ctx = this.ctx[keyword] = Table()
|
341 | } else if (isInlineList(this.ctx[keyword])) {
|
342 | throw this.error(new TomlError("Can't extend an inline array"))
|
343 | } else if (isInlineTable(this.ctx[keyword])) {
|
344 | throw this.error(new TomlError("Can't extend an inline table"))
|
345 | } else if (isList(this.ctx[keyword])) {
|
346 | this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1]
|
347 | } else if (isTable(this.ctx[keyword])) {
|
348 | this.ctx = this.ctx[keyword]
|
349 | } else {
|
350 | throw this.error(new TomlError("Can't redefine an existing key"))
|
351 | }
|
352 | return this.next(this.parseListNext)
|
353 | } else {
|
354 | throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
|
355 | }
|
356 | }
|
357 | parseListEnd (keyword) {
|
358 | if (this.char === ']') {
|
359 | return this.next(this.parseWhitespaceToEOL)
|
360 | } else {
|
361 | throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
|
362 | }
|
363 | }
|
364 |
|
365 |
|
366 | parseValue () {
|
367 | if (this.char === Parser.END) {
|
368 | throw this.error(new TomlError('Key without value'))
|
369 | } else if (this.char === '"') {
|
370 | return this.next(this.parseBasicString)
|
371 | } if (this.char === "'") {
|
372 | return this.next(this.parseLiteralString)
|
373 | } else if (this.char === '-' || this.char === '+') {
|
374 | return this.goto(this.parseNumberSign)
|
375 | } else if (isDigit(this.char)) {
|
376 | return this.goto(this.parseNumberOrDateTime)
|
377 | } else if (this.char === 't' || this.char === 'f') {
|
378 | return this.goto(this.parseBoolean)
|
379 | } else if (this.char === '[') {
|
380 | return this.call(this.parseInlineList, this.recordValue)
|
381 | } else if (this.char === '{') {
|
382 | return this.call(this.parseInlineTable, this.recordValue)
|
383 | } else {
|
384 | throw this.error(new TomlError('Unexpected character, expecting string, number, datetime, boolean, inline array or inline table'))
|
385 | }
|
386 | }
|
387 | recordValue (value) {
|
388 | return this.returnNow(value)
|
389 | }
|
390 |
|
391 |
|
392 | parseKeyword () {
|
393 | if (this.char === '"') {
|
394 | return this.next(this.parseBasicOnlyString)
|
395 | } else {
|
396 | return this.goto(this.parseBareKey)
|
397 | }
|
398 | }
|
399 |
|
400 |
|
401 | parseBareKey () {
|
402 | do {
|
403 | if (this.char === Parser.END) {
|
404 | throw this.error(new TomlError('Key ended without value'))
|
405 | } else if (isAlphaNumHyphen(this.char)) {
|
406 | this.consume()
|
407 | } else if (this.state.buf.length === 0) {
|
408 | throw this.error(new TomlError('Empty bare keys are not allowed'))
|
409 | } else {
|
410 | return this.returnNow()
|
411 | }
|
412 | } while (this.nextChar())
|
413 | }
|
414 |
|
415 |
|
416 | parseLiteralString () {
|
417 | do {
|
418 | if (this.char === "'") {
|
419 |
|
420 | return this.next(this.parseLiteralMultiStringMaybe)
|
421 | } else if (this.atEndOfLine()) {
|
422 | throw this.error(new TomlError('Unterminated string'))
|
423 | } else {
|
424 | this.consume()
|
425 | }
|
426 | } while (this.nextChar())
|
427 | }
|
428 | parseLiteralMultiStringMaybe () {
|
429 | if (this.char === "'") {
|
430 | return this.next(this.parseLiteralMultiString)
|
431 | } else {
|
432 | return this.returnNow()
|
433 | }
|
434 | }
|
435 | parseLiteralMultiString () {
|
436 | if (this.char === '\r') {
|
437 | return null
|
438 | } else if (this.char === '\n') {
|
439 | return this.next(this.parseLiteralMultiStringContent)
|
440 | } else {
|
441 | return this.goto(this.parseLiteralMultiStringContent)
|
442 | }
|
443 | }
|
444 | parseLiteralMultiStringContent () {
|
445 | do {
|
446 | if (this.char === "'") {
|
447 | return this.next(this.parseLiteralMultiEnd)
|
448 | } else if (this.char === Parser.END) {
|
449 | throw this.error(new TomlError('Unterminated multi-line string'))
|
450 | } else {
|
451 | this.consume()
|
452 | }
|
453 | } while (this.nextChar())
|
454 | }
|
455 | parseLiteralMultiEnd () {
|
456 | if (this.char === "'") {
|
457 | return this.next(this.parseLiteralMultiEnd2)
|
458 | } else {
|
459 | this.state.buf += "'"
|
460 | return this.goto(this.parseLiteralMultiStringContent)
|
461 | }
|
462 | }
|
463 | parseLiteralMultiEnd2 () {
|
464 | if (this.char === "'") {
|
465 | return this.return()
|
466 | } else {
|
467 | this.state.buf += "''"
|
468 | return this.goto(this.parseLiteralMultiStringContent)
|
469 | }
|
470 | }
|
471 |
|
472 |
|
473 | parseBasicString () {
|
474 | do {
|
475 | if (this.char === '\\') {
|
476 | return this.call(this.parseEscape, this.recordEscapeReplacement)
|
477 | } else if (this.char === '"') {
|
478 | if (this.state.buf === '') {
|
479 | return this.next(this.parseMultiStringMaybe)
|
480 | } else {
|
481 | return this.return()
|
482 | }
|
483 | } else if (this.atEndOfLine()) {
|
484 | throw this.error(new TomlError('Unterminated string'))
|
485 | } else if (this.char.codePointAt(0) <= 0x1f && this.char !== '\t') {
|
486 | throw this.errorControlCharInString()
|
487 | } else {
|
488 | this.consume()
|
489 | }
|
490 | } while (this.nextChar())
|
491 | }
|
492 |
|
493 | parseBasicOnlyString () {
|
494 | do {
|
495 | if (this.char === '\\') {
|
496 | return this.call(this.parseEscape, this.recordEscapeReplacement)
|
497 | } else if (this.char === '"') {
|
498 | return this.return()
|
499 | } else if (this.atEndOfLine()) {
|
500 | throw this.error(new TomlError('Unterminated string'))
|
501 | } else if (this.char.codePointAt(0) <= 0x1f && this.char !== '\t') {
|
502 | throw this.errorControlCharInString()
|
503 | } else {
|
504 | this.consume()
|
505 | }
|
506 | } while (this.nextChar())
|
507 | }
|
508 | recordEscapeReplacement (replacement) {
|
509 | this.state.buf += replacement
|
510 | return this.goto(this.parseBasicString)
|
511 | }
|
512 | parseMultiStringMaybe () {
|
513 | if (this.char === '"') {
|
514 | return this.next(this.parseMultiString)
|
515 | } else {
|
516 | return this.returnNow()
|
517 | }
|
518 | }
|
519 | parseMultiString () {
|
520 | if (this.char === '\r') {
|
521 | return null
|
522 | } else if (this.char === '\n') {
|
523 | return this.next(this.parseMultiStringContent)
|
524 | } else {
|
525 | return this.goto(this.parseMultiStringContent)
|
526 | }
|
527 | }
|
528 | parseMultiStringContent () {
|
529 | do {
|
530 | if (this.char === '\\') {
|
531 | return this.call(this.parseMultiEscape, this.recordMultiEscapeReplacement)
|
532 | } else if (this.char === '"') {
|
533 | return this.next(this.parseMultiEnd)
|
534 | } else if (this.char === Parser.END) {
|
535 | throw this.error(new TomlError('Unterminated multi-line string'))
|
536 | } else if (this.char.codePointAt(0) <= 0x1f && this.char !== '\t' && this.char !== '\n' && this.char !== '\r') {
|
537 | throw this.errorControlCharInString()
|
538 | } else {
|
539 | this.consume()
|
540 | }
|
541 | } while (this.nextChar())
|
542 | }
|
543 | errorControlCharInString () {
|
544 | const codePoint = this.char.codePointAt(0)
|
545 | let displayCode = '\\u00'
|
546 | if (codePoint < 16) {
|
547 | displayCode += '0'
|
548 | }
|
549 | displayCode += codePoint.toString(16)
|
550 |
|
551 | return this.error(new TomlError(`Control characters (codes < 0x1f) are not allowed in strings, pass it in with ${displayCode}`))
|
552 | }
|
553 | recordMultiEscapeReplacement (replacement) {
|
554 | this.state.buf += replacement
|
555 | return this.goto(this.parseMultiStringContent)
|
556 | }
|
557 | parseMultiEnd () {
|
558 | if (this.char === '"') {
|
559 | return this.next(this.parseMultiEnd2)
|
560 | } else {
|
561 | this.state.buf += '"'
|
562 | return this.goto(this.parseMultiStringContent)
|
563 | }
|
564 | }
|
565 | parseMultiEnd2 () {
|
566 | if (this.char === '"') {
|
567 | return this.return()
|
568 | } else {
|
569 | this.state.buf += '""'
|
570 | return this.goto(this.parseMultiStringContent)
|
571 | }
|
572 | }
|
573 | parseMultiEscape () {
|
574 | if (this.char === '\r' || this.char === '\n') {
|
575 | return this.next(this.parseMultiTrim)
|
576 | } else {
|
577 | return this.goto(this.parseEscape)
|
578 | }
|
579 | }
|
580 | parseMultiTrim () {
|
581 |
|
582 | if (this.char === '\n' || this.char === ' ' || this.char === '\t' || this.char === '\r') {
|
583 | return null
|
584 | } else {
|
585 | return this.returnNow()
|
586 | }
|
587 | }
|
588 | parseEscape () {
|
589 | if (this.char in escapes) {
|
590 | return this.return(escapes[this.char])
|
591 | } else if (this.char === 'u') {
|
592 | return this.call(this.parseSmallUnicode, this.parseUnicodeReturn)
|
593 | } else if (this.char === 'U') {
|
594 | return this.call(this.parseLargeUnicode, this.parseUnicodeReturn)
|
595 | } else {
|
596 | throw this.error(new TomlError('Unknown escape character: ' + this.char))
|
597 | }
|
598 | }
|
599 | parseUnicodeReturn (char) {
|
600 | try {
|
601 | const codePoint = parseInt(char, 16)
|
602 | if (codePoint >= 0xD800 && codePoint <= 0xDFFF) {
|
603 | throw this.error(new TomlError('Invalid unicode, character in range 0xD800 - 0xDFFF is reserved'))
|
604 | }
|
605 | return this.returnNow(String.fromCodePoint(codePoint))
|
606 | } catch (ex) {
|
607 | throw this.error(TomlError.wrap(ex))
|
608 | }
|
609 | }
|
610 | parseSmallUnicode () {
|
611 | if (!isHexit(this.char)) {
|
612 | throw this.error(new TomlError('Invalid character in unicode sequence, expected hex'))
|
613 | } else {
|
614 | this.consume()
|
615 | if (this.state.buf.length >= 4) return this.return()
|
616 | }
|
617 | }
|
618 | parseLargeUnicode () {
|
619 | if (!isHexit(this.char)) {
|
620 | throw this.error(new TomlError('Invalid character in unicode sequence, expected hex'))
|
621 | } else {
|
622 | this.consume()
|
623 | if (this.state.buf.length >= 8) return this.return()
|
624 | }
|
625 | }
|
626 |
|
627 |
|
628 | parseNumberSign () {
|
629 | this.consume()
|
630 | return this.next(this.parseNumberIntegerNoUnder)
|
631 | }
|
632 | parseNumberIntegerNoUnder () {
|
633 | if (this.char === '_') {
|
634 | throw this.error(new TomlError('Unexpected character, expected digit, period (.), exponent marker(e) or whitespace'))
|
635 | } else if (this.atEndOfWord() || (this.char === 'E' || this.char === 'e' || this.char === '.')) {
|
636 | throw this.error(new TomlError('Underscores can only appear between digits'))
|
637 | }
|
638 | return this.goto(this.parseNumberInteger)
|
639 | }
|
640 | parseNumberInteger () {
|
641 | if (isDigit(this.char)) {
|
642 | this.consume()
|
643 | } else if (this.char === '_') {
|
644 | return this.next(this.parseNumberIntegerNoUnder)
|
645 | } else if (this.char === 'E' || this.char === 'e') {
|
646 | this.consume()
|
647 | return this.next(this.parseNumberExponentSign)
|
648 | } else if (this.char === '.') {
|
649 | this.consume()
|
650 | return this.next(this.parseNumberFloatNoUnder)
|
651 | } else {
|
652 | const result = Integer(this.state.buf)
|
653 |
|
654 | if (isNaN(result)) {
|
655 | throw this.error(new TomlError('Invalid number'))
|
656 | } else {
|
657 | return this.returnNow(result)
|
658 | }
|
659 | }
|
660 | }
|
661 | parseNumberFloatNoUnder () {
|
662 | if (this.char === '_') {
|
663 | throw this.error(new TomlError('Unexpected character, expected digit, exponent marker(e) or whitespace'))
|
664 | } else if (this.atEndOfWord() || this.char === 'E' || this.char === 'e') {
|
665 | throw this.error(new TomlError('Incomplete number'))
|
666 | }
|
667 | return this.goto(this.parseNumberFloat)
|
668 | }
|
669 | parseNumberFloat () {
|
670 | if (this.char === '_') {
|
671 | return this.next(this.parseNumberFloatNoUnder)
|
672 | } else if (isDigit(this.char)) {
|
673 | this.consume()
|
674 | } else if (this.char === 'E' || this.char === 'e') {
|
675 | this.consume()
|
676 | return this.next(this.parseNumberExponentSign)
|
677 | } else {
|
678 | return this.returnNow(Float(this.state.buf))
|
679 | }
|
680 | }
|
681 | parseNumberExponentSign () {
|
682 | if (isDigit(this.char)) {
|
683 | return this.goto(this.parseNumberExponent)
|
684 | } else if (this.char === '-' || this.char === '+') {
|
685 | this.consume()
|
686 | this.next(this.parseNumberExponentNoUnder)
|
687 | } else {
|
688 | throw this.error(new TomlError('Unexpected character, expected -, + or digit'))
|
689 | }
|
690 | }
|
691 | parseNumberExponentNoUnder () {
|
692 | if (this.char === '_') {
|
693 | throw this.error(new TomlError('Unexpected character, expected digit or whitespace'))
|
694 | } else if (this.atEndOfWord()) {
|
695 | throw this.error(new TomlError('Incomplete number'))
|
696 | }
|
697 | return this.goto(this.parseNumberExponent)
|
698 | }
|
699 | parseNumberExponent () {
|
700 | if (isDigit(this.char)) {
|
701 | this.consume()
|
702 | } else if (this.char === '_') {
|
703 | return this.next(this.parseNumberExponentNoUnder)
|
704 | } else {
|
705 | return this.returnNow(Float(this.state.buf))
|
706 | }
|
707 | }
|
708 |
|
709 |
|
710 | parseNumberOrDateTime () {
|
711 | if (this.char === '_') {
|
712 | this.next(this.parseNumberIntegerNoUnder)
|
713 | } else if (isDigit(this.char)) {
|
714 | this.consume()
|
715 | if (this.state.buf.length > 4) this.next(this.parseNumberInteger)
|
716 | } else if (this.char === 'E' || this.char === 'e') {
|
717 | this.consume()
|
718 | return this.next(this.parseNumberExponentSign)
|
719 | } else if (this.char === '.') {
|
720 | this.consume()
|
721 | return this.next(this.parseNumberFloatNoUnder)
|
722 | } else if (this.char === '-') {
|
723 | return this.goto(this.parseDateTime)
|
724 | } else {
|
725 | return this.returnNow(Integer(this.state.buf))
|
726 | }
|
727 | }
|
728 |
|
729 |
|
730 | parseDateTime () {
|
731 |
|
732 | if (this.state.buf.length < 4) {
|
733 | throw this.error(new TomlError('Years less than 1000 must be zero padded to four characters'))
|
734 | }
|
735 | this.state.result = this.state.buf
|
736 | this.state.buf = ''
|
737 | return this.next(this.parseDateMonth)
|
738 | }
|
739 | parseDateMonth () {
|
740 | if (this.char === '-') {
|
741 | if (this.state.buf.length < 2) {
|
742 | throw this.error(new TomlError('Months less than 10 must be zero padded to two characters'))
|
743 | }
|
744 | this.state.result += '-' + this.state.buf
|
745 | this.state.buf = ''
|
746 | return this.next(this.parseDateDay)
|
747 | } else if (isDigit(this.char)) {
|
748 | this.consume()
|
749 | } else {
|
750 | throw this.error(new TomlError('Incomplete datetime'))
|
751 | }
|
752 | }
|
753 | parseDateDay () {
|
754 | if (this.char === 'T') {
|
755 | if (this.state.buf.length < 2) {
|
756 | throw this.error(new TomlError('Days less than 10 must be zero padded to two characters'))
|
757 | }
|
758 | this.state.result += '-' + this.state.buf
|
759 | this.state.buf = ''
|
760 | return this.next(this.parseTimeHour)
|
761 | } else if (isDigit(this.char)) {
|
762 | this.consume()
|
763 | } else {
|
764 | throw this.error(new TomlError('Incomplete datetime'))
|
765 | }
|
766 | }
|
767 | parseTimeHour () {
|
768 | if (this.char === ':') {
|
769 | if (this.state.buf.length < 2) {
|
770 | throw this.error(new TomlError('Hours less than 10 must be zero padded to two characters'))
|
771 | }
|
772 | this.state.result += 'T' + this.state.buf
|
773 | this.state.buf = ''
|
774 | return this.next(this.parseTimeMin)
|
775 | } else if (isDigit(this.char)) {
|
776 | this.consume()
|
777 | } else {
|
778 | throw this.error(new TomlError('Incomplete datetime'))
|
779 | }
|
780 | }
|
781 | parseTimeMin () {
|
782 | if (this.state.buf.length < 2 && isDigit(this.char)) {
|
783 | this.consume()
|
784 | } else if (this.state.buf.length === 2 && this.char === ':') {
|
785 | this.state.result += ':' + this.state.buf
|
786 | this.state.buf = ''
|
787 | return this.next(this.parseTimeSec)
|
788 | } else {
|
789 | throw this.error(new TomlError('Incomplete datetime'))
|
790 | }
|
791 | }
|
792 | parseTimeSec () {
|
793 | if (isDigit(this.char)) {
|
794 | this.consume()
|
795 | if (this.state.buf.length === 2) {
|
796 | this.state.result += ':' + this.state.buf
|
797 | this.state.buf = ''
|
798 | return this.next(this.parseTimeZoneOrFraction)
|
799 | }
|
800 | } else {
|
801 | throw this.error(new TomlError('Incomplete datetime'))
|
802 | }
|
803 | }
|
804 |
|
805 | parseTimeZoneOrFraction () {
|
806 | if (this.char === '.') {
|
807 | this.consume()
|
808 | this.next(this.parseDateTimeFraction)
|
809 | } else if (this.char === '-' || this.char === '+') {
|
810 | this.consume()
|
811 | this.next(this.parseTimeZoneHour)
|
812 | } else if (this.char === 'Z') {
|
813 | this.consume()
|
814 | return this.return(createDateTime(this.state.result + this.state.buf))
|
815 | } else {
|
816 | throw this.error(new TomlError('Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z'))
|
817 | }
|
818 | }
|
819 | parseDateTimeFraction () {
|
820 | if (isDigit(this.char)) {
|
821 | this.consume()
|
822 | } else if (this.state.buf.length === 1) {
|
823 | throw this.error(new TomlError('Expected digit in milliseconds'))
|
824 | } else if (this.char === '-' || this.char === '+') {
|
825 | this.consume()
|
826 | this.next(this.parseTimeZoneHour)
|
827 | } else if (this.char === 'Z') {
|
828 | this.consume()
|
829 | return this.return(createDateTime(this.state.result + this.state.buf))
|
830 | } else {
|
831 | throw this.error(new TomlError('Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z'))
|
832 | }
|
833 | }
|
834 | parseTimeZoneHour () {
|
835 | if (isDigit(this.char)) {
|
836 | this.consume()
|
837 |
|
838 | if (/\d\d$/.test(this.state.buf)) return this.next(this.parseTimeZoneSep)
|
839 | } else {
|
840 | throw this.error(new TomlError('Unexpected character in datetime, expected digit'))
|
841 | }
|
842 | }
|
843 | parseTimeZoneSep () {
|
844 | if (this.char === ':') {
|
845 | this.consume()
|
846 | this.next(this.parseTimeZoneMin)
|
847 | } else {
|
848 | throw this.error(new TomlError('Unexpected character in datetime, expected colon'))
|
849 | }
|
850 | }
|
851 | parseTimeZoneMin () {
|
852 | if (isDigit(this.char)) {
|
853 | this.consume()
|
854 | if (/\d\d$/.test(this.state.buf)) return this.return(createDateTime(this.state.result + this.state.buf))
|
855 | } else {
|
856 | throw this.error(new TomlError('Unexpected character in datetime, expected digit'))
|
857 | }
|
858 | }
|
859 |
|
860 |
|
861 | parseBoolean () {
|
862 |
|
863 | if (this.char === 't') {
|
864 | return this.goto(this.parseTrue)
|
865 | } else if (this.char === 'f') {
|
866 | return this.goto(this.parseFalse)
|
867 | }
|
868 | }
|
869 | parseTrue () {
|
870 | if (this.atEndOfWord()) {
|
871 | throw this.error(new TomlError('Incomplete boolean'))
|
872 | } else if ('true'.indexOf(this.state.buf + this.char) !== 0) {
|
873 | throw this.error(new TomlError('Invalid boolean, expected true or false'))
|
874 | } else if (this.state.buf.length === 3) {
|
875 | return this.return(true)
|
876 | } else {
|
877 | this.consume()
|
878 | }
|
879 | }
|
880 | parseFalse () {
|
881 | if (this.atEndOfWord()) {
|
882 | throw this.error(new TomlError('Incomplete boolean'))
|
883 | } else if ('false'.indexOf(this.state.buf + this.char) !== 0) {
|
884 | throw this.error(new TomlError('Invalid boolean, expected true or false'))
|
885 | } else if (this.state.buf.length === 4) {
|
886 | return this.return(false)
|
887 | } else {
|
888 | this.consume()
|
889 | }
|
890 | }
|
891 |
|
892 |
|
893 | parseInlineList () {
|
894 | if (this.char === ' ' || this.char === '\t' || this.char === '\r' || this.char === '\n') {
|
895 | return null
|
896 | } else if (this.char === Parser.END) {
|
897 | throw this.error(new TomlError('Unterminated inline array'))
|
898 | } else if (this.char === '#') {
|
899 | return this.call(this.parseComment)
|
900 | } else if (this.char === ']') {
|
901 | return this.return(this.state.resultArr || InlineList())
|
902 | } else {
|
903 | return this.callNow(this.parseValue, this.recordInlineListValue)
|
904 | }
|
905 | }
|
906 | recordInlineListValue (value) {
|
907 | if (this.state.resultArr) {
|
908 | const listType = this.state.resultArr[_contentType]
|
909 | const valueType = tomlType(value)
|
910 | if (listType !== valueType) {
|
911 | throw this.error(new TomlError(`Inline lists must be a single type, not a mix of ${listType} and ${valueType}`))
|
912 | }
|
913 | } else {
|
914 | this.state.resultArr = InlineList(tomlType(value))
|
915 | }
|
916 | if (isFloat(value) || isInteger(value)) {
|
917 |
|
918 | this.state.resultArr.push(value.valueOf())
|
919 | } else {
|
920 | this.state.resultArr.push(value)
|
921 | }
|
922 | return this.goto(this.parseInlineListNext)
|
923 | }
|
924 | parseInlineListNext () {
|
925 | if (this.char === ' ' || this.char === '\t' || this.char === '\r' || this.char === '\n') {
|
926 | return null
|
927 | } else if (this.char === '#') {
|
928 | return this.call(this.parseComment)
|
929 | } else if (this.char === ',') {
|
930 | return this.next(this.parseInlineList)
|
931 | } else if (this.char === ']') {
|
932 | return this.goto(this.parseInlineList)
|
933 | } else {
|
934 | throw this.error(new TomlError('Invalid character, expected whitespace, comma (,) or close bracket (])'))
|
935 | }
|
936 | }
|
937 |
|
938 |
|
939 | parseInlineTable () {
|
940 | if (this.char === ' ' || this.char === '\t') {
|
941 | return null
|
942 | } else if (this.char === Parser.END || this.char === '#' || this.char === '\n' || this.char === '\r') {
|
943 | throw this.error(new TomlError('Unterminated inline array'))
|
944 | } else if (this.char === '}') {
|
945 | return this.return(this.state.resultTable || InlineTable())
|
946 | } else {
|
947 | if (!this.state.resultTable) this.state.resultTable = InlineTable()
|
948 | return this.callNow(this.parseAssign, this.recordInlineTableValue)
|
949 | }
|
950 | }
|
951 | recordInlineTableValue (kv) {
|
952 | if (isInteger(kv.value) || isFloat(kv.value)) {
|
953 | this.state.resultTable[kv.key] = kv.value.valueOf()
|
954 | } else {
|
955 | this.state.resultTable[kv.key] = kv.value
|
956 | }
|
957 | return this.goto(this.parseInlineTableNext)
|
958 | }
|
959 | parseInlineTableNext () {
|
960 | if (this.char === ' ' || this.char === '\t') {
|
961 | return null
|
962 | } else if (this.char === Parser.END || this.char === '#' || this.char === '\n' || this.char === '\r') {
|
963 | throw this.error(new TomlError('Unterminated inline array'))
|
964 | } else if (this.char === ',') {
|
965 | return this.next(this.parseInlineTable)
|
966 | } else if (this.char === '}') {
|
967 | return this.goto(this.parseInlineTable)
|
968 | } else {
|
969 | throw this.error(new TomlError('Invalid character, expected whitespace, comma (,) or close bracket (])'))
|
970 | }
|
971 | }
|
972 | }
|
973 | return TOMLParser
|
974 | }
|