initial commit
This commit is contained in:
commit
2baf53640d
5 changed files with 142 additions and 0 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "vendor/kojima"]
|
||||||
|
path = vendor/kojima
|
||||||
|
url = https://git.kitsu.cafe/rowan/kojima.git
|
13
package.json
Normal file
13
package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "kuebiko",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "Rowan <rowan@kitsu.cafe> (https://kitsu.cafe)",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"description": ""
|
||||||
|
}
|
0
src/index.js
Normal file
0
src/index.js
Normal file
125
src/parser.js
Normal file
125
src/parser.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import { Ok, Err, curry } from '../vendor/kojima/src/index.js'
|
||||||
|
|
||||||
|
class ParseError extends Error {
|
||||||
|
constructor(message, state, source) {
|
||||||
|
super(message)
|
||||||
|
this.state = state
|
||||||
|
this.source = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tuple = (...values) => Object.freeze(values)
|
||||||
|
const State = value => Tuple([], Iterator.from(value))
|
||||||
|
|
||||||
|
const LowerAlpha = 'abcdefghijklmnopqrstuvwxyz'
|
||||||
|
const UpperAlpha = LowerAlpha.toUpperCase()
|
||||||
|
const Alpha = LowerAlpha + UpperAlpha
|
||||||
|
const Digits = '1234567890'
|
||||||
|
const Alphanumeric = Alpha + Digits
|
||||||
|
|
||||||
|
|
||||||
|
const tee = (iterator, n = 2) => {
|
||||||
|
iterator = Iterator.from(iterator)
|
||||||
|
|
||||||
|
function* gen(current) {
|
||||||
|
while (true) {
|
||||||
|
if (!current.next) {
|
||||||
|
const { done, value } = iterator.next()
|
||||||
|
if (done) { return }
|
||||||
|
current.next = { value }
|
||||||
|
}
|
||||||
|
current = current.next
|
||||||
|
yield current.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array(n).fill({}).map(gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fork = ([tokens, state]) => {
|
||||||
|
const [a, b] = tee(state)
|
||||||
|
return Tuple(
|
||||||
|
Tuple(tokens.slice(), a),
|
||||||
|
Tuple(tokens.slice(), b),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const succeed = (v, [x, y]) => Ok(Tuple(x.concat(v), y))
|
||||||
|
const fail = (msg, state, err = undefined) => Err(new ParseError(msg, state, err))
|
||||||
|
const nth = (n, iter) => iter[n]
|
||||||
|
const next = state => nth(1, state).next().value
|
||||||
|
const diff = (a, b) => b.slice(-Math.max(0, b.length - a.length))
|
||||||
|
const join = curry((delim, val) => val.join(delim))
|
||||||
|
const mapStr = curry((fn, str) => Array.from(str).map(v => fn(v)))
|
||||||
|
|
||||||
|
const any = (...parsers) => state => {
|
||||||
|
for (const parser of parsers) {
|
||||||
|
const [original, clone] = fork(state)
|
||||||
|
const result = parser(clone)
|
||||||
|
if (result.isOk) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fail('no matching parsers', state)
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyOf = curry((str, state) => (
|
||||||
|
any(...mapStr(char, str))(state)
|
||||||
|
))
|
||||||
|
|
||||||
|
const seq = (...parsers) => state => {
|
||||||
|
let acc = Ok(state)
|
||||||
|
|
||||||
|
for (const parser of parsers) {
|
||||||
|
if (acc.isOk) {
|
||||||
|
acc = acc.bind(parser)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = curry((fn, parser, state) => {
|
||||||
|
return parser(state).bind(result => {
|
||||||
|
try {
|
||||||
|
const parsed = diff(state[0], result[0])
|
||||||
|
const backtrack = Tuple(state[0], result[1])
|
||||||
|
return succeed(fn(parsed), backtrack)
|
||||||
|
} catch (e) {
|
||||||
|
return fail('failed to map', state, e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const char = curry((ch, state) => (
|
||||||
|
next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch} `, state)
|
||||||
|
))
|
||||||
|
|
||||||
|
const anyChar = state => {
|
||||||
|
const ch = next(state)
|
||||||
|
return !!ch ? succeed(ch, state) : fail(`could not parse ${ch}`, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
const string = curry((str, state) => (
|
||||||
|
map(
|
||||||
|
join(''),
|
||||||
|
seq(...mapStr(char, str))
|
||||||
|
)(state)
|
||||||
|
))
|
||||||
|
|
||||||
|
const digit = anyOf(Digits)
|
||||||
|
const lowerAlpha = anyOf(LowerAlpha)
|
||||||
|
const upperAlpha = anyOf(UpperAlpha)
|
||||||
|
const alpha = anyOf(Alpha)
|
||||||
|
const alphanumeric = anyOf(Alphanumeric)
|
||||||
|
|
||||||
|
const maybe = curry((parser, state) => {
|
||||||
|
const [original, clone] = fork(state)
|
||||||
|
const result = parser(clone)
|
||||||
|
return result.isOk ? result : succeed([], original)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
1
vendor/kojima
vendored
Submodule
1
vendor/kojima
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1402219aad22f55c39ac448bff372474a17f5c00
|
Loading…
Add table
Reference in a new issue