UNPKG

32.7 kBJavaScriptView Raw
1'use strict'
2/* eslint-disable no-new-wrappers, no-eval */
3module.exports = makeParserClass(require('./parser.js'))
4module.exports.makeParserClass = makeParserClass
5
6class TomlError extends Error {
7 constructor (msg) {
8 super(msg)
9 /* istanbul ignore next */
10 if (Error.captureStackTrace) Error.captureStackTrace(this, TomlError)
11 this.fromTOML = true
12 this.wrapped = null
13 }
14}
15TomlError.wrap = err => {
16 const terr = new TomlError(err.message)
17 terr.code = err.code
18 terr.wrapped = err
19 return terr
20}
21module.exports.TomlError = TomlError
22
23const createDateTime = require('./create-datetime.js')
24
25const escapes = {
26 'b': '\x08',
27 't': '\x09',
28 'n': '\x0a',
29 'f': '\x0c',
30 'r': '\x0d',
31 '"': '\x22',
32 '\\': '\x5c'
33}
34
35function 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}
40function isHexit (char) {
41 /* istanbul ignore next */
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}
46function isAlphaNumQuoteHyphen (char) {
47 /* istanbul ignore next */
48 if (typeof char !== 'string') return false
49 const cp = char.codePointAt(0)
50 // A-Za-z0-9'"_-
51 return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122) || (cp >= 48 && cp <= 57) || cp === 39 || cp === 34 || cp === 95 || cp === 45
52}
53function isAlphaNumHyphen (char) {
54 /* istanbul ignore next */
55 if (typeof char !== 'string') return false
56 const cp = char.codePointAt(0)
57 // A-Za-z0-9_-
58 return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122) || (cp >= 48 && cp <= 57) || cp === 95 || cp === 45
59}
60const _type = Symbol('type')
61const _declared = Symbol('declared')
62
63const INLINE_TABLE = Symbol('inline-table')
64function InlineTable () {
65 return Object.defineProperties({}, {
66 [_type]: {value: INLINE_TABLE}
67 })
68}
69function isInlineTable (obj) {
70 if (obj === null || typeof (obj) !== 'object') return false
71 return obj[_type] === INLINE_TABLE
72}
73
74const TABLE = Symbol('table')
75function Table () {
76 return Object.defineProperties({}, {
77 [_type]: {value: TABLE},
78 [_declared]: {value: false, writable: true}
79 })
80}
81function isTable (obj) {
82 if (obj === null || typeof (obj) !== 'object') return false
83 return obj[_type] === TABLE
84}
85
86const _contentType = Symbol('content-type')
87const INLINE_LIST = Symbol('inline-list')
88function InlineList (type) {
89 return Object.defineProperties([], {
90 [_type]: {value: INLINE_LIST},
91 [_contentType]: {value: type}
92 })
93}
94function isInlineList (obj) {
95 if (obj === null || typeof (obj) !== 'object') return false
96 return obj[_type] === INLINE_LIST
97}
98
99const LIST = Symbol('list')
100function List () {
101 return Object.defineProperties([], {
102 [_type]: {value: LIST}
103 })
104}
105function isList (obj) {
106 if (obj === null || typeof (obj) !== 'object') return false
107 return obj[_type] === LIST
108}
109
110// in an eval, to let bundlers not slurp in a util proxy
111const utilInspect = eval(`require('util').inspect`)
112/* istanbul ignore next */
113const _inspect = (utilInspect && utilInspect.custom) || 'inspect'
114
115const INTEGER = Symbol('integer')
116function Integer (value) {
117 return Object.defineProperties(new Number(value), {
118 [_type]: {value: INTEGER},
119 [_inspect]: {value: () => `[Integer: ${value}]`}
120 })
121}
122function isInteger (obj) {
123 if (obj === null || typeof (obj) !== 'object') return false
124 return obj[_type] === INTEGER
125}
126
127const FLOAT = Symbol('float')
128function Float (value) {
129 return Object.defineProperties(new Number(value), {
130 [_type]: {value: FLOAT},
131 [_inspect]: {value: () => `[Float: ${value}]`}
132 })
133}
134function isFloat (obj) {
135 if (obj === null || typeof (obj) !== 'object') return false
136 return obj[_type] === FLOAT
137}
138
139function tomlType (value) {
140 const type = typeof value
141 if (type === 'object') {
142 /* istanbul ignore if */
143 if (value === null) return 'null'
144 if (value instanceof Date) return 'datetime'
145 /* istanbul ignore else */
146 if (_type in value) {
147 switch (value[_type]) {
148 case INLINE_TABLE: return 'inline-table'
149 case INLINE_LIST: return 'inline-list'
150 /* istanbul ignore next */
151 case TABLE: return 'table'
152 /* istanbul ignore next */
153 case LIST: return 'list'
154 case FLOAT: return 'float'
155 case INTEGER: return 'integer'
156 }
157 }
158 }
159 return type
160}
161
162function makeParserClass (Parser) {
163 class TOMLParser extends Parser {
164 constructor () {
165 super()
166 this.ctx = this.obj = Table()
167 }
168
169 /* MATCH HELPER */
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 // HELPER, this strips any whitespace and comments to the end of the line
194 // then RETURNS. Last state in a production.
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 /* ASSIGNMENT: key = value */
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 // unbox our numbers
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 /* ASSSIGNMENT expression, key = value possibly inside an inline table */
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 /* COMMENTS: #...eol */
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 /* TABLES AND LISTS, [foo] and [[foo]] */
262 parseTableOrList () {
263 if (this.char === '[') {
264 this.next(this.parseList)
265 } else {
266 return this.goto(this.parseTable)
267 }
268 }
269
270 /* TABLE [foo.bar.baz] */
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 /* LIST [[a.b.c]] */
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 /* VALUE string, number, boolean, inline list, inline object */
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 /* KEYS, barewords or double quoted single line strings */
392 parseKeyword () {
393 if (this.char === '"') {
394 return this.next(this.parseBasicOnlyString)
395 } else {
396 return this.goto(this.parseBareKey)
397 }
398 }
399
400 /* KEYS: barewords */
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 /* STRINGS, single quoted (literal) */
416 parseLiteralString () {
417 do {
418 if (this.char === "'") {
419 // FIXME: this allows '' anywhere, should only be at start
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 /* STRINGS double quoted */
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 // used for keys
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 // explicitly whitespace here, END should follow the same path as chars
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 /* NUMBERS */
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 /* istanbul ignore if */
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 /* NUMBERS or DATETIMES */
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 /* DATETIME */
730 parseDateTime () {
731 // we enter here having just consumed the year and about to consume the hyphen
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 // FIXME: No more regexps
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 /* BOOLEAN */
861 parseBoolean () {
862 /* istanbul ignore else */
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 /* INLINE LISTS */
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 // unbox now that we've verified they're ok
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 /* INLINE TABLE */
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}