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