racc-rbs-inline 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/LICENSE.txt +21 -0
- data/README.md +269 -0
- data/data/racc/parser.rbs +99 -0
- data/exe/racc-rbs-inline +6 -0
- data/lib/racc_rbs_inline/annotator.rb +180 -0
- data/lib/racc_rbs_inline/cli.rb +235 -0
- data/lib/racc_rbs_inline/grammar_types.rb +219 -0
- data/lib/racc_rbs_inline/rake_task.rb +34 -0
- data/lib/racc_rbs_inline/version.rb +6 -0
- data/lib/racc_rbs_inline.rb +20 -0
- data/sig/generated/racc_rbs_inline/annotator.rbs +51 -0
- data/sig/generated/racc_rbs_inline/cli.rbs +46 -0
- data/sig/generated/racc_rbs_inline/grammar_types.rbs +48 -0
- data/sig/generated/racc_rbs_inline/rake_task.rbs +16 -0
- data/sig/generated/racc_rbs_inline/version.rbs +5 -0
- data/sig/generated/racc_rbs_inline.rbs +12 -0
- data/sig/manifest.yaml +4 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b215555957e98247fb76e1d65743e02e5b3ec8e84d4c143e40bb59c54b9ec51c
|
|
4
|
+
data.tar.gz: a809fc1c903ecbc510e63839f35d61dda7f349aa7abed5c5588acd4998f0fd47
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ef470246d25e93fa2331241b931e441c13622bfec4afbec0caa2222f6f63a262da9558046c2d33d5497a03881dabb3888b4099ce7d1de02b4904cb08a8ea9467
|
|
7
|
+
data.tar.gz: b3878b62cc6659442d201861fb4db51a9609de9fe4e7d6331b1176f56f4f6f4787beacf3eab1a5953911d952c844300bbe96fe5a4f84e278b0a1b753fab975ad
|
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,269 @@
|
|
|
1
|
+
# racc-rbs-inline
|
|
2
|
+
|
|
3
|
+
Add inline RBS annotations to a Racc-generated Ruby parser, without modifying Racc.
|
|
4
|
+
The annotations work with **rbs-inline 0.14** and **Steep 2.0's native inline support**.
|
|
5
|
+
Generated parsers keep their existing runtime dependencies.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Until the first RubyGems release, add this development dependency to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
group :development do
|
|
13
|
+
gem "racc-rbs-inline", git: "https://github.com/ydah/racc-rbs-inline"
|
|
14
|
+
gem "racc", "1.8.1"
|
|
15
|
+
gem "rbs-inline", "0.14.0" # only needed for the extraction workflow
|
|
16
|
+
gem "steep", "2.0.0"
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run `bundle install`. The annotator requires Ruby 3.2+, Prism 1.0+, Racc 1.6.2+
|
|
21
|
+
(below 2.0), and RBS 4.1+ (below 5.0).
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Add one command after your existing Racc invocation:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bundle exec racc -o lib/parser.rb parser.y
|
|
29
|
+
bundle exec racc-rbs-inline annotate lib/parser.rb
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or generate and annotate together:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bundle exec racc-rbs-inline generate parser.y -o lib/parser.rb -- --embedded --frozen
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`-o` is required. Arguments after `--` are passed to Racc, which runs as a separate
|
|
39
|
+
process. Racc's exit status is preserved if generation fails. For executable
|
|
40
|
+
output with Racc 1.8.1, specify a path such as `--executable=/usr/bin/ruby`;
|
|
41
|
+
Racc itself rejects an omitted executable path on some Ruby versions.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
# Annotate several files in place.
|
|
45
|
+
bundle exec racc-rbs-inline annotate lib/parser.rb lib/other_parser.rb
|
|
46
|
+
|
|
47
|
+
# Print the transformed source, leaving files alone.
|
|
48
|
+
bundle exec racc-rbs-inline annotate --stdout lib/parser.rb
|
|
49
|
+
|
|
50
|
+
# Add semantic value types declared in the grammar.
|
|
51
|
+
bundle exec racc-rbs-inline annotate lib/parser.rb --grammar parser.y
|
|
52
|
+
|
|
53
|
+
# List files needing annotation; exit 1 if any need changes, otherwise 0.
|
|
54
|
+
bundle exec racc-rbs-inline annotate --check lib/parser.rb
|
|
55
|
+
|
|
56
|
+
# Copy runtime signatures (defaults to sig/racc/parser.rbs).
|
|
57
|
+
bundle exec racc-rbs-inline sig --output sig/racc
|
|
58
|
+
|
|
59
|
+
bundle exec racc-rbs-inline --help
|
|
60
|
+
bundle exec racc-rbs-inline --version
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Invalid Ruby, including invalid Ruby exposed when action wrappers are removed,
|
|
64
|
+
is rejected before writing the annotated file. `annotate` validates every input
|
|
65
|
+
before writing any of them. Repeating annotation leaves the file unchanged.
|
|
66
|
+
|
|
67
|
+
The library API returns source text without performing file I/O:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
require "racc_rbs_inline"
|
|
71
|
+
|
|
72
|
+
annotated_source = RaccRbsInline.annotate(source)
|
|
73
|
+
RaccRbsInline.annotated?(annotated_source) # => true
|
|
74
|
+
|
|
75
|
+
# Supply grammar text to add or refresh semantic value types.
|
|
76
|
+
annotated_source = RaccRbsInline.annotate(source,
|
|
77
|
+
grammar: File.read("parser.y"), grammar_filename: "parser.y")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`annotated?` detects the `# rbs_inline: enabled` comment. Without a grammar,
|
|
81
|
+
files already carrying that marker are left untouched. Supplying a grammar
|
|
82
|
+
validates the generated tables and refreshes reduction signatures, including
|
|
83
|
+
files that already have generic annotations.
|
|
84
|
+
|
|
85
|
+
## Semantic value types
|
|
86
|
+
|
|
87
|
+
Declare terminal and nonterminal types in grammar comments:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class TypedCalc
|
|
91
|
+
# @racc-type NUMBER exp target : Integer
|
|
92
|
+
# @racc-type '+' '(' ')' : String
|
|
93
|
+
rule
|
|
94
|
+
target: exp { result = val[0] }
|
|
95
|
+
exp: NUMBER { result = val[0] }
|
|
96
|
+
| exp '+' NUMBER { result = val[0] + val[2] }
|
|
97
|
+
| '(' exp ')' { result = val[1] }
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`generate` and the Rake task read these declarations automatically. For an
|
|
102
|
+
existing generated parser, pass `annotate --grammar parser.y`. Use the same
|
|
103
|
+
Racc version that generated the parser: mismatched rule tables are rejected
|
|
104
|
+
before writing. Invalid RBS types, unknown symbols, and conflicting declarations
|
|
105
|
+
are also errors. Semantic type processing requires quoted grammar symbols to
|
|
106
|
+
be literal strings without Ruby interpolation.
|
|
107
|
+
|
|
108
|
+
Each reduction receives a tuple of the right-hand-side types and returns the
|
|
109
|
+
left-hand-side type. Undeclared symbols stay `untyped`. The initial `result`
|
|
110
|
+
parameter includes both the first right-hand-side type (or `nil` for an empty
|
|
111
|
+
rule) and the left-hand-side type. `no_result_var` grammars receive two
|
|
112
|
+
parameters instead. Racc 1.8's `?`, `*`, and `+` symbols become optional or array
|
|
113
|
+
types; groups become `Array[untyped]`. Generated helper reductions and
|
|
114
|
+
`_reduce_none` retain generic signatures.
|
|
115
|
+
|
|
116
|
+
With the declarations above, returning `val[0]` from the parenthesized rule is
|
|
117
|
+
reported as a `String`/`Integer` mismatch by both checking workflows. A runnable
|
|
118
|
+
example, including token handling, is in
|
|
119
|
+
[test/fixtures/typed_calc.y](test/fixtures/typed_calc.y).
|
|
120
|
+
|
|
121
|
+
## Rake integration
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
require "racc_rbs_inline/rake_task"
|
|
125
|
+
|
|
126
|
+
RaccRbsInline::RakeTask.new(:parser) do |task|
|
|
127
|
+
task.grammar = "parser.y"
|
|
128
|
+
task.output = "lib/parser.rb"
|
|
129
|
+
task.options = ["--embedded", "--frozen"]
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`bundle exec rake parser` regenerates and annotates the output when the grammar
|
|
134
|
+
is newer or the output is absent. The output is also available as a file task.
|
|
135
|
+
|
|
136
|
+
## Type checking
|
|
137
|
+
|
|
138
|
+
First copy the `Racc::Parser` runtime signatures:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
bundle exec racc-rbs-inline sig
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
These signatures live in `data/racc` inside the gem, so they are not automatically
|
|
145
|
+
loaded with this gem's own signatures. Racc signatures have been submitted in
|
|
146
|
+
[gem_rbs_collection PR #1085](https://github.com/ruby/gem_rbs_collection/pull/1085).
|
|
147
|
+
Until they are accepted, use the `sig` command. Once collection support is
|
|
148
|
+
available, use `rbs collection install`
|
|
149
|
+
and remove your copied `sig/racc` directory to avoid duplicate declarations.
|
|
150
|
+
|
|
151
|
+
### Native inline support (Steep 2.0)
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Steepfile
|
|
155
|
+
target :lib do
|
|
156
|
+
signature "sig/racc"
|
|
157
|
+
check "lib", inline: true
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
bundle exec steep check
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Extract signatures with rbs-inline
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# Steepfile
|
|
169
|
+
target :lib do
|
|
170
|
+
signature "sig"
|
|
171
|
+
check "lib"
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
bundle exec rbs-inline --output=sig/generated lib
|
|
177
|
+
bundle exec steep check
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Choose one workflow; do not load extracted declarations while also enabling
|
|
181
|
+
native inline checking for the same parser.
|
|
182
|
+
|
|
183
|
+
A complete small grammar is in [test/fixtures/sum.y](test/fixtures/sum.y).
|
|
184
|
+
Copy that grammar to `parser.y` in a project with the Gemfile above:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
mkdir -p lib
|
|
188
|
+
bundle exec racc-rbs-inline generate parser.y -o lib/parser.rb
|
|
189
|
+
bundle exec racc-rbs-inline sig
|
|
190
|
+
# Save either Steepfile above, then run that workflow's commands.
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The integration tests exercise this grammar through both workflows, including
|
|
194
|
+
strict diagnostics. To enable strict diagnostics in your own target, add
|
|
195
|
+
`configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)`.
|
|
196
|
+
|
|
197
|
+
## What gets annotated
|
|
198
|
+
|
|
199
|
+
The annotator locates classes whose bodies assign `Racc_arg`. It unwraps their
|
|
200
|
+
heredoc `module_eval` calls, then annotates ten tables, three constants, and all
|
|
201
|
+
`_reduce_N` / `_reduce_none` methods. Reduction arguments and results use
|
|
202
|
+
`untyped` unless grammar declarations supply semantic value types.
|
|
203
|
+
|
|
204
|
+
User-written actions, `header`, `inner`, and `footer` code remain your
|
|
205
|
+
responsibility to type. Existing annotations in `inner` become visible after
|
|
206
|
+
unwrapping. Separate instance variable annotations from method definitions
|
|
207
|
+
with a blank line:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
---- inner
|
|
211
|
+
# @rbs @tokens: Array[Array[untyped]]
|
|
212
|
+
|
|
213
|
+
#: (Array[Array[untyped]]) -> void
|
|
214
|
+
def initialize(tokens)
|
|
215
|
+
@tokens = tokens
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Backtrace locations change:** unwrapping `module_eval` means exceptions point
|
|
220
|
+
to the generated `.rb` file instead of the grammar's `.y` lines, as with Racc's
|
|
221
|
+
`--no-line-convert` option. Code relying on `__FILE__`, `__LINE__`, or lexical
|
|
222
|
+
scope inside those wrappers should account for the same change.
|
|
223
|
+
|
|
224
|
+
The implementation preserves other source bytes, including embedded runtime
|
|
225
|
+
code, header/footer wrappers, encoding declarations, and line endings.
|
|
226
|
+
|
|
227
|
+
## Development
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
bundle install
|
|
231
|
+
bundle exec rbs collection install
|
|
232
|
+
ruby script/fetch-racc
|
|
233
|
+
bundle exec rake
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`rake test` runs unit tests, 13 golden/behavior variants, both type-checking
|
|
237
|
+
workflows, semantic value checks, deliberately corrupt table tests, and the
|
|
238
|
+
upstream Racc corpus.
|
|
239
|
+
The corpus is fetched from the pinned `v1.8.1` tag into ignored `tmp/racc` and is
|
|
240
|
+
not redistributed. Set `RACC_CORPUS` to use another local copy. Without the
|
|
241
|
+
corpus, that regression test reports a skip; CI always fetches it.
|
|
242
|
+
|
|
243
|
+
The 58 upstream grammars include three intentional generator errors and two
|
|
244
|
+
with invalid Ruby actions. All other 53 are checked separately to avoid class
|
|
245
|
+
name collisions. User-code signature failures are documented in
|
|
246
|
+
[test/regression_test.rb](test/regression_test.rb); generated table diagnostics
|
|
247
|
+
must be absent. The embedded runtime is checked byte-for-byte while golden
|
|
248
|
+
files normalize its representation, version banners, random delimiter IDs,
|
|
249
|
+
and executable paths.
|
|
250
|
+
|
|
251
|
+
`rake typecheck` regenerates this gem's own signatures and checks them with
|
|
252
|
+
Steep. CI covers Ruby 3.2, 3.3, 3.4, and 4.0, with Racc 1.8.1 as the baseline
|
|
253
|
+
and additional Racc 1.6.2 / 1.7.3 compatibility jobs.
|
|
254
|
+
Racc 1.6.2 does not support `--frozen`; that variant is skipped there.
|
|
255
|
+
Racc 1.6.2 and 1.7.3 cannot generate the four upstream grammars using Racc 1.8's
|
|
256
|
+
group/optional/repetition syntax, so their corpus checks cover the remaining 49.
|
|
257
|
+
|
|
258
|
+
The separate Lrama acceptance job checks the pinned upstream parser before and
|
|
259
|
+
after generation with `--embedded --frozen` (79 examples each), then checks
|
|
260
|
+
generated annotations with strict Steep diagnostics. Run it locally with
|
|
261
|
+
`bundle exec ruby script/test-lrama`; it fetches Lrama into ignored `tmp/lrama`
|
|
262
|
+
and writes reports to `tmp/lrama-acceptance`. It restores the original parser
|
|
263
|
+
after the check and requires a clean checkout at the pinned revision.
|
|
264
|
+
Application type errors remain in the report; two unrelated profiler signatures
|
|
265
|
+
using unsupported `void` unions are excluded from the temporary checking project.
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
[MIT](LICENSE.txt).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Type signatures for the Racc runtime (lib/racc/parser.rb, racc 1.8.x)
|
|
2
|
+
module Racc
|
|
3
|
+
VERSION: String
|
|
4
|
+
Version: String
|
|
5
|
+
Copyright: String
|
|
6
|
+
|
|
7
|
+
Racc_No_Extensions: bool
|
|
8
|
+
|
|
9
|
+
class ParseError < StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class Parser
|
|
13
|
+
# Token symbols as serialized in racc_token_table
|
|
14
|
+
type token_symbol = Symbol | String | false
|
|
15
|
+
|
|
16
|
+
# The 14-element Racc_arg tuple (same order as ParserFileGenerator emits)
|
|
17
|
+
type racc_arg = [
|
|
18
|
+
Array[Integer?], # action_table
|
|
19
|
+
Array[Integer?], # action_check
|
|
20
|
+
Array[Integer?], # action_default
|
|
21
|
+
Array[Integer?], # action_pointer
|
|
22
|
+
Array[Integer?], # goto_table
|
|
23
|
+
Array[Integer?], # goto_check
|
|
24
|
+
Array[Integer?], # goto_default
|
|
25
|
+
Array[Integer?], # goto_pointer
|
|
26
|
+
Integer, # nt_base
|
|
27
|
+
Array[Integer | Symbol], # reduce_table
|
|
28
|
+
Hash[untyped, Integer], # token_table
|
|
29
|
+
Integer, # shift_n
|
|
30
|
+
Integer, # reduce_n
|
|
31
|
+
bool # use_result_var
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
interface _DebugOut
|
|
35
|
+
def print: (*untyped) -> untyped
|
|
36
|
+
def puts: (*untyped) -> untyped
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Racc_Runtime_Version: String
|
|
40
|
+
Racc_Runtime_Core_Version_R: String
|
|
41
|
+
Racc_Runtime_Core_Version_C: String
|
|
42
|
+
Racc_Main_Parsing_Routine: Symbol
|
|
43
|
+
Racc_YY_Parse_Method: Symbol
|
|
44
|
+
Racc_Runtime_Core_Version: String
|
|
45
|
+
Racc_Runtime_Type: String
|
|
46
|
+
|
|
47
|
+
@yydebug: bool
|
|
48
|
+
@racc_debug_out: _DebugOut
|
|
49
|
+
@racc_state: Array[Integer]
|
|
50
|
+
@racc_tstack: Array[Integer]
|
|
51
|
+
@racc_vstack: Array[untyped]
|
|
52
|
+
@racc_t: Integer?
|
|
53
|
+
@racc_val: untyped
|
|
54
|
+
@racc_read_next: bool
|
|
55
|
+
@racc_user_yyerror: bool
|
|
56
|
+
@racc_error_status: Integer
|
|
57
|
+
|
|
58
|
+
def self.racc_runtime_type: () -> String
|
|
59
|
+
|
|
60
|
+
# Subclasses define their own constructors; keep it permissive so that
|
|
61
|
+
# unannotated overrides do not inherit `() -> void` from Object.
|
|
62
|
+
def initialize: (?) -> void
|
|
63
|
+
|
|
64
|
+
# Methods users commonly override: keep types permissive.
|
|
65
|
+
def next_token: () -> Array[untyped]?
|
|
66
|
+
def on_error: (Integer t, untyped val, Array[untyped] vstack) -> untyped
|
|
67
|
+
|
|
68
|
+
def do_parse: () -> untyped
|
|
69
|
+
def yyparse: (untyped recv, interned mid) -> untyped
|
|
70
|
+
def yyerror: () -> bot
|
|
71
|
+
def yyaccept: () -> bot
|
|
72
|
+
def yyerrok: () -> void
|
|
73
|
+
def token_to_str: (Integer t) -> String?
|
|
74
|
+
|
|
75
|
+
def racc_read_token: (Integer t, untyped tok, untyped val) -> void
|
|
76
|
+
def racc_shift: (Integer tok, Array[Integer] tstack, Array[untyped] vstack) -> void
|
|
77
|
+
def racc_reduce: (Array[Integer] toks, Integer sim, Array[Integer] tstack, Array[untyped] vstack) -> void
|
|
78
|
+
def racc_accept: () -> void
|
|
79
|
+
def racc_e_pop: (Array[Integer] state, Array[Integer] tstack, Array[untyped] vstack) -> void
|
|
80
|
+
def racc_next_state: (Integer curstate, Array[Integer] state) -> void
|
|
81
|
+
def racc_print_stacks: (Array[Integer] t, Array[untyped] v) -> void
|
|
82
|
+
def racc_print_states: (Array[Integer] s) -> void
|
|
83
|
+
def racc_token2str: (Integer tok) -> String
|
|
84
|
+
|
|
85
|
+
def _racc_setup: () -> racc_arg
|
|
86
|
+
def _racc_init_sysvars: () -> void
|
|
87
|
+
def _racc_do_parse_rb: (racc_arg arg, bool in_debug) -> untyped
|
|
88
|
+
def _racc_yyparse_rb: (untyped recv, interned mid, racc_arg arg, bool c_debug) -> untyped
|
|
89
|
+
def _racc_evalact: (Integer act, racc_arg arg) -> Integer?
|
|
90
|
+
def _racc_do_reduce: (racc_arg arg, Integer act) -> Integer?
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def _racc_do_parse_c: (racc_arg arg, bool in_debug) -> untyped
|
|
95
|
+
def _racc_yyparse_c: (untyped recv, interned mid, racc_arg arg, bool c_debug) -> untyped
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class ::ParseError = Racc::ParseError
|
data/exe/racc-rbs-inline
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "prism"
|
|
5
|
+
|
|
6
|
+
module RaccRbsInline
|
|
7
|
+
class Annotator
|
|
8
|
+
MAGIC = "# rbs_inline: enabled" #: String
|
|
9
|
+
INTEGER_TABLE = /\Aracc_(?:action|goto)_(?:table|check|pointer|default)\z/ #: Regexp
|
|
10
|
+
REDUCTION = /\A_reduce_(?:\d+|none)\z/ #: Regexp
|
|
11
|
+
CONSTANT_TYPES = {
|
|
12
|
+
Racc_arg: "::Racc::Parser::racc_arg",
|
|
13
|
+
Racc_token_to_s_table: "Array[String]",
|
|
14
|
+
Racc_debug_parser: "bool"
|
|
15
|
+
}.freeze #: Hash[Symbol, String]
|
|
16
|
+
|
|
17
|
+
#: (String source, ?grammar: String?, ?grammar_filename: String) -> String
|
|
18
|
+
def annotate(source, grammar: nil, grammar_filename: "grammar.y")
|
|
19
|
+
existing = annotated?(source)
|
|
20
|
+
return source if existing && !grammar
|
|
21
|
+
|
|
22
|
+
types = nil #: GrammarTypes?
|
|
23
|
+
if grammar
|
|
24
|
+
require_relative "grammar_types"
|
|
25
|
+
types = GrammarTypes.new(grammar, filename: grammar_filename)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
source = unwrap(source)
|
|
29
|
+
source = add_annotations(source, types: types, existing: existing)
|
|
30
|
+
existing ? source : add_magic_comment(source)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#: (String source) -> bool
|
|
34
|
+
def annotated?(source)
|
|
35
|
+
return false unless source.include?(MAGIC)
|
|
36
|
+
|
|
37
|
+
Prism.parse(source).comments.any? { |comment| comment.location.slice.strip == MAGIC }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
#: (String source) -> String
|
|
43
|
+
def unwrap(source)
|
|
44
|
+
edits = [] #: Array[[Integer, Integer, String]]
|
|
45
|
+
binary = source.b
|
|
46
|
+
parser_bodies(parse!(source)).each do |body|
|
|
47
|
+
body.body.each do |statement|
|
|
48
|
+
next unless statement.is_a?(Prism::CallNode) && statement.name == :module_eval && statement.receiver.nil?
|
|
49
|
+
|
|
50
|
+
heredoc = statement.arguments&.arguments&.first
|
|
51
|
+
next unless heredoc.is_a?(Prism::StringNode) && heredoc.heredoc?
|
|
52
|
+
closing = heredoc.closing_loc
|
|
53
|
+
next unless closing
|
|
54
|
+
|
|
55
|
+
start = line_start(binary, statement.location.start_offset)
|
|
56
|
+
edits << [start, closing.end_offset, heredoc.content_loc.slice.b]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
apply(source, edits)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#: (String source, types: GrammarTypes?, existing: bool) -> String
|
|
63
|
+
def add_annotations(source, types:, existing:)
|
|
64
|
+
edits = [] #: Array[[Integer, Integer, String]]
|
|
65
|
+
binary = source.b
|
|
66
|
+
parser_bodies(parse!(source)).each do |body|
|
|
67
|
+
signatures = types ? types.signatures(body: body) : {} #: Hash[Symbol, String]
|
|
68
|
+
body.body.each do |statement|
|
|
69
|
+
case statement
|
|
70
|
+
when Prism::LocalVariableWriteNode
|
|
71
|
+
type = table_type(statement)
|
|
72
|
+
edits << trailing(statement, type) if type && !existing
|
|
73
|
+
when Prism::ConstantWriteNode
|
|
74
|
+
type = CONSTANT_TYPES[statement.name]
|
|
75
|
+
edits << trailing(statement, type) if type && !existing
|
|
76
|
+
when Prism::DefNode
|
|
77
|
+
next unless statement.name.to_s.match?(REDUCTION)
|
|
78
|
+
|
|
79
|
+
count = statement.parameters&.requireds&.size
|
|
80
|
+
unless [2, 3].include?(count)
|
|
81
|
+
raise Error, "Unsupported parameters for #{statement.name}: expected two or three required parameters"
|
|
82
|
+
end
|
|
83
|
+
parameters = "Array[untyped] val, Array[untyped] _values"
|
|
84
|
+
parameters += ", untyped result" if count == 3
|
|
85
|
+
start = line_start(binary, statement.location.start_offset)
|
|
86
|
+
indent = binary.byteslice(start, statement.location.start_offset - start)
|
|
87
|
+
signature = signatures.fetch(statement.name, "#: (#{parameters}) -> untyped")
|
|
88
|
+
finish = start
|
|
89
|
+
if existing && start.positive?
|
|
90
|
+
previous = line_start(binary, start - 1)
|
|
91
|
+
line = binary.byteslice(previous, start - previous)
|
|
92
|
+
start = previous if line&.match?(/\A[ \t]*#:/)
|
|
93
|
+
end
|
|
94
|
+
edits << [start, finish, "#{indent}#{signature}#{newline(source)}".b]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
apply(source, edits)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#: (Prism::LocalVariableWriteNode node) -> String?
|
|
102
|
+
def table_type(node)
|
|
103
|
+
name = node.name.to_s
|
|
104
|
+
return "Array[Integer?]" if name.match?(INTEGER_TABLE)
|
|
105
|
+
return "Array[Integer | Symbol]" if name == "racc_reduce_table"
|
|
106
|
+
return unless name == "racc_token_table"
|
|
107
|
+
|
|
108
|
+
literal = node.value.is_a?(Prism::HashNode) && node.value.elements.all? do |element|
|
|
109
|
+
element.is_a?(Prism::AssocNode) &&
|
|
110
|
+
[Prism::SymbolNode, Prism::StringNode, Prism::FalseNode].any? { |type| element.key.is_a?(type) }
|
|
111
|
+
end
|
|
112
|
+
literal ? "Hash[::Racc::Parser::token_symbol, Integer]" : "Hash[untyped, Integer]"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
#: (Prism::Node node, String type) -> [Integer, Integer, String]
|
|
116
|
+
def trailing(node, type)
|
|
117
|
+
offset = node.location.end_offset
|
|
118
|
+
[offset, offset, " #: #{type}".b]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#: (String source) -> String
|
|
122
|
+
def add_magic_comment(source)
|
|
123
|
+
offset = 0
|
|
124
|
+
source.b.each_line do |line|
|
|
125
|
+
break unless line.start_with?("#!") || line.match?(/\A#[^\r\n]*(?:frozen[_-]string[_-]literal\s*:|(?:coding|encoding)\s*[:=])/i)
|
|
126
|
+
|
|
127
|
+
offset += line.bytesize
|
|
128
|
+
end
|
|
129
|
+
apply(source, [[offset, offset, "#{MAGIC}#{newline(source)}".b]])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
#: (String source) -> String
|
|
133
|
+
def newline(source)
|
|
134
|
+
source.include?("\r\n") ? "\r\n" : "\n"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#: (String source) -> Prism::ProgramNode
|
|
138
|
+
def parse!(source)
|
|
139
|
+
result = Prism.parse(source)
|
|
140
|
+
if result.failure?
|
|
141
|
+
details = result.errors.map { |error| "line #{error.location.start_line}: #{error.message}" }.join(", ")
|
|
142
|
+
raise Error, "Invalid Ruby source: #{details}"
|
|
143
|
+
end
|
|
144
|
+
result.value
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
#: (Prism::ProgramNode program) -> Array[Prism::StatementsNode]
|
|
148
|
+
def parser_bodies(program)
|
|
149
|
+
bodies = [] #: Array[Prism::StatementsNode]
|
|
150
|
+
pending = [program] #: Array[Prism::Node]
|
|
151
|
+
while (node = pending.pop)
|
|
152
|
+
if node.is_a?(Prism::ClassNode) && node.body.is_a?(Prism::StatementsNode) &&
|
|
153
|
+
node.body.body.any? { |statement| statement.is_a?(Prism::ConstantWriteNode) && statement.name == :Racc_arg }
|
|
154
|
+
bodies << node.body
|
|
155
|
+
end
|
|
156
|
+
pending.concat(node.compact_child_nodes)
|
|
157
|
+
end
|
|
158
|
+
raise Error, "Racc parser class not found" if bodies.empty?
|
|
159
|
+
|
|
160
|
+
bodies
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#: (String binary, Integer offset) -> Integer
|
|
164
|
+
def line_start(binary, offset)
|
|
165
|
+
return 0 if offset.zero?
|
|
166
|
+
|
|
167
|
+
previous = binary.rindex("\n", offset - 1)
|
|
168
|
+
previous ? previous + 1 : 0
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#: (String source, Array[[Integer, Integer, String]] edits) -> String
|
|
172
|
+
def apply(source, edits)
|
|
173
|
+
binary = source.b
|
|
174
|
+
edits.sort_by { |start, _, _| -start }.each do |start, finish, replacement|
|
|
175
|
+
binary[start...finish] = replacement
|
|
176
|
+
end
|
|
177
|
+
binary.force_encoding(source.encoding)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|