ibex 0.1.0
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +43 -0
- data/CHANGELOG.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +194 -0
- data/Rakefile +25 -0
- data/Steepfile +10 -0
- data/docs/architecture.md +99 -0
- data/docs/compat-notes.md +37 -0
- data/docs/grammar-reference.md +127 -0
- data/docs/lexer-coverage.md +14 -0
- data/docs/phase10-extensions.md +27 -0
- data/docs/racc-migration.md +47 -0
- data/exe/ibex +6 -0
- data/gemfiles/Gemfile +7 -0
- data/gemfiles/Gemfile.lock +98 -0
- data/lib/ibex/analysis/sets.rb +194 -0
- data/lib/ibex/analysis.rb +8 -0
- data/lib/ibex/cli/counterexample_options.rb +38 -0
- data/lib/ibex/cli/outputs.rb +88 -0
- data/lib/ibex/cli.rb +333 -0
- data/lib/ibex/codegen/dot.rb +52 -0
- data/lib/ibex/codegen/html.rb +110 -0
- data/lib/ibex/codegen/rbs.rb +54 -0
- data/lib/ibex/codegen/report.rb +149 -0
- data/lib/ibex/codegen/ruby.rb +235 -0
- data/lib/ibex/codegen/symbol_labels.rb +18 -0
- data/lib/ibex/error.rb +7 -0
- data/lib/ibex/frontend/action_scanner.rb +266 -0
- data/lib/ibex/frontend/ast.rb +146 -0
- data/lib/ibex/frontend/bootstrap_parser.rb +163 -0
- data/lib/ibex/frontend/dsl.rb +196 -0
- data/lib/ibex/frontend/generated_parser.rb +434 -0
- data/lib/ibex/frontend/generated_parser_base.rb +273 -0
- data/lib/ibex/frontend/grammar.y +156 -0
- data/lib/ibex/frontend/lexer.rb +165 -0
- data/lib/ibex/frontend/parser/declarations.rb +163 -0
- data/lib/ibex/frontend/parser/rules.rb +154 -0
- data/lib/ibex/frontend/parser.rb +23 -0
- data/lib/ibex/frontend/regenerator.rb +41 -0
- data/lib/ibex/frontend/source_cursor.rb +94 -0
- data/lib/ibex/frontend/token_adapter/declaration_state.rb +248 -0
- data/lib/ibex/frontend/token_adapter/delimiter_tracker.rb +37 -0
- data/lib/ibex/frontend/token_adapter/rule_state.rb +162 -0
- data/lib/ibex/frontend/token_adapter.rb +108 -0
- data/lib/ibex/frontend.rb +28 -0
- data/lib/ibex/ir/automaton_ir.rb +109 -0
- data/lib/ibex/ir/grammar_ir.rb +192 -0
- data/lib/ibex/ir/serialize.rb +154 -0
- data/lib/ibex/ir.rb +53 -0
- data/lib/ibex/lalr/builder.rb +270 -0
- data/lib/ibex/lalr/conflict.rb +120 -0
- data/lib/ibex/lalr/conflict_search.rb +332 -0
- data/lib/ibex/lalr/conflict_search_limits.rb +39 -0
- data/lib/ibex/lalr/counterexample.rb +138 -0
- data/lib/ibex/lalr/default_reductions.rb +67 -0
- data/lib/ibex/lalr.rb +27 -0
- data/lib/ibex/normalize/declarations.rb +60 -0
- data/lib/ibex/normalize/diagnostics.rb +99 -0
- data/lib/ibex/normalize/expander.rb +218 -0
- data/lib/ibex/normalize/expression.rb +54 -0
- data/lib/ibex/normalize.rb +166 -0
- data/lib/ibex/runtime/parser.rb +360 -0
- data/lib/ibex/runtime.rb +8 -0
- data/lib/ibex/tables.rb +125 -0
- data/lib/ibex/version.rb +6 -0
- data/lib/ibex.rb +23 -0
- data/sig/ibex/analysis/sets.rbs +72 -0
- data/sig/ibex/analysis.rbs +6 -0
- data/sig/ibex/cli/counterexample_options.rbs +16 -0
- data/sig/ibex/cli/outputs.rbs +31 -0
- data/sig/ibex/cli.rbs +108 -0
- data/sig/ibex/codegen/dot.rbs +19 -0
- data/sig/ibex/codegen/html.rbs +35 -0
- data/sig/ibex/codegen/rbs.rbs +31 -0
- data/sig/ibex/codegen/report.rbs +39 -0
- data/sig/ibex/codegen/ruby.rbs +92 -0
- data/sig/ibex/codegen/symbol_labels.rbs +11 -0
- data/sig/ibex/error.rbs +7 -0
- data/sig/ibex/frontend/action_scanner.rbs +74 -0
- data/sig/ibex/frontend/ast.rbs +223 -0
- data/sig/ibex/frontend/bootstrap_parser.rbs +80 -0
- data/sig/ibex/frontend/dsl.rbs +124 -0
- data/sig/ibex/frontend/generated_parser.rbs +163 -0
- data/sig/ibex/frontend/generated_parser_base.rbs +114 -0
- data/sig/ibex/frontend/lexer.rbs +61 -0
- data/sig/ibex/frontend/parser/declarations.rbs +56 -0
- data/sig/ibex/frontend/parser/rules.rbs +48 -0
- data/sig/ibex/frontend/parser.rbs +16 -0
- data/sig/ibex/frontend/regenerator.rbs +16 -0
- data/sig/ibex/frontend/source_cursor.rbs +73 -0
- data/sig/ibex/frontend/token_adapter/declaration_state.rbs +97 -0
- data/sig/ibex/frontend/token_adapter/delimiter_tracker.rbs +25 -0
- data/sig/ibex/frontend/token_adapter/rule_state.rbs +77 -0
- data/sig/ibex/frontend/token_adapter.rbs +67 -0
- data/sig/ibex/frontend.rbs +15 -0
- data/sig/ibex/ir/automaton_ir.rbs +77 -0
- data/sig/ibex/ir/grammar_ir.rbs +143 -0
- data/sig/ibex/ir/serialize.rbs +50 -0
- data/sig/ibex/ir.rbs +43 -0
- data/sig/ibex/lalr/builder.rbs +85 -0
- data/sig/ibex/lalr/conflict.rbs +47 -0
- data/sig/ibex/lalr/conflict_search.rbs +131 -0
- data/sig/ibex/lalr/conflict_search_limits.rbs +29 -0
- data/sig/ibex/lalr/counterexample.rbs +53 -0
- data/sig/ibex/lalr/default_reductions.rbs +20 -0
- data/sig/ibex/lalr.rbs +23 -0
- data/sig/ibex/normalize/declarations.rbs +20 -0
- data/sig/ibex/normalize/diagnostics.rbs +32 -0
- data/sig/ibex/normalize/expander.rbs +66 -0
- data/sig/ibex/normalize/expression.rbs +21 -0
- data/sig/ibex/normalize.rbs +96 -0
- data/sig/ibex/runtime/parser.rbs +167 -0
- data/sig/ibex/runtime.rbs +6 -0
- data/sig/ibex/tables.rbs +50 -0
- data/sig/ibex/version.rbs +6 -0
- data/sig/ibex.rbs +6 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1cf3b4cceb041622899923a174e36d413adb399c22cdb44f8023f1f199acea73
|
|
4
|
+
data.tar.gz: 62ac1c3098103ac5270a8c684065424fcfc8bb2759332b3414672454ecbb25ca
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ec45347b6c9ec5f427261ade9ea4102a2b7b6583bcec47ee024d920fc624018bae9315a7129657977de47241e867ef23809ccef372169cb8e1d6a9ad858e31b3
|
|
7
|
+
data.tar.gz: a163ab0fb84918570c2f62a9299d395653a0896c510e43a7672677803fe73f6010d74305868e1f6e5018b13b30d053c1a4d0852e16a0941f56a123bb883e7422
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
CacheRootDirectory: tmp/rubocop_cache
|
|
3
|
+
NewCops: enable
|
|
4
|
+
TargetRubyVersion: 3.0
|
|
5
|
+
SuggestExtensions: false
|
|
6
|
+
Exclude:
|
|
7
|
+
- "pkg/**/*"
|
|
8
|
+
- "tmp/**/*"
|
|
9
|
+
- "vendor/**/*"
|
|
10
|
+
- "lib/ibex/frontend/generated_parser.rb"
|
|
11
|
+
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 120
|
|
14
|
+
|
|
15
|
+
Layout/LeadingCommentSpace:
|
|
16
|
+
AllowRBSInlineAnnotation: true
|
|
17
|
+
|
|
18
|
+
Style/StringLiterals:
|
|
19
|
+
EnforcedStyle: double_quotes
|
|
20
|
+
|
|
21
|
+
Metrics/AbcSize:
|
|
22
|
+
Max: 36
|
|
23
|
+
|
|
24
|
+
Metrics/ClassLength:
|
|
25
|
+
Max: 200
|
|
26
|
+
|
|
27
|
+
Metrics/CyclomaticComplexity:
|
|
28
|
+
Max: 10
|
|
29
|
+
|
|
30
|
+
Metrics/MethodLength:
|
|
31
|
+
Max: 30
|
|
32
|
+
|
|
33
|
+
Metrics/ModuleLength:
|
|
34
|
+
Max: 150
|
|
35
|
+
|
|
36
|
+
Metrics/ParameterLists:
|
|
37
|
+
Max: 12
|
|
38
|
+
|
|
39
|
+
Metrics/PerceivedComplexity:
|
|
40
|
+
Max: 10
|
|
41
|
+
|
|
42
|
+
Style/Documentation:
|
|
43
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Establish the Pure Ruby gem, Minitest, lint, CI, and CLI foundations.
|
|
6
|
+
- Add a table-driven Pure Ruby LR runtime with pull/push APIs, recovery, and tracing.
|
|
7
|
+
- Tokenize grammar files with positions, opaque Ruby actions, comments, and user-code blocks.
|
|
8
|
+
- Parse compatible and extended grammar syntax into a serializable, location-preserving AST.
|
|
9
|
+
- Normalize grammars into immutable, versioned JSON IR with EBNF expansion and diagnostics.
|
|
10
|
+
- Compute nullable, FIRST, and FOLLOW sets with deterministic integer bitsets.
|
|
11
|
+
- Build deterministic LALR(1) automata, resolve and retain conflicts, and render state reports.
|
|
12
|
+
- Generate plain or compact Ruby parsers with embedded runtime and source-line mapping support.
|
|
13
|
+
- Preserve per-block user-code source maps through IR and implement compatible default, all-code, and disabled line conversion.
|
|
14
|
+
- Complete the compatible CLI surface and add optional racc black-box result comparisons.
|
|
15
|
+
- Add extended EBNF/named-reference integration, source-facing EBNF labels in text/DOT/HTML reports, and resumable IR pipelines.
|
|
16
|
+
- Add selectable SLR, LALR(1), and canonical LR(1) construction strategies.
|
|
17
|
+
- Add a Ruby DSL frontend that converges on the existing Grammar AST and IR pipeline.
|
|
18
|
+
- Add unifying conflict counterexamples with complete competing derivation trees and bounded nonunifying fallback.
|
|
19
|
+
- Add generated parser RBS output, shipped runtime signatures, and strict structured grammar diagnostics.
|
|
20
|
+
- Expand generated signatures and Steep checking to every library source, including concrete frontend/IR/LALR/codegen/CLI domain
|
|
21
|
+
types and the self-hosted parser.
|
|
22
|
+
- Support quoted, interpolated, and multiple Ruby heredocs plus recursively nested grouped EBNF.
|
|
23
|
+
- Define unknown external tokens to invoke `on_error` before yacc-style recovery.
|
|
24
|
+
- Add post-commit shift, reduction, and successful-recovery observation hooks to the runtime.
|
|
25
|
+
- Add complete quickstart, grammar, migration, architecture, and extension documentation with an executable README test.
|
|
26
|
+
- Self-host the grammar frontend from a committed Ibex grammar with deterministic regeneration and bootstrap parity checks.
|
|
27
|
+
- Version generated parser tables and reject missing or unsupported formats before consuming input.
|
|
28
|
+
- Add grammar-local `pragma extended` selection and document the action and named-reference boundary inside EBNF groups.
|
|
29
|
+
- Add fixed-seed pipeline properties, versioned Grammar/Automaton IR fixtures, and a reproducible whole-builder benchmark.
|
|
30
|
+
- Keep the whole-library Steep statistics in the README synchronized and enforce them in CI.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Ibex
|
|
2
|
+
|
|
3
|
+
Ibex is a Pure Ruby LR parser generator. It accepts racc-compatible grammar files, generates parsers with the familiar
|
|
4
|
+
`do_parse` / `yyparse` API, and requires no C or Java extension. Its staged Grammar IR and Automaton IR can also drive extended
|
|
5
|
+
EBNF syntax, diagnostics, visualizations, and alternate LR construction algorithms.
|
|
6
|
+
|
|
7
|
+
## Requirements and installation
|
|
8
|
+
|
|
9
|
+
Ibex supports Ruby 3.0 or later and has no runtime gem dependencies. From a source checkout:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bundle install
|
|
13
|
+
bundle exec rake
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Build and install the local gem when you want the `ibex` executable on your `PATH`:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
gem build ibex.gemspec
|
|
20
|
+
gem install ./ibex-0.1.0.gem
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Three-minute calculator
|
|
24
|
+
|
|
25
|
+
Save this as `calculator.y`:
|
|
26
|
+
|
|
27
|
+
<!-- calculator-grammar:start -->
|
|
28
|
+
```text
|
|
29
|
+
class Calculator
|
|
30
|
+
token NUM
|
|
31
|
+
preclow
|
|
32
|
+
left '+'
|
|
33
|
+
left '*'
|
|
34
|
+
prechigh
|
|
35
|
+
rule
|
|
36
|
+
expr : expr '+' expr { result = val[0] + val[2] }
|
|
37
|
+
| expr '*' expr { result = val[0] * val[2] }
|
|
38
|
+
| NUM { result = val[0] }
|
|
39
|
+
end
|
|
40
|
+
---- inner
|
|
41
|
+
def parse_tokens(tokens)
|
|
42
|
+
@tokens = tokens
|
|
43
|
+
do_parse
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def next_token
|
|
47
|
+
@tokens.shift
|
|
48
|
+
end
|
|
49
|
+
---- footer
|
|
50
|
+
if $PROGRAM_NAME == __FILE__
|
|
51
|
+
tokens = [[:NUM, 2], ['+', nil], [:NUM, 3], ['*', nil], [:NUM, 4]]
|
|
52
|
+
puts Calculator.new.parse_tokens(tokens)
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
<!-- calculator-grammar:end -->
|
|
56
|
+
|
|
57
|
+
Generate and run it:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
ibex calculator.y
|
|
61
|
+
ruby calculator.rb
|
|
62
|
+
# 14
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
From a checkout without installing the gem, use `bundle exec ruby -Ilib exe/ibex calculator.y` and
|
|
66
|
+
`bundle exec ruby -Ilib calculator.rb` instead.
|
|
67
|
+
|
|
68
|
+
Ibex generates compact tables by default. Compatibility-safe default reductions shrink profitable states while retaining
|
|
69
|
+
explicit error cells, including recovery and undeclared-token behavior. `--table=plain` produces inspectable Hash rows, while
|
|
70
|
+
`-E` embeds the runtime into a single dependency-free output file.
|
|
71
|
+
|
|
72
|
+
## Lexer contract
|
|
73
|
+
|
|
74
|
+
Ibex does not generate a lexer. A pull parser implements `next_token` and returns `[token, value]`; `false` or `nil` marks EOF.
|
|
75
|
+
Bare grammar tokens normally use Ruby symbols (`:NUM`), and quoted grammar tokens use strings (`'+'`). A push source can call
|
|
76
|
+
`yyparse(receiver, method_name)` where the receiver method yields the same pairs.
|
|
77
|
+
|
|
78
|
+
The default `on_error(token_id, value, value_stack)` raises `Ibex::ParseError`. Override it to use yacc-style `error` recovery.
|
|
79
|
+
Semantic actions can call `yyerror`, `yyerrok`, or `yyaccept`, and `expected_tokens` reports valid lookaheads in the current state.
|
|
80
|
+
Parser subclasses can also override `on_shift(token_id, value, state)`,
|
|
81
|
+
`on_reduce(production_id, values, result)`, and `on_error_recover(token_id, value, value_stack)` as no-op-by-default observers.
|
|
82
|
+
Ordinary shifts and the synthetic recovery-token shift use separate hooks; observer return values never replace semantic values.
|
|
83
|
+
|
|
84
|
+
## Extended mode
|
|
85
|
+
|
|
86
|
+
`--mode=extended` enables optional, repeated, and separated values plus named references. A grammar can make the same choice
|
|
87
|
+
locally with `pragma extended` immediately after its `class` header:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
class ExtendedParser
|
|
91
|
+
pragma extended
|
|
92
|
+
rule
|
|
93
|
+
arguments : separated_list(NUM, ',') { result = val[0] }
|
|
94
|
+
sum : NUM:left '+' NUM:right { result = left + right }
|
|
95
|
+
maybe : NUM?
|
|
96
|
+
many : NUM*
|
|
97
|
+
some : NUM+
|
|
98
|
+
pairs : (KEY VALUE)*
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The value conventions are `nil` or a value for `?`, and arrays for `*`, `+`, `separated_list`, and
|
|
103
|
+
`separated_nonempty_list`. Parenthesized sequences and alternatives can be nested; multi-item groups produce an Array value.
|
|
104
|
+
Text, DOT, and HTML automaton reports label lowered helper symbols with these source-level EBNF expressions.
|
|
105
|
+
|
|
106
|
+
## Pipeline and diagnostics
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
ibex --emit=grammar-ir grammar.y > grammar.json
|
|
110
|
+
ibex --from=grammar-ir --emit=automaton-ir grammar.json > automaton.json
|
|
111
|
+
ibex --from=automaton-ir -o parser.rb automaton.json
|
|
112
|
+
ibex -v --dot=states.dot --html=states.html grammar.y
|
|
113
|
+
ibex --algorithm=lr1 grammar.y
|
|
114
|
+
ibex --counterexamples --counterexample-max-tokens=64 --counterexample-max-configurations=100000 grammar.y
|
|
115
|
+
ibex --rbs -o parser.rb grammar.y
|
|
116
|
+
ibex --warnings=all,error -C grammar.y
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Supported construction algorithms are `slr`, `lalr` (default), and canonical `lr1`. Reports retain precedence-resolved
|
|
120
|
+
conflicts and distinguish unifying counterexamples from nonunifying reachability witnesses. Counterexample searches default to
|
|
121
|
+
32 tokens and 50,000 explored configurations; `--counterexample-max-tokens=N` and
|
|
122
|
+
`--counterexample-max-configurations=N` set positive per-run budgets and request a report. `--rbs` writes a signature beside the
|
|
123
|
+
generated parser; `--rbs=FILE` selects another path. Application methods supplied as opaque `---- inner` code can be declared by
|
|
124
|
+
reopening the generated class in an application RBS file.
|
|
125
|
+
|
|
126
|
+
`--warnings=all` prints unused terminals, unreachable nonterminals, duplicate productions, undeclared terminals, and empty-language
|
|
127
|
+
diagnostics. Add `error` (`--warnings=all,error`, or simply `--warnings=error`) to make any such diagnostic fail the command.
|
|
128
|
+
Action exceptions and `inner` methods point back to their `.y` lines by default. `--line-convert-all` extends that mapping to
|
|
129
|
+
`header` and `footer`; `-l` disables every source-line conversion. User-code chunk locations survive Grammar/Automaton IR JSON
|
|
130
|
+
round trips, so resumed generation has the same backtraces as direct generation.
|
|
131
|
+
|
|
132
|
+
## Documentation
|
|
133
|
+
|
|
134
|
+
- [Grammar reference](docs/grammar-reference.md)
|
|
135
|
+
- [racc migration guide](docs/racc-migration.md)
|
|
136
|
+
- [Architecture and IR schemas](docs/architecture.md)
|
|
137
|
+
- [Compatibility observations](docs/compat-notes.md)
|
|
138
|
+
- [Phase 10 extensions](docs/phase10-extensions.md)
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
Run all unit, integration, documentation, and optional local racc black-box tests with `bundle exec rake test`; run style checks
|
|
143
|
+
with `bundle exec rake lint`. The default `bundle exec rake` runs both. Compatibility tests skip automatically when the `racc`
|
|
144
|
+
command is unavailable.
|
|
145
|
+
|
|
146
|
+
Ibex's grammar frontend is self-hosted. Edit `lib/ibex/frontend/grammar.y`, then regenerate and verify the committed parser with:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
bundle exec rake frontend:generate
|
|
150
|
+
git diff --exit-code -- lib/ibex/frontend/generated_parser.rb
|
|
151
|
+
bundle exec ruby -Itest test/frontend/self_host_test.rb
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Normal library and CLI execution use the generated parser. The handwritten `BootstrapParser` is loaded only by this regeneration
|
|
155
|
+
workflow, whose direct dependency graph also works when the generated file is absent. Byte-comparison and AST/error parity tests
|
|
156
|
+
prevent generated-source drift.
|
|
157
|
+
|
|
158
|
+
Fixed-seed property tests exercise SLR, LALR, and LR(1) pipeline invariants, while versioned Grammar and Automaton IR fixtures
|
|
159
|
+
guard byte-stable schema compatibility. Intentional fixture updates are documented in `test/fixtures/ir/README.md`. Run the
|
|
160
|
+
reproducible whole-builder benchmark without a timing pass/fail threshold with:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
benchmark/pipeline.rb --rules 200 --iterations 5 --seed 12345
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Signatures for every Ruby source under `lib/` are generated from rbs-inline annotations and checked with Steep, including the
|
|
167
|
+
self-hosted generated parser. To regenerate the committed signature tree and reproduce its validation locally:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile bundle install
|
|
171
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile ruby -e '
|
|
172
|
+
sources = Dir.glob("lib/**/*.rb").sort
|
|
173
|
+
exec("bundle", "exec", "rbs-inline", "--opt-out", "--base=lib", "--output=sig", *sources)
|
|
174
|
+
'
|
|
175
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile bundle exec rbs -r digest -r json -r optparse -I sig validate
|
|
176
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile bundle exec steep check
|
|
177
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile bundle exec steep stats
|
|
178
|
+
BUNDLE_GEMFILE=gemfiles/Gemfile bundle exec ruby tool/type_stats.rb --write
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
CI performs generation in a clean temporary directory and compares the complete trees, so missing source signatures and stale
|
|
182
|
+
signature files both fail the build.
|
|
183
|
+
<!-- type-stats:start -->
|
|
184
|
+
The current whole-library `steep stats` result is 3,771 typed calls and 449 untyped calls out of 4,220 (89.4% typed).
|
|
185
|
+
The generated signature tree contains 397 explicit `untyped` occurrences across 16 files.
|
|
186
|
+
<!-- type-stats:end -->
|
|
187
|
+
|
|
188
|
+
Those boundaries are concentrated in generated-parser reduction values, heterogeneous JSON decoding/serialization, runtime
|
|
189
|
+
semantic values and parser-table cells, and embedded user Ruby. Token/location records, the complete grammar AST, parser
|
|
190
|
+
classifier state, IR, the public Ruby DSL, bootstrap parser state, analysis, automaton construction, code generators, table
|
|
191
|
+
construction, and CLI options use concrete domain types. The committed self-hosted parser remains in the Steep target; no library
|
|
192
|
+
directory or generated source is excluded.
|
|
193
|
+
|
|
194
|
+
Ibex is available under the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
require "rubocop/rake_task"
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new(:test) do |task|
|
|
8
|
+
task.libs << "test"
|
|
9
|
+
task.pattern = "test/**/*_test.rb"
|
|
10
|
+
task.warning = true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
RuboCop::RakeTask.new(:lint)
|
|
14
|
+
|
|
15
|
+
namespace :frontend do
|
|
16
|
+
desc "Regenerate the self-hosted grammar parser"
|
|
17
|
+
task :generate do
|
|
18
|
+
require_relative "lib/ibex/frontend/regenerator"
|
|
19
|
+
|
|
20
|
+
output = File.expand_path("lib/ibex/frontend/generated_parser.rb", __dir__)
|
|
21
|
+
File.write(output, Ibex::Frontend::Regenerator.generate)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
task default: %i[test lint]
|
data/Steepfile
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Architecture and IR schemas
|
|
2
|
+
|
|
3
|
+
Ibex keeps syntax, grammar meaning, automaton construction, and output concerns behind two versioned immutable contracts.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
.y Lexer -> Token adapter -> self-hosted LR Parser ─┐
|
|
7
|
+
Ruby DSL ────────┴─> Grammar AST -> Normalizer -> Grammar IR
|
|
8
|
+
|
|
|
9
|
+
set analysis
|
|
10
|
+
|
|
|
11
|
+
SLR/LALR/LR1 Builder -> Automaton IR
|
|
12
|
+
|
|
|
13
|
+
Ruby/RBS generators / report / DOT / HTML / counterexamples
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Frontend changes stop at the Normalizer. Algorithm strategies consume Grammar IR and produce identical Automaton IR shapes.
|
|
17
|
+
Outputs consume Automaton IR and never call builder internals. The CLI only connects stages and supports JSON resumption.
|
|
18
|
+
|
|
19
|
+
The text frontend's canonical syntax is `lib/ibex/frontend/grammar.y`. Ibex generates and commits
|
|
20
|
+
`lib/ibex/frontend/generated_parser.rb`; the public `Frontend::Parser` always delegates to that class. Lexer `Token` objects remain
|
|
21
|
+
the semantic values passed through `TokenAdapter`, preserving their `Location` in AST nodes and diagnostics. The explicitly named
|
|
22
|
+
handwritten `BootstrapParser` is excluded from normal loading and exists only to break the regeneration cycle. See
|
|
23
|
+
[ADR 0015](decisions/0015-self-hosted-grammar-frontend.md) for the update procedure and boundary.
|
|
24
|
+
|
|
25
|
+
The RBS generator emits the generated class namespace, superclass, parser-table constants, and `.parser_tables` contract. The
|
|
26
|
+
gem also ships a one-to-one rbs-inline-generated signature tree under `sig/` for every Ruby source in `lib/`, including the
|
|
27
|
+
self-hosted parser. CI regenerates into an empty temporary directory, compares the complete trees, validates the RBS environment,
|
|
28
|
+
and runs Steep against the entire library. Token/location records, grammar AST nodes, parser classifier and bootstrap state, the
|
|
29
|
+
Ruby DSL, IR records, and automaton actions use concrete domain types. Generated-parser reduction values, dynamic parser-table
|
|
30
|
+
cells, decoded JSON values, and user methods embedded as opaque Ruby source remain `untyped`; applications can reopen the generated
|
|
31
|
+
class in their own RBS files to declare embedded methods.
|
|
32
|
+
|
|
33
|
+
## Grammar IR v1
|
|
34
|
+
|
|
35
|
+
Top-level fields:
|
|
36
|
+
|
|
37
|
+
| Field | Meaning |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `ibex_ir`, `schema_version` | `"grammar"`, `1` |
|
|
40
|
+
| `class_name`, `superclass` | Generated Ruby class contract |
|
|
41
|
+
| `start`, `expect`, `options` | Start name, unresolved S/R expectation, result/action flags |
|
|
42
|
+
| `symbols` | Interned terminals and nonterminals; `$eof` id 0 and `error` id 1 |
|
|
43
|
+
| `productions` | Numeric LHS/RHS ids, action, precedence override, source origin |
|
|
44
|
+
| `user_code`, `conversions`, `warnings` | Concatenated code, external token expressions, structured diagnostics |
|
|
45
|
+
| `user_code_chunks` | Optional opaque chunks with first-code-line locations for compatible source mapping |
|
|
46
|
+
|
|
47
|
+
Warning records use stable type names (`undeclared_terminal`, `unused_terminal`, `unreachable_nonterminal`,
|
|
48
|
+
`duplicate_production`, and `empty_language`) and retain source locations. The CLI applies display/error policy at the boundary;
|
|
49
|
+
normalization and IR serialization do not discard diagnostics.
|
|
50
|
+
|
|
51
|
+
A symbol has `id`, `name`, `kind`, `reserved`, optional `prec {associativity, level}`, and `loc`. A production has `id`, `lhs`,
|
|
52
|
+
`rhs`, optional `action`, optional `prec_override`, and `origin`. Synthetic EBNF origins include an additive, deterministic
|
|
53
|
+
`expression` label used by text, DOT, and HTML presentation while numeric symbol identities remain unchanged. An action has opaque
|
|
54
|
+
`code`, `loc`, `named_refs [{name,index}]`, and `context_length`; middle-action helpers use the last field to view preceding stack
|
|
55
|
+
values.
|
|
56
|
+
|
|
57
|
+
IR objects and nested collections are frozen. JSON keys have deterministic order, so dump/load/dump is byte-stable. Incompatible
|
|
58
|
+
schema changes require a new version. The additive `user_code_chunks` field is optional so older schema-v1 JSON remains loadable;
|
|
59
|
+
new text-front-end IR retains it to make direct and resumed `--line-convert-all` generation identical.
|
|
60
|
+
|
|
61
|
+
## Automaton IR v1
|
|
62
|
+
|
|
63
|
+
Top-level fields are `ibex_ir: "automaton"`, `schema_version`, `algorithm`, `grammar_digest`, embedded `grammar`, `states`, and
|
|
64
|
+
`conflict_summary`. Embedding Grammar IR makes automaton JSON sufficient for code generation after `--from=automaton-ir`.
|
|
65
|
+
|
|
66
|
+
Each state contains:
|
|
67
|
+
|
|
68
|
+
- merged items `{production, dot, lookaheads}`;
|
|
69
|
+
- named `transitions`;
|
|
70
|
+
- resolved terminal `actions` and nonterminal `gotos`;
|
|
71
|
+
- an optional reduce `default_action`, selected only when explicit error masks preserve every terminal lookup and reduce the
|
|
72
|
+
total encoded ACTION entries;
|
|
73
|
+
- every conflict, including precedence-resolved conflicts and the resolution reason.
|
|
74
|
+
|
|
75
|
+
`conflict_summary.sr` counts unresolved default-shift conflicts for `expect`; `resolved_sr` counts retained precedence or
|
|
76
|
+
associativity decisions; `rr` counts reduce/reduce cells.
|
|
77
|
+
|
|
78
|
+
## Runtime table contract
|
|
79
|
+
|
|
80
|
+
Generated subclasses expose `.parser_tables` with a required `format_version`, external `tokens`, display `token_names`, ACTION
|
|
81
|
+
and GOTO tables, per-state default actions, and production `{lhs,length,action}` records. The runtime validates the version before
|
|
82
|
+
reading input and rejects missing or unsupported formats with a regeneration instruction; see
|
|
83
|
+
[ADR 0018](decisions/0018-parser-table-format-version.md). Plain tables are arrays of Hash rows. Compact tables use row
|
|
84
|
+
displacement with offsets, values, and row-ownership checks; both expose equivalent lookups. Default reductions are restricted
|
|
85
|
+
to known token ids, and explicit error masks preserve the pre-optimization result of every declared terminal cell, including
|
|
86
|
+
the synthetic `error` terminal. The deterministic size policy is fixed by
|
|
87
|
+
[ADR 0014](decisions/0014-compatibility-safe-default-reductions.md).
|
|
88
|
+
|
|
89
|
+
The runtime maintains state and value stacks, pulls a lookahead only when required, and applies tagged `shift`, `reduce`,
|
|
90
|
+
`accept`, and `error` actions. Recovery pops to a state that shifts token id 1, suppresses repeated reports for three successful
|
|
91
|
+
shifts, and honors `yyerrok`. No-op `on_shift`, `on_reduce`, and `on_error_recover` extension points observe successfully
|
|
92
|
+
committed events without changing parser results; the recovery hook retains the pre-pop error context and is distinct from an
|
|
93
|
+
ordinary token shift. Their ordering and payload contract is fixed by [ADR 0013](decisions/0013-runtime-observation-hooks.md).
|
|
94
|
+
|
|
95
|
+
## Clean-room boundary
|
|
96
|
+
|
|
97
|
+
Implementation work uses public racc documentation, CLI black-box behavior, and published LR algorithms only. racc implementation
|
|
98
|
+
sources and generated source are not inputs to the design. Self-authored compatibility grammars execute both outputs in separate
|
|
99
|
+
processes and compare observable results.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Compatibility notes
|
|
2
|
+
|
|
3
|
+
Compatibility observations are recorded here without consulting racc implementation sources or generated source text.
|
|
4
|
+
|
|
5
|
+
## Observed CLI (2026-07-22)
|
|
6
|
+
|
|
7
|
+
Black-box command: `racc --help`, version 1.8.1.
|
|
8
|
+
|
|
9
|
+
Observed options are `-o/--output-file`, `-t/--debug`, obsolete `-g`, `-v/--verbose`, `-O/--log-file`,
|
|
10
|
+
`-e/--executable`, `-E/--embedded`, `-F/--frozen`, `--line-convert-all`, `-l/--no-line-convert`,
|
|
11
|
+
`-a/--no-omit-actions`, `--superclass`, `-C/--check-only`, `-S/--output-status`, profiling `-P`, internal `-D`,
|
|
12
|
+
`--version`, `--runtime-version`, `--copyright`, and `--help`.
|
|
13
|
+
|
|
14
|
+
Ibex accepts that option set. `-P` and `-D` are accepted compatibility no-ops because they expose generator internals rather
|
|
15
|
+
than parser behavior. Frozen string literals are always emitted, so `-F` is also a no-op. Ibex's default output is `<input>.rb`
|
|
16
|
+
rather than racc's observed `<input>.tab.rb`; `-o` is portable between both tools.
|
|
17
|
+
|
|
18
|
+
## Behavioral probes
|
|
19
|
+
|
|
20
|
+
Self-authored grammars are compiled independently and the generated files are executed without inspecting them. Current probes
|
|
21
|
+
cover arithmetic precedence, empty rules, string tokens, `convert`, `no_result_var`, inline actions, dangling-else `expect`, and
|
|
22
|
+
a generated 500-production grammar. Precedence-resolved conflicts remain in Automaton IR diagnostics but are not counted in CLI
|
|
23
|
+
warnings or `expect`, matching the observed calculator behavior.
|
|
24
|
+
|
|
25
|
+
An inline action's own `val[0]` was observed as `nil`; its result occupies one value position visible to the final action. The
|
|
26
|
+
grammar reference confirms that `convert` takes a quoted string containing Ruby source: for example `NUM ':number'` emits the
|
|
27
|
+
symbol expression, while `NUM '"number"'` emits a Ruby string token.
|
|
28
|
+
|
|
29
|
+
Source-line probes with raises in self-authored `header`, `inner`, and `footer` blocks show three distinct modes. The default maps
|
|
30
|
+
semantic actions and `inner` code to the grammar file but leaves `header` and `footer` on generated-file lines.
|
|
31
|
+
`--line-convert-all` maps all three user-code sections, while `-l` leaves all of them on generated-file lines.
|
|
32
|
+
|
|
33
|
+
With an explicitly declared but syntactically invalid `BAD` token, error recovery results and `on_error` observations (token
|
|
34
|
+
string, value, and value-stack length) match. An undeclared `:BAD` was observed to enter racc's `error` production without an
|
|
35
|
+
`on_error` callback. Ibex intentionally treats it as an unknown lookahead and calls `on_error` before attempting the same error
|
|
36
|
+
recovery flow. This preserves the unexpected token object and value for logging and application policy instead of silently
|
|
37
|
+
entering recovery.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Grammar reference
|
|
2
|
+
|
|
3
|
+
Ibex's default `racc` mode accepts the compatible grammar described here. `--mode=extended` or an explicit grammar-file
|
|
4
|
+
`pragma extended` adds the marked syntax; extensions are never inferred from a production.
|
|
5
|
+
|
|
6
|
+
## File structure
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
class Namespace::Parser < OptionalSuperclass
|
|
10
|
+
pragma extended # optional; must precede ordinary declarations
|
|
11
|
+
declarations
|
|
12
|
+
rule
|
|
13
|
+
productions
|
|
14
|
+
end
|
|
15
|
+
---- header
|
|
16
|
+
Ruby copied before the parser class
|
|
17
|
+
---- inner
|
|
18
|
+
Ruby copied inside the parser class
|
|
19
|
+
---- footer
|
|
20
|
+
Ruby copied after the parser class
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The superclass defaults to `Ibex::Runtime::Parser`. Repeated user-code blocks retain their source order and are concatenated.
|
|
24
|
+
Grammar comments use `#` through end of line or `/* ... */`.
|
|
25
|
+
|
|
26
|
+
## Declarations
|
|
27
|
+
|
|
28
|
+
- `pragma extended` enables extended syntax for this grammar even when the CLI uses its default or explicit `--mode=racc`.
|
|
29
|
+
It must immediately follow the class header, before every ordinary declaration. Unknown, duplicate, and misplaced pragmas
|
|
30
|
+
are positioned errors. The pragma is consumed by the frontend and is not stored in AST or Grammar IR output.
|
|
31
|
+
|
|
32
|
+
- `token NAME ...` declares terminals for typo diagnostics. It is optional. Uppercase names and quoted strings are terminals;
|
|
33
|
+
lowercase names are nonterminals unless they are `error`.
|
|
34
|
+
- A `prechigh ... preclow` block lists precedence from high to low; `preclow ... prechigh` lists it from low to high. Each level
|
|
35
|
+
begins with `left`, `right`, or `nonassoc` followed by one or more terminals.
|
|
36
|
+
- `options no_result_var` makes an action's final expression its value. `omit_action_call` is enabled by default;
|
|
37
|
+
`no_omit_action_call` disables it.
|
|
38
|
+
- `expect N` suppresses the warning when exactly N unresolved shift/reduce conflicts remain. Conflicts resolved by precedence are
|
|
39
|
+
retained in Automaton IR but are not counted.
|
|
40
|
+
- `start name` overrides the first rule as the start symbol.
|
|
41
|
+
- `convert ... end` changes external token objects. The second column is a quoted string containing Ruby source, not the value
|
|
42
|
+
itself: `NUM ':number'` uses `:number`, while `NUM '"number"'` uses the String `"number"`.
|
|
43
|
+
|
|
44
|
+
## Productions and actions
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
rule
|
|
48
|
+
expression : expression '+' expression { result = val[0] + val[2] }
|
|
49
|
+
| '-' expression = UMINUS
|
|
50
|
+
| NUMBER
|
|
51
|
+
| /* empty */
|
|
52
|
+
; /* optional */
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Alternatives use `|`; a trailing semicolon is optional. `= TOKEN` overrides a production's precedence. The `error` terminal
|
|
57
|
+
enables yacc-style recovery.
|
|
58
|
+
|
|
59
|
+
Actions are opaque Ruby between balanced braces. `val` contains RHS values, `result` begins as `val[0]`, and `_values` is a copy
|
|
60
|
+
of the surrounding value stack. With `no_result_var`, the action's evaluated value is used directly. A middle action becomes an
|
|
61
|
+
empty helper production and consumes one value position in the enclosing RHS.
|
|
62
|
+
|
|
63
|
+
Action and `inner` backtraces use the original grammar filename and line by default. `--line-convert-all` applies the same mapping
|
|
64
|
+
to `header` and `footer`; `-l` keeps all backtraces on generated-file lines.
|
|
65
|
+
|
|
66
|
+
The action scanner handles nested braces, quoted/backtick strings and interpolation, `%q/%Q/%w/%W/%i/%I/%x/%r/%s`, regular
|
|
67
|
+
expressions, comments, character literals, and unquoted, single-quoted, double-quoted, or backtick heredocs. Indented, squiggly,
|
|
68
|
+
interpolated, and multiple heredocs on one opener line are supported. See [lexer coverage](lexer-coverage.md).
|
|
69
|
+
|
|
70
|
+
## Runtime errors
|
|
71
|
+
|
|
72
|
+
The default `on_error(token_id, value, value_stack)` raises `Ibex::ParseError`. Override it and return to allow an `error`
|
|
73
|
+
production to recover. Unknown external token objects receive a temporary negative internal id, remain printable through
|
|
74
|
+
`token_to_str`, and always invoke `on_error` before recovery is attempted.
|
|
75
|
+
|
|
76
|
+
Three optional observer methods default to no-ops. `on_shift(token_id, value, state)` follows each ordinary input-token shift;
|
|
77
|
+
`on_reduce(production_id, values, result)` follows a completed semantic action and goto; and
|
|
78
|
+
`on_error_recover(token_id, value, value_stack)` follows a successful synthetic `error` shift while retaining the original
|
|
79
|
+
unexpected-token context. Hook return values are ignored and exceptions propagate. See
|
|
80
|
+
[ADR 0013](decisions/0013-runtime-observation-hooks.md) for exact ordering and snapshot semantics.
|
|
81
|
+
|
|
82
|
+
## Extended EBNF and names
|
|
83
|
+
|
|
84
|
+
Extended mode supports:
|
|
85
|
+
|
|
86
|
+
- `item?`: `nil` or the item value.
|
|
87
|
+
- `item*`: zero or more values as an Array.
|
|
88
|
+
- `item+`: one or more values as an Array.
|
|
89
|
+
- `separated_list(item, separator)`: zero or more item values; separators are omitted.
|
|
90
|
+
- `separated_nonempty_list(item, separator)`: one or more item values.
|
|
91
|
+
- `symbol:name`: binds the corresponding RHS value as a local variable in the final action.
|
|
92
|
+
|
|
93
|
+
Parenthesized groups may contain sequences, alternatives, and nested EBNF, for example `(KEY VALUE)*`, `(A | B)+`, or
|
|
94
|
+
`separated_list((KEY VALUE), ',')`. A one-item group has that item's value; a multi-item group has an Array of its item values;
|
|
95
|
+
an empty group has `nil`. Named references must be unique in an outer alternative and cannot use `result`, `val`, or `_values`;
|
|
96
|
+
references inside a group are rejected because the group is lowered behind one outer value slot. Text, DOT, and HTML reports
|
|
97
|
+
render lowered helper nonterminals as their original EBNF expressions instead of exposing generated helper names.
|
|
98
|
+
|
|
99
|
+
Actions and named references are supported on an outer production alternative, but not inside a parenthesized EBNF group.
|
|
100
|
+
Move the action or binding to a separately named ordinary rule and reference that rule from the group.
|
|
101
|
+
|
|
102
|
+
## Strict diagnostics
|
|
103
|
+
|
|
104
|
+
Grammar IR retains structured diagnostics for undeclared or unused terminals, unreachable nonterminals, duplicate productions,
|
|
105
|
+
and a start symbol that cannot derive any terminal sentence. They remain silent by default for compatibility. `--warnings=all`
|
|
106
|
+
prints them, `--warnings=all,error` or `--warnings=error` promotes them to command failures, and `--warnings=none` explicitly
|
|
107
|
+
suppresses them.
|
|
108
|
+
|
|
109
|
+
## Ruby DSL
|
|
110
|
+
|
|
111
|
+
The DSL builds the same AST and IR without evaluating grammar text:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
ast = Ibex::Frontend::DSL.grammar(class_name: "Calculator") do |grammar|
|
|
115
|
+
grammar.token(:NUM)
|
|
116
|
+
grammar.precedence { |levels| levels.left("'+'"); levels.left("'*'") }
|
|
117
|
+
grammar.rule(:expr) do |rule|
|
|
118
|
+
rule.alt(:expr, "'+'", :expr, action: " result = val[0] + val[2] ")
|
|
119
|
+
rule.alt(:NUM)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
grammar_ir = Ibex::Normalizer.new(ast).normalize
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The builder also provides `options`, `expect`, `start`, `convert`, `user_code`, `ref(as:)`, `optional`, `star`, `plus`,
|
|
127
|
+
`separated_list`, and `inline`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Lexer coverage
|
|
2
|
+
|
|
3
|
+
| Construct | Status | Notes |
|
|
4
|
+
|---|---|---|
|
|
5
|
+
| Nested action braces | Supported | Balanced recursively |
|
|
6
|
+
| Single, double, and backtick strings | Supported | Escapes and `#{...}` interpolation are skipped |
|
|
7
|
+
| `%q`, `%Q`, `%w`, `%W`, `%i`, `%I`, `%x`, `%r`, `%s` | Supported | Paired and single-character delimiters |
|
|
8
|
+
| Regular expressions | Supported | Conservative expression-prefix heuristic; character classes and flags supported |
|
|
9
|
+
| `#` comments and `?x` characters | Supported | Ignored for brace balancing |
|
|
10
|
+
| `<<ID`, `<<-ID`, `<<~ID` heredocs | Supported | Terminators obey indentation mode |
|
|
11
|
+
| Single/double/backtick quoted heredocs | Supported | Quoted identifiers may contain any non-quote, non-newline text |
|
|
12
|
+
| Interpolated and multiple heredocs | Supported | Bodies are opaque; multiple openers on one line are consumed in order |
|
|
13
|
+
| Grammar `#` and `/* ... */` comments | Supported | Removed before tokenization |
|
|
14
|
+
| `---- header`, `inner`, `footer` | Supported | Must begin in column one; order and duplicates retained |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Phase 10 extensions
|
|
2
|
+
|
|
3
|
+
## E3: conflict witnesses
|
|
4
|
+
|
|
5
|
+
`Ibex::LALR::Counterexample` accepts only Automaton IR. It explores parser-stack configurations, forces each pair of competing
|
|
6
|
+
actions at the requested conflict, and then searches for a common suffix that lets both branches accept the same terminal
|
|
7
|
+
sentence. A successful result contains `unifying: true`, the conflict lookahead position, and both complete derivation trees.
|
|
8
|
+
|
|
9
|
+
Because unifying-counterexample search is not guaranteed to terminate for every context-free grammar, the search has explicit
|
|
10
|
+
token and configuration budgets. The defaults are 32 tokens and 50,000 explored configurations. Pass `max_tokens:` and
|
|
11
|
+
`max_configurations:` to `Ibex::LALR::Counterexample.new` or `Ibex::Codegen::Report.render` to adjust them through the Ruby API.
|
|
12
|
+
For generated reports, use the positive-integer CLI options `--counterexample-max-tokens=N` and
|
|
13
|
+
`--counterexample-max-configurations=N`; either option also requests a report. The token budget covers each unifying-search
|
|
14
|
+
candidate and any returned unifying counterexample, including a non-EOF conflict lookahead; the EOF marker does not consume a
|
|
15
|
+
token. If no common sentence is found within those budgets, the result is marked `unifying: false` and contains the deterministic
|
|
16
|
+
shortest reachability witness, which may exceed the unifying-search token budget. This distinction prevents a nonunifying
|
|
17
|
+
diagnostic from being presented as proof of ambiguity.
|
|
18
|
+
|
|
19
|
+
## E7: Ruby DSL
|
|
20
|
+
|
|
21
|
+
`Ibex::Frontend::DSL.grammar` constructs the existing AST with synthetic locations and joins the normal pipeline at Normalizer.
|
|
22
|
+
Text and DSL inputs therefore share symbol classification, EBNF expansion, validation, algorithms, and outputs.
|
|
23
|
+
|
|
24
|
+
## E9: construction algorithms
|
|
25
|
+
|
|
26
|
+
The builder first constructs canonical LR(1). `lr1` retains the states, `lalr` merges equal LR(0) cores, and `slr` applies FOLLOW
|
|
27
|
+
sets to completed items in the LR(0) states. All strategies use the same conflict resolver and Automaton IR.
|