class Sexp::Matcher::Parser

Converts from a lispy string to Sexp matchers in a safe manner.

"(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }

Constants

ALLOWED

A collection of allowed commands to convert into matchers.

Attributes

tokens[RW]

The stream of tokens to parse. See lex.

Public Class Methods

new(s) click to toggle source

Create a new Parser instance on s

# File lib/sexp_matcher.rb, line 409
def initialize s
  self.tokens = lex s
end

Public Instance Methods

lex(s) click to toggle source

Converts s into a stream of tokens and adds them to tokens.

# File lib/sexp_matcher.rb, line 416
def lex s
  s.scan %r%[()\[\]]|\"[^"]*\"|/[^/]*/|:?[\w?!=~-]+%
end
next_token() click to toggle source

Returns the next token and removes it from the stream or raises if empty.

# File lib/sexp_matcher.rb, line 423
def next_token
  raise SyntaxError, "unbalanced input" if tokens.empty?
  tokens.shift
end
parse() click to toggle source

Parses tokens and returns a Matcher instance.

# File lib/sexp_matcher.rb, line 438
def parse
  result = parse_sexp until tokens.empty?
  result
end
parse_cmd() click to toggle source

Parses a balanced command. A command is denoted by square brackets and must conform to a whitelisted set of allowed commands (see ALLOWED).

# File lib/sexp_matcher.rb, line 515
def parse_cmd
  args = []
  args << parse_sexp while peek_token && peek_token != "]"
  next_token # pop off "]"

  cmd = args.shift
  args = Sexp.q(*args)

  raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd

  result = Sexp.send cmd, *args

  result
end
parse_list() click to toggle source

Parses a balanced list of expressions and returns the equivalent matcher.

# File lib/sexp_matcher.rb, line 496
def parse_list
  result = []

  result << parse_sexp while peek_token && peek_token != ")"
  next_token # pop off ")"

  Sexp.q(*result)
end
parse_sexp() click to toggle source

Parses a string into a sexp matcher:

SEXP : "(" SEXP:args* ")"          => Sexp.q(*args)
     | "[" CMD:cmd sexp:args* "]"  => Sexp.cmd(*args)
     | "nil"                       => nil
     | /\d+/:n                     => n.to_i
     | "___"                       => Sexp.___
     | "_"                         => Sexp._
     | /^\/(.*)\/$/:re             => Regexp.new re[0]
     | /^"(.*)"$/:s                => String.new s[0]
     | UP_NAME:name                => Object.const_get name
     | NAME:name                   => name.to_sym

UP_NAME: /[A-Z]w*/

NAME : /:?[\w?!=~-]+/
 CMD : t | k | m | atom | not? | - | any | child | include
# File lib/sexp_matcher.rb, line 460
def parse_sexp
  token = next_token

  case token
  when "(" then
    parse_list
  when "[" then
    parse_cmd
  when "nil" then
    nil
  when /^\d+$/ then
    token.to_i
  when "___" then
    Sexp.___
  when "_" then
    Sexp._
  when %r%^/(.*)/$% then
    re = $1
    raise SyntaxError, "Not allowed: /%p/" % [re] unless
      re =~ /\A([\w()|.*+^$]+)\z/
    Regexp.new re
  when /^"(.*)"$/ then
    $1
  when /^([A-Z]\w*)$/ then
    Object.const_get $1
  when /^:?([\w?!=~-]+)$/ then
    $1.to_sym
  else
    raise SyntaxError, "unhandled token: %p" % [token]
  end
end
peek_token() click to toggle source

Returns the next token without removing it from the stream.

# File lib/sexp_matcher.rb, line 431
def peek_token
  tokens.first
end