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), ___) }
A collection of allowed commands to convert into matchers.
The stream of tokens to parse. See lex.
Create a new Parser instance on s
# File lib/sexp.rb, line 752 def initialize s self.tokens = [] lex s end
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
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
Parses tokens and returns a Matcher
instance.
# File lib/sexp.rb, line 782 def parse result = parse_sexp until tokens.empty? result end
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
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
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
Returns the next token without removing it from the stream.
# File lib/sexp.rb, line 775 def peek_token tokens.first end