chore(complex-matcher): new parser implementation (#37)

This commit is contained in:
Julien Fontanet 2018-02-01 11:27:49 +01:00 committed by GitHub
parent 9479b7dc74
commit ff2c69102d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -238,172 +238,225 @@ export class TruthyProperty extends Node {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// terms = null || term+ // https://gist.github.com/yelouafi/556e5159e869952335e01f6b473c4ec1
// *null = /$/
// term = ws (and | or | not | property | truthyProperty | numberOrString) ws
// ws = ' '*
// *and = "(" terms ")"
// *or = "|" ws "(" terms ")"
// *not = "!" term
// *property = string ws ":" term
// *truthyProperty = string ws "?"
// numberOrString = string
// string = quotedString | rawString
// quotedString = "\"" ( /[^"\]/ | "\\\\" | "\\\"" )+
// rawString = /[a-z0-9-_.]+/i
export const parse = invoke(() => {
let i
let n
let input
// ----- class Failure {
constructor (pos, expected) {
const backtrace = parser => () => { this.expected = expected
const pos = i this.pos = pos
const node = parser()
if (node !== undefined) {
return node
}
i = pos
} }
// ----- get value () {
throw new Error(
`parse error: expected ${this.expected} at position ${this.pos}`
)
}
}
const parseTerms = Node => { class Success {
let term = parseTerm() constructor (pos, value) {
if (!term) { this.pos = pos
return new Null() this.value = value
} }
}
const terms = [term] // -------------------------------------------------------------------
while ((term = parseTerm())) {
terms.push(term)
}
return new Node(terms)
}
const parseTerm = () => {
parseWs()
const child = class P {
parseAnd() || static alt (...parsers) {
parseOr() || const { length } = parsers
parseNot() || return new P((input, pos, end) => {
parseProperty() || for (let i = 0; i < length; ++i) {
parseTruthyProperty() || const result = parsers[i]._parse(input, pos, end)
parseNumberOrString() if (result instanceof Success) {
if (child) { return result
parseWs() }
return child }
} return new Failure(pos, 'alt')
})
} }
const parseWs = () => {
while (input[i] === ' ') {
++i
}
return true static grammar (rules) {
const grammar = {}
Object.keys(rules).forEach(k => {
const rule = rules[k]
grammar[k] = rule instanceof P ? rule : P.lazy(rule, grammar)
})
return grammar
} }
const parseAnd = backtrace(() => {
let and static lazy (parserCreator, arg) {
if (input[i++] === '(' && (and = parseTerm(And)) && input[i++] === ')') { const parser = new P((input, pos, end) =>
return and (parser._parse = parserCreator(arg)._parse)(input, pos, end)
} )
}) return parser
const parseOr = backtrace(() => {
let or
if (
input[i++] === '|' &&
parseWs() &&
input[i++] === '(' &&
(or = parseTerms(Or)) &&
input[i++] === ')'
) {
return or
}
})
const parseNot = backtrace(() => {
let child
if (input[i++] === '!' && (child = parseTerm())) {
return new Not(child)
}
})
const parseProperty = backtrace(() => {
let name, child
if (
(name = parseString()) &&
parseWs() &&
input[i++] === ':' &&
(child = parseTerm())
) {
return new Property(name, child)
}
})
const parseNumberOrString = () => {
let str = parseQuotedString()
if (str !== undefined) {
return new StringNode(str)
}
str = parseRawString()
if (str !== undefined) {
const asNum = +str
return Number.isNaN(asNum) ? new StringNode(str) : new NumberNode(asNum)
}
} }
const parseString = () => {
let value static regex (regex) {
if ( regex = new RegExp(regex.source, 'y')
(value = parseQuotedString()) !== undefined || return new P((input, pos) => {
(value = parseRawString()) !== undefined regex.lastIndex = pos
) { const matches = regex.exec(input)
return value return matches !== null
} ? new Success(regex.lastIndex, matches[0])
: new Failure(pos, regex)
})
} }
const parseQuotedString = backtrace(() => {
if (input[i++] !== '"') { static seq (...parsers) {
return const { length } = parsers
return new P((input, pos, end) => {
const values = new Array(length)
for (let i = 0; i < length; ++i) {
const result = parsers[i]._parse(input, pos, end)
if (result instanceof Failure) {
return result
}
pos = result.pos
values[i] = result.value
}
return new Success(pos, values)
})
}
static text (text) {
const { length } = text
return new P(
(input, pos) =>
input.startsWith(text, pos)
? new Success(pos + length, text)
: new Failure(pos, `'${text}'`)
)
}
constructor (parse) {
this._parse = parse
}
map (fn) {
return new P((input, pos, end) => {
const result = this._parse(input, pos, end)
if (result instanceof Success) {
result.value = fn(result.value)
}
return result
})
}
parse (input, pos = 0, end = input.length) {
return this._parse(input, pos, end).value
}
repeat (min = 0, max = Infinity) {
return new P((input, pos, end) => {
const value = []
let result
let i = 0
while (i < min) {
++i
result = this._parse(input, pos, end)
if (result instanceof Failure) {
return result
}
value.push(result.value)
pos = result.pos
}
while (i < max && (result = this._parse(input, pos, end)) instanceof Success) {
++i
value.push(result.value)
pos = result.pos
}
return new Success(pos, value)
})
}
skip (otherParser) {
return new P((input, pos, end) => {
const result = this._parse(input, pos, end)
if (result instanceof Failure) {
return result
}
const otherResult = otherParser._parse(input, result.pos, end)
if (otherResult instanceof Failure) {
return otherResult
}
result.pos = otherResult.pos
return result
})
}
}
P.eof = new P(
(input, pos, end) =>
pos < end ? new Failure(pos, 'end of input') : new Success(pos)
)
// -------------------------------------------------------------------
const parser = P.grammar({
default: r =>
P.seq(r.ws, r.term.repeat(), P.eof)
.map(([, terms]) => (terms.length === 0 ? new Null() : new And(terms))),
quotedString: new P((input, pos, end) => {
if (input[pos] !== '"') {
return new Failure(pos, '"')
} }
++pos
const value = [] const value = []
let char let char
while (i < n && (char = input[i++]) !== '"') { while (pos < end && (char = input[pos++]) !== '"') {
if (char === '\\') { if (char === '\\') {
char = input[i++] char = input[pos++]
} }
value.push(char) value.push(char)
} }
return value.join('') return new Success(pos, value.join(''))
}) }),
const parseRawString = () => { rawString: new P((input, pos, end) => {
let value = '' let value = ''
let c let c
while ((c = input[i]) && RAW_STRING_CHARS[c]) { while (pos < end && RAW_STRING_CHARS[(c = input[pos])]) {
++i ++pos
value += c value += c
} }
if (value.length) { return value.length === 0
return value ? new Failure(pos, 'a raw string')
} : new Success(pos, value)
} }),
const parseTruthyProperty = backtrace(() => { string: r => P.alt(r.quotedString, r.rawString),
let name term: r =>
if ((name = parseString()) && parseWs() && input[i++] === '?') { P.alt(
return new TruthyProperty(name) P.seq(P.text('('), r.ws, r.term.repeat(1), P.text(')')).map(
} _ => new And(_[2])
}) ),
P.seq(
return input_ => { P.text('|'),
i = 0 r.ws,
input = input_.split('') P.text('('),
n = input.length r.ws,
r.term.repeat(1),
try { P.text(')')
return parseTerms(And) ).map(_ => new Or(_[4])),
} finally { P.seq(P.text('!'), r.ws, r.term).map(_ => new Not(_[2])),
input = undefined P.seq(r.string, r.ws, P.text(':'), r.ws, r.term).map(
} _ => new Property(_[0], _[4])
} ),
}) P.seq(r.string, P.text('?')).map(_ => new TruthyProperty(_[0])),
P.alt(
r.quotedString.map(_ => new StringNode(_)),
r.rawString.map(str => {
const asNum = +str
return Number.isNaN(asNum)
? new StringNode(str)
: new NumberNode(asNum)
})
),
).skip(r.ws),
ws: P.regex(/\s*/),
}).default
export const parse = parser.parse.bind(parser)
// ------------------------------------------------------------------- // -------------------------------------------------------------------