You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.0 KiB
JavaScript
161 lines
4.0 KiB
JavaScript
import {tokenizer, SourceLocation, tokTypes as tt, Node, lineBreak, isNewLine} from ".."
|
|
|
|
// Registered plugins
|
|
export const pluginsLoose = {}
|
|
|
|
export class LooseParser {
|
|
constructor(input, options) {
|
|
this.toks = tokenizer(input, options)
|
|
this.options = this.toks.options
|
|
this.input = this.toks.input
|
|
this.tok = this.last = {type: tt.eof, start: 0, end: 0}
|
|
if (this.options.locations) {
|
|
let here = this.toks.curPosition()
|
|
this.tok.loc = new SourceLocation(this.toks, here, here)
|
|
}
|
|
this.ahead = []; // Tokens ahead
|
|
this.context = []; // Indentation contexted
|
|
this.curIndent = 0
|
|
this.curLineStart = 0
|
|
this.nextLineStart = this.lineEnd(this.curLineStart) + 1
|
|
// Load plugins
|
|
this.options.pluginsLoose = options.pluginsLoose || {}
|
|
this.loadPlugins(this.options.pluginsLoose)
|
|
}
|
|
|
|
startNode() {
|
|
return new Node(this.toks, this.tok.start, this.options.locations ? this.tok.loc.start : null)
|
|
}
|
|
|
|
storeCurrentPos() {
|
|
return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start
|
|
}
|
|
|
|
startNodeAt(pos) {
|
|
if (this.options.locations) {
|
|
return new Node(this.toks, pos[0], pos[1])
|
|
} else {
|
|
return new Node(this.toks, pos)
|
|
}
|
|
}
|
|
|
|
finishNode(node, type) {
|
|
node.type = type
|
|
node.end = this.last.end
|
|
if (this.options.locations)
|
|
node.loc.end = this.last.loc.end
|
|
if (this.options.ranges)
|
|
node.range[1] = this.last.end
|
|
return node
|
|
}
|
|
|
|
dummyNode(type) {
|
|
let dummy = this.startNode()
|
|
dummy.type = type
|
|
dummy.end = dummy.start
|
|
if (this.options.locations)
|
|
dummy.loc.end = dummy.loc.start
|
|
if (this.options.ranges)
|
|
dummy.range[1] = dummy.start
|
|
this.last = {type: tt.name, start: dummy.start, end: dummy.start, loc: dummy.loc}
|
|
return dummy
|
|
}
|
|
|
|
dummyIdent() {
|
|
let dummy = this.dummyNode("Identifier")
|
|
dummy.name = "✖"
|
|
return dummy
|
|
}
|
|
|
|
dummyString() {
|
|
let dummy = this.dummyNode("Literal")
|
|
dummy.value = dummy.raw = "✖"
|
|
return dummy
|
|
}
|
|
|
|
eat(type) {
|
|
if (this.tok.type === type) {
|
|
this.next()
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
isContextual(name) {
|
|
return this.tok.type === tt.name && this.tok.value === name
|
|
}
|
|
|
|
eatContextual(name) {
|
|
return this.tok.value === name && this.eat(tt.name)
|
|
}
|
|
|
|
canInsertSemicolon() {
|
|
return this.tok.type === tt.eof || this.tok.type === tt.braceR ||
|
|
lineBreak.test(this.input.slice(this.last.end, this.tok.start))
|
|
}
|
|
|
|
semicolon() {
|
|
return this.eat(tt.semi)
|
|
}
|
|
|
|
expect(type) {
|
|
if (this.eat(type)) return true
|
|
for (let i = 1; i <= 2; i++) {
|
|
if (this.lookAhead(i).type == type) {
|
|
for (let j = 0; j < i; j++) this.next()
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
pushCx() {
|
|
this.context.push(this.curIndent)
|
|
}
|
|
|
|
popCx() {
|
|
this.curIndent = this.context.pop()
|
|
}
|
|
|
|
lineEnd(pos) {
|
|
while (pos < this.input.length && !isNewLine(this.input.charCodeAt(pos))) ++pos
|
|
return pos
|
|
}
|
|
|
|
indentationAfter(pos) {
|
|
for (let count = 0;; ++pos) {
|
|
let ch = this.input.charCodeAt(pos)
|
|
if (ch === 32) ++count
|
|
else if (ch === 9) count += this.options.tabSize
|
|
else return count
|
|
}
|
|
}
|
|
|
|
closes(closeTok, indent, line, blockHeuristic) {
|
|
if (this.tok.type === closeTok || this.tok.type === tt.eof) return true
|
|
return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() &&
|
|
(!blockHeuristic || this.nextLineStart >= this.input.length ||
|
|
this.indentationAfter(this.nextLineStart) < indent)
|
|
}
|
|
|
|
tokenStartsLine() {
|
|
for (let p = this.tok.start - 1; p >= this.curLineStart; --p) {
|
|
let ch = this.input.charCodeAt(p)
|
|
if (ch !== 9 && ch !== 32) return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
extend(name, f) {
|
|
this[name] = f(this[name])
|
|
}
|
|
|
|
loadPlugins(pluginConfigs) {
|
|
for (let name in pluginConfigs) {
|
|
let plugin = pluginsLoose[name]
|
|
if (!plugin) throw new Error("Plugin '" + name + "' not found")
|
|
plugin(this, pluginConfigs[name])
|
|
}
|
|
}
|
|
}
|