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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "optparse"
|
|
7
|
+
require "rbconfig"
|
|
8
|
+
require "tempfile"
|
|
9
|
+
require "tmpdir"
|
|
10
|
+
require_relative "../racc_rbs_inline"
|
|
11
|
+
|
|
12
|
+
module RaccRbsInline
|
|
13
|
+
class CLI
|
|
14
|
+
HELP = <<~TEXT
|
|
15
|
+
Usage: racc-rbs-inline COMMAND [OPTIONS]
|
|
16
|
+
|
|
17
|
+
annotate [--stdout | --check] [--grammar GRAMMAR] FILE...
|
|
18
|
+
generate GRAMMAR -o OUT [-- RACC_OPTIONS]
|
|
19
|
+
sig [--output DIR] (default: sig/racc)
|
|
20
|
+
--version
|
|
21
|
+
--help
|
|
22
|
+
TEXT
|
|
23
|
+
|
|
24
|
+
# @rbs @out: untyped
|
|
25
|
+
# @rbs @err: untyped
|
|
26
|
+
|
|
27
|
+
#: (Array[String] argv, ?out: untyped, ?err: untyped) -> Integer
|
|
28
|
+
def self.run(argv, out: $stdout, err: $stderr)
|
|
29
|
+
new(out: out, err: err).run(argv)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: (out: untyped, err: untyped) -> void
|
|
33
|
+
def initialize(out:, err:)
|
|
34
|
+
@out = out
|
|
35
|
+
@err = err
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#: (Array[String] argv) -> Integer
|
|
39
|
+
def run(argv)
|
|
40
|
+
args = argv.dup
|
|
41
|
+
case args.shift
|
|
42
|
+
when "annotate" then annotate(args)
|
|
43
|
+
when "generate" then generate(args)
|
|
44
|
+
when "sig" then sig(args)
|
|
45
|
+
when "--version", "-v"
|
|
46
|
+
raise Error, "unexpected arguments" unless args.empty?
|
|
47
|
+
@out.puts(VERSION)
|
|
48
|
+
0
|
|
49
|
+
when "--help", "-h"
|
|
50
|
+
@out.puts(HELP)
|
|
51
|
+
0
|
|
52
|
+
else
|
|
53
|
+
raise Error, HELP
|
|
54
|
+
end
|
|
55
|
+
rescue Error, OptionParser::ParseError, SystemCallError, IOError => error
|
|
56
|
+
@err.puts("racc-rbs-inline: #{error.message}")
|
|
57
|
+
1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
#: (Array[String] args) -> Integer
|
|
63
|
+
def annotate(args)
|
|
64
|
+
stdout = false
|
|
65
|
+
check = false
|
|
66
|
+
grammar_path = nil #: String?
|
|
67
|
+
parser = OptionParser.new do |options|
|
|
68
|
+
options.banner = "Usage: racc-rbs-inline annotate [--stdout | --check] [--grammar GRAMMAR] FILE..."
|
|
69
|
+
options.on("--stdout", "Write annotated source to standard output") { stdout = true }
|
|
70
|
+
options.on("--check", "List files needing annotation without writing") { check = true }
|
|
71
|
+
options.on("--grammar FILE", "Use semantic types declared in a Racc grammar") { |path| grammar_path = path }
|
|
72
|
+
options.on("-h", "--help") { @out.puts(options); return 0 }
|
|
73
|
+
end
|
|
74
|
+
parser.parse!(args)
|
|
75
|
+
raise Error, "annotate requires at least one file" if args.empty?
|
|
76
|
+
raise Error, "--stdout and --check cannot be combined" if stdout && check
|
|
77
|
+
grammar_filename = grammar_path
|
|
78
|
+
grammar = grammar_filename ? File.read(grammar_filename) : nil
|
|
79
|
+
|
|
80
|
+
# Validate every input before replacing any of them.
|
|
81
|
+
results = args.map do |path|
|
|
82
|
+
source = File.binread(path)
|
|
83
|
+
[path, source, RaccRbsInline.annotate(source, grammar: grammar, grammar_filename: grammar_filename || "grammar.y")]
|
|
84
|
+
rescue Error => error
|
|
85
|
+
raise Error, "#{path}: #{error.message}"
|
|
86
|
+
end #: Array[[String, String, String]]
|
|
87
|
+
changed = false
|
|
88
|
+
results.each do |path, source, annotated|
|
|
89
|
+
if stdout
|
|
90
|
+
@out.write(annotated)
|
|
91
|
+
elsif source != annotated
|
|
92
|
+
changed = true
|
|
93
|
+
if check
|
|
94
|
+
@out.puts(path)
|
|
95
|
+
else
|
|
96
|
+
replace(path, annotated, File.stat(path).mode)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
check && changed ? 1 : 0
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#: (Array[String] args) -> Integer
|
|
104
|
+
def generate(args)
|
|
105
|
+
separator = args.index("--")
|
|
106
|
+
racc_options = [] #: Array[String]
|
|
107
|
+
if separator
|
|
108
|
+
racc_options = args.drop(separator + 1)
|
|
109
|
+
args = args.take(separator)
|
|
110
|
+
end
|
|
111
|
+
output_option = nil #: String?
|
|
112
|
+
parser = OptionParser.new do |options|
|
|
113
|
+
options.banner = "Usage: racc-rbs-inline generate GRAMMAR -o OUT [-- RACC_OPTIONS]"
|
|
114
|
+
options.on("-o", "--output FILE") { |path| output_option = path }
|
|
115
|
+
options.on("-h", "--help") { @out.puts(options); return 0 }
|
|
116
|
+
end
|
|
117
|
+
parser.parse!(args)
|
|
118
|
+
output = output_option || raise(Error, "generate requires -o OUT")
|
|
119
|
+
raise Error, "generate requires one grammar" unless args.length == 1
|
|
120
|
+
grammar = args.fetch(0)
|
|
121
|
+
if same_file?(grammar, output)
|
|
122
|
+
raise Error, "grammar and output must be different files"
|
|
123
|
+
end
|
|
124
|
+
raise Error, "pass the output path with -o before --" if racc_options.any? { |option| option == "--" || option.match?(/\A(?:-o|--output-file(?:=|\z))/) }
|
|
125
|
+
default_log = validate_log_paths(racc_options, grammar, output)
|
|
126
|
+
|
|
127
|
+
Dir.mktmpdir("racc-rbs-inline-", File.expand_path(File.dirname(output))) do |directory|
|
|
128
|
+
generated = File.join(directory, File.basename(output))
|
|
129
|
+
if File.exist?(output)
|
|
130
|
+
File.write(generated, "")
|
|
131
|
+
File.chmod(File.stat(output).mode & 0o777, generated)
|
|
132
|
+
end
|
|
133
|
+
# Racc derives verbose log names from the first dot in the output path.
|
|
134
|
+
racc_output = generated.delete_prefix("#{Dir.pwd}/")
|
|
135
|
+
validate_log_path(racc_output.sub(/(?:\..*?)?\z/, ".output"), grammar, output) if default_log
|
|
136
|
+
stdout, stderr, status = Open3.capture3(*racc_command, *racc_options, "-o", racc_output, grammar)
|
|
137
|
+
@out.write(stdout)
|
|
138
|
+
@err.write(stderr)
|
|
139
|
+
return status.exitstatus || 1 unless status.success?
|
|
140
|
+
|
|
141
|
+
require_relative "grammar_types"
|
|
142
|
+
grammar_source = File.read(grammar)
|
|
143
|
+
typed_grammar = GrammarTypes.declared?(grammar_source, filename: grammar) ? grammar_source : nil
|
|
144
|
+
annotated = RaccRbsInline.annotate(File.binread(generated), grammar: typed_grammar, grammar_filename: grammar)
|
|
145
|
+
# Racc's --verbose option also produces a .output file beside the parser.
|
|
146
|
+
artifacts = Dir.children(directory).reject { |name| name == File.basename(generated) }
|
|
147
|
+
artifacts.each do |name|
|
|
148
|
+
validate_log_path(File.join(File.dirname(output), name), grammar, output)
|
|
149
|
+
end
|
|
150
|
+
replace(output, annotated, File.stat(generated).mode)
|
|
151
|
+
artifacts.each do |name|
|
|
152
|
+
File.rename(File.join(directory, name), File.join(File.dirname(output), name))
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
0
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#: (Array[String] options, String grammar, String output) -> bool
|
|
159
|
+
def validate_log_paths(options, grammar, output)
|
|
160
|
+
remaining = options.dup
|
|
161
|
+
verbose = false
|
|
162
|
+
explicit_log = false
|
|
163
|
+
parser = OptionParser.new do |flags|
|
|
164
|
+
flags.on("-O", "--log-file FILE") do |path|
|
|
165
|
+
validate_log_path(path, grammar, output)
|
|
166
|
+
explicit_log = true
|
|
167
|
+
end
|
|
168
|
+
flags.on("-v", "--verbose") { verbose = true }
|
|
169
|
+
# Recognize argument boundaries and short clusters; Racc validates all other options.
|
|
170
|
+
flags.on("-e", "--executable [PATH]") {}
|
|
171
|
+
flags.on("-o", "--output-file FILE") {}
|
|
172
|
+
flags.on("-D FLAGS") {}
|
|
173
|
+
flags.on("--superclass CLASS") {}
|
|
174
|
+
%w[-t -g -E -F -l -a -C -S -P --help --version].each { |flag| flags.on(flag) {} }
|
|
175
|
+
end
|
|
176
|
+
until remaining.empty?
|
|
177
|
+
begin
|
|
178
|
+
parser.order!(remaining) {}
|
|
179
|
+
rescue OptionParser::InvalidOption
|
|
180
|
+
next
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
verbose && !explicit_log
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#: (String path, String grammar, String output) -> void
|
|
187
|
+
def validate_log_path(path, grammar, output)
|
|
188
|
+
if same_file?(path, grammar) || same_file?(path, output)
|
|
189
|
+
raise Error, "Racc log file must differ from grammar and output: #{path}"
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#: (String first, String second) -> bool
|
|
194
|
+
def same_file?(first, second)
|
|
195
|
+
File.realdirpath(first) == File.realdirpath(second) || File.identical?(first, second)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
#: () -> Array[String]
|
|
199
|
+
def racc_command
|
|
200
|
+
[RbConfig.ruby, Gem.bin_path("racc", "racc")]
|
|
201
|
+
rescue Gem::LoadError, RuntimeError
|
|
202
|
+
["racc"]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
#: (Array[String] args) -> Integer
|
|
206
|
+
def sig(args)
|
|
207
|
+
output = "sig/racc"
|
|
208
|
+
parser = OptionParser.new do |options|
|
|
209
|
+
options.banner = "Usage: racc-rbs-inline sig [--output DIR]"
|
|
210
|
+
options.on("-o", "--output DIR") { |path| output = path }
|
|
211
|
+
options.on("-h", "--help") { @out.puts(options); return 0 }
|
|
212
|
+
end
|
|
213
|
+
parser.parse!(args)
|
|
214
|
+
raise Error, "sig does not take positional arguments" unless args.empty?
|
|
215
|
+
source = File.binread(File.expand_path("../../data/racc/parser.rbs", File.dirname(__FILE__)))
|
|
216
|
+
FileUtils.mkdir_p(output)
|
|
217
|
+
path = File.join(output, "parser.rbs")
|
|
218
|
+
mode = File.exist?(path) ? File.stat(path).mode : 0o666 & ~File.umask
|
|
219
|
+
replace(path, source, mode)
|
|
220
|
+
0
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
#: (String path, String source, Integer mode) -> void
|
|
224
|
+
def replace(path, source, mode)
|
|
225
|
+
path = File.realpath(path) if File.symlink?(path)
|
|
226
|
+
Tempfile.create([".racc-rbs-inline-", ".tmp"], File.dirname(path)) do |file|
|
|
227
|
+
file.binmode
|
|
228
|
+
file.write(source)
|
|
229
|
+
file.flush
|
|
230
|
+
File.chmod(mode & 0o777, file.path)
|
|
231
|
+
File.rename(file.path, path)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "prism"
|
|
5
|
+
require "rbs"
|
|
6
|
+
require "strscan"
|
|
7
|
+
require "racc/grammarfileparser"
|
|
8
|
+
|
|
9
|
+
module RaccRbsInline
|
|
10
|
+
class GrammarTypes
|
|
11
|
+
# @rbs @rules: Array[untyped]
|
|
12
|
+
# @rbs @table: untyped
|
|
13
|
+
# @rbs @result_var: bool
|
|
14
|
+
# @rbs @types: Hash[Symbol | String, String]
|
|
15
|
+
|
|
16
|
+
#: (String source, ?filename: String) -> bool
|
|
17
|
+
def self.declared?(source, filename: "grammar.y")
|
|
18
|
+
!grammar_comment_lines(source, filename).empty?
|
|
19
|
+
rescue Racc::CompileError, ArgumentError => error
|
|
20
|
+
raise Error, "#{filename}: #{error.message}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#: (String source, ?filename: String) -> void
|
|
24
|
+
def initialize(source, filename: "grammar.y")
|
|
25
|
+
raise Error, "Grammar is empty" if source.strip.empty?
|
|
26
|
+
|
|
27
|
+
comment_lines = self.class.grammar_comment_lines(source, filename) { |text| literal_string(text) }
|
|
28
|
+
result = Racc::GrammarFileParser.parse(source, filename)
|
|
29
|
+
states = Racc::States.new(result.grammar).nfa
|
|
30
|
+
states.dfa
|
|
31
|
+
@table = states.state_transition_table
|
|
32
|
+
@rules = []
|
|
33
|
+
result.grammar.each { |rule| @rules << rule }
|
|
34
|
+
@result_var = result.params.result_var?
|
|
35
|
+
@types = declarations(source, comment_lines)
|
|
36
|
+
known = result.grammar.symboltable.symbols.reject(&:dummy?).map(&:value)
|
|
37
|
+
unknown = @types.keys - known
|
|
38
|
+
raise Error, "Unknown grammar symbols: #{unknown.map(&:inspect).join(', ')}" unless unknown.empty?
|
|
39
|
+
rescue Racc::CompileError, RBS::ParsingError, ArgumentError => error
|
|
40
|
+
raise Error, "#{filename}: #{error.message}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#: (body: Prism::StatementsNode) -> Hash[Symbol, String]
|
|
44
|
+
def signatures(body:)
|
|
45
|
+
validate_tables(body)
|
|
46
|
+
signatures = {} #: Hash[Symbol, String]
|
|
47
|
+
return signatures if @types.empty?
|
|
48
|
+
|
|
49
|
+
body.body.each do |node|
|
|
50
|
+
next unless node.is_a?(Prism::DefNode)
|
|
51
|
+
match = node.name.to_s.match(/\A_reduce_(\d+)\z/)
|
|
52
|
+
next unless match
|
|
53
|
+
|
|
54
|
+
rule = @rules[match[1].to_i]
|
|
55
|
+
raise Error, "Reduction #{node.name} does not exist in the grammar" unless rule
|
|
56
|
+
next if rule.target.dummy?
|
|
57
|
+
|
|
58
|
+
expected = @result_var ? 3 : 2
|
|
59
|
+
raise Error, "Reduction #{node.name} parameters do not match the grammar" unless node.parameters&.requireds&.length == expected
|
|
60
|
+
|
|
61
|
+
types = rule.symbols.map { |symbol| type_for(symbol) } #: Array[String]
|
|
62
|
+
target = type_for(rule.target)
|
|
63
|
+
parameters = "[#{types.join(', ')}] val, Array[untyped] _values"
|
|
64
|
+
if @result_var
|
|
65
|
+
first = types.first || "nil"
|
|
66
|
+
parameters += ", #{union(first, target)} result"
|
|
67
|
+
end
|
|
68
|
+
signatures[node.name] = "#: (#{parameters}) -> #{target}"
|
|
69
|
+
end
|
|
70
|
+
signatures
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
#: (String source, String filename) ?{ (String) -> String } -> Array[Integer]
|
|
76
|
+
def self.grammar_comment_lines(source, filename, &decode)
|
|
77
|
+
scanner = Racc::GrammarFileScanner.new(source, filename)
|
|
78
|
+
raise Error, "Grammar has no rules" unless scanner.instance_variable_get(:@line)
|
|
79
|
+
# Never let Racc evaluate tokens while locating declaration comments.
|
|
80
|
+
scanner.define_singleton_method(:eval) { |text| decode ? decode.call(text) : text }
|
|
81
|
+
lines = [] #: Array[Integer]
|
|
82
|
+
record = lambda do
|
|
83
|
+
line = scanner.instance_variable_get(:@line) #: String?
|
|
84
|
+
if scanner.instance_variable_get(:@in_block).nil? && line&.match?(/\A\s*#\s*@racc-type\b/)
|
|
85
|
+
lines << scanner.lineno
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
advance = scanner.method(:next_line)
|
|
89
|
+
scanner.define_singleton_method(:next_line) do
|
|
90
|
+
available = advance.call
|
|
91
|
+
record.call if available
|
|
92
|
+
available
|
|
93
|
+
end
|
|
94
|
+
record.call
|
|
95
|
+
scanner.yylex { |_token| }
|
|
96
|
+
lines
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#: (String source, Array[Integer] comment_lines) -> Hash[Symbol | String, String]
|
|
100
|
+
def declarations(source, comment_lines)
|
|
101
|
+
types = {} #: Hash[Symbol | String, String]
|
|
102
|
+
lines = source.lines
|
|
103
|
+
comment_lines.each do |number|
|
|
104
|
+
line = lines.fetch(number - 1)
|
|
105
|
+
match = line.match(/\A\s*#\s*@racc-type\b(.*)/)
|
|
106
|
+
next unless match
|
|
107
|
+
|
|
108
|
+
scanner = StringScanner.new(match[1])
|
|
109
|
+
symbols = [] #: Array[Symbol | String]
|
|
110
|
+
loop do
|
|
111
|
+
scanner.skip(/\s*/)
|
|
112
|
+
break if scanner.scan(/:/)
|
|
113
|
+
token = scanner.scan(/[A-Za-z_]\w*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/)
|
|
114
|
+
raise Error, "Invalid @racc-type declaration on line #{number}" unless token
|
|
115
|
+
symbols << (token.start_with?("'", '"') ? literal_string(token).b : token.to_sym)
|
|
116
|
+
unless scanner.eos? || scanner.peek(1).match?(/[\s:]/)
|
|
117
|
+
raise Error, "Invalid @racc-type symbols on line #{number}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
raise Error, "Missing symbols in @racc-type on line #{number}" if symbols.empty?
|
|
121
|
+
|
|
122
|
+
type = parse_type(scanner.rest.strip).to_s
|
|
123
|
+
symbols.each do |symbol|
|
|
124
|
+
if types.key?(symbol) && types[symbol] != type
|
|
125
|
+
raise Error, "Conflicting types for #{symbol.inspect} on line #{number}"
|
|
126
|
+
end
|
|
127
|
+
types[symbol] = type
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
types
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#: (String source) -> String
|
|
134
|
+
def literal_string(source)
|
|
135
|
+
parsed = Prism.parse(source)
|
|
136
|
+
nodes = parsed.value.statements.body
|
|
137
|
+
node = nodes.first
|
|
138
|
+
unless parsed.success? && nodes.length == 1 && node.is_a?(Prism::StringNode)
|
|
139
|
+
raise Error, "Grammar symbols must be literal strings without interpolation"
|
|
140
|
+
end
|
|
141
|
+
node.unescaped
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
#: (untyped symbol) -> String
|
|
145
|
+
def type_for(symbol)
|
|
146
|
+
return @types.fetch(symbol.value, "untyped") unless symbol.dummy?
|
|
147
|
+
|
|
148
|
+
case symbol.value.to_s
|
|
149
|
+
when /\A-option@/
|
|
150
|
+
rule = @rules.find { |candidate| candidate.target == symbol && !candidate.symbols.empty? }
|
|
151
|
+
inner = parse_type(type_for(rule.symbols.first))
|
|
152
|
+
RBS::Types::Optional.new(type: inner, location: nil).to_s
|
|
153
|
+
when /\A-many1?@/
|
|
154
|
+
rule = @rules.find { |candidate| candidate.target == symbol && !candidate.symbols.empty? }
|
|
155
|
+
"Array[#{type_for(rule.symbols.first)}]"
|
|
156
|
+
when /\A-group@/
|
|
157
|
+
"Array[untyped]"
|
|
158
|
+
else
|
|
159
|
+
"untyped"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#: (String first, String target) -> String
|
|
164
|
+
def union(first, target)
|
|
165
|
+
return target if first == target
|
|
166
|
+
|
|
167
|
+
types = [first, target].map { |type| parse_type(type) }
|
|
168
|
+
RBS::Types::Union.new(types: types, location: nil).to_s
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
#: (String source) -> RBS::Types::t
|
|
172
|
+
def parse_type(source)
|
|
173
|
+
RBS::Parser.parse_type(source, require_eof: true, void_allowed: false) || raise(Error, "Missing RBS type in @racc-type declaration")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
#: (Prism::StatementsNode body) -> void
|
|
177
|
+
def validate_tables(body)
|
|
178
|
+
expected = {
|
|
179
|
+
racc_action_table: @table.action_table,
|
|
180
|
+
racc_action_check: @table.action_check,
|
|
181
|
+
racc_action_default: @table.action_default,
|
|
182
|
+
racc_action_pointer: @table.action_pointer,
|
|
183
|
+
racc_goto_table: @table.goto_table,
|
|
184
|
+
racc_goto_check: @table.goto_check,
|
|
185
|
+
racc_goto_default: @table.goto_default,
|
|
186
|
+
racc_goto_pointer: @table.goto_pointer,
|
|
187
|
+
racc_reduce_table: @table.reduce_table,
|
|
188
|
+
racc_reduce_n: @rules.length,
|
|
189
|
+
racc_nt_base: @table.nt_base,
|
|
190
|
+
racc_use_result_var: @result_var,
|
|
191
|
+
Racc_token_to_s_table: @table.token_to_s_table
|
|
192
|
+
} #: Hash[Symbol, untyped]
|
|
193
|
+
expected.each do |name, value|
|
|
194
|
+
nodes = body.body.select do |node|
|
|
195
|
+
(node.is_a?(Prism::LocalVariableWriteNode) || node.is_a?(Prism::ConstantWriteNode)) && node.name == name
|
|
196
|
+
end
|
|
197
|
+
node = nodes.first
|
|
198
|
+
unless nodes.length == 1 && (node.is_a?(Prism::LocalVariableWriteNode) || node.is_a?(Prism::ConstantWriteNode)) && literal(node.value) == value
|
|
199
|
+
raise Error, "Generated #{name} does not match the grammar; regenerate with the same Racc version"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
#: (Prism::Node node) -> untyped
|
|
205
|
+
def literal(node)
|
|
206
|
+
case node
|
|
207
|
+
when Prism::ArrayNode then node.elements.map { |element| literal(element) }
|
|
208
|
+
when Prism::IntegerNode then node.value
|
|
209
|
+
when Prism::SymbolNode then node.unescaped.to_sym
|
|
210
|
+
when Prism::StringNode then node.unescaped
|
|
211
|
+
when Prism::TrueNode then true
|
|
212
|
+
when Prism::FalseNode then false
|
|
213
|
+
when Prism::NilNode then nil
|
|
214
|
+
else
|
|
215
|
+
raise Error, "Generated grammar tables must contain only literals"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "rake"
|
|
5
|
+
require_relative "cli"
|
|
6
|
+
|
|
7
|
+
module RaccRbsInline
|
|
8
|
+
class RakeTask
|
|
9
|
+
include Rake::DSL
|
|
10
|
+
|
|
11
|
+
attr_accessor :grammar #: String?
|
|
12
|
+
attr_accessor :output #: String?
|
|
13
|
+
attr_accessor :options #: Array[String]
|
|
14
|
+
|
|
15
|
+
#: (?Symbol | String name) ?{ (RakeTask) -> void } -> void
|
|
16
|
+
def initialize(name = :parser)
|
|
17
|
+
@grammar = nil
|
|
18
|
+
@output = nil
|
|
19
|
+
@options = []
|
|
20
|
+
yield self if block_given?
|
|
21
|
+
grammar = @grammar
|
|
22
|
+
output = @output
|
|
23
|
+
raise ArgumentError, "grammar and output are required" unless grammar && output
|
|
24
|
+
|
|
25
|
+
desc "Generate #{output} with RBS inline annotations"
|
|
26
|
+
task name => output
|
|
27
|
+
file output => grammar do
|
|
28
|
+
FileUtils.mkdir_p(File.dirname(output))
|
|
29
|
+
status = CLI.run(["generate", grammar, "-o", output, "--", *@options])
|
|
30
|
+
raise Error, "parser generation failed (exit #{status})" unless status.zero?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "racc_rbs_inline/version"
|
|
5
|
+
|
|
6
|
+
module RaccRbsInline
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
#: (String source, ?grammar: String?, ?grammar_filename: String) -> String
|
|
10
|
+
def self.annotate(source, grammar: nil, grammar_filename: "grammar.y")
|
|
11
|
+
Annotator.new.annotate(source, grammar: grammar, grammar_filename: grammar_filename)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
#: (String source) -> bool
|
|
15
|
+
def self.annotated?(source)
|
|
16
|
+
Annotator.new.annotated?(source)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
require_relative "racc_rbs_inline/annotator"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Generated from lib/racc_rbs_inline/annotator.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RaccRbsInline
|
|
4
|
+
class Annotator
|
|
5
|
+
MAGIC: String
|
|
6
|
+
|
|
7
|
+
INTEGER_TABLE: Regexp
|
|
8
|
+
|
|
9
|
+
REDUCTION: Regexp
|
|
10
|
+
|
|
11
|
+
CONSTANT_TYPES: Hash[Symbol, String]
|
|
12
|
+
|
|
13
|
+
# : (String source, ?grammar: String?, ?grammar_filename: String) -> String
|
|
14
|
+
def annotate: (String source, ?grammar: String?, ?grammar_filename: String) -> String
|
|
15
|
+
|
|
16
|
+
# : (String source) -> bool
|
|
17
|
+
def annotated?: (String source) -> bool
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
# : (String source) -> String
|
|
22
|
+
def unwrap: (String source) -> String
|
|
23
|
+
|
|
24
|
+
# : (String source, types: GrammarTypes?, existing: bool) -> String
|
|
25
|
+
def add_annotations: (String source, types: GrammarTypes?, existing: bool) -> String
|
|
26
|
+
|
|
27
|
+
# : (Prism::LocalVariableWriteNode node) -> String?
|
|
28
|
+
def table_type: (Prism::LocalVariableWriteNode node) -> String?
|
|
29
|
+
|
|
30
|
+
# : (Prism::Node node, String type) -> [Integer, Integer, String]
|
|
31
|
+
def trailing: (Prism::Node node, String type) -> [ Integer, Integer, String ]
|
|
32
|
+
|
|
33
|
+
# : (String source) -> String
|
|
34
|
+
def add_magic_comment: (String source) -> String
|
|
35
|
+
|
|
36
|
+
# : (String source) -> String
|
|
37
|
+
def newline: (String source) -> String
|
|
38
|
+
|
|
39
|
+
# : (String source) -> Prism::ProgramNode
|
|
40
|
+
def parse!: (String source) -> Prism::ProgramNode
|
|
41
|
+
|
|
42
|
+
# : (Prism::ProgramNode program) -> Array[Prism::StatementsNode]
|
|
43
|
+
def parser_bodies: (Prism::ProgramNode program) -> Array[Prism::StatementsNode]
|
|
44
|
+
|
|
45
|
+
# : (String binary, Integer offset) -> Integer
|
|
46
|
+
def line_start: (String binary, Integer offset) -> Integer
|
|
47
|
+
|
|
48
|
+
# : (String source, Array[[Integer, Integer, String]] edits) -> String
|
|
49
|
+
def apply: (String source, Array[[ Integer, Integer, String ]] edits) -> String
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Generated from lib/racc_rbs_inline/cli.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RaccRbsInline
|
|
4
|
+
class CLI
|
|
5
|
+
HELP: ::String
|
|
6
|
+
|
|
7
|
+
@out: untyped
|
|
8
|
+
|
|
9
|
+
@err: untyped
|
|
10
|
+
|
|
11
|
+
# : (Array[String] argv, ?out: untyped, ?err: untyped) -> Integer
|
|
12
|
+
def self.run: (Array[String] argv, ?out: untyped, ?err: untyped) -> Integer
|
|
13
|
+
|
|
14
|
+
# : (out: untyped, err: untyped) -> void
|
|
15
|
+
def initialize: (out: untyped, err: untyped) -> void
|
|
16
|
+
|
|
17
|
+
# : (Array[String] argv) -> Integer
|
|
18
|
+
def run: (Array[String] argv) -> Integer
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# : (Array[String] args) -> Integer
|
|
23
|
+
def annotate: (Array[String] args) -> Integer
|
|
24
|
+
|
|
25
|
+
# : (Array[String] args) -> Integer
|
|
26
|
+
def generate: (Array[String] args) -> Integer
|
|
27
|
+
|
|
28
|
+
# : (Array[String] options, String grammar, String output) -> bool
|
|
29
|
+
def validate_log_paths: (Array[String] options, String grammar, String output) -> bool
|
|
30
|
+
|
|
31
|
+
# : (String path, String grammar, String output) -> void
|
|
32
|
+
def validate_log_path: (String path, String grammar, String output) -> void
|
|
33
|
+
|
|
34
|
+
# : (String first, String second) -> bool
|
|
35
|
+
def same_file?: (String first, String second) -> bool
|
|
36
|
+
|
|
37
|
+
# : () -> Array[String]
|
|
38
|
+
def racc_command: () -> Array[String]
|
|
39
|
+
|
|
40
|
+
# : (Array[String] args) -> Integer
|
|
41
|
+
def sig: (Array[String] args) -> Integer
|
|
42
|
+
|
|
43
|
+
# : (String path, String source, Integer mode) -> void
|
|
44
|
+
def replace: (String path, String source, Integer mode) -> void
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Generated from lib/racc_rbs_inline/grammar_types.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RaccRbsInline
|
|
4
|
+
class GrammarTypes
|
|
5
|
+
@rules: Array[untyped]
|
|
6
|
+
|
|
7
|
+
@table: untyped
|
|
8
|
+
|
|
9
|
+
@result_var: bool
|
|
10
|
+
|
|
11
|
+
@types: Hash[Symbol | String, String]
|
|
12
|
+
|
|
13
|
+
# : (String source, ?filename: String) -> bool
|
|
14
|
+
def self.declared?: (String source, ?filename: String) -> bool
|
|
15
|
+
|
|
16
|
+
# : (String source, ?filename: String) -> void
|
|
17
|
+
def initialize: (String source, ?filename: String) -> void
|
|
18
|
+
|
|
19
|
+
# : (body: Prism::StatementsNode) -> Hash[Symbol, String]
|
|
20
|
+
def signatures: (body: Prism::StatementsNode) -> Hash[Symbol, String]
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# : (String source, String filename) ?{ (String) -> String } -> Array[Integer]
|
|
25
|
+
def self.grammar_comment_lines: (String source, String filename) ?{ (String) -> String } -> Array[Integer]
|
|
26
|
+
|
|
27
|
+
# : (String source, Array[Integer] comment_lines) -> Hash[Symbol | String, String]
|
|
28
|
+
def declarations: (String source, Array[Integer] comment_lines) -> Hash[Symbol | String, String]
|
|
29
|
+
|
|
30
|
+
# : (String source) -> String
|
|
31
|
+
def literal_string: (String source) -> String
|
|
32
|
+
|
|
33
|
+
# : (untyped symbol) -> String
|
|
34
|
+
def type_for: (untyped symbol) -> String
|
|
35
|
+
|
|
36
|
+
# : (String first, String target) -> String
|
|
37
|
+
def union: (String first, String target) -> String
|
|
38
|
+
|
|
39
|
+
# : (String source) -> RBS::Types::t
|
|
40
|
+
def parse_type: (String source) -> RBS::Types::t
|
|
41
|
+
|
|
42
|
+
# : (Prism::StatementsNode body) -> void
|
|
43
|
+
def validate_tables: (Prism::StatementsNode body) -> void
|
|
44
|
+
|
|
45
|
+
# : (Prism::Node node) -> untyped
|
|
46
|
+
def literal: (Prism::Node node) -> untyped
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Generated from lib/racc_rbs_inline/rake_task.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module RaccRbsInline
|
|
4
|
+
class RakeTask
|
|
5
|
+
include Rake::DSL
|
|
6
|
+
|
|
7
|
+
attr_accessor grammar: String?
|
|
8
|
+
|
|
9
|
+
attr_accessor output: String?
|
|
10
|
+
|
|
11
|
+
attr_accessor options: Array[String]
|
|
12
|
+
|
|
13
|
+
# : (?Symbol | String name) ?{ (RakeTask) -> void } -> void
|
|
14
|
+
def initialize: (?Symbol | String name) ?{ (RakeTask) -> void } -> void
|
|
15
|
+
end
|
|
16
|
+
end
|