127 lines
2.9 KiB
JavaScript
Executable file
127 lines
2.9 KiB
JavaScript
Executable file
'use strict'
|
|
const ParserEND = 0x110000
|
|
class ParserError extends Error {
|
|
/* istanbul ignore next */
|
|
constructor (msg, filename, linenumber) {
|
|
super('[ParserError] ' + msg, filename, linenumber)
|
|
this.name = 'ParserError'
|
|
this.code = 'ParserError'
|
|
if (Error.captureStackTrace) Error.captureStackTrace(this, ParserError)
|
|
}
|
|
}
|
|
class State {
|
|
constructor (parser) {
|
|
this.parser = parser
|
|
this.buf = ''
|
|
this.returned = null
|
|
this.result = null
|
|
this.resultTable = null
|
|
this.resultArr = null
|
|
}
|
|
}
|
|
class Parser {
|
|
constructor () {
|
|
this.pos = 0
|
|
this.col = 0
|
|
this.line = 0
|
|
this.obj = {}
|
|
this.ctx = this.obj
|
|
this.stack = []
|
|
this._buf = ''
|
|
this.char = null
|
|
this.ii = 0
|
|
this.state = new State(this.parseStart)
|
|
}
|
|
|
|
parse (str) {
|
|
/* istanbul ignore next */
|
|
if (str.length === 0 || str.length == null) return
|
|
|
|
this._buf = String(str)
|
|
this.ii = -1
|
|
this.char = -1
|
|
let getNext
|
|
while (getNext === false || this.nextChar()) {
|
|
getNext = this.runOne()
|
|
}
|
|
this._buf = null
|
|
}
|
|
nextChar () {
|
|
if (this.char === 0x0A) {
|
|
++this.line
|
|
this.col = -1
|
|
}
|
|
++this.ii
|
|
this.char = this._buf.codePointAt(this.ii)
|
|
++this.pos
|
|
++this.col
|
|
return this.haveBuffer()
|
|
}
|
|
haveBuffer () {
|
|
return this.ii < this._buf.length
|
|
}
|
|
runOne () {
|
|
return this.state.parser.call(this, this.state.returned)
|
|
}
|
|
finish () {
|
|
this.char = ParserEND
|
|
let last
|
|
do {
|
|
last = this.state.parser
|
|
this.runOne()
|
|
} while (this.state.parser !== last)
|
|
|
|
this.ctx = null
|
|
this.state = null
|
|
this._buf = null
|
|
|
|
return this.obj
|
|
}
|
|
next (fn) {
|
|
/* istanbul ignore next */
|
|
if (typeof fn !== 'function') throw new ParserError('Tried to set state to non-existent state: ' + JSON.stringify(fn))
|
|
this.state.parser = fn
|
|
}
|
|
goto (fn) {
|
|
this.next(fn)
|
|
return this.runOne()
|
|
}
|
|
call (fn, returnWith) {
|
|
if (returnWith) this.next(returnWith)
|
|
this.stack.push(this.state)
|
|
this.state = new State(fn)
|
|
}
|
|
callNow (fn, returnWith) {
|
|
this.call(fn, returnWith)
|
|
return this.runOne()
|
|
}
|
|
return (value) {
|
|
/* istanbul ignore next */
|
|
if (this.stack.length === 0) throw this.error(new ParserError('Stack underflow'))
|
|
if (value === undefined) value = this.state.buf
|
|
this.state = this.stack.pop()
|
|
this.state.returned = value
|
|
}
|
|
returnNow (value) {
|
|
this.return(value)
|
|
return this.runOne()
|
|
}
|
|
consume () {
|
|
/* istanbul ignore next */
|
|
if (this.char === ParserEND) throw this.error(new ParserError('Unexpected end-of-buffer'))
|
|
this.state.buf += this._buf[this.ii]
|
|
}
|
|
error (err) {
|
|
err.line = this.line
|
|
err.col = this.col
|
|
err.pos = this.pos
|
|
return err
|
|
}
|
|
/* istanbul ignore next */
|
|
parseStart () {
|
|
throw new ParserError('Must declare a parseStart method')
|
|
}
|
|
}
|
|
Parser.END = ParserEND
|
|
Parser.Error = ParserError
|
|
module.exports = Parser
|