@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 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.16";
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.16';
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.16",
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.4",
53
- "@tabnas/bnf": ">=0.1.22"
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 }