# -*- Ruby -*- %% { ##################################################### # Structure to hold composite method names SymbolEntry = Struct.new(:type, :name, :chain) # Structure to hold position information Position = Struct.new(:container_type, :container, :position_type, :position) # Structure to hold breakpoint information Breakpoint = Struct.new(:position, :negate, :condition) # Structure to hold list information List = Struct.new(:position, :num) DEFAULT_OPTS = { :debug=>false, :file_exists_proc => Proc.new{|filename| File.readable?(filename) && !File.directory?(filename) } } def initialize(str, opts={}) @opts = DEFAULT_OPTS.merge(opts) setup_parser(str, opts[:debug]) @file_exists_proc = @opts[:file_exists_proc] end } ##################################################### upcase_letter = /[A-Z]/ downcase_letter = /[a-z]/ suffix_letter = /[=!?]/ letter = upcase_letter | downcase_letter id_symbol = letter | "_" | [0-9] # An variable or a method identifier # Examples: # var1 # my_var? # But not: Variable or @var vm_identifier = < (downcase_letter | "_") id_symbol* suffix_letter? > { SymbolEntry.new(:variable, text) } # Examples: # var1 # But not: my_var?, my_var! variable_identifier = < (downcase_letter | "_") id_symbol* > { SymbolEntry.new(:variable, text) } # Examples: # MY_CONSTANT # MyConstant_01 # But not: # MyConstant_01? constant_identifier = < upcase_letter id_symbol* > { SymbolEntry.new(:constant, text) } # Examples: # $global_variable # We won't try for funny global names like $$, $? $:, $', etc global_identifier = < "$" (constant_identifier | variable_identifier) > { SymbolEntry.new(:global, text) } # Examples: # Foo # foo # But not: # foo!, @foo, Class.foo local_internal_identifier = constant_identifier | variable_identifier # Examples: # Foo, foo, foo! # foo # But not: # @foo, Class.foo local_identifier = constant_identifier | vm_identifier # Example: @foo instance_identifier = < '@' local_identifier > { SymbolEntry.new(:instance, text) } # Example: @@foo classvar_identifier = ('@@' local_identifier:id ) { SymbolEntry.new(:classvar, id) } identifier = global_identifier | instance_identifier | classvar_identifier | local_identifier id_separator = < '::'|'.' > { text } # Like of class_module_chain *after* the first name. So we don't # allow sigils in the initial id. That is we don't allow: # Class.@name1.@@name2.$name3 # But we do allow final sigils: # class.name!, class.name= internal_class_module_chain = < local_internal_identifier:parent id_separator:sep internal_class_module_chain:child > { SymbolEntry.new(parent.type, text, [parent, child, sep]) } | local_identifier # I think strict Ruby rules are that once one goes from :: to . # There is no going back. That is, A.B::C is invalid. # # Also I think method names can't be constants. But such # subtleties we'll handle when we process the final structure. # Examples: # Object, A::B, A.b @@foo.bar, $foo.bar.baz? class_module_chain = < identifier:parent id_separator:sep internal_class_module_chain:child > { SymbolEntry.new(parent.type, text, [parent, child, sep]) } | identifier ############################################################## # Location-specific things. This is used in conjunction with # method-like things above. sp = /[ \t]/ - = sp+ dbl_escapes = "\\\"" { '"' } | "\\n" { "\n" } | "\\t" { "\t" } | "\\\\" { "\\" } escapes = "\\\"" { '"' } | "\\n" { "\n" } | "\\t" { "\t" } | "\\ " { " " } | "\\:" { ":" } | "\\\\" { "\\" } dbl_seq = < /[^\\"]+/ > { text } dbl_not_quote = (dbl_escapes | dbl_seq)+:ary { ary } dbl_string = "\"" dbl_not_quote:ary "\"" { ary.join } not_space_colon = escapes | < /[^ \t\n:]/ > { text } not_space_colons = ( not_space_colon )+:ary { ary.join } filename = dbl_string | not_space_colons file_pos_sep = sp+ | ':' integer = { text.to_i } line_number = integer vm_offset = '@' integer:int { Position.new(nil, nil, :offset, int) } # Examples: # @43 # 5 position = vm_offset | line_number:l { Position.new(nil, nil, :line, l) } # Examples: # Myclass.fn @5 # bytecode offset 5 of fn # Myclass.fn:@5 # same as above # Myclass.fn 5 # line number 5 of fn # Note: Myclass.fn could be either a filename or a method name # The below ordering is important. # 1. Numbers can't be method names they are first. If there's a # file with that name, later we'll allow quoting to indicate filename. # 2. filename:position can't also be a method so that's next # 3. It is possible a filename can be a method name, but we # test using File.exist? so we want to put this first. # Later "quoting" will skip the File.exist? # 4. Class module *with* a position is next and has to be before # without a position, else we would stop early before handling # the position. location = position | :file &{ @file_exists_proc.call(file) } file_pos_sep position:pos { Position.new(:file, file, pos.position_type, pos.position) } | :file &{ @file_exists_proc.call(file) } { Position.new(:file, file, nil, nil) } | class_module_chain?:fn file_pos_sep position:pos { Position.new(:fn, fn, pos.position_type, pos.position) } | class_module_chain?:fn { Position.new(:fn, fn, nil, nil) } if_unless = <"if" | "unless"> { text } condition = { text} breakpoint_stmt_no_condition = location:loc { Breakpoint.new(loc, false, 'true') } # Note that the first word "break" is handled in the command. # Also, "break" with nothing else is handled there as well breakpoint_stmt = location:loc - if_unless:iu - condition:cond { Breakpoint.new(loc, iu == 'unless', cond) } | breakpoint_stmt_no_condition # Note that the first word "list", "list>" or handled in # the command. Also, "list" with nothing else is # handled there as well list_special_targets = <'.' | '-'> { text } list_stmt = (list_special_targets | location):loc - (integer:int)? { List.new(loc, int) } | (list_special_targets | location):loc { List.new(loc, nil) }