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



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

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

Instance Attribute Details

#tokensObject

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_tokenObject

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

Raises:

  • (SyntaxError)


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

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

#parseObject

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_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)


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_listObject

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_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"


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_tokenObject

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