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.rb, line 752
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.rb, line 760
def lex s
  tokens.concat 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.rb, line 767
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.rb, line 782
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.rb, line 855
def parse_cmd
  args = []
  args << parse_sexp while peek_token && peek_token != "]"
  next_token # pop off "]"

  cmd = args.shift
  args = Sexp.s(*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.rb, line 836
def parse_list
  result = []

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

  Sexp.s(*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]
     | NAME:name                   => name.to_sym
NAME : /\w+/
 CMD : "t" | "m" | "atom"
# File lib/sexp.rb, line 802
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 /^\w+$/ then
    token.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.rb, line 775
def peek_token
  tokens.first
end