class J2119::Matcher
Does the heavy lifting of parsing j2119 files to extract all
the assertions, making egregious use of regular expressions.
This is the kind of thing I actively discourage when other
programmers suggest it. If I were a real grown-up I'd implement a proper lexer and bullet-proof parser.
Constants
- CHILD_ROLE
- MUST
- RELATION
- RELATIONAL
- RELATIONS
- S
- TYPES
- V
Attributes
constraint_match[R]
crutch for RE debugging
eachof_match[R]
actual exports
only_one_match[R]
crutch for RE debugging
role_matcher[R]
actual exports
roledef_match[R]
crutch for RE debugging
Public Class Methods
new(root)
click to toggle source
# File lib/j2119/matcher.rb, line 147 def initialize(root) constants @roles = [] add_role root reconstruct end
tokenize_strings(s)
click to toggle source
# File lib/j2119/matcher.rb, line 160 def self.tokenize_strings(s) # should be a way to do this with capture groups but I'm not smart enough strings = [] r = Regexp.new '^[^"]*"([^"]*)"' while s =~ r strings << $1 s = $' end strings end
Public Instance Methods
add_role(role)
click to toggle source
# File lib/j2119/matcher.rb, line 154 def add_role(role) @roles << role @role_matcher = @roles.join('|') reconstruct end
build(re, line)
click to toggle source
# File lib/j2119/matcher.rb, line 205 def build(re, line) data = {} match = re.match(line) unless match puts "No names for: #{line}" end match.names.each do |name| data[name] = match[name] end data end
build_constraint(line)
click to toggle source
# File lib/j2119/matcher.rb, line 229 def build_constraint(line) build(@constraint_match, line) end
build_only_one(line)
click to toggle source
# File lib/j2119/matcher.rb, line 217 def build_only_one(line) build(@only_one_match, line) end
build_role_def(line)
click to toggle source
# File lib/j2119/matcher.rb, line 201 def build_role_def(line) build(@roledef_match, line) end
constants()
click to toggle source
constants that need help from oxford
# File lib/j2119/matcher.rb, line 64 def constants if !@@initialized @@initialized = true @@strings = Oxford.re(S, :capture_name => 'strings') enum = "one\s+of\s+#{@@strings}" @@predicate = "(#{RELATIONAL}|#{enum})" end end
is_constraint_line(line)
click to toggle source
# File lib/j2119/matcher.rb, line 221 def is_constraint_line(line) line =~ @constraint_start end
is_only_one_match_line(line)
click to toggle source
# File lib/j2119/matcher.rb, line 225 def is_only_one_match_line(line) line =~ @only_one_start end
is_role_def_line(line)
click to toggle source
# File lib/j2119/matcher.rb, line 197 def is_role_def_line(line) line =~ %r{is\s+an?\s+"[^"]*"\.\s*$} end
make_type_regex()
click to toggle source
# File lib/j2119/matcher.rb, line 175 def make_type_regex # add modified numeric types types = TYPES.clone number_types = [ 'float', 'integer', 'numeric' ] number_modifiers = [ 'positive', 'negative', 'nonnegative' ] number_types.each do |number_type| number_modifiers.each do |number_modifier| types << "#{number_modifier}-#{number_type}" end end # add array types array_types = types.map { |t| "#{t}-array" } types |= array_types nonempty_array_types = array_types.map { |t| "nonempty-#{t}" } types |= nonempty_array_types nullable_types = types.map { |t| "nullable-#{t}" } types |= nullable_types @type_regex = types.join('|') end
reconstruct()
click to toggle source
# File lib/j2119/matcher.rb, line 75 def reconstruct make_type_regex # conditional clause excluded_roles = "not\\s+" + Oxford.re(@role_matcher, :capture_name => 'excluded', :use_article => true) + "\\s+" conditional = "which\\s+is\\s+" + excluded_roles # regex for matching constraint lines c_start = '^An?\s+' + "(?<role>#{@role_matcher})" + '\s+' + "(#{conditional})?" + MUST + '\s+have\s+an?\s+' field_list = "one\\s+of\\s+" + Oxford.re('"[^"]+"', :capture_name => 'field_list') c_match = c_start + "((?<type>#{@type_regex})\\s+)?" + "field\\s+named\\s+" + "((\"(?<field_name>[^\"]+)\")|(#{field_list}))" + '(\s+whose\s+value\s+MUST\s+be\s+' + @@predicate + ')?' + '(' + CHILD_ROLE + ')?' + '\.' # regexp for matching lines of the form # "An X MUST have only one of "Y", "Z", and "W". # There's a pattern here, building a separate regex rather than # adding more complexity to @constraint_matcher. Any further # additions should be done this way, and # TODO: Break @constraint_matcher into a bunch of smaller patterns # like this. oo_start = '^An?\s+' + "(?<role>#{@role_matcher})" + '\s+' + MUST + '\s+have\s+only\s+' oo_field_list = "one\\s+of\\s+" + Oxford.re('"[^"]+"', :capture_name => 'field_list', :connector => 'and') oo_match = oo_start + oo_field_list # regex for matching role-def lines val_match = "whose\\s+\"(?<fieldtomatch>[^\"]+)\"" + "\\s+field's\\s+value\\s+is\\s+" + "(?<valtomatch>(\"[^\"]*\")|([^\"\\s]\\S+))\\s+" with_a_match = "with\\s+an?\\s+\"(?<with_a_field>[^\"]+)\"\\s+field\\s" rd_match = '^An?\s+' + "(?<role>#{@role_matcher})" + '\s+' + "((?<val_match_present>#{val_match})|(#{with_a_match}))?" + "is\\s+an?\\s+" + "\"(?<newrole>[^\"]*)\"\\.\\s*$" @roledef_match = Regexp.new(rd_match) @constraint_start = Regexp.new(c_start) @constraint_match = Regexp.new(c_match) @only_one_start = Regexp.new(oo_start) @only_one_match = Regexp.new(oo_match) eo_match = "^Each\\s+of\\s" + Oxford.re(@role_matcher, :capture_name => 'each_of', :use_article => true, :connector => 'and') + "\\s+(?<trailer>.*)$" @eachof_match = Regexp.new(eo_match) end
tokenize_values(vals)
click to toggle source
# File lib/j2119/matcher.rb, line 171 def tokenize_values(vals) vals.gsub(',', ' ').gsub('or', ' ').split(/\s+/) end