Class: Sexp::Matcher::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/sexp.rb

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

Instance Method Summary collapse

Constructor Details

#initialize(s) ⇒ Parser

Create a new Parser instance on s



752
753
754
755
# File 'lib/sexp.rb', line 752

def initialize s
  self.tokens = []
  lex s
end

Instance Attribute Details

#tokensObject

The stream of tokens to parse. See #lex.



747
748
749
# File 'lib/sexp.rb', line 747

def tokens
  @tokens
end

Instance Method Details

#lex(s) ⇒ Object

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



760
761
762
# File 'lib/sexp.rb', line 760

def lex s
  tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%)
end

#next_tokenObject

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

Raises:

  • (SyntaxError)


767
768
769
770
# File 'lib/sexp.rb', line 767

def next_token
  raise SyntaxError, "unbalanced input" if tokens.empty?
  tokens.shift
end

#parseObject

Parses tokens and returns a Matcher instance.



782
783
784
785
# File 'lib/sexp.rb', line 782

def parse
  result = parse_sexp until tokens.empty?
  result
end

#parse_cmdObject

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

Raises:

  • (SyntaxError)


855
856
857
858
859
860
861
862
863
864
865
866
867
868
# 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_listObject

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



836
837
838
839
840
841
842
843
# 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_sexpObject

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"


802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
# 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_tokenObject

Returns the next token without removing it from the stream.



775
776
777
# File 'lib/sexp.rb', line 775

def peek_token
  tokens.first
end