1 | 'use strict'
|
2 | const ParserEND = Symbol('END')
|
3 | class ParserError extends Error {
|
4 |
|
5 | constructor (msg, filename, linenumber) {
|
6 | super('[ParserError] ' + msg, filename, linenumber)
|
7 | this.code = 'ParserError'
|
8 | if (Error.captureStackTrace) Error.captureStackTrace(this, ParserError)
|
9 | }
|
10 | }
|
11 | class State {
|
12 | constructor (parser) {
|
13 | this.parser = parser
|
14 | this.buf = ''
|
15 | this.returned = null
|
16 | this.result = null
|
17 | this.resultTable = null
|
18 | this.resultArr = null
|
19 | }
|
20 | }
|
21 | class Parser {
|
22 | constructor () {
|
23 | this.pos = 0
|
24 | this.col = 0
|
25 | this.line = 0
|
26 | this.obj = {}
|
27 | this.ctx = this.obj
|
28 | this.stack = []
|
29 | this._buf = ''
|
30 | this.char = null
|
31 | this.ii = 0
|
32 | this.state = new State(this.parseStart)
|
33 | }
|
34 | parse (str) {
|
35 |
|
36 | if (str.length === 0) return
|
37 |
|
38 | this._buf = String(str)
|
39 | this.ii = 0
|
40 | this.char = this._buf[0]
|
41 | let getNext
|
42 | do {
|
43 | getNext = this.runOne()
|
44 | } while (getNext === false || this.nextChar())
|
45 | this._buf = null
|
46 | }
|
47 | nextChar () {
|
48 | if (this.char === '\n') {
|
49 | ++this.line
|
50 | this.col = -1
|
51 | }
|
52 | ++this.ii
|
53 | this.char = this._buf[this.ii]
|
54 | ++this.pos
|
55 | ++this.col
|
56 | return this.haveBuffer()
|
57 | }
|
58 | haveBuffer () {
|
59 | return this.ii < this._buf.length
|
60 | }
|
61 | runOne () {
|
62 | return this.state.parser.call(this, this.state.returned)
|
63 | }
|
64 | finish () {
|
65 | this.char = ParserEND
|
66 | let last
|
67 | do {
|
68 | last = this.state.parser
|
69 | this.runOne()
|
70 | } while (this.state.parser !== last)
|
71 |
|
72 | this.ctx = null
|
73 | this.state = null
|
74 | this._buf = null
|
75 |
|
76 | return this.obj
|
77 | }
|
78 | next (fn) {
|
79 |
|
80 | if (typeof fn !== 'function') throw new ParserError('Tried to set state to non-existent state: ' + JSON.stringify(fn))
|
81 | this.state.parser = fn
|
82 | }
|
83 | goto (fn) {
|
84 | this.next(fn)
|
85 | return this.runOne()
|
86 | }
|
87 | call (fn, returnWith) {
|
88 | if (returnWith) this.next(returnWith)
|
89 | this.stack.push(this.state)
|
90 | this.state = new State(fn)
|
91 | }
|
92 | callNow (fn, returnWith) {
|
93 | this.call(fn, returnWith)
|
94 | return this.runOne()
|
95 | }
|
96 | return (value) {
|
97 |
|
98 | if (!this.stack.length) throw this.error(new ParserError('Stack underflow'))
|
99 | if (value === undefined) value = this.state.buf
|
100 | this.state = this.stack.pop()
|
101 | this.state.returned = value
|
102 | }
|
103 | returnNow (value) {
|
104 | this.return(value)
|
105 | return this.runOne()
|
106 | }
|
107 | consume () {
|
108 |
|
109 | if (this.char === ParserEND) throw this.error(new ParserError('Unexpected end-of-buffer'))
|
110 | this.state.buf += this.char
|
111 | }
|
112 | error (err) {
|
113 | err.line = this.line
|
114 | err.col = this.col
|
115 | err.pos = this.pos
|
116 | return err
|
117 | }
|
118 |
|
119 | parseStart () {
|
120 | throw new ParserError('Must declare a parseStart method')
|
121 | }
|
122 | }
|
123 | Parser.END = ParserEND
|
124 | Parser.Error = ParserError
|
125 | module.exports = Parser
|