class Fop::Parser

Constants

DIGIT
Error
REGEX_LAZY_WILDCARD
REGEX_MATCHES
REGEX_START
TR_REGEX

OPS_WITH_OPTIONAL_ARGS = [Tokenizer::OP_REPLACE]

Attributes

errors[R]

Public Class Methods

new(src, debug: false) click to toggle source
# File lib/fop/parser.rb, line 26
def initialize(src, debug: false)
  @tokenizer = Tokenizer.new(src)
  @errors = []
end

Public Instance Methods

build_regex!(wildcard, token, src = token.val) click to toggle source
# File lib/fop/parser.rb, line 166
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
parse() click to toggle source
# 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) click to toggle source
# File lib/fop/parser.rb, line 63
def parse_exp!(wildcard = false)
  exp = Nodes::Expression.new(wildcard)
  parse_exp_match! exp
  parse_exp_operator! exp
  if exp.operator_token
    parse_exp_arg! exp
  end
  return exp
end
parse_exp_arg!(exp) click to toggle source
# File lib/fop/parser.rb, line 110
def parse_exp_arg!(exp)
  @tokenizer.escape.whitespace = false
  @tokenizer.escape.whitespace_sep = false
  @tokenizer.escape.operators = true
  @tokenizer.escape.regex = true
  @tokenizer.escape.regex_capture = false if exp.regex_match

  arg = Nodes::Arg.new([], false)
  exp.args = []
  found_close, eof = false, false
  until found_close or eof
    t = @tokenizer.next
    case t.type
    when Tokens::TEXT
      arg.segments << t.val
    when Tokens::REG_CAPTURE
      arg.has_captures = true
      arg.segments << 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::WHITESPACE_SEP
      if arg.segments.any?
        exp.args << arg
        arg = Nodes::Arg.new([])
      end
    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
  exp.args << arg if arg.segments.any?

  #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) click to toggle source
# File lib/fop/parser.rb, line 73
def parse_exp_match!(exp)
  @tokenizer.escape.whitespace = false
  @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) click to toggle source
# File lib/fop/parser.rb, line 96
def parse_exp_operator!(exp)
  @tokenizer.escape.whitespace = false
  @tokenizer.escape.operators = false
  t = @tokenizer.next
  case t.type
  when Tokens::EXP_CLOSE
    # no op
  when Tokens::OPERATOR, Tokens::TEXT
    exp.operator_token = t
  else
    errors << Error.new(:syntax, t, "Unexpected #{t.type}; expected an operator")
  end
end
parse_regex!(wildcard) click to toggle source
# File lib/fop/parser.rb, line 151
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