trouble-in-terror-town/node_modules/@iarna/toml/lib/toml-parser.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

1379 lines
44 KiB
JavaScript

'use strict'
/* eslint-disable no-new-wrappers, no-eval, camelcase, operator-linebreak */
module.exports = makeParserClass(require('./parser.js'))
module.exports.makeParserClass = makeParserClass
class TomlError extends Error {
constructor (msg) {
super(msg)
this.name = 'TomlError'
/* istanbul ignore next */
if (Error.captureStackTrace) Error.captureStackTrace(this, TomlError)
this.fromTOML = true
this.wrapped = null
}
}
TomlError.wrap = err => {
const terr = new TomlError(err.message)
terr.code = err.code
terr.wrapped = err
return terr
}
module.exports.TomlError = TomlError
const createDateTime = require('./create-datetime.js')
const createDateTimeFloat = require('./create-datetime-float.js')
const createDate = require('./create-date.js')
const createTime = require('./create-time.js')
const CTRL_I = 0x09
const CTRL_J = 0x0A
const CTRL_M = 0x0D
const CTRL_CHAR_BOUNDARY = 0x1F // the last non-character in the latin1 region of unicode, except DEL
const CHAR_SP = 0x20
const CHAR_QUOT = 0x22
const CHAR_NUM = 0x23
const CHAR_APOS = 0x27
const CHAR_PLUS = 0x2B
const CHAR_COMMA = 0x2C
const CHAR_HYPHEN = 0x2D
const CHAR_PERIOD = 0x2E
const CHAR_0 = 0x30
const CHAR_1 = 0x31
const CHAR_7 = 0x37
const CHAR_9 = 0x39
const CHAR_COLON = 0x3A
const CHAR_EQUALS = 0x3D
const CHAR_A = 0x41
const CHAR_E = 0x45
const CHAR_F = 0x46
const CHAR_T = 0x54
const CHAR_U = 0x55
const CHAR_Z = 0x5A
const CHAR_LOWBAR = 0x5F
const CHAR_a = 0x61
const CHAR_b = 0x62
const CHAR_e = 0x65
const CHAR_f = 0x66
const CHAR_i = 0x69
const CHAR_l = 0x6C
const CHAR_n = 0x6E
const CHAR_o = 0x6F
const CHAR_r = 0x72
const CHAR_s = 0x73
const CHAR_t = 0x74
const CHAR_u = 0x75
const CHAR_x = 0x78
const CHAR_z = 0x7A
const CHAR_LCUB = 0x7B
const CHAR_RCUB = 0x7D
const CHAR_LSQB = 0x5B
const CHAR_BSOL = 0x5C
const CHAR_RSQB = 0x5D
const CHAR_DEL = 0x7F
const SURROGATE_FIRST = 0xD800
const SURROGATE_LAST = 0xDFFF
const escapes = {
[CHAR_b]: '\u0008',
[CHAR_t]: '\u0009',
[CHAR_n]: '\u000A',
[CHAR_f]: '\u000C',
[CHAR_r]: '\u000D',
[CHAR_QUOT]: '\u0022',
[CHAR_BSOL]: '\u005C'
}
function isDigit (cp) {
return cp >= CHAR_0 && cp <= CHAR_9
}
function isHexit (cp) {
return (cp >= CHAR_A && cp <= CHAR_F) || (cp >= CHAR_a && cp <= CHAR_f) || (cp >= CHAR_0 && cp <= CHAR_9)
}
function isBit (cp) {
return cp === CHAR_1 || cp === CHAR_0
}
function isOctit (cp) {
return (cp >= CHAR_0 && cp <= CHAR_7)
}
function isAlphaNumQuoteHyphen (cp) {
return (cp >= CHAR_A && cp <= CHAR_Z)
|| (cp >= CHAR_a && cp <= CHAR_z)
|| (cp >= CHAR_0 && cp <= CHAR_9)
|| cp === CHAR_APOS
|| cp === CHAR_QUOT
|| cp === CHAR_LOWBAR
|| cp === CHAR_HYPHEN
}
function isAlphaNumHyphen (cp) {
return (cp >= CHAR_A && cp <= CHAR_Z)
|| (cp >= CHAR_a && cp <= CHAR_z)
|| (cp >= CHAR_0 && cp <= CHAR_9)
|| cp === CHAR_LOWBAR
|| cp === CHAR_HYPHEN
}
const _type = Symbol('type')
const _declared = Symbol('declared')
const hasOwnProperty = Object.prototype.hasOwnProperty
const defineProperty = Object.defineProperty
const descriptor = {configurable: true, enumerable: true, writable: true, value: undefined}
function hasKey (obj, key) {
if (hasOwnProperty.call(obj, key)) return true
if (key === '__proto__') defineProperty(obj, '__proto__', descriptor)
return false
}
const INLINE_TABLE = Symbol('inline-table')
function InlineTable () {
return Object.defineProperties({}, {
[_type]: {value: INLINE_TABLE}
})
}
function isInlineTable (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === INLINE_TABLE
}
const TABLE = Symbol('table')
function Table () {
return Object.defineProperties({}, {
[_type]: {value: TABLE},
[_declared]: {value: false, writable: true}
})
}
function isTable (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === TABLE
}
const _contentType = Symbol('content-type')
const INLINE_LIST = Symbol('inline-list')
function InlineList (type) {
return Object.defineProperties([], {
[_type]: {value: INLINE_LIST},
[_contentType]: {value: type}
})
}
function isInlineList (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === INLINE_LIST
}
const LIST = Symbol('list')
function List () {
return Object.defineProperties([], {
[_type]: {value: LIST}
})
}
function isList (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === LIST
}
// in an eval, to let bundlers not slurp in a util proxy
let _custom
try {
const utilInspect = eval("require('util').inspect")
_custom = utilInspect.custom
} catch (_) {
/* eval require not available in transpiled bundle */
}
/* istanbul ignore next */
const _inspect = _custom || 'inspect'
class BoxedBigInt {
constructor (value) {
try {
this.value = global.BigInt.asIntN(64, value)
} catch (_) {
/* istanbul ignore next */
this.value = null
}
Object.defineProperty(this, _type, {value: INTEGER})
}
isNaN () {
return this.value === null
}
/* istanbul ignore next */
toString () {
return String(this.value)
}
/* istanbul ignore next */
[_inspect] () {
return `[BigInt: ${this.toString()}]}`
}
valueOf () {
return this.value
}
}
const INTEGER = Symbol('integer')
function Integer (value) {
let num = Number(value)
// -0 is a float thing, not an int thing
if (Object.is(num, -0)) num = 0
/* istanbul ignore else */
if (global.BigInt && !Number.isSafeInteger(num)) {
return new BoxedBigInt(value)
} else {
/* istanbul ignore next */
return Object.defineProperties(new Number(num), {
isNaN: {value: function () { return isNaN(this) }},
[_type]: {value: INTEGER},
[_inspect]: {value: () => `[Integer: ${value}]`}
})
}
}
function isInteger (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === INTEGER
}
const FLOAT = Symbol('float')
function Float (value) {
/* istanbul ignore next */
return Object.defineProperties(new Number(value), {
[_type]: {value: FLOAT},
[_inspect]: {value: () => `[Float: ${value}]`}
})
}
function isFloat (obj) {
if (obj === null || typeof (obj) !== 'object') return false
return obj[_type] === FLOAT
}
function tomlType (value) {
const type = typeof value
if (type === 'object') {
/* istanbul ignore if */
if (value === null) return 'null'
if (value instanceof Date) return 'datetime'
/* istanbul ignore else */
if (_type in value) {
switch (value[_type]) {
case INLINE_TABLE: return 'inline-table'
case INLINE_LIST: return 'inline-list'
/* istanbul ignore next */
case TABLE: return 'table'
/* istanbul ignore next */
case LIST: return 'list'
case FLOAT: return 'float'
case INTEGER: return 'integer'
}
}
}
return type
}
function makeParserClass (Parser) {
class TOMLParser extends Parser {
constructor () {
super()
this.ctx = this.obj = Table()
}
/* MATCH HELPER */
atEndOfWord () {
return this.char === CHAR_NUM || this.char === CTRL_I || this.char === CHAR_SP || this.atEndOfLine()
}
atEndOfLine () {
return this.char === Parser.END || this.char === CTRL_J || this.char === CTRL_M
}
parseStart () {
if (this.char === Parser.END) {
return null
} else if (this.char === CHAR_LSQB) {
return this.call(this.parseTableOrList)
} else if (this.char === CHAR_NUM) {
return this.call(this.parseComment)
} else if (this.char === CTRL_J || this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
return null
} else if (isAlphaNumQuoteHyphen(this.char)) {
return this.callNow(this.parseAssignStatement)
} else {
throw this.error(new TomlError(`Unknown character "${this.char}"`))
}
}
// HELPER, this strips any whitespace and comments to the end of the line
// then RETURNS. Last state in a production.
parseWhitespaceToEOL () {
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
return null
} else if (this.char === CHAR_NUM) {
return this.goto(this.parseComment)
} else if (this.char === Parser.END || this.char === CTRL_J) {
return this.return()
} else {
throw this.error(new TomlError('Unexpected character, expected only whitespace or comments till end of line'))
}
}
/* ASSIGNMENT: key = value */
parseAssignStatement () {
return this.callNow(this.parseAssign, this.recordAssignStatement)
}
recordAssignStatement (kv) {
let target = this.ctx
let finalKey = kv.key.pop()
for (let kw of kv.key) {
if (hasKey(target, kw) && (!isTable(target[kw]) || target[kw][_declared])) {
throw this.error(new TomlError("Can't redefine existing key"))
}
target = target[kw] = target[kw] || Table()
}
if (hasKey(target, finalKey)) {
throw this.error(new TomlError("Can't redefine existing key"))
}
// unbox our numbers
if (isInteger(kv.value) || isFloat(kv.value)) {
target[finalKey] = kv.value.valueOf()
} else {
target[finalKey] = kv.value
}
return this.goto(this.parseWhitespaceToEOL)
}
/* ASSSIGNMENT expression, key = value possibly inside an inline table */
parseAssign () {
return this.callNow(this.parseKeyword, this.recordAssignKeyword)
}
recordAssignKeyword (key) {
if (this.state.resultTable) {
this.state.resultTable.push(key)
} else {
this.state.resultTable = [key]
}
return this.goto(this.parseAssignKeywordPreDot)
}
parseAssignKeywordPreDot () {
if (this.char === CHAR_PERIOD) {
return this.next(this.parseAssignKeywordPostDot)
} else if (this.char !== CHAR_SP && this.char !== CTRL_I) {
return this.goto(this.parseAssignEqual)
}
}
parseAssignKeywordPostDot () {
if (this.char !== CHAR_SP && this.char !== CTRL_I) {
return this.callNow(this.parseKeyword, this.recordAssignKeyword)
}
}
parseAssignEqual () {
if (this.char === CHAR_EQUALS) {
return this.next(this.parseAssignPreValue)
} else {
throw this.error(new TomlError('Invalid character, expected "="'))
}
}
parseAssignPreValue () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else {
return this.callNow(this.parseValue, this.recordAssignValue)
}
}
recordAssignValue (value) {
return this.returnNow({key: this.state.resultTable, value: value})
}
/* COMMENTS: #...eol */
parseComment () {
do {
if (this.char === Parser.END || this.char === CTRL_J) {
return this.return()
}
} while (this.nextChar())
}
/* TABLES AND LISTS, [foo] and [[foo]] */
parseTableOrList () {
if (this.char === CHAR_LSQB) {
this.next(this.parseList)
} else {
return this.goto(this.parseTable)
}
}
/* TABLE [foo.bar.baz] */
parseTable () {
this.ctx = this.obj
return this.goto(this.parseTableNext)
}
parseTableNext () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else {
return this.callNow(this.parseKeyword, this.parseTableMore)
}
}
parseTableMore (keyword) {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else if (this.char === CHAR_RSQB) {
if (hasKey(this.ctx, keyword) && (!isTable(this.ctx[keyword]) || this.ctx[keyword][_declared])) {
throw this.error(new TomlError("Can't redefine existing key"))
} else {
this.ctx = this.ctx[keyword] = this.ctx[keyword] || Table()
this.ctx[_declared] = true
}
return this.next(this.parseWhitespaceToEOL)
} else if (this.char === CHAR_PERIOD) {
if (!hasKey(this.ctx, keyword)) {
this.ctx = this.ctx[keyword] = Table()
} else if (isTable(this.ctx[keyword])) {
this.ctx = this.ctx[keyword]
} else if (isList(this.ctx[keyword])) {
this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1]
} else {
throw this.error(new TomlError("Can't redefine existing key"))
}
return this.next(this.parseTableNext)
} else {
throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
}
}
/* LIST [[a.b.c]] */
parseList () {
this.ctx = this.obj
return this.goto(this.parseListNext)
}
parseListNext () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else {
return this.callNow(this.parseKeyword, this.parseListMore)
}
}
parseListMore (keyword) {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else if (this.char === CHAR_RSQB) {
if (!hasKey(this.ctx, keyword)) {
this.ctx[keyword] = List()
}
if (isInlineList(this.ctx[keyword])) {
throw this.error(new TomlError("Can't extend an inline array"))
} else if (isList(this.ctx[keyword])) {
const next = Table()
this.ctx[keyword].push(next)
this.ctx = next
} else {
throw this.error(new TomlError("Can't redefine an existing key"))
}
return this.next(this.parseListEnd)
} else if (this.char === CHAR_PERIOD) {
if (!hasKey(this.ctx, keyword)) {
this.ctx = this.ctx[keyword] = Table()
} else if (isInlineList(this.ctx[keyword])) {
throw this.error(new TomlError("Can't extend an inline array"))
} else if (isInlineTable(this.ctx[keyword])) {
throw this.error(new TomlError("Can't extend an inline table"))
} else if (isList(this.ctx[keyword])) {
this.ctx = this.ctx[keyword][this.ctx[keyword].length - 1]
} else if (isTable(this.ctx[keyword])) {
this.ctx = this.ctx[keyword]
} else {
throw this.error(new TomlError("Can't redefine an existing key"))
}
return this.next(this.parseListNext)
} else {
throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
}
}
parseListEnd (keyword) {
if (this.char === CHAR_RSQB) {
return this.next(this.parseWhitespaceToEOL)
} else {
throw this.error(new TomlError('Unexpected character, expected whitespace, . or ]'))
}
}
/* VALUE string, number, boolean, inline list, inline object */
parseValue () {
if (this.char === Parser.END) {
throw this.error(new TomlError('Key without value'))
} else if (this.char === CHAR_QUOT) {
return this.next(this.parseDoubleString)
} if (this.char === CHAR_APOS) {
return this.next(this.parseSingleString)
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
return this.goto(this.parseNumberSign)
} else if (this.char === CHAR_i) {
return this.next(this.parseInf)
} else if (this.char === CHAR_n) {
return this.next(this.parseNan)
} else if (isDigit(this.char)) {
return this.goto(this.parseNumberOrDateTime)
} else if (this.char === CHAR_t || this.char === CHAR_f) {
return this.goto(this.parseBoolean)
} else if (this.char === CHAR_LSQB) {
return this.call(this.parseInlineList, this.recordValue)
} else if (this.char === CHAR_LCUB) {
return this.call(this.parseInlineTable, this.recordValue)
} else {
throw this.error(new TomlError('Unexpected character, expecting string, number, datetime, boolean, inline array or inline table'))
}
}
recordValue (value) {
return this.returnNow(value)
}
parseInf () {
if (this.char === CHAR_n) {
return this.next(this.parseInf2)
} else {
throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))
}
}
parseInf2 () {
if (this.char === CHAR_f) {
if (this.state.buf === '-') {
return this.return(-Infinity)
} else {
return this.return(Infinity)
}
} else {
throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))
}
}
parseNan () {
if (this.char === CHAR_a) {
return this.next(this.parseNan2)
} else {
throw this.error(new TomlError('Unexpected character, expected "nan"'))
}
}
parseNan2 () {
if (this.char === CHAR_n) {
return this.return(NaN)
} else {
throw this.error(new TomlError('Unexpected character, expected "nan"'))
}
}
/* KEYS, barewords or basic, literal, or dotted */
parseKeyword () {
if (this.char === CHAR_QUOT) {
return this.next(this.parseBasicString)
} else if (this.char === CHAR_APOS) {
return this.next(this.parseLiteralString)
} else {
return this.goto(this.parseBareKey)
}
}
/* KEYS: barewords */
parseBareKey () {
do {
if (this.char === Parser.END) {
throw this.error(new TomlError('Key ended without value'))
} else if (isAlphaNumHyphen(this.char)) {
this.consume()
} else if (this.state.buf.length === 0) {
throw this.error(new TomlError('Empty bare keys are not allowed'))
} else {
return this.returnNow()
}
} while (this.nextChar())
}
/* STRINGS, single quoted (literal) */
parseSingleString () {
if (this.char === CHAR_APOS) {
return this.next(this.parseLiteralMultiStringMaybe)
} else {
return this.goto(this.parseLiteralString)
}
}
parseLiteralString () {
do {
if (this.char === CHAR_APOS) {
return this.return()
} else if (this.atEndOfLine()) {
throw this.error(new TomlError('Unterminated string'))
} else if (this.char === CHAR_DEL || (this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I)) {
throw this.errorControlCharInString()
} else {
this.consume()
}
} while (this.nextChar())
}
parseLiteralMultiStringMaybe () {
if (this.char === CHAR_APOS) {
return this.next(this.parseLiteralMultiString)
} else {
return this.returnNow()
}
}
parseLiteralMultiString () {
if (this.char === CTRL_M) {
return null
} else if (this.char === CTRL_J) {
return this.next(this.parseLiteralMultiStringContent)
} else {
return this.goto(this.parseLiteralMultiStringContent)
}
}
parseLiteralMultiStringContent () {
do {
if (this.char === CHAR_APOS) {
return this.next(this.parseLiteralMultiEnd)
} else if (this.char === Parser.END) {
throw this.error(new TomlError('Unterminated multi-line string'))
} else if (this.char === CHAR_DEL || (this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I && this.char !== CTRL_J && this.char !== CTRL_M)) {
throw this.errorControlCharInString()
} else {
this.consume()
}
} while (this.nextChar())
}
parseLiteralMultiEnd () {
if (this.char === CHAR_APOS) {
return this.next(this.parseLiteralMultiEnd2)
} else {
this.state.buf += "'"
return this.goto(this.parseLiteralMultiStringContent)
}
}
parseLiteralMultiEnd2 () {
if (this.char === CHAR_APOS) {
return this.return()
} else {
this.state.buf += "''"
return this.goto(this.parseLiteralMultiStringContent)
}
}
/* STRINGS double quoted */
parseDoubleString () {
if (this.char === CHAR_QUOT) {
return this.next(this.parseMultiStringMaybe)
} else {
return this.goto(this.parseBasicString)
}
}
parseBasicString () {
do {
if (this.char === CHAR_BSOL) {
return this.call(this.parseEscape, this.recordEscapeReplacement)
} else if (this.char === CHAR_QUOT) {
return this.return()
} else if (this.atEndOfLine()) {
throw this.error(new TomlError('Unterminated string'))
} else if (this.char === CHAR_DEL || (this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I)) {
throw this.errorControlCharInString()
} else {
this.consume()
}
} while (this.nextChar())
}
recordEscapeReplacement (replacement) {
this.state.buf += replacement
return this.goto(this.parseBasicString)
}
parseMultiStringMaybe () {
if (this.char === CHAR_QUOT) {
return this.next(this.parseMultiString)
} else {
return this.returnNow()
}
}
parseMultiString () {
if (this.char === CTRL_M) {
return null
} else if (this.char === CTRL_J) {
return this.next(this.parseMultiStringContent)
} else {
return this.goto(this.parseMultiStringContent)
}
}
parseMultiStringContent () {
do {
if (this.char === CHAR_BSOL) {
return this.call(this.parseMultiEscape, this.recordMultiEscapeReplacement)
} else if (this.char === CHAR_QUOT) {
return this.next(this.parseMultiEnd)
} else if (this.char === Parser.END) {
throw this.error(new TomlError('Unterminated multi-line string'))
} else if (this.char === CHAR_DEL || (this.char <= CTRL_CHAR_BOUNDARY && this.char !== CTRL_I && this.char !== CTRL_J && this.char !== CTRL_M)) {
throw this.errorControlCharInString()
} else {
this.consume()
}
} while (this.nextChar())
}
errorControlCharInString () {
let displayCode = '\\u00'
if (this.char < 16) {
displayCode += '0'
}
displayCode += this.char.toString(16)
return this.error(new TomlError(`Control characters (codes < 0x1f and 0x7f) are not allowed in strings, use ${displayCode} instead`))
}
recordMultiEscapeReplacement (replacement) {
this.state.buf += replacement
return this.goto(this.parseMultiStringContent)
}
parseMultiEnd () {
if (this.char === CHAR_QUOT) {
return this.next(this.parseMultiEnd2)
} else {
this.state.buf += '"'
return this.goto(this.parseMultiStringContent)
}
}
parseMultiEnd2 () {
if (this.char === CHAR_QUOT) {
return this.return()
} else {
this.state.buf += '""'
return this.goto(this.parseMultiStringContent)
}
}
parseMultiEscape () {
if (this.char === CTRL_M || this.char === CTRL_J) {
return this.next(this.parseMultiTrim)
} else if (this.char === CHAR_SP || this.char === CTRL_I) {
return this.next(this.parsePreMultiTrim)
} else {
return this.goto(this.parseEscape)
}
}
parsePreMultiTrim () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else if (this.char === CTRL_M || this.char === CTRL_J) {
return this.next(this.parseMultiTrim)
} else {
throw this.error(new TomlError("Can't escape whitespace"))
}
}
parseMultiTrim () {
// explicitly whitespace here, END should follow the same path as chars
if (this.char === CTRL_J || this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M) {
return null
} else {
return this.returnNow()
}
}
parseEscape () {
if (this.char in escapes) {
return this.return(escapes[this.char])
} else if (this.char === CHAR_u) {
return this.call(this.parseSmallUnicode, this.parseUnicodeReturn)
} else if (this.char === CHAR_U) {
return this.call(this.parseLargeUnicode, this.parseUnicodeReturn)
} else {
throw this.error(new TomlError('Unknown escape character: ' + this.char))
}
}
parseUnicodeReturn (char) {
try {
const codePoint = parseInt(char, 16)
if (codePoint >= SURROGATE_FIRST && codePoint <= SURROGATE_LAST) {
throw this.error(new TomlError('Invalid unicode, character in range 0xD800 - 0xDFFF is reserved'))
}
return this.returnNow(String.fromCodePoint(codePoint))
} catch (err) {
throw this.error(TomlError.wrap(err))
}
}
parseSmallUnicode () {
if (!isHexit(this.char)) {
throw this.error(new TomlError('Invalid character in unicode sequence, expected hex'))
} else {
this.consume()
if (this.state.buf.length >= 4) return this.return()
}
}
parseLargeUnicode () {
if (!isHexit(this.char)) {
throw this.error(new TomlError('Invalid character in unicode sequence, expected hex'))
} else {
this.consume()
if (this.state.buf.length >= 8) return this.return()
}
}
/* NUMBERS */
parseNumberSign () {
this.consume()
return this.next(this.parseMaybeSignedInfOrNan)
}
parseMaybeSignedInfOrNan () {
if (this.char === CHAR_i) {
return this.next(this.parseInf)
} else if (this.char === CHAR_n) {
return this.next(this.parseNan)
} else {
return this.callNow(this.parseNoUnder, this.parseNumberIntegerStart)
}
}
parseNumberIntegerStart () {
if (this.char === CHAR_0) {
this.consume()
return this.next(this.parseNumberIntegerExponentOrDecimal)
} else {
return this.goto(this.parseNumberInteger)
}
}
parseNumberIntegerExponentOrDecimal () {
if (this.char === CHAR_PERIOD) {
this.consume()
return this.call(this.parseNoUnder, this.parseNumberFloat)
} else if (this.char === CHAR_E || this.char === CHAR_e) {
this.consume()
return this.next(this.parseNumberExponentSign)
} else {
return this.returnNow(Integer(this.state.buf))
}
}
parseNumberInteger () {
if (isDigit(this.char)) {
this.consume()
} else if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnder)
} else if (this.char === CHAR_E || this.char === CHAR_e) {
this.consume()
return this.next(this.parseNumberExponentSign)
} else if (this.char === CHAR_PERIOD) {
this.consume()
return this.call(this.parseNoUnder, this.parseNumberFloat)
} else {
const result = Integer(this.state.buf)
/* istanbul ignore if */
if (result.isNaN()) {
throw this.error(new TomlError('Invalid number'))
} else {
return this.returnNow(result)
}
}
}
parseNoUnder () {
if (this.char === CHAR_LOWBAR || this.char === CHAR_PERIOD || this.char === CHAR_E || this.char === CHAR_e) {
throw this.error(new TomlError('Unexpected character, expected digit'))
} else if (this.atEndOfWord()) {
throw this.error(new TomlError('Incomplete number'))
}
return this.returnNow()
}
parseNoUnderHexOctBinLiteral () {
if (this.char === CHAR_LOWBAR || this.char === CHAR_PERIOD) {
throw this.error(new TomlError('Unexpected character, expected digit'))
} else if (this.atEndOfWord()) {
throw this.error(new TomlError('Incomplete number'))
}
return this.returnNow()
}
parseNumberFloat () {
if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnder, this.parseNumberFloat)
} else if (isDigit(this.char)) {
this.consume()
} else if (this.char === CHAR_E || this.char === CHAR_e) {
this.consume()
return this.next(this.parseNumberExponentSign)
} else {
return this.returnNow(Float(this.state.buf))
}
}
parseNumberExponentSign () {
if (isDigit(this.char)) {
return this.goto(this.parseNumberExponent)
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
this.consume()
this.call(this.parseNoUnder, this.parseNumberExponent)
} else {
throw this.error(new TomlError('Unexpected character, expected -, + or digit'))
}
}
parseNumberExponent () {
if (isDigit(this.char)) {
this.consume()
} else if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnder)
} else {
return this.returnNow(Float(this.state.buf))
}
}
/* NUMBERS or DATETIMES */
parseNumberOrDateTime () {
if (this.char === CHAR_0) {
this.consume()
return this.next(this.parseNumberBaseOrDateTime)
} else {
return this.goto(this.parseNumberOrDateTimeOnly)
}
}
parseNumberOrDateTimeOnly () {
// note, if two zeros are in a row then it MUST be a date
if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnder, this.parseNumberInteger)
} else if (isDigit(this.char)) {
this.consume()
if (this.state.buf.length > 4) this.next(this.parseNumberInteger)
} else if (this.char === CHAR_E || this.char === CHAR_e) {
this.consume()
return this.next(this.parseNumberExponentSign)
} else if (this.char === CHAR_PERIOD) {
this.consume()
return this.call(this.parseNoUnder, this.parseNumberFloat)
} else if (this.char === CHAR_HYPHEN) {
return this.goto(this.parseDateTime)
} else if (this.char === CHAR_COLON) {
return this.goto(this.parseOnlyTimeHour)
} else {
return this.returnNow(Integer(this.state.buf))
}
}
parseDateTimeOnly () {
if (this.state.buf.length < 4) {
if (isDigit(this.char)) {
return this.consume()
} else if (this.char === CHAR_COLON) {
return this.goto(this.parseOnlyTimeHour)
} else {
throw this.error(new TomlError('Expected digit while parsing year part of a date'))
}
} else {
if (this.char === CHAR_HYPHEN) {
return this.goto(this.parseDateTime)
} else {
throw this.error(new TomlError('Expected hyphen (-) while parsing year part of date'))
}
}
}
parseNumberBaseOrDateTime () {
if (this.char === CHAR_b) {
this.consume()
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerBin)
} else if (this.char === CHAR_o) {
this.consume()
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerOct)
} else if (this.char === CHAR_x) {
this.consume()
return this.call(this.parseNoUnderHexOctBinLiteral, this.parseIntegerHex)
} else if (this.char === CHAR_PERIOD) {
return this.goto(this.parseNumberInteger)
} else if (isDigit(this.char)) {
return this.goto(this.parseDateTimeOnly)
} else {
return this.returnNow(Integer(this.state.buf))
}
}
parseIntegerHex () {
if (isHexit(this.char)) {
this.consume()
} else if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnderHexOctBinLiteral)
} else {
const result = Integer(this.state.buf)
/* istanbul ignore if */
if (result.isNaN()) {
throw this.error(new TomlError('Invalid number'))
} else {
return this.returnNow(result)
}
}
}
parseIntegerOct () {
if (isOctit(this.char)) {
this.consume()
} else if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnderHexOctBinLiteral)
} else {
const result = Integer(this.state.buf)
/* istanbul ignore if */
if (result.isNaN()) {
throw this.error(new TomlError('Invalid number'))
} else {
return this.returnNow(result)
}
}
}
parseIntegerBin () {
if (isBit(this.char)) {
this.consume()
} else if (this.char === CHAR_LOWBAR) {
return this.call(this.parseNoUnderHexOctBinLiteral)
} else {
const result = Integer(this.state.buf)
/* istanbul ignore if */
if (result.isNaN()) {
throw this.error(new TomlError('Invalid number'))
} else {
return this.returnNow(result)
}
}
}
/* DATETIME */
parseDateTime () {
// we enter here having just consumed the year and about to consume the hyphen
if (this.state.buf.length < 4) {
throw this.error(new TomlError('Years less than 1000 must be zero padded to four characters'))
}
this.state.result = this.state.buf
this.state.buf = ''
return this.next(this.parseDateMonth)
}
parseDateMonth () {
if (this.char === CHAR_HYPHEN) {
if (this.state.buf.length < 2) {
throw this.error(new TomlError('Months less than 10 must be zero padded to two characters'))
}
this.state.result += '-' + this.state.buf
this.state.buf = ''
return this.next(this.parseDateDay)
} else if (isDigit(this.char)) {
this.consume()
} else {
throw this.error(new TomlError('Incomplete datetime'))
}
}
parseDateDay () {
if (this.char === CHAR_T || this.char === CHAR_SP) {
if (this.state.buf.length < 2) {
throw this.error(new TomlError('Days less than 10 must be zero padded to two characters'))
}
this.state.result += '-' + this.state.buf
this.state.buf = ''
return this.next(this.parseStartTimeHour)
} else if (this.atEndOfWord()) {
return this.returnNow(createDate(this.state.result + '-' + this.state.buf))
} else if (isDigit(this.char)) {
this.consume()
} else {
throw this.error(new TomlError('Incomplete datetime'))
}
}
parseStartTimeHour () {
if (this.atEndOfWord()) {
return this.returnNow(createDate(this.state.result))
} else {
return this.goto(this.parseTimeHour)
}
}
parseTimeHour () {
if (this.char === CHAR_COLON) {
if (this.state.buf.length < 2) {
throw this.error(new TomlError('Hours less than 10 must be zero padded to two characters'))
}
this.state.result += 'T' + this.state.buf
this.state.buf = ''
return this.next(this.parseTimeMin)
} else if (isDigit(this.char)) {
this.consume()
} else {
throw this.error(new TomlError('Incomplete datetime'))
}
}
parseTimeMin () {
if (this.state.buf.length < 2 && isDigit(this.char)) {
this.consume()
} else if (this.state.buf.length === 2 && this.char === CHAR_COLON) {
this.state.result += ':' + this.state.buf
this.state.buf = ''
return this.next(this.parseTimeSec)
} else {
throw this.error(new TomlError('Incomplete datetime'))
}
}
parseTimeSec () {
if (isDigit(this.char)) {
this.consume()
if (this.state.buf.length === 2) {
this.state.result += ':' + this.state.buf
this.state.buf = ''
return this.next(this.parseTimeZoneOrFraction)
}
} else {
throw this.error(new TomlError('Incomplete datetime'))
}
}
parseOnlyTimeHour () {
/* istanbul ignore else */
if (this.char === CHAR_COLON) {
if (this.state.buf.length < 2) {
throw this.error(new TomlError('Hours less than 10 must be zero padded to two characters'))
}
this.state.result = this.state.buf
this.state.buf = ''
return this.next(this.parseOnlyTimeMin)
} else {
throw this.error(new TomlError('Incomplete time'))
}
}
parseOnlyTimeMin () {
if (this.state.buf.length < 2 && isDigit(this.char)) {
this.consume()
} else if (this.state.buf.length === 2 && this.char === CHAR_COLON) {
this.state.result += ':' + this.state.buf
this.state.buf = ''
return this.next(this.parseOnlyTimeSec)
} else {
throw this.error(new TomlError('Incomplete time'))
}
}
parseOnlyTimeSec () {
if (isDigit(this.char)) {
this.consume()
if (this.state.buf.length === 2) {
return this.next(this.parseOnlyTimeFractionMaybe)
}
} else {
throw this.error(new TomlError('Incomplete time'))
}
}
parseOnlyTimeFractionMaybe () {
this.state.result += ':' + this.state.buf
if (this.char === CHAR_PERIOD) {
this.state.buf = ''
this.next(this.parseOnlyTimeFraction)
} else {
return this.return(createTime(this.state.result))
}
}
parseOnlyTimeFraction () {
if (isDigit(this.char)) {
this.consume()
} else if (this.atEndOfWord()) {
if (this.state.buf.length === 0) throw this.error(new TomlError('Expected digit in milliseconds'))
return this.returnNow(createTime(this.state.result + '.' + this.state.buf))
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z'))
}
}
parseTimeZoneOrFraction () {
if (this.char === CHAR_PERIOD) {
this.consume()
this.next(this.parseDateTimeFraction)
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
this.consume()
this.next(this.parseTimeZoneHour)
} else if (this.char === CHAR_Z) {
this.consume()
return this.return(createDateTime(this.state.result + this.state.buf))
} else if (this.atEndOfWord()) {
return this.returnNow(createDateTimeFloat(this.state.result + this.state.buf))
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z'))
}
}
parseDateTimeFraction () {
if (isDigit(this.char)) {
this.consume()
} else if (this.state.buf.length === 1) {
throw this.error(new TomlError('Expected digit in milliseconds'))
} else if (this.char === CHAR_HYPHEN || this.char === CHAR_PLUS) {
this.consume()
this.next(this.parseTimeZoneHour)
} else if (this.char === CHAR_Z) {
this.consume()
return this.return(createDateTime(this.state.result + this.state.buf))
} else if (this.atEndOfWord()) {
return this.returnNow(createDateTimeFloat(this.state.result + this.state.buf))
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected period (.), minus (-), plus (+) or Z'))
}
}
parseTimeZoneHour () {
if (isDigit(this.char)) {
this.consume()
// FIXME: No more regexps
if (/\d\d$/.test(this.state.buf)) return this.next(this.parseTimeZoneSep)
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected digit'))
}
}
parseTimeZoneSep () {
if (this.char === CHAR_COLON) {
this.consume()
this.next(this.parseTimeZoneMin)
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected colon'))
}
}
parseTimeZoneMin () {
if (isDigit(this.char)) {
this.consume()
if (/\d\d$/.test(this.state.buf)) return this.return(createDateTime(this.state.result + this.state.buf))
} else {
throw this.error(new TomlError('Unexpected character in datetime, expected digit'))
}
}
/* BOOLEAN */
parseBoolean () {
/* istanbul ignore else */
if (this.char === CHAR_t) {
this.consume()
return this.next(this.parseTrue_r)
} else if (this.char === CHAR_f) {
this.consume()
return this.next(this.parseFalse_a)
}
}
parseTrue_r () {
if (this.char === CHAR_r) {
this.consume()
return this.next(this.parseTrue_u)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseTrue_u () {
if (this.char === CHAR_u) {
this.consume()
return this.next(this.parseTrue_e)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseTrue_e () {
if (this.char === CHAR_e) {
return this.return(true)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseFalse_a () {
if (this.char === CHAR_a) {
this.consume()
return this.next(this.parseFalse_l)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseFalse_l () {
if (this.char === CHAR_l) {
this.consume()
return this.next(this.parseFalse_s)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseFalse_s () {
if (this.char === CHAR_s) {
this.consume()
return this.next(this.parseFalse_e)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
parseFalse_e () {
if (this.char === CHAR_e) {
return this.return(false)
} else {
throw this.error(new TomlError('Invalid boolean, expected true or false'))
}
}
/* INLINE LISTS */
parseInlineList () {
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M || this.char === CTRL_J) {
return null
} else if (this.char === Parser.END) {
throw this.error(new TomlError('Unterminated inline array'))
} else if (this.char === CHAR_NUM) {
return this.call(this.parseComment)
} else if (this.char === CHAR_RSQB) {
return this.return(this.state.resultArr || InlineList())
} else {
return this.callNow(this.parseValue, this.recordInlineListValue)
}
}
recordInlineListValue (value) {
if (this.state.resultArr) {
const listType = this.state.resultArr[_contentType]
const valueType = tomlType(value)
if (listType !== valueType) {
throw this.error(new TomlError(`Inline lists must be a single type, not a mix of ${listType} and ${valueType}`))
}
} else {
this.state.resultArr = InlineList(tomlType(value))
}
if (isFloat(value) || isInteger(value)) {
// unbox now that we've verified they're ok
this.state.resultArr.push(value.valueOf())
} else {
this.state.resultArr.push(value)
}
return this.goto(this.parseInlineListNext)
}
parseInlineListNext () {
if (this.char === CHAR_SP || this.char === CTRL_I || this.char === CTRL_M || this.char === CTRL_J) {
return null
} else if (this.char === CHAR_NUM) {
return this.call(this.parseComment)
} else if (this.char === CHAR_COMMA) {
return this.next(this.parseInlineList)
} else if (this.char === CHAR_RSQB) {
return this.goto(this.parseInlineList)
} else {
throw this.error(new TomlError('Invalid character, expected whitespace, comma (,) or close bracket (])'))
}
}
/* INLINE TABLE */
parseInlineTable () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else if (this.char === Parser.END || this.char === CHAR_NUM || this.char === CTRL_J || this.char === CTRL_M) {
throw this.error(new TomlError('Unterminated inline array'))
} else if (this.char === CHAR_RCUB) {
return this.return(this.state.resultTable || InlineTable())
} else {
if (!this.state.resultTable) this.state.resultTable = InlineTable()
return this.callNow(this.parseAssign, this.recordInlineTableValue)
}
}
recordInlineTableValue (kv) {
let target = this.state.resultTable
let finalKey = kv.key.pop()
for (let kw of kv.key) {
if (hasKey(target, kw) && (!isTable(target[kw]) || target[kw][_declared])) {
throw this.error(new TomlError("Can't redefine existing key"))
}
target = target[kw] = target[kw] || Table()
}
if (hasKey(target, finalKey)) {
throw this.error(new TomlError("Can't redefine existing key"))
}
if (isInteger(kv.value) || isFloat(kv.value)) {
target[finalKey] = kv.value.valueOf()
} else {
target[finalKey] = kv.value
}
return this.goto(this.parseInlineTableNext)
}
parseInlineTableNext () {
if (this.char === CHAR_SP || this.char === CTRL_I) {
return null
} else if (this.char === Parser.END || this.char === CHAR_NUM || this.char === CTRL_J || this.char === CTRL_M) {
throw this.error(new TomlError('Unterminated inline array'))
} else if (this.char === CHAR_COMMA) {
return this.next(this.parseInlineTable)
} else if (this.char === CHAR_RCUB) {
return this.goto(this.parseInlineTable)
} else {
throw this.error(new TomlError('Invalid character, expected whitespace, comma (,) or close bracket (])'))
}
}
}
return TOMLParser
}