class Plate::Lexer

Attributes

current_indent[RW]
indent_stack[RW]
tokens[RW]

Public Instance Methods

dedent() click to toggle source
# File lib/plate/lexer.rb, line 7
def dedent
  indent_stack.pop
  self.current_indent = indent_stack.last || 0
  tokens << [:DEDENT, current_indent]
end
tokenize(code) click to toggle source
# File lib/plate/lexer.rb, line 13
def tokenize(code)
  code.chomp!
  code.gsub!(/^\s*\/\/.*\n/, '')
  self.tokens = []

  self.current_indent = 0
  self.indent_stack = []

  i = 0
  while i < code.size
    chunk = code[i..-1]

    if i == 0 && front_matter = chunk[/\A---\n(.*?)---/m, 1]
      tokens << [:FRONT_MATTER, front_matter]
      tokens << [:NEWLINE, "\n"]
      i += front_matter.size + 8

    elsif identifier = chunk[/\A([a-z][\w:\/\. \-]*)/, 1]
      if KEYWORDS.include?(identifier)
        tokens << [identifier.upcase.to_sym, identifier]
      elsif identifier =~ /\A\w+\z/
        tokens << [:IDENTIFIER, identifier]
      else
        tokens << [:STRING, identifier]
      end
      i += identifier.size

    elsif klass = chunk[/\A\.([\w\-\.]+)/, 1]
      tokens << [:CLASS, klass]
      i += klass.size + 1

    elsif style = chunk[/\A@([\w\-]+)/, 1]
      tokens << [:STYLE, style]
      i += style.size + 1

    elsif script = chunk[/\A\$([\w\-]+)/, 1]
      tokens << [:SCRIPT, script]
      i += script.size + 1

    elsif value = chunk[/\A:([^\n]*)/, 1]
      tokens << [":", ":"]
      tokens << [(value.strip =~ /\A[a-z]\w*\z/ ? :IDENTIFIER : :STRING), value.strip]
      i += value.size + 1

    elsif attrs = chunk[/\A(\s*\{.*)\}/, 1]
      tokens << ["{", "{"]
      attrs.gsub(/\A.*{/, '').split(/,\s*/).each_with_index do |attr, i|
        unless i.zero?
          tokens << [",", ","]
        end
        tokens << [:ATTRIBUTE, attr]
      end
      tokens << ["}", "}"]
      tokens << [:TERMINATOR, " "]
      i += attrs.size + 1

    elsif header = chunk[/\A(#+\s*)/, 1]
      tokens << [:HEADER, header.strip]
      i += header.size

    elsif highlight = chunk[/\A(```.+?```)/m, 1]
      tokens << ["```", "```"]
      tokens << [:HIGHLIGHT, highlight.gsub(/\A```\n/, '').gsub(/\n.*```\z/, '')]
      tokens << ["```", "```"]
      i += highlight.size

    elsif string = chunk[/\A((?:[^\n\[\]\(\)!\\]|\\.)(?:[^\n\[\]\(\)\\]|\\.)*)/, 1]
      tokens << [:STRING, string]
      i += string.size

    elsif indent = chunk[/\A\n( *)/m, 1]
      if indent.size.odd? || 
        (indent.size > current_indent && indent.size != current_indent + 2)
        raise "Bad indent, #{current_indent} -> #{indent.size}: #{chunk[/\A\n(.*)\n/, 1]}"
      end

      if indent.size > current_indent
        self.current_indent = indent.size
        indent_stack.push(current_indent)
        tokens << [:INDENT, indent.size]
      elsif indent.size == current_indent
        tokens << [:NEWLINE, "\n"]
      elsif indent.size < current_indent
        begin
          dedent
        end while indent.size < current_indent
        tokens << [:NEWLINE, "\n"]
      end
      i += indent.size + 1

    #elsif chunk.match(/\A\n/)
    #  tokens << [:NEWLINE, "\n"]
    #  i += 1

    elsif open = chunk[/\A(!?\[)/, 1]
      tokens << [:TERMINATOR, " "]
      tokens << [open, open]
      i += open.size 

    elsif close = chunk[/\A(\))/, 1]
      tokens << [close, close]
      tokens << [:TERMINATOR, " "]
      i += 1

    #elsif chunk.match(/\A\s/)
    #  tokens << [:TERMINATOR, " "]
    #  tokens << [:STRING, " "]
    #  i += 1

    else
      value = chunk[0, 1]
      tokens << [value, value]
      i += 1
    end
  end

  if current_indent > 0
    begin
      dedent
    end while current_indent > 0
    tokens << [:NEWLINE, "\n"]
  end
  tokens
end