This commit is contained in:
Rowan 2025-07-02 11:13:04 -04:00
parent 3a668ccad4
commit b880de0b08
49 changed files with 998 additions and 3527 deletions

0
dist/index.d.ts vendored
View file

1712
dist/index.js vendored

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
{
"compilerOptions": {
"strict": true,
"moduleResolution": "node",
"module": "es2020",
"target": "es6",
"lib": ["es2022", "dom"],
"checkJs": false,
},
"exclude": [
"node_modules"
]
}

496
package-lock.json generated
View file

@ -1,496 +0,0 @@
{
"name": "kuebiko",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kuebiko",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
"kojima": "git+https://git.kitsu.cafe/rowan/kojima.git"
},
"devDependencies": {
"esbuild": "^0.25.2",
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git",
"typescript": "^5.8.3"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/esbuild": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.2",
"@esbuild/android-arm": "0.25.2",
"@esbuild/android-arm64": "0.25.2",
"@esbuild/android-x64": "0.25.2",
"@esbuild/darwin-arm64": "0.25.2",
"@esbuild/darwin-x64": "0.25.2",
"@esbuild/freebsd-arm64": "0.25.2",
"@esbuild/freebsd-x64": "0.25.2",
"@esbuild/linux-arm": "0.25.2",
"@esbuild/linux-arm64": "0.25.2",
"@esbuild/linux-ia32": "0.25.2",
"@esbuild/linux-loong64": "0.25.2",
"@esbuild/linux-mips64el": "0.25.2",
"@esbuild/linux-ppc64": "0.25.2",
"@esbuild/linux-riscv64": "0.25.2",
"@esbuild/linux-s390x": "0.25.2",
"@esbuild/linux-x64": "0.25.2",
"@esbuild/netbsd-arm64": "0.25.2",
"@esbuild/netbsd-x64": "0.25.2",
"@esbuild/openbsd-arm64": "0.25.2",
"@esbuild/openbsd-x64": "0.25.2",
"@esbuild/sunos-x64": "0.25.2",
"@esbuild/win32-arm64": "0.25.2",
"@esbuild/win32-ia32": "0.25.2",
"@esbuild/win32-x64": "0.25.2"
}
},
"node_modules/folktest": {
"version": "1.0.0",
"resolved": "git+https://git.kitsu.cafe/rowan/folktest.git#cbf48ff3b1334eb883f202a77a5bc89d24534520",
"dev": true,
"license": "GPL-3.0-or-later"
},
"node_modules/izuna": {
"version": "1.0.0",
"resolved": "git+https://git.kitsu.cafe/rowan/izuna.git#4ab7c265d83856f2dc527780a3ac87b3d54676f1",
"license": "GPL-3.0-or-later"
},
"node_modules/kojima": {
"version": "1.0.0",
"resolved": "git+https://git.kitsu.cafe/rowan/kojima.git#b997bca5e31323bb81b38fafe5f823cae6e574de",
"license": "GPL-3.0-or-later",
"dependencies": {
"esbuild": "^0.25.2",
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
"typescript": "^5.8.2"
}
},
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}

View file

@ -1,24 +1,13 @@
{ {
"name": "kuebiko", "name": "kuebiko",
"version": "1.0.0", "version": "2.0.0",
"type": "module",
"main": "./src/index.js",
"types": "./typings/index.d.ts",
"author": "Rowan <rowan@kitsu.cafe> (https://kitsu.cafe)", "author": "Rowan <rowan@kitsu.cafe> (https://kitsu.cafe)",
"type": "module",
"main": "index.js",
"scripts": { "scripts": {
"test": "./tests/index.js", "test": "node --test"
"build": "esbuild ./src/index.js --bundle --outfile=dist/index.js"
}, },
"keywords": [], "keywords": ["parser", "functional", "combinator"],
"license": "ISC", "license": "MIT",
"description": "", "description": ""
"devDependencies": {
"esbuild": "^0.25.2",
"folktest": "git+https://git.kitsu.cafe/rowan/folktest.git",
"typescript": "^5.8.3"
},
"dependencies": {
"izuna": "git+https://git.kitsu.cafe/rowan/izuna.git",
"kojima": "git+https://git.kitsu.cafe/rowan/kojima.git"
}
} }

View file

@ -1,83 +0,0 @@
import { chain, curry } from 'izuna'
import { any, map, seq } from './combinator.js'
import { not } from './cond.js'
import { Digits, LowerAlpha, UpperAlpha } from './const.js'
import { clone, fail, join, mapStr, next, succeed } from './fn.js'
import { ok } from 'kojima'
/** @import { ParserState } from './state.js' */
export const char = curry(
/**
* @param {string} ch
* @param {ParserState} state
*/
(ch, state) => {
return next(state) === ch ? succeed(ch, state) : fail(`could not parse ${ch} `, state)
})
export const tag = curry(
/**
* @param {string} str
* @param {ParserState} state
*/
(str, state) => (
map(
join(''),
seq(...mapStr(char, str))
)(state)
))
export const charNoCase = curry((ch, state) => {
return any(
char(ch.toLowerCase()),
char(ch.toUpperCase())
)(state)
})
export const tagNoCase = curry(
(str, state) => (
map(
join(''),
seq(...mapStr(charNoCase, str))
)(state)
))
export const anyChar = state => {
const ch = next(state)
return !!ch ? succeed(ch, state) : fail('end of input', state)
}
export const take = curry(
(limit, state) => {
let res = ok(state)
while (res.isOk() && limit > 0) {
res = chain(anyChar, res)
limit -= 1
}
return res
}
)
export const oneOf = curry(
/**
* @param {string} str
* @param {ParserState} state
*/
(str, state) => (
any(...mapStr(char, str))(state)
))
export const noneOf = curry((str, state) => seq(not(any(oneOf(str), eof)), anyChar)(state))
export const digit = oneOf(Digits)
export const lowerAlpha = oneOf(LowerAlpha)
export const upperAlpha = oneOf(UpperAlpha)
export const alpha = any(lowerAlpha, upperAlpha)
export const alphanumeric = any(alpha, digit)
export const eof = state => {
return clone(state).next().done ? succeed([], state) : fail('not end of stream', state)
}

View file

@ -1,11 +0,0 @@
export const Clone = Symbol('clone')
export const clone = target => {
if (target[Clone]) {
return target[Clone]()
} else {
return structuredClone(target)
}
}

View file

@ -1,71 +0,0 @@
import { chain, curry } from 'izuna'
import { ok } from 'kojima'
import { clone, diff, fail, succeed } from './fn.js'
import { state as State } from './state.js'
import { skip } from './cond.js'
/** @import { ParserState } from './state.js' */
/**
* @param {...Parser} parsers
*/
export const seq = (...parsers) =>
/** @param {ParserState} state */
state => {
let acc = ok(state)
for (const parser of parsers) {
if (acc.isOk()) {
acc = chain(parser, acc)
} else {
break
}
}
return acc
}
/**
* @param {...any} parsers
*/
export const any = (...parsers) =>
/**
* @param {ParserState} state
*/
state => {
for (const parser of parsers) {
//console.log('combinator.js::35/any:', state)
const result = parser(clone(state))
if (result.isOk()) {
return result
}
}
return fail('no matching parsers', state)
}
export const map = curry(
/**
* @param {(...args: any[]) => any} fn
* @param {Parser} parser
* @param {ParserState} state
*/
(fn, parser, state) => {
return parser(state).chain(result => {
try {
/** @type {Result<ParserState, ParseError>} */
const parsed = fn(diff(state[0], result[0]))
const backtrack = State(result[1], state[0])
return succeed(parsed, backtrack)
} catch (e) {
return fail('failed to map', state, e)
}
})
})
export const delimited = curry((first, second, third, state) => seq(skip(first), second, skip(third))(state))
export const preceded = curry((first, second, state) => seq(skip(first), second)(state))
export const terminated = curry((first, second, state) => seq(first, skip(second))(state))
export const separatedPair = curry((first, delimiter, second, state) => seq(first, skip(delimiter), second)(state))

View file

@ -1,55 +0,0 @@
import { ParseError } from './state.js'
import { clone, fail, succeed } from './fn.js'
import { anyChar } from './byte.js'
import { ok } from 'kojima'
import { chain, curry, pipe } from 'izuna'
/** @import { Result } from '../vendor/kojima/src/index.js' */
/** @import { ParserState } from './state.js' */
export const maybe = curry(
/**
* @param {(...args: any[]) => Result<ParserState, ParseError>} parser
* @param {ParserState} state
*/
(parser, state) => {
const result = parser(clone(state))
return result.isOk() ? result : succeed([], state)
})
export const not = curry((parser, state) => {
const result = parser(clone(state))
if (result.isOk()) {
return fail(`'not' parser failed`, state)
} else {
return succeed([], state)
}
})
export const until = curry((parser, state) => {
let result = ok(state)
while (result.isOk()) {
result = result.chain(pipe(clone, parser))
if (result.isOk()) {
break
} else {
result = anyChar(state)
}
}
return result
})
export const verify = curry((parser, predicate, state) => {
const result = chain(parser, clone(state))
return chain(x => (
predicate(x) ? result : fail('verification failed', state)
), result)
})
export const skip = curry((parser, state) => {
return chain(_ => succeed([], state), parser(state))
})

View file

@ -1,6 +0,0 @@
export const LowerAlpha = 'abcdefghijklmnopqrstuvwxyz'
export const UpperAlpha = LowerAlpha.toUpperCase()
export const Alpha = LowerAlpha + UpperAlpha
export const Digits = '1234567890'
export const Alphanumeric = Alpha + Digits

View file

@ -1,69 +0,0 @@
import { err, ok } from 'kojima'
import { concat, curry } from 'izuna'
import { ParseError, state } from './state.js'
import { IndexableIterator } from './iter.js'
/** @import { ParserState } from './state.js' */
export const iter = value => new IndexableIterator(value)
/**
* @param {ParserState} state
*/
export const clone = (state) => state.clone()
/**
* @template T
* @param {T | T[]} v
* @param {ParserState} state
*/
export const succeed = (v, [x, y]) => {
return ok(state(y, concat(v, x)))
}
/**
* @param {string} msg
* @param {ParserState} state
* @param {Error} [e]
*/
export const fail = (msg, state, e = undefined) => err(new ParseError(msg, state, e))
/**
* @template T
* @param {number & keyof T} n
* @param {T[]} iter
*/
export const nth = (n, iter) => iter[n]
/**
* @param {ParserState} state
*/
export const next = state => state.next().value
/**
* @template T
* @param {T[]} a
* @param {T[]} b
*/
export const diff = (a, b) => b.slice(-Math.max(0, b.length - a.length))
export const join = curry(
/**
* @param {string} delim
* @param {string[]} val
*/
(delim, val) => val.join(delim)
)
export const mapStr = curry(
/**
* @param {(...args: any[]) => any} fn
* @param {string} str
*/
(fn, str) => Array.from(str).flatMap(x => {
return fn(x)
})
)

View file

@ -1,6 +0,0 @@
export * from './byte.js'
export * from './combinator.js'
export * from './cond.js'
export * from './multi.js'
export * from './state.js'

View file

@ -1,418 +0,0 @@
import { Clone } from './clone.js'
export class IndexableIterator extends Iterator {
_value
_index
constructor(value, index = 0) {
super()
this._value = value
this._index = index
}
clone() {
return this[Clone]()
}
done() {
return this._index >= this._value.length
}
next() {
if (this.done()) {
return { value: undefined, done: true }
} else {
return { value: this._value[this._index++], done: false }
}
}
[Clone]() {
return new IndexableIterator(
this._value,
this._index
)
}
[Symbol.iterator]() {
return this.clone()
}
}
/**
* @template T
* @implements Iterator<T>
* @implements Iterable<T>
*/
class Iter {
/**
* @template T
* @param {any} value
* @returns {value is Iterable<T>}
*/
static _isIterable(value) {
return typeof value[Symbol.iterator] === 'function'
}
/**
* @template T
* @param {T} value
*/
static from(value) {
if (value instanceof Iter) {
return value
}
if (Array.isArray(value) || typeof value === 'string') {
return new IndexableIterator(value)
}
if (Iter._isIterable(value)) {
const iterator = value[Symbol.iterator]()
if (iterator instanceof Iter) {
return iterator
}
return new this(iterator, value)
}
throw new TypeError('object is not an iterator')
}
/**
* @param {number} limit
*/
drop(limit) {
return new DropIter(this, limit)
}
/**
* @param {(value: T, index: number) => boolean} callbackFn
*/
every(callbackFn) {
let next = this.next()
let index = 0
let result = true
while (!next.done) {
if (!callbackFn(next.value, index)) {
result = false
break
}
next = this.next()
index += 1
}
this.return()
return result
}
/**
* @param {(value: T, index: number) => boolean} callbackFn
*/
filter(callbackFn) {
return new FilterIter(this, callbackFn)
}
/**
* @param {(value: T, index: number) => boolean} callbackFn
*/
find(callbackFn) {
let next = this.next()
let index = 0
while (!next.done) {
if (callbackFn(next.value, index)) {
this.return()
return next.value
}
next = this.next()
index += 1
}
}
/**
* @param {<U>(value: T, index: number) => U} callbackFn
*/
flatMap(callbackFn) {
return new FlatMapIter(this, callbackFn)
}
/**
* @param {(value: T, index: number) => void} callbackFn
*/
forEach(callbackFn) {
let next = this.next()
let index = 0
while (!next.done) {
callbackFn(next.value, index)
next = this.next()
index += 1
}
}
/**
* @param {<U>(value: T, index: number) => U} callbackFn
*/
map(callbackFn) {
return new MapIter(this, callbackFn)
}
/**
* @template U
* @param {(accumulator: U, value: T, index: number) => U} callbackFn
* @param {U} init
*/
reduce(callbackFn, init) {
let next = this.next()
let index = 0
let acc = init
while (!next.done) {
acc = callbackFn(acc, next.value, index)
next = this.next()
index += 1
}
this.return()
return acc
}
/**
* @param {(value: T, index: number) => boolean} callbackFn
*/
some(callbackFn) {
let next = this.next()
let index = 0
let result = false
while (!next.done) {
if (callbackFn(next.value, index)) {
result = true
break
}
next = this.next()
index += 1
}
this.return()
return result
}
/**
* @param {number} limit
*/
take(limit) {
return new TakeIter(this, limit)
}
/*
* @returns {T[]}
*/
toArray() {
/** @type {T[]} */
const result = []
for (const item of this) {
result.push(item)
}
return result
}
*[Symbol.iterator]() {
return this
}
}
/**
* @template T
* @extends Iter<T>
*/
class DropIter extends Iter {
_limit
/**
* @param {Iterator<T>} iterator
* @param {number} limit
*/
constructor(iterator, limit) {
super(iterator)
this._limit = limit
}
/**
* @param {any} value
*/
next(value) {
for (let i = this._limit; i > 0; i--) {
const next = super.next(value)
if (next.done) {
return next
}
}
return super.next(value)
}
}
/**
* @template T
* @extends Iter<T>
*/
class FilterIter extends Iter {
_filter
_index = 0
/**
* @param {Iterator<T>} iterator
* @param {(value: T, index: number) => boolean} callbackFn
*/
constructor(iterator, callbackFn) {
super(iterator)
this._filter = callbackFn
}
/**
* @param {any} [value]
* @returns {IteratorResult<T>}
*/
next(value) {
let next = super.next(value)
while (!next.done && !this._filter(next.value, this._index)) {
next = super.next(value)
this._index += 1
}
return next
}
}
/**
* @template T
* @extends Iter<T>
*/
class FlatMapIter extends Iter {
_flatMap
_index = 0
/** @type {Iterator<T> | undefined} */
_inner = undefined
/**
* @param {Iterator<T>} iterator
* @param {<U>(value: T, index: number) => Iterator<U> | Iterable<U>} callbackFn
*/
constructor(iterator, callbackFn) {
super(iterator)
this._flatMap = callbackFn
}
/**
* @param {any} value
* @returns {IteratorResult<T>}
*/
next(value) {
if (this._inner) {
const innerResult = this._inner.next(value)
if (!innerResult.done) {
this._index += 1
return { value: innerResult.value, done: false }
}
this._inner = undefined
}
const outerResult = super.next(value)
if (outerResult.done) {
return { value: undefined, done: true }
}
const nextIterable = this._flatMap(outerResult.value, this._index || 0)
if (Iter._isIterable(nextIterable)) {
this._inner = Iter.from(nextIterable)
return this.next(value)
} else {
throw new TypeError('value is not an iterator')
}
}
}
/**
* @template T
* @extends Iter<T>
*/
class MapIter extends Iter {
_map
_index = 0
/**
* @param {Iterator<T>} iterator
* @param {<U>(value: T, index: number) => U} callbackFn
*/
constructor(iterator, callbackFn) {
super(iterator)
this._map = callbackFn
}
/** @param {any} value */
next(value) {
let next = super.next(value)
if (next.done) {
return next
}
const result = {
done: false,
value: this._map(next.value, this._index)
}
this._index += 1
return result
}
}
/**
* @template T
* @extends Iter<T>
*/
class TakeIter extends Iter {
_limit
/**
* @param {Iterator<T>} iterator
* @param {number} limit
*/
constructor(iterator, limit) {
super(iterator)
this._limit = limit
}
/**
* @param {any} value
* @returns {IteratorResult<T>}
*/
next(value) {
if (this._limit > 0) {
const next = super.next(value)
if (!next.done) {
this._limit -= 1
}
return next
}
return { value: undefined, done: true }
}
}

63
src/iterator.js Normal file
View file

@ -0,0 +1,63 @@
import { Option } from './option.js'
import { range, tee } from './utils.js'
export class Stream {
_iterator
_index
constructor(iterator, index = 0) {
this._iterator = Iterator.from(iterator)
this._index = index
}
get index() {
return this._index
}
clone() {
const [a, b] = tee(this._iterator)
this._iterator = a
return new Stream(b, this.index)
}
consume() {
return Option.from(this.next().value)
}
*take(limit = 1) {
for (const _index of range(limit)) {
const next = this.next()
if (next.done) { return }
yield next.value
}
}
drop(limit = 1) {
while (limit > 0 && this.consume().isSome()) {
limit -= 1
}
return this
}
peek() {
const [a, b] = tee(this._iterator)
this._iterator = a
return Option.from(b.next().value)
}
next() {
const result = this._iterator.next()
if (!result.done) {
this._index += 1
}
return result
}
[Symbol.iterator]() {
return this
}
}

View file

@ -1,68 +0,0 @@
import { ok } from 'kojima'
import { curry, } from 'izuna'
import { clone } from './fn.js'
import { verify } from './cond.js'
import { anyChar } from './byte.js'
/** @import { ParseError, ParserState } from './state.js' */
/** @import { Result } from '../vendor/kojima/src/index.js' */
export const takeWhile = curry((predicate, state) => {
let result = ok(state)
while (result.isOk()) {
result = verify(anyChar, predicate, state)
}
return result
})
export const takeUntil = curry((parser, state) => not(takeWhile(parser, state)))
export const skip = curry((parser, state) => {
const tokens = state[0]
const result = parser(state)
if (result.isOk()) {
return result.map(other => [tokens, other[1]])
} else {
return result
}
})
export const many = curry((parser, state) => {
let result = ok(state)
while (true) {
const res = parser(clone(state))
if (res.isOk()) {
result = res
} else {
break
}
}
return result
})
export const many1 = parser => seq(parser, many(parser))
const _range = (start, end, step = 1, deny = []) => {
const len = end - start - deny.length + 1
const result = new Array(len)
for (let i = 0, n = start; i <= len; i += i, n += step) {
if (deny.includes(n)) { continue }
result[i] = n
}
return result
}
const code = s => s.codePointAt(0)
export const range = (start, end, deny = []) => {
return anyChar(
_range(code(start), code(end), deny.map(code)).map(String.fromCodePoint)
)
}

106
src/option.js Normal file
View file

@ -0,0 +1,106 @@
import { Result } from './result.js'
export class Option {
static some(value) {
return new Some(value)
}
static none() {
return None
}
static from(value) {
if (value == null) {
return None
} else {
return new Some(value)
}
}
isSome() {
return this instanceof Some
}
isNone() {
return this === None
}
}
export class Some extends Option {
#value
constructor(value) {
super()
this.#value = value
Object.freeze(this)
}
ap(value) {
return this.#value(value)
}
filter(predicate) {
if (predicate(this.#value)) {
return this
} else {
return None
}
}
andThen(fn) {
return fn(this.#value)
}
map(fn) {
return new Some(fn(this.#value))
}
okOr(_error) {
return Result.ok(this.#value)
}
okOrElse(_fn) {
return Result.ok(this.#value)
}
unwrap() {
return this.#value
}
}
class _None extends Option {
constructor() {
super()
Object.freeze(this)
}
ap(_value) {
return this
}
filter(_predicate) {
return this
}
map(_fn) {
return this
}
andThen(_fn) {
return this
}
okOr(error) {
return Result.err(error)
}
okOrElse(fn) {
return Result.err(fn())
}
unwrap() {
throw new UnwrapError('attempted to unwrap a None value')
}
}
export const None = new _None()

194
src/parser.js Normal file
View file

@ -0,0 +1,194 @@
import { Stream } from './iterator.js'
import { Result } from './result.js'
import { curry, range } from './utils.js'
import * as string from './string.js'
export class ParseError extends Error {
name
index
description
constructor(index, description) {
super()
this.name = 'ParseError'
this.index = index
this.description = description
}
}
export class EofError extends ParseError {
name = 'EofError'
description = 'End of stream reached'
}
// convenience factory functions and cached callbacks
const eofErr = index => () => new EofError(index)
const parseErr = (index, desc) => () => new ParseError(index, desc)
const eq = a => b => a === b
const toString = value => value.toString()
export const pure = curry((value, input) => Result.ok([value, input]))
export const fail = curry((error, input) => Result.err(new ParseError(input.index, error)))
export const anyItem = () => input => {
return input.peek().okOrElse(eofErr(input.index))
.map(value => [value, input.drop()])
}
export const satisfy = curry((predicate, input) => {
return input.peek()
.okOrElse(eofErr(input.index))
.filterOrElse(
predicate,
value => parseErr(input.index, `Value did not match predicate: ${value}`)
)
.map(value => [value, input.drop()])
})
export const literal = curry((value, input) => (
satisfy(eq(value), input)
))
export const bind = curry((parser, transform, input) =>
parser(input).andThen(([value, rest]) => transform(value)(rest))
)
export const map = curry((parser, morphism, input) => (
parser(input).map(([value, rest]) => [morphism(value), rest])
))
export const seq = curry((a, b, input) => (
bind(a, x => map(b, y => [x, y]), input)
))
export const alt = (...parsers) => input => {
for (const p of parsers) {
const result = p(input.clone())
if (result.isOk()) {
return result
}
}
return Result.err(new ParseError(input.index, "No parsers matched alt"))
}
export const many = curry((parser, input) => {
const results = []
let stream = input
while (true) {
const result = parser(stream.clone())
if (result.isOk()) {
const [value, rest] = result.unwrap()
results.push(value)
stream = rest
} else {
break
}
}
return Result.ok([results, stream])
})
export const many1 = curry((parser, input) => (
parser(input).andThen(([first, rest]) => (
many(parser, rest).map(([others, rest]) => (
[[first, ...others], rest]
))
))
))
export const optional = curry((parser, input) =>
parser(input.clone()).orElse(() => Result.ok([undefined, input]))
)
export const eof = () => input => (
anyItem()(input)
.andThen(([value, rest]) =>
Result.err(
new ParseError(rest.index, `Expected EOF, found ${value}`)
)
)
.orElse(err => {
if (err instanceof EofError) {
return Result.ok([null, input])
} else {
return Result.err(err)
}
})
)
export const take = curry((limit, input) => {
const result = []
const next = anyItem()
let stream = input
for (const _index of range(limit)) {
const nextResult = next(stream)
if (nextResult.isOk()) {
const [value, rest] = nextResult.unwrap()
result.push(value)
stream = rest
} else {
return nextResult
}
}
return Result.ok([result, stream])
})
export const drop = curry((limit, input) =>
skip(take(limit), input)
)
export const skip = curry((parser, input) =>
parser(input).map(([_, rest]) => [undefined, rest])
)
const streamInfo = (stream, n = 3) => {
const clone = stream.clone()
const values = clone.take(n).map(toString).join(', ')
const hasMore = clone.peek().isSome()
const ellip = hasMore ? '...' : ''
return `Stream { index = ${stream.index}, values = [${values}${ellip}]`
}
export const trace = curry((parser, label, input) => {
const name = label || parser.name || 'anonymous parser'
console.log(`trace(parser = ${name}, input = ${streamInfo(input)})`)
const result = parser(input)
if (result.isOk()) {
const [value, rest] = result.unwrap()
console.log(` success: value = ${toString(value)}, remaining = ${streamInfo(rest)}`)
} else {
const err = result.unwrapErr()
console.log(` fail: error = ${err}`)
}
})
// cached parsers
const LetterParser = alt(...Array.from(string.AsciiLetters).map(x => literal(x)))
const DigitParser = alt(...Array.from(string.Digits).map(x => literal(x)))
const WhitespaceParser = alt(...Array.from(string.Whitespace).map(x => literal(x)))
export const letter = () => LetterParser
export const digit = () => DigitParser
export const whitespace = () => WhitespaceParser
export const parseSome = curry((parser, value) =>
parser(new Stream(value))
)
export const parse = curry((parser, value) => {
const result = parseSome(seq(parser, eof()), value)
if (result.isOk()) {
const [[value, _rest], _stream] = result.unwrap()
return Result.ok(value)
} else {
return result
}
})

163
src/result.js Normal file
View file

@ -0,0 +1,163 @@
import { None } from './option.js'
class UnwrapError extends Error {
constructor(message) {
super(message)
this.name = 'UnwrapError'
if (Error.captureStackTrace) {
Error.captureStackTrace(this, UnwrapError)
}
}
}
export class Result {
static ok(value) {
return new Ok(value)
}
static err(error) {
return new Err(error)
}
isOk() {
return this instanceof Ok
}
isErr() {
return this instanceof Err
}
}
export class Ok extends Result {
#value
constructor(value) {
super()
this.#value = value
Object.freeze(this)
}
ap(value) {
return this.#value(value)
}
andThen(fn) {
return fn(this.#value)
}
map(fn) {
return new Ok(fn(this.#value))
}
filterOr(predicate, err) {
if (predicate(this.#value)) {
return this
} else {
return Result.err(err)
}
}
filterOrElse(predicate, fn) {
if (predicate(this.#value)) {
return this
} else {
return Result.err(fn(this.#value))
}
}
inspect(fn) {
fn(this.#value)
return this
}
ok() {
return Option.some(this.#value)
}
err() {
return None
}
orOther(_value) {
return this
}
orElse(_fn) {
return this
}
unwrap() {
return this.#value
}
unwrapErr() {
throw new UnwrapError('attempted to unwrapErr an Ok value')
}
toString() {
return `Ok(${this.#value})`
}
}
export class Err extends Result {
#error
constructor(error) {
super()
this.#error = error
Object.freeze(this)
}
ap(_value) {
return this
}
andThen(_fn) {
return this
}
map(_fn) {
return this
}
filterOr(_predicate, _err) {
return this
}
filterOrElse(_predicate, _fn) {
return this
}
inspect(_fn) {
return this
}
ok() {
return None
}
err() {
return Option.some(this.#error)
}
orOther(value) {
return value
}
orElse(fn) {
return fn(this.#error)
}
unwrap() {
throw new UnwrapError('attempted to unwrap an Err value')
}
unwrapErr() {
return this.#error
}
toString() {
return `Err(${this.#error})`
}
}

View file

@ -1,60 +0,0 @@
import { ok } from 'kojima'
import { chain, compose, curry, pipe } from 'izuna'
import { eof } from './byte.js'
import { Tuple } from './tuple.js'
import { iter } from './fn.js'
import { Clone } from './clone.js'
/**
* @typedef {Readonly<[any[], Iterator<any>]>} ParserState
*/
export class ParseError extends Error {
/**
* @param {string} message
* @param {ParserState} state
* @param {Error} [cause]
*/
constructor(message, state, cause) {
super(message, { cause })
this.state = state
}
}
export class State extends Tuple {
constructor(remaining, read = []) {
super([['read', read], ['remaining', remaining]])
}
static from(values) {
return new State(iter(values))
}
next() {
return this.remaining.next()
}
toString() {
return `State(${this.read}, ${[...this.clone().remaining]})`
}
[Clone]() {
return Object.freeze(new State(this.remaining.clone(), this.read))
}
[Symbol.iterator]() {
return super[Symbol.iterator]()
}
}
export const state = (remaining, read) => new State(remaining, read)
export const parse = curry((parser, input) => pipe(
iter,
state,
ok,
chain(parser),
)(input))
export const parseAll = curry((parser, input) => compose(eof, parse(parser), input))

10
src/string.js Normal file
View file

@ -0,0 +1,10 @@
export const LowerAscii = 'abcdefghijklmnopqrstuvwxyz'
export const UpperAscii = LowerAscii.toLocaleLowerCase()
export const AsciiLetters = LowerAscii + UpperAscii
export const HexDigits = '0123456789abcdefABCDEF'
export const OctDigits = '01234567'
export const Punctuation = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~.'
export const Whitespace = ' \t\r\n\x0c\x0b'
export const Digits = '0123456789'
export const Printable = Digits + AsciiLetters + Punctuation + Whitespace

View file

@ -1,61 +0,0 @@
import { entries } from 'izuna'
import { Clone } from './clone.js'
import { IndexableIterator } from './iter.js'
/**
* @param {...any} values
*/
export class Tuple {
_values
_length
get length() {
return this._length
}
constructor(values) {
if (values.length > 0) {
this._length = values.length
values.forEach(this._setEntry.bind(this))
}
this._values = values
}
_setEntry([key, value], index) {
if (typeof key !== 'number') {
Object.defineProperty(this, key, {
get() {
return this[index]
}
})
}
this[index] = value
}
static from(values) {
return Object.freeze(new this(entries(values)))
}
toString() {
return `(${this._values.map(([_, v]) => v.toString())})`
}
clone() {
return this[Clone]()
}
[Clone]() {
return Object.freeze(new this.constructor(this._values))
}
[Symbol.iterator]() {
return new IndexableIterator(this._values.map(([_, v]) => v))
}
}
export const tuple = Tuple.from

41
src/utils.js Normal file
View file

@ -0,0 +1,41 @@
export const curry = (fn, arity = fn.length) => {
return function curried(...args) {
if (args.length >= arity) {
return fn.apply(this, args)
} else {
return function(...nextArgs) {
return curried.apply(this, args.concat(nextArgs))
}
}
}
}
export function* range(start, stop, step = 1) {
if (stop == null) {
stop = start
start = 0
}
for (let i = start; i < stop; i += step) {
yield i
}
}
export function 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)
}

205
tests/combinators.test.js Normal file
View file

@ -0,0 +1,205 @@
import { drop, skip, literal, bind, seq, many, parse, parseSome, ParseError, EofError, map, alt, many1, optional } from '../src/parser.js'
import { Err } from '../src/result.js'
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
const eq = (a, b) => assert.deepStrictEqual(a, b)
const parseErr = (i, desc) => new Err(new ParseError(i, desc))
const eof = (i = 0) => new Err(new EofError(i))
const streamState = stream => {
return [stream.index, Array.from(stream.clone())]
}
describe('Combinators', () => {
it('bind', () => {
const a = literal('a')
const b = literal('b')
// simple parser
const p = bind(a, () => b)
let res = parseSome(p, 'ab')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, 'b')
eq(streamState(r1), [2, []])
// complex parser
const pab = bind(a, x => map(b, y => `${x}${y}`))
res = parseSome(pab, 'abc')
const [v2, r2] = res.unwrap()
assert(res.isOk())
eq(v2, 'ab')
eq(streamState(r2), [2, ['c']])
// fail on first parser
res = parseSome(p, 'xb')
eq(res, parseErr(0, 'Value did not match predicate: x'))
// fail on second parser
res = parseSome(p, 'ax')
eq(res, parseErr(1, 'Value did not match predicate: x'))
// eof on first parser
res = parseSome(p, '')
eq(res, eof())
// eof on second parser
res = parseSome(p, 'a')
eq(res, eof())
})
it('map', () => {
const p = map(literal('a'), char => char.toUpperCase())
let res = parseSome(p, 'abc')
assert(res.isOk())
const [value, rest] = res.unwrap()
eq(value, 'A')
eq(streamState(rest), [1, ['b', 'c']])
res = parseSome(p, 'xyz')
eq(res, parseErr(0, 'Value did not match predicate: x'))
})
it('seq', () => {
const p = seq(literal('a'), literal('b'))
let res = parseSome(p, 'abc')
assert(res.isOk())
const [value, rest] = res.unwrap()
eq(value, ['a', 'b'])
eq(streamState(rest), [2, ['c']])
res = parseSome(p, 'xbc')
eq(res, parseErr(0, 'Value did not match predicate: x'))
res = parseSome(p, 'axc')
eq(res, parseErr(1, 'Value did not match predicate: x'))
})
it('alt', () => {
const p = alt(literal('a'), literal('b'))
let res = parseSome(p, 'abc')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, 'a')
eq(streamState(r1), [1, ['b', 'c']])
res = parseSome(p, 'bac')
assert(res.isOk())
const [v2, r2] = res.unwrap()
eq(v2, 'b')
eq(streamState(r2), [1, ['a', 'c']])
res = parseSome(p, 'xyz')
eq(res, parseErr(1, 'No parsers matched alt'))
res = parseSome(alt(), 'abc')
eq(res, parseErr(0, 'No parsers matched alt'))
})
it('many', () => {
const p = many(literal('a'))
let res = parseSome(p, 'xyz')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, [])
eq(streamState(r1), [0, ['x', 'y', 'z']])
res = parseSome(p, 'abc')
assert(res.isOk())
const [v2, r2] = res.unwrap()
eq(v2, ['a'])
eq(streamState(r2), [1, ['b', 'c']])
res = parseSome(p, 'aaabc')
assert(res.isOk())
const [v3, r3] = res.unwrap()
eq(v3, ['a', 'a', 'a'])
eq(streamState(r3), [3, ['b', 'c']])
res = parse(p, 'aaa')
assert(res.isOk())
eq(res.unwrap(), ['a', 'a', 'a'])
res = parseSome(p, '')
assert(res.isOk())
const [v4, r4] = res.unwrap()
eq(v4, [])
eq(streamState(r4), [0, []])
})
it('many1', () => {
const p = many1(literal('a'))
let res = parseSome(p, 'abc')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, ['a'])
eq(streamState(r1), [1, ['b', 'c']])
res = parseSome(p, 'aaab')
assert(res.isOk())
const [v2, r2] = res.unwrap()
eq(v2, ['a', 'a', 'a'])
eq(streamState(r2), [3, ['b']])
res = parseSome(p, 'xyz')
eq(res, parseErr(0, 'Value did not match predicate: x'))
res = parseSome(p, '')
eq(res, eof())
})
it('optional', () => {
const p = optional(literal('a'))
let res = parseSome(p, 'abc')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, 'a')
eq(streamState(r1), [1, ['b', 'c']])
res = parseSome(p, 'xyz')
assert(res.isOk())
const [v2, r2] = res.unwrap()
eq(v2, undefined)
eq(streamState(r2), [0, ['x', 'y', 'z']])
res = parseSome(p, '')
assert(res.isOk())
const [v3, r3] = res.unwrap()
eq(v3, undefined)
eq(streamState(r3), [0, []])
})
it('drop', () => {
const p = drop(1)
let res = parseSome(p, 'abc')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, undefined)
eq(streamState(r1), [1, ['b', 'c']])
res = parseSome(p, '')
eq(res, eof())
})
it('skip', () => {
const p = skip(literal('a'))
let res = parseSome(p, 'abc')
assert(res.isOk())
const [v1, r1] = res.unwrap()
eq(v1, undefined)
eq(streamState(r1), [1, ['b', 'c']])
res = parseSome(p, 'xyz')
eq(res, parseErr(0, 'Value did not match predicate: x'))
res = parseSome(p, '')
eq(res, eof())
})
})

2
tests/index.d.ts vendored
View file

@ -1,2 +0,0 @@
#!/usr/bin/env node
export {};

View file

@ -1,7 +0,0 @@
#!/usr/bin/env node
import { TerminalRunner } from 'folktest'
import * as Tests from './units/index.js'
console.log(TerminalRunner(Tests).toString())

127
tests/iterator.test.js Normal file
View file

@ -0,0 +1,127 @@
import { Stream } from '../src/iterator.js'
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
describe('Stream class', () => {
it('should iterate a range', () => {
const stream = new Stream([1, 2, 3])
assert.equal(stream.index, 0)
assert.deepStrictEqual(stream.next(), { value: 1, done: false })
assert.equal(stream.index, 1)
assert.deepStrictEqual(stream.next(), { value: 2, done: false })
assert.equal(stream.index, 2)
assert.deepStrictEqual(stream.next(), { value: 3, done: false })
assert.equal(stream.index, 3)
assert.deepStrictEqual(stream.next(), { value: undefined, done: true })
assert.equal(stream.index, 3)
})
it('should handle an empty stream', () => {
const stream = new Stream([])
assert.deepStrictEqual(stream.next(), { value: undefined, done: true })
assert.equal(stream.index, 0)
})
it('should handle a single item', () => {
const stream = new Stream([1])
assert.equal(stream.index, 0)
assert.deepStrictEqual(stream.next(), { value: 1, done: false })
assert.equal(stream.index, 1)
assert.deepStrictEqual(stream.next(), { value: undefined, done: true })
assert.equal(stream.index, 1)
})
it('should should an independent stream', () => {
const stream = new Stream([1, 2, 3])
assert.deepStrictEqual(stream.next(), { value: 1, done: false })
assert.equal(stream.index, 1)
const clone = stream.clone()
assert.equal(clone.index, 1)
assert.deepStrictEqual(clone.next(), { value: 2, done: false })
assert.equal(clone.index, 2)
assert.deepStrictEqual(clone.next(), { value: 3, done: false })
assert.equal(clone.index, 3)
assert.deepStrictEqual(stream.next(), { value: 2, done: false })
assert.equal(stream.index, 2)
})
it('should support multiple clones', () => {
const stream = new Stream('abcdefg')
stream.next()
stream.next()
assert.deepStrictEqual(stream.index, 2)
const clone1 = stream.clone()
assert.deepStrictEqual(clone1.index, 2)
stream.next()
stream.next()
assert.deepStrictEqual(stream.index, 4)
const clone2 = stream.clone()
assert.deepStrictEqual(clone2.index, 4)
assert.deepStrictEqual(clone1.next(), { value: 'c', done: false })
assert.deepStrictEqual(clone1.next(), { value: 'd', done: false })
assert.deepStrictEqual(clone1.next(), { value: 'e', done: false })
assert.deepStrictEqual(clone1.index, 5)
assert.deepStrictEqual(clone2.next(), { value: 'e', done: false })
assert.deepStrictEqual(clone2.next(), { value: 'f', done: false })
assert.deepStrictEqual(clone2.index, 6)
assert.deepStrictEqual(stream.next(), { value: 'e', done: false })
assert.deepStrictEqual(stream.index, 5)
})
it('should yield a value on consume', () => {
const stream = new Stream('abc')
assert.deepStrictEqual(stream.consume().unwrap(), 'a')
assert.deepStrictEqual(stream.consume().unwrap(), 'b')
assert.deepStrictEqual(stream.consume().unwrap(), 'c')
assert(stream.consume().isNone())
})
it('should take from a stream', () => {
const stream5 = new Stream([1, 2, 3, 4, 5])
const take3 = stream5.take(3)
assert.deepStrictEqual(Array.from(take3), [1, 2, 3])
assert.deepStrictEqual(stream5.index, 3)
assert.deepStrictEqual(Array.from(stream5), [4, 5])
const emptyStream = new Stream([])
const take0 = emptyStream.take(3)
assert.deepStrictEqual(Array.from(take0), [])
assert.deepStrictEqual(emptyStream.index, 0)
assert.deepStrictEqual(Array.from(emptyStream), [])
const streamDefault = new Stream('abc')
const genDefault = streamDefault.take()
assert.deepStrictEqual(Array.from(genDefault), ['a'])
assert.deepStrictEqual(streamDefault.index, 1)
assert.deepStrictEqual(Array.from(streamDefault), ['b', 'c'])
const stream3 = new Stream([10, 20, 30])
const genZero = streamDefault.take(0)
assert.deepStrictEqual(Array.from(genZero), [])
assert.deepStrictEqual(stream3.index, 0)
assert.deepStrictEqual(Array.from(stream3), [10, 20, 30])
})
})

82
tests/primitives.test.js Normal file
View file

@ -0,0 +1,82 @@
import { pure, fail, anyItem, satisfy, literal, parse, parseSome, ParseError, EofError } from '../src/parser.js'
import { Err, Result } from '../src/result.js'
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
const eq = (a, b) => assert.deepStrictEqual(a, b)
const parseErr = (i, desc) => new Err(new ParseError(i, desc))
const eof = (i = 0) => new Err(new EofError(i))
const streamState = stream => {
return [stream.index, Array.from(stream.clone())]
}
describe('Primitive parsers', () => {
it('pure', () => {
const result = parseSome(pure(123), 'abc')
const [value, stream] = result.unwrap()
eq(value, 123)
eq(streamState(stream), [0, ['a', 'b', 'c']])
const empty = parse(pure(123), [])
eq(empty, Result.ok(123))
})
it('fail', () => {
const msg = 'Expected failure'
const parser = fail(msg)
const expected = parseErr(0, msg)
eq(parse(parser, 'abc'), expected)
eq(parse(parser, ''), expected)
})
it('anyItem', () => {
const parser = anyItem()
eq(parse(parser, 'a'), Result.ok('a'))
const many = parseSome(parser, 'abc')
assert(many.isOk())
const [value, rest] = many.unwrap()
eq(value, 'a')
eq(streamState(rest), [1, ['b', 'c']])
eq(parse(parser, ''), eof())
})
it('satisfy', () => {
const pa = satisfy(x => x === 'a')
const result = parseSome(pa, 'abc')
assert(result.isOk())
const [value, rest] = result.unwrap()
eq(value, 'a')
eq(streamState(rest), [1, ['b', 'c']])
const err = parse(pa, 'cba')
eq(err, parseErr(0, 'Value did not match predicate: c'))
const eofErr = parse(pa, '')
eq(eofErr, eof())
})
it('literal', () => {
const px = literal('x')
const result = parseSome(px, 'xyz')
assert(result.isOk())
const [value, rest] = result.unwrap()
eq(value, 'x')
eq(streamState(rest), [1, ['y', 'z']])
const err = parse(px, 'yz')
eq(err, parseErr(0, 'Value did not match predicate: y'))
const eofErr = parse(px, '')
eq(eofErr, eof())
})
})

View file

@ -1 +0,0 @@
export const Byte: any[];

View file

@ -1,70 +0,0 @@
import { it, assert, assertEq } from 'folktest'
import { char, charNoCase, noneOf, oneOf, parse, tag, tagNoCase, take, takeWhile } from '../../src/index.js'
import { Alphanumeric } from '../../src/const.js'
import { chain, curry, map } from 'izuna'
import { assertState, parseEq } from './common.js'
export const Byte = [
it('char', () => {
const parser = char('a')
parseEq(parser, 'abc', ['a'])
assert(parse(parser, ' abc').isErr())
assert(parse(parser, 'bc').isErr())
assert(parse(parser, '').isErr())
}),
it('oneOf', () => {
parse(oneOf('abc'), 'b').chain(assertState('b'))
assert(parse(oneOf('a'), 'bc').isErr())
assert(parse(oneOf('a'), '').isErr())
}),
it('noneOf', () => {
parse(noneOf('abc'), 'z').chain(assertState(['z']))
assert(parse(noneOf('ab'), 'a').isErr())
assert(parse(noneOf('ab'), '').isErr())
}),
it('tag', () => {
const parser = tag('Hello')
parse(parser, 'Hello, World!').chain(assertState(['Hello']))
assert(parse(parser, 'Something').isErr())
assert(parse(parser, '').isErr())
}),
it('charNoCase', () => {
const parser = charNoCase('a')
parse(parser, 'abc').chain(assertState(['a']))
parse(parser, 'Abc').chain(assertState(['A']))
assert(parse(parser, ' abc').isErr())
assert(parse(parser, 'bc').isErr())
assert(parse(parser, '').isErr())
}),
it('tagNoCase', () => {
const parser = tagNoCase('hello')
parse(parser, 'Hello, World!').chain(assertState(['Hello']))
parse(parser, 'hello, World!').chain(assertState(['hello']))
parse(parser, 'HeLlO, World!').chain(assertState(['HeLlO']))
assert(parse(parser, 'Something').isErr())
assert(parse(parser, '').isErr())
}),
it('take', () => {
const parser = take(6)
parse(parser, '1234567').chain(assertState('123456'.split('')))
parse(parser, 'things').chain(assertState('things'.split('')))
assert(parse(parser, 'short').isErr())
assert(parse(parser, '').isErr())
}),
it('takeWhile', () => {
const parser = takeWhile(x => Alphanumeric.includes(x))
parse(parser, 'latin123').chain(assertState(['latin']))
parse(parser, '123').chain(assertState([]))
parse(parser, 'latin').chain(assertState(['latin']))
parse(parser, '').chain(assertState([]))
})
]

View file

@ -1,3 +0,0 @@
export const assertState: any;
export const parseEq: any;
export const parseErr: any;

View file

@ -1,15 +0,0 @@
import { chain, curry } from 'izuna'
import { parse } from '../../src/index.js'
export const assertState = curry((expected, state) => {
assertEq(expected, state[0])
})
export const parseEq = curry((parser, input, expected) =>
chain(assertState(expected), parse(parser, input))
)
export const parseErr = curry((parser, input) =>
assert(parse(parser, input).isErr(), `expected an error but '${input}' parsed successfully`)
)

View file

@ -1 +0,0 @@
export const Conditionals: any[];

View file

@ -1,11 +0,0 @@
import { it, assertEq } from 'folktest'
import { anyChar, parse } from '../../src/index.js'
import { skip } from '../../src/cond.js'
import { parseEq } from './common.js'
export const Conditionals = [
it('skip', () => {
parseEq(skip(anyChar), 'test', [])
})
]

View file

@ -1,3 +0,0 @@
export * from "./bytes.js";
export * from "./iter.js";
export * from "./cond.js";

View file

@ -1,4 +0,0 @@
export * from './bytes.js'
export * from './iter.js'
export * from './cond.js'

View file

@ -1 +0,0 @@
export const Iterator: any[];

View file

@ -1,21 +0,0 @@
import { it, assert, assertEq } from 'folktest'
import { IndexableIterator } from '../../src/iter.js'
import { state } from '../../src/state.js'
export const Iterator = [
it('should be cloneable', () => {
const hi = new IndexableIterator('hi :3')
const hihi = hi.clone()
assertEq(hi.next().value, 'h')
assertEq(hi.next().value, 'i')
assertEq(hihi.next().value, 'h')
}),
it('should have iterator helpers', () => {
const aaaaa = new IndexableIterator('awawawawa').filter(x => x !== 'w')
assertEq([...aaaaa].join(''), 'aaaaa')
})
]

View file

@ -1,116 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
"emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./typings", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
"declarationDir": "./typings", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"src"
]
}

16
typings/byte.d.ts vendored
View file

@ -1,16 +0,0 @@
/** @import { ParserState } from './state.js' */
export const char: any;
export const tag: any;
export const charNoCase: any;
export const tagNoCase: any;
export function anyChar(state: any): any;
export const take: any;
export const oneOf: any;
export const noneOf: any;
export const digit: any;
export const lowerAlpha: any;
export const upperAlpha: any;
export const alpha: (state: ParserState) => any;
export const alphanumeric: (state: ParserState) => any;
export function eof(state: any): any;
import type { ParserState } from './state.js';

2
typings/clone.d.ts vendored
View file

@ -1,2 +0,0 @@
export const Clone: unique symbol;
export function clone(target: any): any;

View file

@ -1,14 +0,0 @@
export function seq(...parsers: Parser[]): (
/** @param {ParserState} state */
state: ParserState) => any;
export function any(...parsers: any[]): (
/**
* @param {ParserState} state
*/
state: ParserState) => any;
export const map: any;
export const delimited: any;
export const preceded: any;
export const terminated: any;
export const separatedPair: any;
import type { ParserState } from './state.js';

7
typings/cond.d.ts vendored
View file

@ -1,7 +0,0 @@
/** @import { Result } from '../vendor/kojima/src/index.js' */
/** @import { ParserState } from './state.js' */
export const maybe: any;
export const not: any;
export const until: any;
export const verify: any;
export const skip: any;

5
typings/const.d.ts vendored
View file

@ -1,5 +0,0 @@
export const LowerAlpha: "abcdefghijklmnopqrstuvwxyz";
export const UpperAlpha: string;
export const Alpha: string;
export const Digits: "1234567890";
export const Alphanumeric: string;

11
typings/fn.d.ts vendored
View file

@ -1,11 +0,0 @@
export function iter(value: any): IndexableIterator;
export function clone(state: ParserState): any;
export function succeed<T>(v: T | T[], [x, y]: ParserState): any;
export function fail(msg: string, state: ParserState, e?: Error): any;
export function nth<T>(n: number & keyof T, iter: T[]): T[][number & keyof T];
export function next(state: ParserState): any;
export function diff<T>(a: T[], b: T[]): T[];
export const join: any;
export const mapStr: any;
import { IndexableIterator } from './iter.js';
import type { ParserState } from './state.js';

5
typings/index.d.ts vendored
View file

@ -1,5 +0,0 @@
export * from "./byte.js";
export * from "./combinator.js";
export * from "./cond.js";
export * from "./multi.js";
export * from "./state.js";

14
typings/iter.d.ts vendored
View file

@ -1,14 +0,0 @@
export class IndexableIterator {
constructor(value: any, index?: number);
_value: any;
_index: number;
clone(): IndexableIterator;
done(): boolean;
next(): {
value: any;
done: boolean;
};
[Clone](): IndexableIterator;
[Symbol.iterator](): IndexableIterator;
}
import { Clone } from './clone.js';

8
typings/multi.d.ts vendored
View file

@ -1,8 +0,0 @@
/** @import { ParseError, ParserState } from './state.js' */
/** @import { Result } from '../vendor/kojima/src/index.js' */
export const takeWhile: any;
export const takeUntil: any;
export const skip: any;
export const many: any;
export function many1(parser: any): any;
export function range(start: any, end: any, deny?: any[]): any;

24
typings/state.d.ts vendored
View file

@ -1,24 +0,0 @@
/**
* @typedef {Readonly<[any[], Iterator<any>]>} ParserState
*/
export class ParseError extends Error {
/**
* @param {string} message
* @param {ParserState} state
* @param {Error} [cause]
*/
constructor(message: string, state: ParserState, cause?: Error);
state: readonly [any[], Iterator<any, any, any>];
}
export class State extends Tuple {
static from(values: any): State;
constructor(remaining: any, read?: any[]);
next(): any;
[Clone](): Readonly<State>;
}
export function state(remaining: any, read: any): State;
export const parse: any;
export const parseAll: any;
export type ParserState = Readonly<[any[], Iterator<any>]>;
import { Tuple } from './tuple.js';
import { Clone } from './clone.js';

18
typings/tuple.d.ts vendored
View file

@ -1,18 +0,0 @@
/**
* @param {...any} values
*/
export class Tuple {
static from(values: any): Readonly<Tuple>;
constructor(values: any);
_values: any;
_length: any;
get length(): any;
_setEntry([key, value]: [any, any], index: any): void;
toString(): string;
clone(): any;
[Clone](): any;
[Symbol.iterator](): IndexableIterator;
}
export function tuple(values: any): Readonly<Tuple>;
import { Clone } from './clone.js';
import { IndexableIterator } from './iter.js';