class Mustermann::AST::Parser

Simple, StringScanner based parser. @!visibility private

Attributes

buffer[R]

@!visibility private

pattern[R]

@!visibility private

string[R]

@!visibility private

Public Class Methods

new(pattern: nil, **options) click to toggle source

@!visibility private

# File lib/mustermann/ast/parser.rb, line 47
def initialize(pattern: nil, **options)
  @pattern = pattern
end
on(*chars, &block) click to toggle source

Defines another grammar rule for first character.

@see Mustermann::Rails @see Mustermann::Sinatra @see Mustermann::Template @!visibility private

# File lib/mustermann/ast/parser.rb, line 24
def self.on(*chars, &block)
  chars.each do |char|
    define_method("read %p" % char, &block)
  end
end
parse(string, **options) click to toggle source

@param [String] string to be parsed @return [Mustermann::AST::Node] parse tree for string @!visibility private

# File lib/mustermann/ast/parser.rb, line 14
def self.parse(string, **options)
  new(**options).parse(string)
end
suffix(pattern = /./, after: :node, &block) click to toggle source

Defines another grammar rule for a suffix.

@see Mustermann::Sinatra @!visibility private

# File lib/mustermann/ast/parser.rb, line 34
def self.suffix(pattern = /./, after: :node, &block)
  @suffix ||= []
  @suffix << [pattern, after, block] if block
  @suffix
end

Public Instance Methods

default_node(char) click to toggle source

Create a node for a character we don't have an explicit rule for.

@param [String] char the character @return [Mustermann::AST::Node] the node @!visibility private

# File lib/mustermann/ast/parser.rb, line 78
def default_node(char)
  char == ?/ ? node(:separator, char) : node(:char, char)
end
expect(regexp, char: nil, **options) click to toggle source

Asserts a regular expression matches what's next on the buffer. Will return corresponding MatchData if regexp includes named captures.

@param [Regexp] regexp expected to match @return [String, MatchData] the match @raise [Mustermann::ParseError] if expectation wasn't met @!visibility private

# File lib/mustermann/ast/parser.rb, line 139
def expect(regexp, char: nil, **options)
  scan(regexp) || unexpected(char, **options)
end
min_size(start, stop, node) click to toggle source

sets start on node to start if it's not set to a lower value. sets stop on node to stop if it's not set to a higher value. @return [Mustermann::AST::Node] the node passed as third argument @!visibility private

# File lib/mustermann/ast/parser.rb, line 98
def min_size(start, stop, node)
  stop  ||= start
  start ||= stop
  node.start = start unless node.start and node.start < start
  node.stop  = stop  unless node.stop  and node.stop  > stop
  node
end
node(type, *args, &block) click to toggle source

@example

node(:char, 'x').compile =~ 'x' # => true

@param [Symbol] type node type @return [Mustermann::AST::Node] @!visibility private

# File lib/mustermann/ast/parser.rb, line 66
def node(type, *args, &block)
  type  = Node[type] unless type.respond_to? :new
  start = pos
  node  = block ? type.parse(*args, &block) : type.new(*args)
  min_size(start, pos, node)
end
parse(string) click to toggle source

@param [String] string to be parsed @return [Mustermann::AST::Node] parse tree for string @!visibility private

# File lib/mustermann/ast/parser.rb, line 54
def parse(string)
  @string = string
  @buffer = ::StringScanner.new(string)
  node(:root, string) { read unless eos? }
end
read() click to toggle source

Reads the next element from the buffer. @return [Mustermann::AST::Node] next element @!visibility private

# File lib/mustermann/ast/parser.rb, line 85
def read
  start  = pos
  char   = getch
  method = "read %p" % char
  element= respond_to?(method) ? send(method, char) : default_node(char)
  min_size(start, pos, element)
  read_suffix(element)
end
read_args(key_separator, close, separator: ?,, symbol_keys: true, **options) click to toggle source

Reads an argument string of the format arg1,args2,key:value

@!visibility private

# File lib/mustermann/ast/parser.rb, line 170
def read_args(key_separator, close, separator: ?,, symbol_keys: true, **options)
  list, map = [], {}
  while buffer.peek(1) != close
    scan(separator)
    entries = read_list(close, separator, separator: key_separator, **options)
    case entries.size
    when 1 then list += entries
    when 2 then map[symbol_keys ? entries.first.to_sym : entries.first] = entries.last
    else        unexpected(key_separator)
    end
    buffer.pos -= 1
  end
  expect(close)
  [list, map]
end
read_brackets(open, close, char: nil, escape: ?\\, quote: false, **options) click to toggle source

Allows to read a string inside brackets. It does not expect the string to start with an opening bracket.

@example

buffer.string = "fo<o>>ba<r>"
read_brackets(?<, ?>) # => "fo<o>"
buffer.rest # => "ba<r>"

@!visibility private

# File lib/mustermann/ast/parser.rb, line 152
def read_brackets(open, close, char: nil, escape: ?\\, quote: false, **options)
  result = String.new
  escape = false if escape.nil?
  while current = getch
    case current
    when close  then return result
    when open   then result << open   << read_brackets(open, close) << close
    when escape then result << escape << getch
    else result << current
    end
  end
  unexpected(char, **options)
end
read_escaped(close, escape: ?\\, **options) click to toggle source

Read a string until a terminating character, ignoring escaped versions of said character.

@!visibility private

# File lib/mustermann/ast/parser.rb, line 208
def read_escaped(close, escape: ?\\, **options)
  result = String.new
  while current = getch
    case current
    when close  then return result
    when escape then result << getch
    else result << current
    end
  end
  unexpected(current, **options)
end
read_list(*close, separator: ?,, escape: ?\\, quotes: [?", ?'], ignore: " ", **options) click to toggle source

Reads a separated list with the ability to quote, escape and add spaces.

@!visibility private

# File lib/mustermann/ast/parser.rb, line 189
def read_list(*close, separator: ?,, escape: ?\\, quotes: [?", ?'], ignore: " ", **options)
  result = []
  while current = getch
    element = result.empty? ? result : result.last
    case current
    when *close    then return result
    when ignore    then nil # do nothing
    when separator then result  << String.new
    when escape    then element << getch
    when *quotes   then element << read_escaped(current, escape: escape)
    else element << current
    end
  end
  unexpected(current, **options)
end
read_suffix(element) click to toggle source

Checks for a potential suffix on the buffer. @param [Mustermann::AST::Node] element node without suffix @return [Mustermann::AST::Node] node with suffix @!visibility private

# File lib/mustermann/ast/parser.rb, line 110
def read_suffix(element)
  self.class.suffix.inject(element) do |ele, (regexp, after, callback)|
    next ele unless ele.is_a?(after) and payload = scan(regexp)
    content = instance_exec(payload, ele, &callback)
    min_size(element.start, pos, content)
  end
end
scan(regexp) click to toggle source

Wrapper around {StringScanner#scan} that turns strings into escaped regular expressions and returns a MatchData if the regexp has any named captures.

@param [Regexp, String] regexp @see StringScanner#scan @return [String, MatchData, nil] @!visibility private

# File lib/mustermann/ast/parser.rb, line 126
def scan(regexp)
  regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
  string = buffer.scan(regexp)
  regexp.names.any? ? regexp.match(string) : string
end
unexpected(char = nil, exception: ParseError) click to toggle source

Helper for raising an exception for an unexpected character. Will read character from buffer if buffer is passed in.

@param [String, nil] char the unexpected character @raise [Mustermann::ParseError, Exception] @!visibility private

# File lib/mustermann/ast/parser.rb, line 226
def unexpected(char = nil, exception: ParseError)
  char ||= getch
  char = "space" if char == " "
  raise exception, "unexpected #{char || "end of string"} while parsing #{string.inspect}"
end