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



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

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

Instance Attribute Details

#tokensObject

The stream of tokens to parse. See #lex.



756
757
758
# File 'lib/sexp.rb', line 756

def tokens
  @tokens
end

Instance Method Details

#lex(s) ⇒ Object

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



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

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)


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

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

#parseObject

Parses tokens and returns a Matcher instance.



791
792
793
794
# File 'lib/sexp.rb', line 791

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)


864
865
866
867
868
869
870
871
872
873
874
875
876
877
# File 'lib/sexp.rb', line 864

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.



845
846
847
848
849
850
851
852
# File 'lib/sexp.rb', line 845

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"


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
837
838
839
# File 'lib/sexp.rb', line 811

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.



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

def peek_token
  tokens.first
end