@tabnas/abnf 0.4.16 → 0.4.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abnf.d.ts +1 -1
- package/dist/abnf.js +1 -1
- package/package.json +5 -3
- package/src/abnf.ts +92 -0
- package/src/bin/tabnas-abnf-cli.ts +222 -0
- package/src/compile.ts +62 -0
- package/src/converter.ts +1155 -0
- package/src/tsconfig.json +16 -0
package/dist/abnf.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ import { abnfCompile, toRecognitionSpec, toPureSpec, toJsonic, attachActions, at
|
|
|
4
4
|
declare const abnf: Plugin;
|
|
5
5
|
export { VERSION, abnf, abnfConvert, parseAbnf, emitGrammarSpec, eliminateLeftRecursion, abnfRules, AbnfParseError, abnfCompile, toRecognitionSpec, toPureSpec, toJsonic, attachActions, attachActionSlots, markListing, AbnfCompileError, AbnfActionError, };
|
|
6
6
|
export type { AbnfConvertOptions, AbnfCompileOptions, ActionsMap };
|
|
7
|
-
declare const VERSION = "0.4.
|
|
7
|
+
declare const VERSION = "0.4.17";
|
package/dist/abnf.js
CHANGED
|
@@ -36,6 +36,6 @@ exports.abnf = abnf;
|
|
|
36
36
|
// VERSION is this package's version. It MUST equal package.json "version":
|
|
37
37
|
// the release orchestrator rewrites both, and the version test fails the
|
|
38
38
|
// build if they drift. Mirrors `const VERSION` in go/abnf.go.
|
|
39
|
-
const VERSION = '0.4.
|
|
39
|
+
const VERSION = '0.4.17';
|
|
40
40
|
exports.VERSION = VERSION;
|
|
41
41
|
//# sourceMappingURL=abnf.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tabnas/abnf",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.17",
|
|
4
4
|
"main": "dist/abnf.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/abnf.d.ts",
|
|
@@ -39,7 +39,9 @@
|
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"files": [
|
|
41
41
|
"dist",
|
|
42
|
+
"src",
|
|
42
43
|
"bin",
|
|
44
|
+
"README.md",
|
|
43
45
|
"LICENSE"
|
|
44
46
|
],
|
|
45
47
|
"bin": {
|
|
@@ -49,8 +51,8 @@
|
|
|
49
51
|
".": "./dist/abnf.js"
|
|
50
52
|
},
|
|
51
53
|
"peerDependencies": {
|
|
52
|
-
"@tabnas/parser": ">=0.12.
|
|
53
|
-
"@tabnas/bnf": ">=0.1.
|
|
54
|
+
"@tabnas/parser": ">=0.12.8",
|
|
55
|
+
"@tabnas/bnf": ">=0.1.24"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"@tabnas/debug": "*",
|
package/src/abnf.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* Copyright (c) 2025-2026 Richard Rodger and other contributors, MIT License */
|
|
2
|
+
|
|
3
|
+
/* abnf.ts
|
|
4
|
+
* ABNF plugin — adds `tn.abnf(src)` (install) and `tn.abnf.toSpec(src)`
|
|
5
|
+
* (build without installing) to an Tabnas instance.
|
|
6
|
+
*
|
|
7
|
+
* The conversion logic itself lives in ./converter.ts; this file
|
|
8
|
+
* exposes it both as a Plugin (for `tn.use(abnf)`) and as bare
|
|
9
|
+
* exports (for code that wants to convert without an instance).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
Tabnas,
|
|
14
|
+
GrammarSpec,
|
|
15
|
+
Plugin,
|
|
16
|
+
} from '@tabnas/parser'
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
abnf as abnfConvert,
|
|
20
|
+
parseAbnf,
|
|
21
|
+
emitGrammarSpec,
|
|
22
|
+
eliminateLeftRecursion,
|
|
23
|
+
abnfRules,
|
|
24
|
+
AbnfParseError,
|
|
25
|
+
AbnfConvertOptions,
|
|
26
|
+
} from './converter'
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
abnfCompile,
|
|
30
|
+
toRecognitionSpec,
|
|
31
|
+
toPureSpec,
|
|
32
|
+
toJsonic,
|
|
33
|
+
attachActions,
|
|
34
|
+
attachActionSlots,
|
|
35
|
+
markListing,
|
|
36
|
+
AbnfCompileError,
|
|
37
|
+
AbnfActionError,
|
|
38
|
+
AbnfCompileOptions,
|
|
39
|
+
ActionsMap,
|
|
40
|
+
} from './compile'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// Plugin entry point. Decorates the instance with a callable `abnf`
|
|
44
|
+
// member that converts and installs a grammar, plus `abnf.toSpec` for
|
|
45
|
+
// callers that just want the spec.
|
|
46
|
+
type AbnfPluginOptions = AbnfConvertOptions & { actions?: ActionsMap }
|
|
47
|
+
|
|
48
|
+
const abnf: Plugin = function abnf(tn: Tabnas, _options?: any): void {
|
|
49
|
+
const fn = ((src: string, opts?: AbnfPluginOptions): GrammarSpec => {
|
|
50
|
+
// User actions wrap the compiler's closures, so convert in closure
|
|
51
|
+
// mode (never builtins) and request marks when actions are supplied.
|
|
52
|
+
const spec = abnfConvert(src,
|
|
53
|
+
opts?.actions ? { ...opts, builtins: false, marks: true } : opts)
|
|
54
|
+
if (opts?.actions) attachActions(spec, opts.actions)
|
|
55
|
+
tn.grammar(spec)
|
|
56
|
+
return spec
|
|
57
|
+
}) as ((src: string, opts?: AbnfPluginOptions) => GrammarSpec) & {
|
|
58
|
+
toSpec: (src: string, opts?: AbnfConvertOptions) => GrammarSpec
|
|
59
|
+
}
|
|
60
|
+
fn.toSpec = (src: string, opts?: AbnfConvertOptions): GrammarSpec =>
|
|
61
|
+
abnfConvert(src, opts)
|
|
62
|
+
tn.abnf = fn
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
VERSION,
|
|
68
|
+
abnf,
|
|
69
|
+
abnfConvert,
|
|
70
|
+
parseAbnf,
|
|
71
|
+
emitGrammarSpec,
|
|
72
|
+
eliminateLeftRecursion,
|
|
73
|
+
abnfRules,
|
|
74
|
+
AbnfParseError,
|
|
75
|
+
abnfCompile,
|
|
76
|
+
toRecognitionSpec,
|
|
77
|
+
toPureSpec,
|
|
78
|
+
toJsonic,
|
|
79
|
+
attachActions,
|
|
80
|
+
attachActionSlots,
|
|
81
|
+
markListing,
|
|
82
|
+
AbnfCompileError,
|
|
83
|
+
AbnfActionError,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type { AbnfConvertOptions, AbnfCompileOptions, ActionsMap }
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// VERSION is this package's version. It MUST equal package.json "version":
|
|
90
|
+
// the release orchestrator rewrites both, and the version test fails the
|
|
91
|
+
// build if they drift. Mirrors `const VERSION` in go/abnf.go.
|
|
92
|
+
const VERSION = '0.4.17'
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger and other contributors, MIT License */
|
|
2
|
+
|
|
3
|
+
/* tabnas-abnf-cli.ts
|
|
4
|
+
* CLI wrapper for the ABNF -> tabnas grammar spec converter.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Fs from 'node:fs'
|
|
8
|
+
|
|
9
|
+
import { abnfConvert, abnfCompile, markListing, AbnfCompileError } from '../abnf'
|
|
10
|
+
import { Tabnas } from '@tabnas/parser'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export async function run(argv: string[], console: Console) {
|
|
14
|
+
const args = {
|
|
15
|
+
help: false,
|
|
16
|
+
stdin: false,
|
|
17
|
+
files: [] as string[],
|
|
18
|
+
inline: [] as string[],
|
|
19
|
+
start: undefined as string | undefined,
|
|
20
|
+
tag: undefined as string | undefined,
|
|
21
|
+
space: 2,
|
|
22
|
+
// When set, convert and install the grammar, parse each sample,
|
|
23
|
+
// and report the tree (or an error) instead of the spec.
|
|
24
|
+
parse: [] as string[],
|
|
25
|
+
parseFiles: [] as string[],
|
|
26
|
+
// Compilation mode: emit a pure-data grammar as jsonic text.
|
|
27
|
+
compile: false,
|
|
28
|
+
// With --compile, emit the full AST grammar (tree builtins kept)
|
|
29
|
+
// instead of the recognition-only grammar.
|
|
30
|
+
full: false,
|
|
31
|
+
// List the marks the compiler assigns (for @rule:o|c:mark actions).
|
|
32
|
+
marks: false,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let aI = 2; aI < argv.length; aI++) {
|
|
36
|
+
const arg = argv[aI]
|
|
37
|
+
if ('-' === arg) {
|
|
38
|
+
args.stdin = true
|
|
39
|
+
} else if ('--help' === arg || '-h' === arg) {
|
|
40
|
+
args.help = true
|
|
41
|
+
} else if ('--file' === arg || '-f' === arg) {
|
|
42
|
+
args.files.push(argv[++aI])
|
|
43
|
+
} else if ('--start' === arg || '-s' === arg) {
|
|
44
|
+
args.start = argv[++aI]
|
|
45
|
+
} else if ('--tag' === arg || '-t' === arg) {
|
|
46
|
+
args.tag = argv[++aI]
|
|
47
|
+
} else if ('--compact' === arg || '-c' === arg) {
|
|
48
|
+
args.space = 0
|
|
49
|
+
} else if ('--compile' === arg || '-C' === arg) {
|
|
50
|
+
args.compile = true
|
|
51
|
+
} else if ('--full' === arg || '-F' === arg) {
|
|
52
|
+
args.compile = true
|
|
53
|
+
args.full = true
|
|
54
|
+
} else if ('--marks' === arg || '-m' === arg) {
|
|
55
|
+
args.marks = true
|
|
56
|
+
} else if ('--parse' === arg || '-P' === arg) {
|
|
57
|
+
args.parse.push(argv[++aI])
|
|
58
|
+
} else if ('--parse-file' === arg) {
|
|
59
|
+
args.parseFiles.push(argv[++aI])
|
|
60
|
+
} else if (arg && !arg.startsWith('-')) {
|
|
61
|
+
args.inline.push(arg)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (args.help) {
|
|
66
|
+
return help(console)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let src = ''
|
|
70
|
+
for (const fp of args.files) {
|
|
71
|
+
if ('string' === typeof fp && '' !== fp) {
|
|
72
|
+
src += Fs.readFileSync(fp).toString() + '\n'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const inline of args.inline) {
|
|
76
|
+
src += inline + '\n'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if ('' === src.trim() || args.stdin) {
|
|
80
|
+
src += await readStdin(console)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const spec = abnfConvert(src, { start: args.start, tag: args.tag })
|
|
84
|
+
|
|
85
|
+
// Marks mode: list the user-action marks the compiler assigns.
|
|
86
|
+
if (args.marks) {
|
|
87
|
+
console.log(markListing(abnfConvert(src, {
|
|
88
|
+
start: args.start, tag: args.tag, marks: true,
|
|
89
|
+
})))
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Compilation mode: emit a pure recognition grammar as jsonic text.
|
|
94
|
+
// No function references — recognition is fully structural. Grammars
|
|
95
|
+
// that need control functions (probe / unbounded lookahead) are
|
|
96
|
+
// refused until those ship as engine `$`-builtins.
|
|
97
|
+
if (args.compile) {
|
|
98
|
+
try {
|
|
99
|
+
console.log(abnfCompile(src, {
|
|
100
|
+
start: args.start,
|
|
101
|
+
tag: args.tag,
|
|
102
|
+
indent: args.space || 2,
|
|
103
|
+
recognition: !args.full,
|
|
104
|
+
}))
|
|
105
|
+
} catch (e: any) {
|
|
106
|
+
if (e instanceof AbnfCompileError) {
|
|
107
|
+
console.error(e.message)
|
|
108
|
+
process.exitCode = 1
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
throw e
|
|
112
|
+
}
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Parse-mode: validate the grammar against one or more sample
|
|
117
|
+
// inputs and print their parse trees. Exits 1 if any sample fails.
|
|
118
|
+
if (args.parse.length > 0 || args.parseFiles.length > 0) {
|
|
119
|
+
const samples: { label: string; input: string }[] = []
|
|
120
|
+
for (const fp of args.parseFiles) {
|
|
121
|
+
samples.push({
|
|
122
|
+
label: fp,
|
|
123
|
+
input: Fs.readFileSync(fp).toString(),
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
for (const inp of args.parse) {
|
|
127
|
+
samples.push({ label: inp, input: inp })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Bare engine: the ABNF spec we're about to install supplies the
|
|
131
|
+
// grammar, so we don't need any other plugin.
|
|
132
|
+
const j = new Tabnas()
|
|
133
|
+
j.grammar(spec)
|
|
134
|
+
|
|
135
|
+
let failed = 0
|
|
136
|
+
for (const { label, input } of samples) {
|
|
137
|
+
try {
|
|
138
|
+
const tree = j.parse(input)
|
|
139
|
+
console.log(
|
|
140
|
+
`ok: ${JSON.stringify(label)} -> ` +
|
|
141
|
+
JSON.stringify(tree, null, args.space || undefined))
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
failed++
|
|
144
|
+
const msg = (e?.message || String(e)).split('\n')[0]
|
|
145
|
+
console.error(`fail: ${JSON.stringify(label)}: ${msg}`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (failed > 0) {
|
|
149
|
+
process.exitCode = 1
|
|
150
|
+
}
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(JSON.stringify(spec, null, args.space || undefined))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async function readStdin(console: Console): Promise<string> {
|
|
159
|
+
if ('string' === typeof (console as any).test$) {
|
|
160
|
+
return (console as any).test$
|
|
161
|
+
}
|
|
162
|
+
if (process.stdin.isTTY) return ''
|
|
163
|
+
let s = ''
|
|
164
|
+
process.stdin.setEncoding('utf8')
|
|
165
|
+
for await (const p of process.stdin) s += p
|
|
166
|
+
return s
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
function help(console: Console) {
|
|
171
|
+
console.log(`
|
|
172
|
+
tabnas-abnf: convert an ABNF grammar into a tabnas grammar spec.
|
|
173
|
+
|
|
174
|
+
Usage: tabnas-abnf <args> [<abnf-source>]*
|
|
175
|
+
|
|
176
|
+
Arguments:
|
|
177
|
+
- Read ABNF source from stdin.
|
|
178
|
+
--file <path> Read ABNF source from <path> (repeatable).
|
|
179
|
+
-f <path>
|
|
180
|
+
|
|
181
|
+
--start <name> Set the start rule (defaults to the first
|
|
182
|
+
-s <name> production).
|
|
183
|
+
|
|
184
|
+
--tag <name> Group tag applied to every emitted alt.
|
|
185
|
+
-t <name> Defaults to \`abnf\`.
|
|
186
|
+
|
|
187
|
+
--compact Emit single-line JSON (default indent is 2).
|
|
188
|
+
-c
|
|
189
|
+
|
|
190
|
+
--compile Compilation mode: emit a pure-data *recognition*
|
|
191
|
+
-C grammar as jsonic text (no closures; control
|
|
192
|
+
and tree building reference engine
|
|
193
|
+
\`$\`-builtins).
|
|
194
|
+
|
|
195
|
+
--full With compilation mode, emit the full AST
|
|
196
|
+
-F grammar (tree \`$\`-builtins retained) instead
|
|
197
|
+
of recognition-only. Still pure data.
|
|
198
|
+
|
|
199
|
+
--marks List the per-alt marks the compiler assigns,
|
|
200
|
+
-m usable as \`@<rule>:o|c:<mark>\` action refs.
|
|
201
|
+
|
|
202
|
+
--parse <input> Parse <input> against the generated grammar
|
|
203
|
+
-P <input> and print its parse tree. Repeatable.
|
|
204
|
+
Exits non-zero if any sample fails.
|
|
205
|
+
|
|
206
|
+
--parse-file <path> Parse the contents of <path> against the
|
|
207
|
+
generated grammar (repeatable).
|
|
208
|
+
|
|
209
|
+
--help Print this help message.
|
|
210
|
+
-h
|
|
211
|
+
|
|
212
|
+
Grammar dialect:
|
|
213
|
+
Rules are ABNF-style: \`name = element ...\`, with \`/\` for choice and
|
|
214
|
+
double-quoted literals. For example: \`greet = "hi" / "hello"\`.
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
> tabnas-abnf 'greet = "hi" / "hello"'
|
|
218
|
+
> tabnas-abnf -f grammar.abnf
|
|
219
|
+
> echo 'g = "a"' | tabnas-abnf -
|
|
220
|
+
> tabnas-abnf -f grammar.abnf --parse 'hi'
|
|
221
|
+
`)
|
|
222
|
+
}
|
package/src/compile.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */
|
|
2
|
+
|
|
3
|
+
/* compile.ts
|
|
4
|
+
* Compilation mode for ABNF: convert the source, then serialise the
|
|
5
|
+
* resulting spec as pure-data tabnas grammar text.
|
|
6
|
+
*
|
|
7
|
+
* The spec transforms themselves (recognition/pure lowering, jsonic
|
|
8
|
+
* serialisation, user action attachment) are notation-neutral and live
|
|
9
|
+
* in `@tabnas/bnf`. This file keeps the ABNF-named public API those
|
|
10
|
+
* transforms used to be exported under, plus `abnfCompile`, which is
|
|
11
|
+
* the only piece that needs to know how to parse ABNF.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { GrammarSpec } from '@tabnas/parser'
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
compileSpec,
|
|
18
|
+
toRecognitionSpec,
|
|
19
|
+
toPureSpec,
|
|
20
|
+
toJsonic,
|
|
21
|
+
attachActions,
|
|
22
|
+
attachActionSlots,
|
|
23
|
+
markListing,
|
|
24
|
+
CompileError,
|
|
25
|
+
ActionError,
|
|
26
|
+
} from '@tabnas/bnf'
|
|
27
|
+
|
|
28
|
+
import type {
|
|
29
|
+
CompileOptions,
|
|
30
|
+
JsonicOptions,
|
|
31
|
+
ActionsMap,
|
|
32
|
+
} from '@tabnas/bnf'
|
|
33
|
+
|
|
34
|
+
import { abnf as abnfConvert } from './converter'
|
|
35
|
+
|
|
36
|
+
// Historical names for the shared errors and options. These are
|
|
37
|
+
// re-exports, not `const` aliases: a `const` would carry only the value
|
|
38
|
+
// side, so `let e: AbnfCompileError` would stop compiling (TS2749).
|
|
39
|
+
export { CompileError as AbnfCompileError, ActionError as AbnfActionError }
|
|
40
|
+
type AbnfCompileOptions = CompileOptions
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// Compile ABNF source into a pure-data tabnas grammar as jsonic text.
|
|
44
|
+
// Always converts with `builtins: true` so probe dispatch and tree
|
|
45
|
+
// building serialise as `@…$` builtin refs (no closures).
|
|
46
|
+
export function abnfCompile(src: string, opts: AbnfCompileOptions = {}): string {
|
|
47
|
+
const spec: GrammarSpec = abnfConvert(src,
|
|
48
|
+
{ start: opts.start, tag: opts.tag, builtins: true, marks: true })
|
|
49
|
+
return compileSpec(spec, opts)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
toRecognitionSpec,
|
|
55
|
+
toPureSpec,
|
|
56
|
+
toJsonic,
|
|
57
|
+
attachActions,
|
|
58
|
+
attachActionSlots,
|
|
59
|
+
markListing,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type { AbnfCompileOptions, JsonicOptions, ActionsMap }
|