lib/vendor/sexpistol/sexpistol/sexpistol_parser.rb in qcmd-0.1.13 vs lib/vendor/sexpistol/sexpistol/sexpistol_parser.rb in qcmd-0.1.14
- old
+ new
@@ -1,14 +1,18 @@
require 'strscan'
+class ParseException < Exception
+end
+
class SexpistolParser < StringScanner
def initialize(string)
# step through string counting closing parens, exclude parens in string literals
in_string_literal = false
escape_char = false
paren_count = 0
+
string.bytes.each do |byte|
if escape_char
escape_char = false
next
end
@@ -29,35 +33,35 @@
in_string_literal = !in_string_literal
end
end
if paren_count > 0
- raise Exception, "Missing closing parentheses"
+ raise ParseException.new("Missing closing parentheses")
elsif paren_count < 0
- raise Exception, "Missing opening parentheses"
+ raise ParseException.new("Missing opening parentheses")
end
super(string)
end
def parse
exp = []
while true
case fetch_token
- when '('
- exp << parse
- when ')'
- break
- when :"'"
- case fetch_token
- when '(' then exp << [:quote].concat([parse])
- else exp << [:quote, @token]
- end
- when String, Fixnum, Float, Symbol
- exp << @token
- when nil
- break
+ when '('
+ exp << parse
+ when ')'
+ break
+ when :"'"
+ case fetch_token
+ when '(' then exp << [:quote].concat([parse])
+ else exp << [:quote, @token]
+ end
+ when String, Fixnum, Float, Symbol
+ exp << @token
+ when nil
+ break
end
end
exp
end
@@ -71,14 +75,14 @@
matched
# Match a string literal
elsif scan(/"([^"\\]|\\.)*"/)
eval(matched)
# Match a float literal
- elsif scan(/[\-\+]? [0-9]+ ((e[0-9]+) | (\.[0-9]+(e[0-9]+)?))(\)| )(\s|$)/x)
+ elsif scan(/[\-\+]? [0-9]+ ((e[0-9]+) | (\.[0-9]+(e[0-9]+)?))#{ expression_ender }/x)
matched.to_f
# Match an integer literal
- elsif scan(/[\-\+]?[0-9]+(\)| )(\s|$)/x)
+ elsif scan(/[\-\+]?[0-9]+#{ expression_ender }/)
matched.to_i
# Match a comma (for comma quoting)
elsif scan(/'/)
matched.to_sym
# Match a symbol
@@ -87,8 +91,16 @@
# If we've gotten here then we have an invalid token
else
near = scan %r{.{0,20}}
raise "Invalid character at position #{pos} near '#{near}'."
end
+ end
+
+ def expression_ender
+ # end Fixnum and Float matchers with a non-grouping positive lookahead
+ # assertion that matches a closing paren, whitespace, or string end. The
+ # positive lookahead (?=...) ensures the string scanner includes the ending
+ # character in next fetch_token call.
+ '(?=(?:\)|\s|$))'
end
end