Class: Ibex::CLI
Overview
Command-line pipeline coordinator.
rubocop:disable Metrics/ClassLength -- inline type contracts add lines without adding runtime responsibilities.
Constant Summary
collapse
- SUBCOMMAND_HANDLERS =
{
"check" => %i[CLIAmbiguity run_check_command],
"diagnose" => %i[CLIDiagnostics run_diagnose_command],
"coverage" => %i[CLICoverage run_coverage_command],
"debug" => %i[CLIDebug run_debug_command],
"doc" => %i[CLIDocumentation run_documentation_command],
"errors" => %i[CLIErrorMessages run_error_messages_command],
"explain" => %i[CLIExplain run_explain_command],
"fmt" => %i[CLIFormatting run_format_command],
"test" => %i[CLIGrammarTests run_grammar_tests_command],
"lsp" => %i[CLILSP run_lsp_command],
"migrate-check" => %i[CLIRaccMigration run_migrate_check_command],
"migrate-harness" => %i[CLIRaccMigration run_migrate_harness_command],
"samples" => %i[CLISamples run_samples_command],
"validate-ir" => %i[CLIIRTools run_validate_ir_command],
"compare" => %i[CLIIRTools run_compare_command],
"migrate-ir" => %i[CLIIRTools run_migrate_ir_command]
}.freeze
Constants included
from CLIOutputs
Ibex::CLIOutputs::WARNING_MESSAGES
Ibex::CLICounterexampleOptions::DEFAULTS
Class Method Summary
collapse
Instance Method Summary
collapse
-
#action_source_output_path(output_path) ⇒ String
-
#action_source_source(automaton) ⇒ String
-
#activate_cli_feature(feature) ⇒ void
-
#add_compatibility_options(options) ⇒ void
-
#add_information_options(options) ⇒ void
-
#add_output_options(options) ⇒ void
-
#add_pipeline_options(options) ⇒ void
-
#add_signature_output_options(options) ⇒ void
-
#build_automaton(grammar, input_path) ⇒ IR::Automaton
-
#canonical_target_path(path, seen = {}) ⇒ String
-
#dispatch_automaton(automaton, path) ⇒ Integer
-
#dispatch_grammar(grammar, path) ⇒ Integer
-
#dispatch_subcommand(arguments) ⇒ Integer?
-
#emit_ast(ast) ⇒ Integer
-
#emit_automaton(grammar, input_path) ⇒ Integer
-
#emit_grammar(grammar) ⇒ Integer
-
#emit_lexer(grammar) ⇒ Integer
-
#emit_loaded_automaton(automaton, input_path) ⇒ Integer
-
#emit_ruby(grammar, input_path) ⇒ Integer
-
#emit_sets(grammar) ⇒ Integer
-
#generate_ruby(automaton, input_path) ⇒ Integer
-
#generation_paths(input_path) ⇒ Hash[Symbol, String?]
-
#informational_result(parser) ⇒ Integer?
-
#initialize(stdout:, stderr:, stdin: $stdin, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil) ⇒ CLI
constructor
-
#input_path(remaining) ⇒ String
-
#manifest_output_path_for(output_path) ⇒ String
-
#normalize_grammar_path(path) ⇒ IR::Grammar
-
#option_parser ⇒ OptionParser
-
#portable_target_key(path) ⇒ [ String, String ]
-
#prepare_loaded_automaton(automaton, input_path) ⇒ void
-
#print_copyright ⇒ Integer
-
#print_help(parser) ⇒ Integer
-
#print_version ⇒ Integer
-
#process_grammar(path) ⇒ Integer
-
#process_ir(path) ⇒ Integer
-
#rbs_output_path(output_path) ⇒ String
-
#rbs_source(automaton) ⇒ String
-
#resolve_grammar_path(path) ⇒ Frontend::Resolution
-
#run(arguments) ⇒ Integer
-
#run_watch_feature(path) ⇒ Integer
-
#same_file_target?(left, right) ⇒ Boolean
-
#validate_action_source_generation_options ⇒ void
-
#validate_generation_options ⇒ void
-
#validate_generation_paths!(input_path, source_paths: [input_path]) ⇒ void
-
#validate_manifest_generation_options ⇒ void
-
#validate_verification_generation_options ⇒ void
-
#validate_watch_generation_options ⇒ void
-
#validate_watch_information_options ⇒ void
-
#verify_file(path, source, label) ⇒ void
-
#verify_generated_outputs(automaton, output_path, source, action_source) ⇒ Integer
-
#write_action_source(output_path, source) ⇒ void
-
#write_railroad(grammar) ⇒ void
-
#write_rbs(automaton, output_path) ⇒ void
-
#write_visualizations(automaton) ⇒ void
Methods included from CLIOutputs
#atomic_write_ir, #conflict_count, #conflict_messages, #default_output_path, #format_grammar_warning, #handle_grammar_warnings, #ir_file_mode, #report_conflicts, #report_status, #suggest_ielr, #warning_categories, #write_report
#add_generation_manifest, #artifact_label, #begin_artifact_generation, #ensure_generation_inputs_stable!, #finish_artifact_generation, #generation_artifacts, #generation_inputs_stable?, #generation_manifest_options, #manifest_output_path, #record_generation_input, #register_artifact, #verify_artifact_generation
#add_error_messages_generation_option, #configured_error_messages, #validate_messages_options
#add_counterexample_options, #positive_counterexample_limit
Constructor Details
#initialize(stdout:, stderr:, stdin: $stdin, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil) ⇒ CLI
Returns a new instance of CLI.
153
154
155
156
157
158
159
160
161
162
|
# File 'lib/ibex/cli.rb', line 153
def initialize(stdout:, stderr:, stdin: $stdin, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil)
@stdin = stdin
@stdout = stdout
@stderr = stderr
@watch_clock = watch_clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
@watch_sleeper = watch_sleeper || ->(seconds) { sleep(seconds) }
@watch_iteration_hook = watch_iteration_hook || ->(_event, _iteration, _paths) {}
@options = { emit: "ruby", mode: :default, table: :compact, line_convert: true }
.merge(CLICounterexampleOptions::DEFAULTS)
end
|
Class Method Details
.start(arguments, stdin: $stdin, stdout: $stdout, stderr: $stderr, watch_clock: nil, watch_sleeper: nil, watch_iteration_hook: nil) ⇒ Integer
rubocop:disable Layout/LineLength
144
145
146
147
148
149
150
|
# File 'lib/ibex/cli.rb', line 144
def self.start(arguments, stdin: $stdin, stdout: $stdout, stderr: $stderr, watch_clock: nil, watch_sleeper: nil,
watch_iteration_hook: nil)
new(
stdin: stdin, stdout: stdout, stderr: stderr, watch_clock: watch_clock,
watch_sleeper: watch_sleeper, watch_iteration_hook: watch_iteration_hook
).run(arguments)
end
|
Instance Method Details
#action_source_output_path(output_path) ⇒ String
721
722
723
724
725
726
727
|
# File 'lib/ibex/cli.rb', line 721
def action_source_output_path(output_path)
configured_path = @options[:action_source]
path = configured_path == true ? default_output_path(output_path, ".actions.rb") : configured_path
raise Ibex::Error, "(cli):1:1: action source output path is required" unless path.is_a?(String) && !path.empty?
path
end
|
#action_source_source(automaton) ⇒ String
730
731
732
733
|
# File 'lib/ibex/cli.rb', line 730
def action_source_source(automaton)
require_relative "codegen/action_source"
Codegen::ActionSource.new(automaton, omit_action_call: @options[:omit_actions]).generate
end
|
#activate_cli_feature(feature) ⇒ void
This method returns an undefined value.
203
204
205
206
|
# File 'lib/ibex/cli.rb', line 203
def activate_cli_feature(feature)
extension = Ibex.const_get(feature)
extend extension unless singleton_class.ancestors.include?(extension)
end
|
#add_compatibility_options(options) ⇒ void
This method returns an undefined value.
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
# File 'lib/ibex/cli.rb', line 316
def add_compatibility_options(options)
options.on("-F", "--frozen", "emit frozen string literals") { @options[:frozen] = true }
options.on("--line-convert-all", "convert all source lines") do
@options[:line_convert] = true
@options[:line_convert_all] = true
end
options.on("-l", "--no-line-convert", "use generated-file action lines") do
@options[:line_convert] = false
@options[:line_convert_all] = false
end
options.on("-a", "--no-omit-actions", "generate implicit action methods") { @options[:omit_actions] = false }
options.on("--superclass=CLASS", "override parser superclass") { |value| @options[:superclass] = value }
options.on("--check", "verify generated parser content without rewriting") { @options[:verify_output] = true }
options.on("-C", "--check-only", "check grammar and exit") { @options[:check_only] = true }
options.on("-S", "--output-status", "show pipeline status") { @options[:status] = true }
options.on("-P", "accept the compatibility profiling flag") { @options[:profile] = true }
options.on("-D FLAGS", "accept internal compatibility flags") { |value| @options[:debug_flags] = value }
end
|
This method returns an undefined value.
336
337
338
339
340
341
|
# File 'lib/ibex/cli.rb', line 336
def add_information_options(options)
options.on("--version", "show version") { @options[:version] = true }
options.on("--runtime-version", "show runtime version") { @options[:runtime_version] = true }
options.on("--copyright", "show copyright") { @options[:copyright] = true }
options.on("--help", "show help") { @options[:help] = true }
end
|
#add_output_options(options) ⇒ void
This method returns an undefined value.
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
# File 'lib/ibex/cli.rb', line 279
def add_output_options(options)
options.on("-o", "--output-file=FILE", "generated parser path") { |value| @options[:output] = value }
options.on("-E", "--embedded", "embed the Pure Ruby runtime") { @options[:embedded] = true }
options.on("-t", "--debug", "generate a debug-capable parser") { @options[:debug] = true }
options.on("-g", "obsolete alias for --debug") { @options[:debug] = true }
options.on("-v", "--verbose", "write an automaton report") { @options[:verbose] = true }
add_counterexample_options(options)
add_signature_output_options(options)
options.on("--manifest[=FILE]", "write a generation manifest (defaults beside parser)") do |value|
@options[:manifest] = value || true
end
options.on("--dot=FILE", "write Graphviz DOT") { |value| @options[:dot] = value }
options.on("--mermaid=FILE", "write a Mermaid flowchart") { |value| @options[:mermaid] = value }
options.on("--html=FILE", "write a self-contained HTML report") { |value| @options[:html] = value }
options.on("--railroad=FILE", "write a self-contained SVG railroad diagram") do |value|
@options[:railroad] = value
end
options.on("-O", "--log-file=FILE", "automaton report path") do |value|
@options[:verbose] = true
@options[:log_file] = value
end
options.on("-e", "--executable [RUBY]", "add a shebang") do |value|
@options[:executable] = value || "/usr/bin/env ruby"
end
end
|
#add_pipeline_options(options) ⇒ void
This method returns an undefined value.
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
|
# File 'lib/ibex/cli.rb', line 245
def add_pipeline_options(options)
options.on("--emit=FORMAT", "ast, sets, lexer-ir, grammar-ir, automaton-ir, or ruby") do |value|
@options[:emit] = value
end
options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
@options[:from] = value
end
options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
options.on("--table=FORMAT", %w[plain compact], "parser table format") do |value|
@options[:table] = value.to_sym
end
options.on(
"--cst-trivia=POLICY",
%w[leading balanced drop attach],
"leading, balanced, drop, or attach (alias for leading)"
) do |value|
@options[:cst_trivia] = value.to_sym
end
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
@options[:algorithm] = value.to_sym
end
options.on("--suggest-ielr", "check whether IELR removes unexpected LALR conflicts") do
@options[:suggest_ielr] = true
end
options.on("--entry-isolation", "build independent state sets for each start symbol") do
@options[:entry_isolation] = true
end
options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
@options[:warnings] = warning_categories(value)
end
options.on("--watch", "regenerate file outputs when grammar sources change") { @options[:watch] = true }
end
|
#add_signature_output_options(options) ⇒ void
This method returns an undefined value.
306
307
308
309
310
311
312
313
|
# File 'lib/ibex/cli.rb', line 306
def add_signature_output_options(options)
options.on("--rbs[=FILE]", "write an RBS signature (defaults beside parser)") do |value|
@options[:rbs] = value || true
end
options.on("--action-source[=FILE]", "write static-check-only action Ruby (defaults beside parser)") do |value|
@options[:action_source] = value || true
end
end
|
#build_automaton(grammar, input_path) ⇒ IR::Automaton
744
745
746
747
748
749
750
751
752
753
754
755
|
# File 'lib/ibex/cli.rb', line 744
def build_automaton(grammar, input_path)
algorithm = @options[:algorithm] || :lalr
report_status("building #{algorithm} automaton")
automaton = LALR::Builder.new(
grammar, algorithm: algorithm, entry_isolation: @options[:entry_isolation] == true
).build
report_conflicts(automaton, input_path)
suggest_ielr(automaton, input_path)
write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
write_visualizations(automaton) unless @options[:verify_output]
automaton
end
|
#canonical_target_path(path, seen = {}) ⇒ String
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
|
# File 'lib/ibex/cli.rb', line 463
def canonical_target_path(path, seen = {})
suffix = [] cursor = File.expand_path(path)
until File.exist?(cursor) || File.symlink?(cursor)
parent = File.dirname(cursor)
return path if parent == cursor
suffix.unshift(File.basename(cursor))
cursor = parent
end
if File.symlink?(cursor)
raise Errno::ELOOP, cursor if seen[cursor]
seen[cursor] = true
target = File.expand_path(File.readlink(cursor), File.dirname(cursor))
return File.join(canonical_target_path(target, seen), *suffix)
end
File.join(File.realpath(cursor), *suffix)
end
|
#dispatch_automaton(automaton, path) ⇒ Integer
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
|
# File 'lib/ibex/cli.rb', line 568
def dispatch_automaton(automaton, path)
handle_grammar_warnings(automaton.grammar, path)
return 0 if @options[:check_only]
write_railroad(automaton.grammar) unless @options[:verify_output]
return emit_sets(automaton.grammar) if @options[:emit] == "sets"
return emit_lexer(automaton.grammar) if @options[:emit] == "lexer-ir"
return emit_grammar(automaton.grammar) if @options[:emit] == "grammar-ir"
return emit_loaded_automaton(automaton, path) if @options[:emit] == "automaton-ir"
if @options[:emit] == "ruby"
prepare_loaded_automaton(automaton, path)
return generate_ruby(automaton, path)
end
raise Ibex::Error, "(cli):1:1: AST cannot be reconstructed from Automaton IR"
end
|
#dispatch_grammar(grammar, path) ⇒ Integer
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
|
# File 'lib/ibex/cli.rb', line 550
def dispatch_grammar(grammar, path)
handle_grammar_warnings(grammar, path)
if @options[:check_only]
build_automaton(grammar, path) if @options[:warnings]&.include?(:error)
return 0
end
write_railroad(grammar) unless @options[:verify_output]
return emit_sets(grammar) if @options[:emit] == "sets"
return emit_lexer(grammar) if @options[:emit] == "lexer-ir"
return emit_grammar(grammar) if @options[:emit] == "grammar-ir"
return emit_automaton(grammar, path) if @options[:emit] == "automaton-ir"
return emit_ruby(grammar, path) if @options[:emit] == "ruby"
raise Ibex::Error, "(cli):1:1: emit format #{@options[:emit].inspect} is not available yet"
end
|
#dispatch_subcommand(arguments) ⇒ Integer?
193
194
195
196
197
198
199
200
|
# File 'lib/ibex/cli.rb', line 193
def dispatch_subcommand(arguments)
definition = SUBCOMMAND_HANDLERS[arguments.first]
return unless definition
feature, handler = definition
activate_cli_feature(feature)
send(handler, arguments.drop(1))
end
|
#emit_ast(ast) ⇒ Integer
605
606
607
608
|
# File 'lib/ibex/cli.rb', line 605
def emit_ast(ast)
@stdout.puts(JSON.pretty_generate(ast.to_h))
0
end
|
#emit_automaton(grammar, input_path) ⇒ Integer
642
643
644
645
646
647
|
# File 'lib/ibex/cli.rb', line 642
def emit_automaton(grammar, input_path)
automaton = build_automaton(grammar, input_path)
finish_artifact_generation(@generation_sources)
@stdout.write(IR::Serialize.dump(automaton))
0
end
|
#emit_grammar(grammar) ⇒ Integer
611
612
613
614
615
|
# File 'lib/ibex/cli.rb', line 611
def emit_grammar(grammar)
finish_artifact_generation(@generation_sources)
@stdout.write(IR::Serialize.dump(grammar))
0
end
|
#emit_lexer(grammar) ⇒ Integer
618
619
620
621
622
623
624
625
|
# File 'lib/ibex/cli.rb', line 618
def emit_lexer(grammar)
lexer = grammar.lexer
raise Ibex::Error, "(cli):1:1: grammar does not declare a lexer" unless lexer
finish_artifact_generation(@generation_sources)
@stdout.write(IR::Serialize.dump(lexer))
0
end
|
#emit_loaded_automaton(automaton, input_path) ⇒ Integer
736
737
738
739
740
741
|
# File 'lib/ibex/cli.rb', line 736
def emit_loaded_automaton(automaton, input_path)
prepare_loaded_automaton(automaton, input_path)
finish_artifact_generation(@generation_sources)
@stdout.write(IR::Serialize.dump(automaton))
0
end
|
#emit_ruby(grammar, input_path) ⇒ Integer
650
651
652
653
|
# File 'lib/ibex/cli.rb', line 650
def emit_ruby(grammar, input_path)
automaton = build_automaton(grammar, input_path)
generate_ruby(automaton, input_path)
end
|
#emit_sets(grammar) ⇒ Integer
628
629
630
631
632
633
634
635
636
637
638
639
|
# File 'lib/ibex/cli.rb', line 628
def emit_sets(grammar)
sets = Analysis::Sets.new(grammar)
nonterminals = grammar.nonterminals.sort_by(&:name)
output = {
nullable: nonterminals.filter_map { |symbol| symbol.name if sets.nullable?(symbol) },
first: nonterminals.to_h { |symbol| [symbol.name, sets.first(symbol).sort] },
follow: nonterminals.to_h { |symbol| [symbol.name, sets.follow(symbol).sort] }
}
finish_artifact_generation(@generation_sources)
@stdout.puts(JSON.pretty_generate(output))
0
end
|
#generate_ruby(automaton, input_path) ⇒ Integer
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
|
# File 'lib/ibex/cli.rb', line 656
def generate_ruby(automaton, input_path)
source = Codegen::Ruby.new(
automaton, table: @options[:table], embedded: @options.fetch(:embedded, false),
line_convert: @options.fetch(:line_convert), debug: @options.fetch(:debug, false),
line_convert_all: @options.fetch(:line_convert_all, false),
omit_action_call: @options[:omit_actions], superclass: @options[:superclass],
executable: @options[:executable], cst_trivia: @options.fetch(:cst_trivia, :leading),
error_messages: configured_error_messages(automaton)
).generate
output_path = @options[:output] || default_output_path(input_path, ".rb")
action_source = action_source_source(automaton) if @options[:action_source]
write_action_source(output_path, action_source) if action_source
register_artifact(:parser, output_path, source, mode: (0o755 if @options[:executable]), status: true)
write_rbs(automaton, output_path) if @options[:rbs]
finish_artifact_generation(@generation_sources)
end
|
#generation_paths(input_path) ⇒ Hash[Symbol, String?]
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
|
# File 'lib/ibex/cli.rb', line 485
def generation_paths(input_path)
paths = { input: input_path, messages: @options[:messages] } if @options[:emit] == "ruby" && !@options[:check_only]
output = @options[:output] || default_output_path(input_path, ".rb")
paths[:parser] = output
paths[:rbs] = rbs_output_path(output) if @options[:rbs]
paths[:action_source] = action_source_output_path(output) if @options[:action_source]
paths[:manifest] = manifest_output_path_for(output) if @options[:manifest]
end
unless @options[:verify_output]
paths.merge!(dot: @options[:dot], mermaid: @options[:mermaid], html: @options[:html],
railroad: @options[:railroad])
paths[:report] = @options[:log_file] || default_output_path(input_path, ".output") if @options[:verbose]
end
paths
end
|
344
345
346
347
348
349
350
|
# File 'lib/ibex/cli.rb', line 344
def informational_result(parser)
return print_version if @options[:version] || @options[:runtime_version]
return print_copyright if @options[:copyright]
return print_help(parser) if @options[:help]
nil
end
|
353
354
355
356
357
358
|
# File 'lib/ibex/cli.rb', line 353
def input_path(remaining)
path = remaining.first || raise(Ibex::Error, "(cli):1:1: grammar file is required")
raise Ibex::Error, "(cli):1:1: only one grammar file may be specified" if remaining.length > 1
path
end
|
#manifest_output_path_for(output_path) ⇒ String
794
795
796
797
798
799
|
# File 'lib/ibex/cli.rb', line 794
def manifest_output_path_for(output_path)
configured = @options[:manifest]
return configured if configured.is_a?(String) && !configured.empty?
default_output_path(output_path, ".ibex.json")
end
|
#normalize_grammar_path(path) ⇒ IR::Grammar
526
527
528
|
# File 'lib/ibex/cli.rb', line 526
def normalize_grammar_path(path)
Normalizer.new(resolve_grammar_path(path), mode: @options[:mode]).normalize
end
|
#option_parser ⇒ OptionParser
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
|
# File 'lib/ibex/cli.rb', line 215
def option_parser
OptionParser.new do |options|
options.banner = "Usage: ibex [options] grammarfile"
add_pipeline_options(options)
add_output_options(options)
add_error_messages_generation_option(options)
add_compatibility_options(options)
add_information_options(options)
options.separator("")
options.separator("Subcommands:")
options.separator(" check --ambiguity search for ambiguity within explicit budgets")
options.separator(" coverage collect, merge, or check runtime coverage")
options.separator(" debug AUTOMATON [TOKEN] simulate validated Automaton IR tables")
options.separator(" diagnose collect frontend diagnostics")
options.separator(" doc render grammar documentation")
options.separator(" errors --list|--update list or update example-keyed syntax error messages")
options.separator(" explain explain selected parser conflicts")
options.separator(" fmt format grammar source")
options.separator(" test run grammar-declared source examples")
options.separator(" lsp run the language server over stdio")
options.separator(" migrate-check statically check a racc grammar migration")
options.separator(" migrate-harness generate a differential subprocess harness")
options.separator(" samples generate bounded terminal sentences")
options.separator(" validate-ir FILE validate a versioned IR document")
options.separator(" compare BEFORE AFTER compare two versioned IR documents")
options.separator(" migrate-ir INPUT --to=2 migrate a versioned IR document")
end
end
|
#portable_target_key(path) ⇒ [ String, String ]
451
452
453
454
455
456
457
458
459
460
|
# File 'lib/ibex/cli.rb', line 451
def portable_target_key(path)
canonical = canonical_target_path(path)
basename = File.basename(canonical)
folded = if basename.encoding == Encoding::UTF_8 && basename.valid_encoding?
basename.unicode_normalize(:nfc).downcase(:fold).unicode_normalize(:nfc)
else
basename.downcase
end
[File.dirname(canonical), folded]
end
|
#prepare_loaded_automaton(automaton, input_path) ⇒ void
This method returns an undefined value.
758
759
760
761
762
763
|
# File 'lib/ibex/cli.rb', line 758
def prepare_loaded_automaton(automaton, input_path)
report_conflicts(automaton, input_path)
suggest_ielr(automaton, input_path)
write_report(automaton, input_path) if @options[:verbose] && !@options[:verify_output]
write_visualizations(automaton) unless @options[:verify_output]
end
|
#print_copyright ⇒ Integer
599
600
601
602
|
# File 'lib/ibex/cli.rb', line 599
def print_copyright
@stdout.puts("Ibex #{VERSION} Copyright (c) 2026 Yudai Takada")
0
end
|
#print_help(parser) ⇒ Integer
593
594
595
596
|
# File 'lib/ibex/cli.rb', line 593
def print_help(parser)
@stdout.puts(parser)
0
end
|
#print_version ⇒ Integer
587
588
589
590
|
# File 'lib/ibex/cli.rb', line 587
def print_version
@stdout.puts("ibex #{VERSION}")
0
end
|
#process_grammar(path) ⇒ Integer
503
504
505
506
507
508
509
510
511
512
513
514
515
516
|
# File 'lib/ibex/cli.rb', line 503
def process_grammar(path)
return process_ir(path) if @options[:from]
begin_artifact_generation
report_status("reading #{path}")
resolution = resolve_grammar_path(path)
@generation_inputs = @last_resolver.source_records
@generation_sources = @generation_inputs.map(&:path)
validate_generation_paths!(path, source_paths: resolution.files)
return emit_ast(resolution.root) if @options[:emit] == "ast"
grammar = Normalizer.new(resolution, mode: @options[:mode]).normalize
dispatch_grammar(grammar, path)
end
|
#process_ir(path) ⇒ Integer
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
|
# File 'lib/ibex/cli.rb', line 531
def process_ir(path)
require_relative "ir/validator"
begin_artifact_generation
report_status("reading #{path}")
source = File.binread(path)
input = record_generation_input(path, source)
validate_generation_paths!(path, source_paths: [input.path])
value = IR::Validator.validate(source)
expected = @options[:from] == "grammar-ir" ? IR::Grammar : IR::Automaton
raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input" unless value.is_a?(expected)
return dispatch_grammar(value, path) if value.is_a?(IR::Grammar)
return dispatch_automaton(value, path) if value.is_a?(IR::Automaton)
raise Ibex::Error, "#{path}:1:1: expected #{@options[:from]} input"
end
|
#rbs_output_path(output_path) ⇒ String
698
699
700
701
702
703
704
|
# File 'lib/ibex/cli.rb', line 698
def rbs_output_path(output_path)
configured_path = @options[:rbs]
path = configured_path == true ? default_output_path(output_path, ".rbs") : configured_path
raise ArgumentError, "RBS output path is required" unless path.is_a?(String)
path
end
|
#rbs_source(automaton) ⇒ String
707
708
709
710
711
712
|
# File 'lib/ibex/cli.rb', line 707
def rbs_source(automaton)
require_relative "codegen/rbs"
Codegen::RBS.new(
automaton, superclass: @options[:superclass], omit_action_call: @options[:omit_actions]
).generate
end
|
519
520
521
522
523
|
# File 'lib/ibex/cli.rb', line 519
def resolve_grammar_path(path)
loader = Frontend::SourceLoader.new(record_reads: true)
@last_resolver = Frontend::Resolver.new(path, mode: @options[:mode], loader: loader)
@last_resolver.resolve
end
|
#run(arguments) ⇒ Integer
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
# File 'lib/ibex/cli.rb', line 166
def run(arguments)
subcommand = dispatch_subcommand(arguments)
return subcommand unless subcommand.nil?
parser = option_parser
remaining = parser.parse(arguments)
validate_watch_information_options
information = informational_result(parser)
return information unless information.nil?
validate_messages_options
validate_generation_options
path = input_path(remaining)
validate_generation_paths!(path)
if @options[:watch]
run_watch_feature(path)
else
process_grammar(path)
end
rescue OptionParser::ParseError, Ibex::Error, SystemCallError, SystemStackError => e
@stderr.puts(e.message)
1
end
|
#run_watch_feature(path) ⇒ Integer
209
210
211
212
|
# File 'lib/ibex/cli.rb', line 209
def run_watch_feature(path)
activate_cli_feature(:CLIWatch)
send(:run_watch, path)
end
|
#same_file_target?(left, right) ⇒ Boolean
437
438
439
440
441
442
443
444
445
446
447
448
|
# File 'lib/ibex/cli.rb', line 437
def same_file_target?(left, right)
expanded_left = File.expand_path(left)
expanded_right = File.expand_path(right)
return true if expanded_left == expanded_right
return true if File.exist?(expanded_left) && File.exist?(expanded_right) &&
File.identical?(expanded_left, expanded_right)
portable_target_key(expanded_left) == portable_target_key(expanded_right)
rescue SystemCallError
expanded_left == expanded_right
end
|
#validate_action_source_generation_options ⇒ void
This method returns an undefined value.
381
382
383
384
385
386
|
# File 'lib/ibex/cli.rb', line 381
def validate_action_source_generation_options
return unless @options[:action_source]
raise Ibex::Error, "(cli):1:1: --action-source requires --emit=ruby" unless @options[:emit] == "ruby"
raise Ibex::Error, "(cli):1:1: --action-source and --check-only cannot be combined" if @options[:check_only]
end
|
#validate_generation_options ⇒ void
This method returns an undefined value.
361
362
363
364
365
366
367
368
369
370
|
# File 'lib/ibex/cli.rb', line 361
def validate_generation_options
if @options[:entry_isolation] && @options[:from] == "automaton-ir"
raise Ibex::Error, "(cli):1:1: --entry-isolation cannot be combined with --from=automaton-ir"
end
validate_watch_generation_options if @options[:watch]
validate_manifest_generation_options
validate_action_source_generation_options
validate_verification_generation_options
end
|
#validate_generation_paths!(input_path, source_paths: [input_path]) ⇒ void
This method returns an undefined value.
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
|
# File 'lib/ibex/cli.rb', line 415
def validate_generation_paths!(input_path, source_paths: [input_path])
outputs = generation_paths(input_path).except(:input, :messages)
paths = outputs.filter_map do |kind, path|
[kind, path] if path
end source_entries = source_paths.map.with_index do |path, index|
[index.zero? ? :input : :"include_#{index}", path]
end
source_entries << [:messages, @options[:messages]] if @options[:messages]
paths.concat(source_entries)
collision = paths.combination(2).find do |pair|
left = pair.fetch(0)
right = pair.fetch(1)
same_file_target?(left.fetch(1), right.fetch(1))
end
return unless collision
labels = collision.map { |kind, path| "#{kind}=#{path}" }
raise Ibex::Error, "(cli):1:1: paths must be distinct: #{labels.join(', ')}"
end
|
#validate_manifest_generation_options ⇒ void
This method returns an undefined value.
373
374
375
376
377
378
|
# File 'lib/ibex/cli.rb', line 373
def validate_manifest_generation_options
return unless @options[:manifest]
raise Ibex::Error, "(cli):1:1: --manifest requires --emit=ruby" unless @options[:emit] == "ruby"
raise Ibex::Error, "(cli):1:1: --manifest and --check-only cannot be combined" if @options[:check_only]
end
|
#validate_verification_generation_options ⇒ void
This method returns an undefined value.
389
390
391
392
393
394
|
# File 'lib/ibex/cli.rb', line 389
def validate_verification_generation_options
return unless @options[:verify_output]
raise Ibex::Error, "(cli):1:1: --check requires --emit=ruby" unless @options[:emit] == "ruby"
raise Ibex::Error, "(cli):1:1: --check and --check-only cannot be combined" if @options[:check_only]
end
|
#validate_watch_generation_options ⇒ void
This method returns an undefined value.
405
406
407
408
409
410
411
412
|
# File 'lib/ibex/cli.rb', line 405
def validate_watch_generation_options
raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --from" if @options[:from]
raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check" if @options[:verify_output]
raise Ibex::Error, "(cli):1:1: --watch cannot be combined with --check-only" if @options[:check_only]
return if @options[:emit] == "ruby"
raise Ibex::Error, "(cli):1:1: --watch requires --emit=ruby with file outputs"
end
|
This method returns an undefined value.
397
398
399
400
401
402
|
# File 'lib/ibex/cli.rb', line 397
def validate_watch_information_options
return unless @options[:watch]
return unless %i[version runtime_version copyright help].any? { |key| @options[key] }
raise Ibex::Error, "(cli):1:1: --watch cannot be combined with information options"
end
|
#verify_file(path, source, label) ⇒ void
This method returns an undefined value.
683
684
685
686
687
688
|
# File 'lib/ibex/cli.rb', line 683
def verify_file(path, source, label)
raise Ibex::Error, "#{path}:1:1: generated #{label} is missing" unless File.exist?(path)
return if File.binread(path) == source
raise Ibex::Error, "#{path}:1:1: generated #{label} is stale; regenerate it with the same options"
end
|
#verify_generated_outputs(automaton, output_path, source, action_source) ⇒ Integer
674
675
676
677
678
679
680
|
# File 'lib/ibex/cli.rb', line 674
def verify_generated_outputs(automaton, output_path, source, action_source)
verify_file(output_path, source, "parser")
verify_file(rbs_output_path(output_path), rbs_source(automaton), "RBS signature") if @options[:rbs]
verify_file(action_source_output_path(output_path), action_source, "action source") if action_source
report_status("verified #{output_path}")
0
end
|
#write_action_source(output_path, source) ⇒ void
This method returns an undefined value.
715
716
717
718
|
# File 'lib/ibex/cli.rb', line 715
def write_action_source(output_path, source)
path = action_source_output_path(output_path)
register_artifact(:action_source, path, source, status: true)
end
|
#write_railroad(grammar) ⇒ void
This method returns an undefined value.
785
786
787
788
789
790
791
|
# File 'lib/ibex/cli.rb', line 785
def write_railroad(grammar)
path = @options[:railroad]
return unless path
require_relative "codegen/railroad"
register_artifact(:railroad, path, Codegen::Railroad.render(grammar))
end
|
#write_rbs(automaton, output_path) ⇒ void
This method returns an undefined value.
691
692
693
694
695
|
# File 'lib/ibex/cli.rb', line 691
def write_rbs(automaton, output_path)
path = rbs_output_path(output_path)
source = rbs_source(automaton)
register_artifact(:rbs, path, source, status: true)
end
|
#write_visualizations(automaton) ⇒ void
This method returns an undefined value.
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
|
# File 'lib/ibex/cli.rb', line 766
def write_visualizations(automaton)
dot_path = @options[:dot]
mermaid_path = @options[:mermaid]
html_path = @options[:html]
if dot_path
require_relative "codegen/dot"
register_artifact(:dot, dot_path, Codegen::Dot.render(automaton))
end
if mermaid_path
require_relative "codegen/mermaid"
register_artifact(:mermaid, mermaid_path, Codegen::Mermaid.render(automaton))
end
return unless html_path
require_relative "codegen/html"
register_artifact(:html, html_path, Codegen::HTML.render(automaton))
end
|