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],
"diff" => %i[CLIAnalysis run_diff_command],
"diagnose" => %i[CLIDiagnostics run_diagnose_command],
"coverage" => %i[CLICoverage run_coverage_command], "config" => %i[CLIConfig run_config_command],
"debug" => %i[CLIDebug run_debug_command],
"doc" => %i[CLIDocumentation run_documentation_command],
"errors" => %i[CLIErrorMessages run_error_messages_command],
"equiv" => %i[CLIEquiv run_equiv_command],
"explain" => %i[CLIExplain run_explain_command],
"fmt" => %i[CLIFormatting run_format_command],
"fix" => %i[CLIFix run_fix_command],
"fuzz" => %i[CLIFuzz run_fuzz_command],
"test" => %i[CLIGrammarTests run_grammar_tests_command],
"lsp" => %i[CLILSP run_lsp_command],
"import" => %i[CLIBisonImport run_bison_import_command],
"metrics" => %i[CLIAnalysis run_metrics_command],
"migrate-check" => %i[CLIRaccMigration run_migrate_check_command],
"migrate-harness" => %i[CLIRaccMigration run_migrate_harness_command],
"reduce" => %i[CLIReduce run_reduce_command],
"samples" => %i[CLISamples run_samples_command],
"verify" => %i[CLIVerify run_verify_command],
"validate-ir" => %i[CLIIRTools run_validate_ir_command],
"compare" => %i[CLIIRTools run_compare_command]
}.freeze
Constants included
from CLIOutputs
Ibex::CLIOutputs::WARNING_MESSAGE_IDS
Ibex::CLICounterexampleOptions::DEFAULTS
Class Method Summary
collapse
Instance Method Summary
collapse
-
#action_source_output_path(output_path) ⇒ String
-
#action_source_source(automaton) ⇒ String
-
#activate_analysis_grammar(grammar, options: @options, explicit_keys: @configuration_explicit_options.keys) ⇒ IR::Grammar
-
#activate_cli_feature(feature) ⇒ void
-
#activate_generation_grammar(grammar) ⇒ void
-
#add_compatibility_options(options) ⇒ void
-
#add_information_options(options) ⇒ void
-
#add_output_options(options) ⇒ void
-
#add_pipeline_options(options) ⇒ void
rubocop:disable Metrics/MethodLength -- pipeline flags share one parser boundary.
-
#add_signature_output_options(options) ⇒ void
-
#add_subcommand_help(options) ⇒ void
-
#bison_source?(source) ⇒ Boolean
-
#build_automaton(grammar, input_path) ⇒ IR::Automaton
-
#canonical_target_path(path, seen = {}) ⇒ String
-
#configuration_value(name) ⇒ Object
Configuration values have already been validated by Configuration::Resolver; callers narrow the value according to the option they are consuming.
-
#construct_analysis_automaton(grammar, options, explicit_keys) ⇒ [ IR::Grammar, Symbol, IR::Automaton ]
-
#dispatch_automaton(automaton, path) ⇒ Integer
-
#dispatch_grammar(grammar, path) ⇒ Integer
-
#dispatch_subcommand(arguments) ⇒ Integer?
-
#effective_configuration ⇒ Configuration::Resolver
-
#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
-
#extract_language(arguments) ⇒ Array[String]
-
#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
-
#local_configuration_value(options, name) ⇒ Object
-
#manifest_output_path_for(output_path) ⇒ String
-
#mark_configuration_option(name) ⇒ void
-
#noncanonical_analysis_grammar(grammar, configuration) ⇒ IR::Grammar
-
#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
-
#report_noncanonical_analysis(configuration) ⇒ void
-
#resolve_configuration_options(options, explicit_keys:) ⇒ Configuration::Resolver
Resolve a command-local legacy option hash without promoting it into the reusable CLI instance's generation settings.
-
#resolve_grammar_path(path) ⇒ Frontend::Resolution
-
#run(arguments) ⇒ Integer
-
#run_watch_feature(path) ⇒ Integer
-
#same_file_target?(left, right) ⇒ Boolean
-
#select_configuration_mode(value) ⇒ void
-
#set_configuration_option(name, value) ⇒ void
OptionParser accepts a heterogeneous set of operation and configuration values.
-
#set_local_configuration_option(options, name, value) ⇒ void
-
#validate_action_source_generation_options ⇒ void
-
#validate_automaton_construction_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_ielr_report(grammar, automaton) ⇒ 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, #grammar_warning_message, #handle_grammar_warnings, #ir_file_mode, #report_conflicts, #report_status, #suggest_ielr, #warning_categories, #write_report
#add_generation_manifest, #append_ir_manifest_options, #artifact_label, #begin_artifact_generation, #construction_authority, #effective_cst_trivia, #ensure_generation_inputs_stable!, #finish_artifact_generation, #generation_artifacts, #generation_inputs_stable?, #generation_manifest_options, #manifest_effective_configuration, #manifest_output_path, #record_generation_input, #register_artifact, #stringify_contract, #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.
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
|
# File 'lib/ibex/cli.rb', line 202
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) {}
@language = Messages.language(ENV.fetch("IBEX_LANG", nil))
@options = {
emit: "ruby", mode: Configuration::Registry.fetch("grammar.mode").default,
table: Configuration::Registry.fetch("table.representation").default, line_convert: true
}
.merge(CLICounterexampleOptions::DEFAULTS)
@configuration_explicit_options = {}
@generation_grammar = nil
@generation_automaton = @analysis_configuration = nil
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
193
194
195
196
197
198
199
|
# File 'lib/ibex/cli.rb', line 193
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
1006
1007
1008
1009
1010
1011
1012
|
# File 'lib/ibex/cli.rb', line 1006
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
1015
1016
1017
1018
1019
1020
|
# File 'lib/ibex/cli.rb', line 1015
def action_source_source(automaton)
require_relative "codegen/action_source"
Codegen::ActionSource.new(
automaton, omit_action_call: configuration_value("actions.omit_calls")
).generate
end
|
#activate_analysis_grammar(grammar, options: @options, explicit_keys: @configuration_explicit_options.keys) ⇒ IR::Grammar
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
|
# File 'lib/ibex/cli.rb', line 525
def activate_analysis_grammar(grammar, options: @options, explicit_keys: @configuration_explicit_options.keys)
adapter = Configuration::CLIAdapter.new(options, explicit_keys: explicit_keys)
cli = adapter.configuration_values
contract = grammar.parser_contract
grammar_values = contract&.configuration_values || {}
locations = contract&.configuration_locations || {}
overrides = {} algorithm = "parser.algorithm"
if grammar_values.key?(algorithm) && cli.key?(algorithm) &&
grammar_values.fetch(algorithm) != cli.fetch(algorithm)
overrides[algorithm] = cli.delete(algorithm)
end
source_locations = {} source_locations[:grammar] = locations unless locations.empty?
configuration = Configuration::Resolver.new(
grammar: grammar_values, cli: cli, analysis_overrides: overrides, locations: source_locations
)
@analysis_configuration = configuration
active = noncanonical_analysis_grammar(grammar, configuration)
@generation_grammar = active
report_noncanonical_analysis(configuration)
active
end
|
#activate_cli_feature(feature) ⇒ void
This method returns an undefined value.
344
345
346
347
|
# File 'lib/ibex/cli.rb', line 344
def activate_cli_feature(feature)
extension = Ibex.const_get(feature)
extend extension unless singleton_class.ancestors.include?(extension)
end
|
#activate_generation_grammar(grammar) ⇒ void
This method returns an undefined value.
831
832
833
834
835
|
# File 'lib/ibex/cli.rb', line 831
def activate_generation_grammar(grammar)
@analysis_configuration = nil
@generation_grammar = grammar
effective_configuration
end
|
#add_compatibility_options(options) ⇒ void
This method returns an undefined value.
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
|
# File 'lib/ibex/cli.rb', line 486
def add_compatibility_options(options)
options.on("-F", "--frozen", "emit frozen string literals") do
set_configuration_option(:frozen, true)
end
options.on("--line-convert-all", "convert all source lines") do
@options[:line_convert_all] = true
set_configuration_option(:line_convert, true)
end
options.on("-l", "--no-line-convert", "use generated-file action lines") do
@options[:line_convert_all] = false
set_configuration_option(:line_convert, false)
end
options.on("-a", "--no-omit-actions", "generate implicit action methods") do
set_configuration_option(:omit_actions, false)
end
options.on("--superclass=CLASS", "override parser superclass") do |value|
set_configuration_option(:superclass, value)
end
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.
512
513
514
515
516
517
518
519
520
|
# File 'lib/ibex/cli.rb', line 512
def add_information_options(options)
options.on("--lang=LANG", "built-in diagnostic language (en or ja)") do |value|
@language = Messages.language(value)
end
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.
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
|
# File 'lib/ibex/cli.rb', line 443
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") do
set_configuration_option(:embedded, true)
end
options.on("-t", "--debug", "generate a debug-capable parser") do
set_configuration_option(:debug, true)
end
options.on("-g", "obsolete alias for --debug") do
set_configuration_option(:debug, true)
end
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|
set_configuration_option(:executable, value || "/usr/bin/env ruby")
end
end
|
#add_pipeline_options(options) ⇒ void
This method returns an undefined value.
rubocop:disable Metrics/MethodLength -- pipeline flags share one parser boundary.
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
|
# File 'lib/ibex/cli.rb', line 399
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| select_configuration_mode(value) }
options.on("--table=FORMAT", %w[plain compact], "parser table format") do |value|
set_configuration_option(: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|
set_configuration_option(:cst_trivia, value.to_sym)
end
options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
set_configuration_option(:algorithm, value.to_sym)
end
options.on("--ielr-strategy=NAME", %w[direct partition], "IELR construction strategy") do |value|
@options[:ielr_strategy] = value.to_sym
end
options.on("--ielr-report", "report canonical-vs-IELR action differences") do
@options[:ielr_report] = true
end
options.on("--remove-unreachable", "compact unreachable IELR states after resolution") do
@options[:remove_unreachable] = true
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
set_configuration_option(: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.
476
477
478
479
480
481
482
483
|
# File 'lib/ibex/cli.rb', line 476
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
|
#add_subcommand_help(options) ⇒ void
This method returns an undefined value.
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
|
# File 'lib/ibex/cli.rb', line 369
def add_subcommand_help(options)
options.separator("")
options.separator("Subcommands:")
options.separator(" check --ambiguity search for ambiguity within explicit budgets")
options.separator(CONFIG_SUBCOMMAND_HELP)
options.separator(" debug AUTOMATON [TOKEN] simulate validated Automaton IR tables")
options.separator(" diagnose collect frontend diagnostics")
options.separator(" diff OLD NEW classify grammar and automaton changes")
options.separator(" doc render grammar documentation")
options.separator(" errors --list|--update list or update example-keyed syntax error messages")
options.separator(" equiv LEFT RIGHT search for bounded language differences")
options.separator(" explain explain selected parser conflicts")
options.separator(" fmt format grammar source")
options.separator(" fix propose bounded-equivalent conflict repairs")
options.separator(" fuzz run bounded grammar-derived differential fuzzing")
options.separator(" test run grammar-declared source examples")
options.separator(" lsp run the language server over stdio")
options.separator(" import bison FILE import Bison grammar structure for analysis")
options.separator(" migrate-check statically check a racc grammar migration")
options.separator(" migrate-harness generate a differential subprocess harness")
options.separator(" metrics GRAMMAR report deterministic grammar/table metrics")
options.separator(" reduce delta-debug a failing token, line, or byte sequence")
options.separator(" samples generate bounded terminal sentences")
options.separator(" verify AUTOMATON independently verify Automaton IR semantics")
options.separator(" validate-ir FILE validate the current IR document")
options.separator(" compare BEFORE AFTER compare current IR documents")
end
|
#bison_source?(source) ⇒ Boolean
768
769
770
|
# File 'lib/ibex/cli.rb', line 768
def bison_source?(source)
source.lines.count { |line| line.match?(%r{^\s*%%(?:\s|/|$)}) } >= 2
end
|
#build_automaton(grammar, input_path) ⇒ IR::Automaton
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
|
# File 'lib/ibex/cli.rb', line 1031
def build_automaton(grammar, input_path)
algorithm = configuration_value("parser.algorithm")
report_status("building #{algorithm} automaton")
automaton = LALR::Builder.new(
grammar, algorithm: algorithm, ielr_strategy: @options.fetch(:ielr_strategy, :partition),
remove_unreachable: @options.fetch(:remove_unreachable, false),
entry_isolation: configuration_value("parser.entries") == :isolated
).build
write_ielr_report(grammar, automaton) if @options[:ielr_report]
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
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
|
# File 'lib/ibex/cli.rb', line 692
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
|
#configuration_value(name) ⇒ Object
Configuration values have already been validated by Configuration::Resolver;
callers narrow the value according to the option they are consuming.
303
304
305
|
# File 'lib/ibex/cli.rb', line 303
def configuration_value(name)
effective_configuration.value(name)
end
|
#construct_analysis_automaton(grammar, options, explicit_keys) ⇒ [ IR::Grammar, Symbol, IR::Automaton ]
551
552
553
554
555
556
557
558
559
560
|
# File 'lib/ibex/cli.rb', line 551
def construct_analysis_automaton(grammar, options, explicit_keys)
active = activate_analysis_grammar(grammar, options: options, explicit_keys: explicit_keys)
algorithm = configuration_value("parser.algorithm") automaton = LALR::Builder.new(
active, algorithm: algorithm, ielr_strategy: options.fetch(:ielr_strategy, :partition),
remove_unreachable: options.fetch(:remove_unreachable, false),
entry_isolation: configuration_value("parser.entries") == :isolated
).build
[active, algorithm, automaton]
end
|
#dispatch_automaton(automaton, path) ⇒ Integer
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
|
# File 'lib/ibex/cli.rb', line 811
def dispatch_automaton(automaton, path)
activate_generation_grammar(automaton.grammar)
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
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
|
# File 'lib/ibex/cli.rb', line 792
def dispatch_grammar(grammar, path)
activate_generation_grammar(grammar)
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?
334
335
336
337
338
339
340
341
|
# File 'lib/ibex/cli.rb', line 334
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
|
270
271
272
273
274
275
276
277
278
279
|
# File 'lib/ibex/cli.rb', line 270
def effective_configuration
grammar = @generation_grammar
contract = grammar&.parser_contract
@analysis_configuration || Configuration::CLIAdapter.new(
@options, explicit_keys: @configuration_explicit_options.keys
).resolve(
grammar: contract&.configuration_values || {},
locations: contract&.configuration_locations || {}
)
end
|
#emit_ast(ast) ⇒ Integer
879
880
881
882
|
# File 'lib/ibex/cli.rb', line 879
def emit_ast(ast)
@stdout.puts(JSON.pretty_generate(ast.to_h))
0
end
|
#emit_automaton(grammar, input_path) ⇒ Integer
916
917
918
919
920
921
|
# File 'lib/ibex/cli.rb', line 916
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
885
886
887
888
889
|
# File 'lib/ibex/cli.rb', line 885
def emit_grammar(grammar)
finish_artifact_generation(@generation_sources)
@stdout.write(IR::Serialize.dump(grammar))
0
end
|
#emit_lexer(grammar) ⇒ Integer
892
893
894
895
896
897
898
899
|
# File 'lib/ibex/cli.rb', line 892
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
1023
1024
1025
1026
1027
1028
|
# File 'lib/ibex/cli.rb', line 1023
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
924
925
926
927
|
# File 'lib/ibex/cli.rb', line 924
def emit_ruby(grammar, input_path)
automaton = build_automaton(grammar, input_path)
generate_ruby(automaton, input_path)
end
|
#emit_sets(grammar) ⇒ Integer
902
903
904
905
906
907
908
909
910
911
912
913
|
# File 'lib/ibex/cli.rb', line 902
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
|
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
|
# File 'lib/ibex/cli.rb', line 308
def (arguments)
remaining = [] index = 0
while index < arguments.length
argument = arguments.fetch(index)
if argument == "--lang"
value = arguments[index + 1]
raise OptionParser::MissingArgument, "--lang" unless value
@language = Messages.language(value)
index += 2
elsif argument.start_with?("--lang=")
value = argument.delete_prefix("--lang=")
raise OptionParser::MissingArgument, "--lang" if value.empty?
@language = Messages.language(value)
index += 1
else
remaining << argument
index += 1
end
end
remaining
end
|
#generate_ruby(automaton, input_path) ⇒ Integer
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
|
# File 'lib/ibex/cli.rb', line 930
def generate_ruby(automaton, input_path)
@generation_automaton = automaton
line_mapping = configuration_value("source.line_mapping") executable = configuration_value("build.executable") table = configuration_value("table.representation") embedded = configuration_value("runtime.embedded") debug = configuration_value("build.debug") omit_action_call = configuration_value("actions.omit_calls") superclass = configuration_value("parser.superclass") cst_trivia = configuration_value("cst.trivia") source = Codegen::Ruby.new(
automaton, table: table, embedded: embedded,
line_convert: line_mapping != :none, debug: debug,
line_convert_all: line_mapping == :all,
omit_action_call: omit_action_call, superclass: superclass,
executable: executable, cst_trivia: cst_trivia,
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 executable), status: true)
write_rbs(automaton, output_path) if @options[:rbs]
finish_artifact_generation(@generation_sources)
end
|
#generation_paths(input_path) ⇒ Hash[Symbol, String?]
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
|
# File 'lib/ibex/cli.rb', line 714
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
|
563
564
565
566
567
568
569
|
# File 'lib/ibex/cli.rb', line 563
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
|
572
573
574
575
576
577
|
# File 'lib/ibex/cli.rb', line 572
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
|
#local_configuration_value(options, name) ⇒ Object
289
290
291
292
|
# File 'lib/ibex/cli.rb', line 289
def local_configuration_value(options, name)
explicit_keys = options.fetch(:configuration_explicit)
resolve_configuration_options(options, explicit_keys: explicit_keys).value(name)
end
|
#manifest_output_path_for(output_path) ⇒ String
1090
1091
1092
1093
1094
1095
|
# File 'lib/ibex/cli.rb', line 1090
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
|
#mark_configuration_option(name) ⇒ void
This method returns an undefined value.
252
253
254
|
# File 'lib/ibex/cli.rb', line 252
def mark_configuration_option(name)
@configuration_explicit_options[name] = true
end
|
#noncanonical_analysis_grammar(grammar, configuration) ⇒ IR::Grammar
838
839
840
841
842
843
844
845
|
# File 'lib/ibex/cli.rb', line 838
def noncanonical_analysis_grammar(grammar, configuration)
selection = configuration.fetch("parser.algorithm")
return grammar if selection.canonical
require_relative "configuration/analysis_grammar"
algorithm = selection.value Configuration::AnalysisGrammar.for_algorithm(grammar, algorithm)
end
|
#normalize_grammar_path(path) ⇒ IR::Grammar
755
756
757
758
759
760
761
762
763
764
765
|
# File 'lib/ibex/cli.rb', line 755
def normalize_grammar_path(path)
source = File.binread(path)
if bison_source?(source)
require_relative "bison_import"
imported = BisonImport::Importer.new(source, file: path).run
ast = Frontend::Parser.new(imported.source, file: path, mode: :extended).parse
return Normalizer.new(ast, mode: :extended).normalize
end
Normalizer.new(resolve_grammar_path(path), mode: configuration_value("grammar.mode")).normalize
end
|
#option_parser ⇒ OptionParser
356
357
358
359
360
361
362
363
364
365
366
|
# File 'lib/ibex/cli.rb', line 356
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)
add_subcommand_help(options)
end
end
|
#portable_target_key(path) ⇒ [ String, String ]
680
681
682
683
684
685
686
687
688
689
|
# File 'lib/ibex/cli.rb', line 680
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.
1054
1055
1056
1057
1058
1059
|
# File 'lib/ibex/cli.rb', line 1054
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
873
874
875
876
|
# File 'lib/ibex/cli.rb', line 873
def print_copyright
@stdout.puts("Ibex #{VERSION} Copyright (c) 2026 Yudai Takada")
0
end
|
#print_help(parser) ⇒ Integer
867
868
869
870
|
# File 'lib/ibex/cli.rb', line 867
def print_help(parser)
@stdout.puts(parser)
0
end
|
#print_version ⇒ Integer
861
862
863
864
|
# File 'lib/ibex/cli.rb', line 861
def print_version
@stdout.puts("ibex #{VERSION}")
0
end
|
#process_grammar(path) ⇒ Integer
732
733
734
735
736
737
738
739
740
741
742
743
744
745
|
# File 'lib/ibex/cli.rb', line 732
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: configuration_value("grammar.mode")).normalize
dispatch_grammar(grammar, path)
end
|
#process_ir(path) ⇒ Integer
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
|
# File 'lib/ibex/cli.rb', line 773
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
981
982
983
984
985
986
987
|
# File 'lib/ibex/cli.rb', line 981
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
990
991
992
993
994
995
996
997
|
# File 'lib/ibex/cli.rb', line 990
def rbs_source(automaton)
require_relative "codegen/rbs"
superclass = configuration_value("parser.superclass") omit_action_call = configuration_value("actions.omit_calls") Codegen::RBS.new(
automaton, superclass: superclass, omit_action_call: omit_action_call
).generate
end
|
#report_noncanonical_analysis(configuration) ⇒ void
This method returns an undefined value.
848
849
850
851
852
853
854
855
856
857
858
|
# File 'lib/ibex/cli.rb', line 848
def report_noncanonical_analysis(configuration)
selection = configuration.fetch("parser.algorithm")
return if selection.canonical
analysis = selection.to_h.fetch("analysis") @stderr.puts(
"noncanonical analysis configuration: parser.algorithm " \
"declared=#{analysis.fetch('declared')} selected=#{analysis.fetch('selected')} " \
"override=true canonical_generation=false"
)
end
|
#resolve_configuration_options(options, explicit_keys:) ⇒ Configuration::Resolver
Resolve a command-local legacy option hash without promoting it into the
reusable CLI instance's generation settings.
284
285
286
|
# File 'lib/ibex/cli.rb', line 284
def resolve_configuration_options(options, explicit_keys:)
Configuration::CLIAdapter.new(options, explicit_keys: explicit_keys).resolve
end
|
748
749
750
751
752
|
# File 'lib/ibex/cli.rb', line 748
def resolve_grammar_path(path)
loader = Frontend::SourceLoader.new(record_reads: true)
@last_resolver = Frontend::Resolver.new(path, mode: configuration_value("grammar.mode"), loader: loader)
@last_resolver.resolve
end
|
#run(arguments) ⇒ Integer
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
|
# File 'lib/ibex/cli.rb', line 222
def run(arguments)
arguments = (arguments)
@generation_grammar = nil
@generation_automaton = @analysis_configuration = nil
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(Messages.translate("cli.error", language: @language, detail: e.message))
1
end
|
#run_watch_feature(path) ⇒ Integer
350
351
352
353
|
# File 'lib/ibex/cli.rb', line 350
def run_watch_feature(path)
activate_cli_feature(:CLIWatch)
send(:run_watch, path)
end
|
#same_file_target?(left, right) ⇒ Boolean
666
667
668
669
670
671
672
673
674
675
676
677
|
# File 'lib/ibex/cli.rb', line 666
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
|
#select_configuration_mode(value) ⇒ void
This method returns an undefined value.
265
266
267
|
# File 'lib/ibex/cli.rb', line 265
def select_configuration_mode(value)
set_configuration_option(:mode, value.to_sym)
end
|
#set_configuration_option(name, value) ⇒ void
This method returns an undefined value.
OptionParser accepts a heterogeneous set of operation and configuration
values. The CLIAdapter is the validation boundary for this dynamic map.
259
260
261
262
|
# File 'lib/ibex/cli.rb', line 259
def set_configuration_option(name, value)
@options[name] = value
mark_configuration_option(name)
end
|
#set_local_configuration_option(options, name, value) ⇒ void
This method returns an undefined value.
295
296
297
298
|
# File 'lib/ibex/cli.rb', line 295
def set_local_configuration_option(options, name, value)
options[name] = value
options.fetch(:configuration_explicit) << name
end
|
#validate_action_source_generation_options ⇒ void
This method returns an undefined value.
610
611
612
613
614
615
|
# File 'lib/ibex/cli.rb', line 610
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_automaton_construction_options ⇒ void
This method returns an undefined value.
590
591
592
593
594
595
596
597
598
599
|
# File 'lib/ibex/cli.rb', line 590
def validate_automaton_construction_options
return unless @options[:from] == "automaton-ir"
if @configuration_explicit_options[:algorithm]
raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with --from=automaton-ir"
end
return unless @configuration_explicit_options[:entry_isolation]
raise Ibex::Error, "(cli):1:1: --entry-isolation cannot be combined with --from=automaton-ir"
end
|
#validate_generation_options ⇒ void
This method returns an undefined value.
580
581
582
583
584
585
586
587
|
# File 'lib/ibex/cli.rb', line 580
def validate_generation_options
validate_automaton_construction_options
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.
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
|
# File 'lib/ibex/cli.rb', line 644
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.
602
603
604
605
606
607
|
# File 'lib/ibex/cli.rb', line 602
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.
618
619
620
621
622
623
|
# File 'lib/ibex/cli.rb', line 618
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.
634
635
636
637
638
639
640
641
|
# File 'lib/ibex/cli.rb', line 634
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.
626
627
628
629
630
631
|
# File 'lib/ibex/cli.rb', line 626
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.
966
967
968
969
970
971
|
# File 'lib/ibex/cli.rb', line 966
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
957
958
959
960
961
962
963
|
# File 'lib/ibex/cli.rb', line 957
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.
1000
1001
1002
1003
|
# File 'lib/ibex/cli.rb', line 1000
def write_action_source(output_path, source)
path = action_source_output_path(output_path)
register_artifact(:action_source, path, source, status: true)
end
|
#write_ielr_report(grammar, automaton) ⇒ void
This method returns an undefined value.
#write_railroad(grammar) ⇒ void
This method returns an undefined value.
1081
1082
1083
1084
1085
1086
1087
|
# File 'lib/ibex/cli.rb', line 1081
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.
974
975
976
977
978
|
# File 'lib/ibex/cli.rb', line 974
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.
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
|
# File 'lib/ibex/cli.rb', line 1062
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
|