Class: Sexp::Matcher::Parser
Overview
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), ___) }
Constant Summary collapse
- ALLOWED =
A collection of allowed commands to convert into matchers.
[:t, :m, :atom].freeze
Instance Attribute Summary collapse
-
#tokens ⇒ Object
The stream of tokens to parse.
Instance Method Summary collapse
-
#initialize(s) ⇒ Parser
constructor
Create a new Parser instance on
s
. -
#lex(s) ⇒ Object
Converts
s
into a stream of tokens and adds them totokens
. -
#next_token ⇒ Object
Returns the next token and removes it from the stream or raises if empty.
-
#parse ⇒ Object
Parses tokens and returns a
Matcher
instance. -
#parse_cmd ⇒ Object
Parses a balanced command.
-
#parse_list ⇒ Object
Parses a balanced list of expressions and returns the equivalent matcher.
-
#parse_sexp ⇒ Object
Parses a string into a sexp matcher:.
-
#peek_token ⇒ Object
Returns the next token without removing it from the stream.
Constructor Details
Instance Attribute Details
#tokens ⇒ Object
The stream of tokens to parse. See #lex.
753 754 755 |
# File 'lib/sexp.rb', line 753 def tokens @tokens end |
Instance Method Details
#lex(s) ⇒ Object
Converts s
into a stream of tokens and adds them to tokens
.
766 767 768 |
# File 'lib/sexp.rb', line 766 def lex s tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%) # " end |
#next_token ⇒ Object
Returns the next token and removes it from the stream or raises if empty.
773 774 775 776 |
# File 'lib/sexp.rb', line 773 def next_token raise SyntaxError, "unbalanced input" if tokens.empty? tokens.shift end |
#parse ⇒ Object
Parses tokens and returns a Matcher
instance.
788 789 790 791 |
# File 'lib/sexp.rb', line 788 def parse result = parse_sexp until tokens.empty? result end |
#parse_cmd ⇒ Object
Parses a balanced command. A command is denoted by square brackets and must conform to a whitelisted set of allowed commands (see ALLOWED
).
861 862 863 864 865 866 867 868 869 870 871 872 873 874 |
# File 'lib/sexp.rb', line 861 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 ⇒ Object
Parses a balanced list of expressions and returns the equivalent matcher.
842 843 844 845 846 847 848 849 |
# File 'lib/sexp.rb', line 842 def parse_list result = [] result << parse_sexp while peek_token && peek_token != ")" next_token # pop off ")" Sexp.s(*result) end |
#parse_sexp ⇒ Object
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"
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 |
# File 'lib/sexp.rb', line 808 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 ⇒ Object
Returns the next token without removing it from the stream.
781 782 783 |
# File 'lib/sexp.rb', line 781 def peek_token tokens.first end |