Class: Fop::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/fop/parser.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

DIGIT =
/^[0-9]$/
REGEX_START =
"^".freeze
REGEX_LAZY_WILDCARD =
".*?".freeze
REGEX_MATCHES =
{
  "N" => "[0-9]+".freeze,
  "W" => "\\w+".freeze,
  "A" => "[a-zA-Z]+".freeze,
  "*" => ".*".freeze,
}.freeze
OPS_WITH_OPTIONAL_ARGS =
[Tokenizer::OP_REPLACE]
TR_REGEX =
/.*/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(src, debug: false) ⇒ Parser

Returns a new instance of Parser.



26
27
28
29
# File 'lib/fop/parser.rb', line 26

def initialize(src, debug: false)
  @tokenizer = Tokenizer.new(src)
  @errors = []
end

Instance Attribute Details

#errorsObject (readonly)

Returns the value of attribute errors.



24
25
26
# File 'lib/fop/parser.rb', line 24

def errors
  @errors
end

Instance Method Details

#build_regex!(wildcard, token, src = token.val) ⇒ Object



155
156
157
158
159
160
# File 'lib/fop/parser.rb', line 155

def build_regex!(wildcard, token, src = token.val)
  Regexp.new((wildcard ? REGEX_LAZY_WILDCARD : REGEX_START) + src)
rescue RegexpError => e
  errors << Error.new(:regex, token, e.message)
  nil
end

#parseObject



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/fop/parser.rb', line 31

def parse
  nodes = []
  wildcard = false
  eof = false
  # Top-level parsing. It will always be looking for a String, Regex, or Expression.
  until eof
    @tokenizer.reset_escapes!
    t = @tokenizer.next
    case t.type
    when Tokens::WILDCARD
      errors << Error.new(:syntax, t, "Consecutive wildcards") if wildcard
      wildcard = true
    when Tokens::TEXT
      reg = build_regex!(wildcard, t, Regexp.escape(t.val))
      nodes << Nodes::Text.new(wildcard, t.val, reg)
      wildcard = false
    when Tokens::EXP_OPEN
      nodes << parse_exp!(wildcard)
      wildcard = false
    when Tokens::REG_DELIM
      nodes << parse_regex!(wildcard)
      wildcard = false
    when Tokens::EOF
      eof = true
    else
      errors << Error.new(:syntax, t, "Unexpected #{t.type}")
    end
  end
  nodes << Nodes::Text.new(true, "", TR_REGEX) if wildcard
  return nodes, @errors
end

#parse_exp!(wildcard = false) ⇒ Object



63
64
65
66
67
68
69
70
71
# File 'lib/fop/parser.rb', line 63

def parse_exp!(wildcard = false)
  exp = Nodes::Expression.new(wildcard)
  parse_exp_match! exp
  op_token = parse_exp_operator! exp
  if exp.operator
    parse_exp_arg! exp, op_token
  end
  return exp
end

#parse_exp_arg!(exp, op_token) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/fop/parser.rb', line 109

def parse_exp_arg!(exp, op_token)
  @tokenizer.escape.operators = true
  @tokenizer.escape.regex = true
  @tokenizer.escape.regex_capture = false if exp.regex_match

  exp.arg = []
  found_close, eof = false, false
  until found_close or eof
    t = @tokenizer.next
    case t.type
    when Tokens::TEXT
      exp.arg << t.val
    when Tokens::REG_CAPTURE
      exp.arg << t.val.to_i - 1
      errors << Error.new(:syntax, t, "Invalid regex capture; must be between 0 and 9 (found #{t.val})") unless t.val =~ DIGIT
      errors << Error.new(:syntax, t, "Unexpected regex capture; expected str or '}'") if !exp.regex_match
    when Tokens::EXP_CLOSE
      found_close = true
    when Tokens::EOF
      eof = true
      errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected str or '}'")
    else
      errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected str or '}'")
    end
  end

  if exp.arg.size != 1 and !OPS_WITH_OPTIONAL_ARGS.include?(exp.operator)
    errors << Error.new(:arg, op_token, "Operator '#{op_token.val}' requires an argument")
  end
end

#parse_exp_match!(exp) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/fop/parser.rb', line 73

def parse_exp_match!(exp)
  @tokenizer.escape.operators = false
  t = @tokenizer.next
  case t.type
  when Tokens::TEXT, Tokens::WILDCARD
    exp.match = t.val
    if (src = REGEX_MATCHES[exp.match])
      reg = Regexp.new((exp.wildcard ? REGEX_LAZY_WILDCARD : REGEX_START) + src)
      exp.regex = Nodes::Regex.new(exp.wildcard, src, reg)
    else
      errors << Error.new(:name, t, "Unknown match type '#{exp.match}'") if exp.regex.nil?
    end
  when Tokens::REG_DELIM
    exp.regex = parse_regex!(exp.wildcard)
    exp.match = exp.regex&.src
    exp.regex_match = true
    @tokenizer.reset_escapes!
  else
    errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected a string or a regex")
  end
end

#parse_exp_operator!(exp) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/fop/parser.rb', line 95

def parse_exp_operator!(exp)
  @tokenizer.escape.operators = false
  t = @tokenizer.next
  case t.type
  when Tokens::EXP_CLOSE
    # no op
  when Tokens::OPERATOR
    exp.operator = t.val
  else
    errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected an operator")
  end
  t
end

#parse_regex!(wildcard) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/fop/parser.rb', line 140

def parse_regex!(wildcard)
  @tokenizer.regex_mode!
  t = @tokenizer.next
  reg = Nodes::Regex.new(wildcard, t.val)
  if t.type == Tokens::TEXT
    reg.regex = build_regex!(wildcard, t)
  else
    errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected a string of regex")
  end

  t = @tokenizer.next
  errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected a string of regex") unless t.type == Tokens::REG_DELIM
  reg
end