Class: Slim::Parser Private

Inherits:
Object
  • Object
show all
Includes:
Temple::Mixins::Options
Defined in:
lib/slim/parser.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Parses Slim code and transforms it to a Temple expression

Defined Under Namespace

Classes: SyntaxError

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Parser

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Parser.



29
30
31
32
# File 'lib/slim/parser.rb', line 29

def initialize(options = {})
  super
  @tab = ' ' * @options[:tabsize]
end

Instance Method Details

#compile(str) ⇒ Array

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Compile string to Temple expression

Parameters:

  • str (String)

    Slim code

Returns:

  • (Array)

    Temple expression representing the code



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/slim/parser.rb', line 38

def compile(str)
  lineno = 0
  result = [:multi]

  # Since you can indent however you like in Slim, we need to keep a list
  # of how deeply indented you are. For instance, in a template like this:
  #
  #   ! doctype     # 0 spaces
  #   html          # 0 spaces
  #    head         # 1 space
  #       title     # 4 spaces
  #
  # indents will then contain [0, 1, 4] (when it's processing the last line.)
  #
  # We uses this information to figure out how many steps we must "jump"
  # out when we see an de-indented line.
  indents = [0]

  # Whenever we want to output something, we'll *always* output it to the
  # last stack in this array. So when there's a line that expects
  # indentation, we simply push a new stack onto this array. When it
  # processes the next line, the content will then be outputted into that
  # stack.
  stacks = [result]

  # String buffer used for broken line (Lines ending with \)
  broken_line = nil

  # We have special treatment for text blocks:
  #
  #   |
  #     Hello
  #     World!
  #
  block_indent, in_comment, text_indent = nil, false, nil

  str.each_line do |line|
    lineno += 1

    # Remove the newline at the end
    line.chomp!

    # Handle broken lines
    if broken_line
      if broken_line[-1] == ?\\
        broken_line << "\n" << line
        next
      end
      broken_line = nil
    end

    if line.strip.empty?
      # This happens to be an empty line, so we'll just have to make sure
      # the generated code includes a newline (so the line numbers in the
      # stack trace for an exception matches the ones in the template).
      stacks.last << [:newline]
      next
    end

    # Figure out the indentation. Kinda ugly/slow way to support tabs,
    # but remember that this is only done at parsing time.
    indent = line[/^[ \t]*/].gsub("\t", @tab).size

    # Remove the indentation
    line.lstrip!

    # Handle blocks with multiple lines
    if block_indent
      if indent > block_indent
        # This line happens to be indented deeper (or equal) than the block start character (|, ', `, /).
        # This means that it's a part of the block.

        if !in_comment
          # The indentation of first line of the text block determines the text base indentation.
          newline = text_indent ? "\n" : ''
          text_indent ||= indent

          # The text block lines must be at least indented as deep as the first line.
          offset = indent - text_indent
          syntax_error! 'Unexpected text indentation', line, lineno if offset < 0

          # Generate the additional spaces in front.
          stacks.last << [:slim, :text, newline + (' ' * offset) + line]
        end

        stacks.last << [:newline]
        next
      end

      # It's guaranteed that we're now *not* in a block, because
      # the indent was less than the block start indent.
      block_indent = text_indent = nil
      in_comment = false
    end

    # If there's more stacks than indents, it means that the previous
    # line is expecting this line to be indented.
    expecting_indentation = stacks.size > indents.size

    if indent > indents.last
      # This line was actually indented, so we'll have to check if it was
      # supposed to be indented or not.
      syntax_error! 'Unexpected indentation', line, lineno unless expecting_indentation

      indents << indent
    else
      # This line was *not* indented more than the line before,
      # so we'll just forget about the stack that the previous line pushed.
      stacks.pop if expecting_indentation

      # This line was deindented.
      # Now we're have to go through the all the indents and figure out
      # how many levels we've deindented.
      while indent < indents.last
        indents.pop
        stacks.pop
      end

      # This line's indentation happens lie "between" two other line's
      # indentation:
      #
      #   hello
      #       world
      #     this      # <- This should not be possible!
      syntax_error! 'Malformed indentation', line, lineno if indents.last < indent
    end

    case line[0]
    when ?|, ?', ?/
      # Found a block.

      # We're now expecting the next line to be indented, so we'll need
      # to push a block to the stack.
      block = [:multi]
      stacks.last << (line[0] == ?' ? [:multi, block, [:slim, :text, ' ']] : block)
      stacks << block
      block_indent = indent

      in_comment = line[0] == ?/
      line.slice!(0)
      if !in_comment && !line.strip.empty?
        block << [:slim, :text, line.sub(/^( )/, '')]
        text_indent = block_indent + ($1 ? 2 : 1)
      end
    when ?-
      # Found a code block.
      # We expect the line to be broken or the next line to be indented.
      block = [:multi]
      broken_line = line[1..-1].strip
      stacks.last << [:slim, :control, broken_line, block]
      stacks << block
    when ?=
      # Found an output block.
      # We expect the line to be broken or the next line to be indented.
      block = [:multi]
      escape = line[1] != ?=
      broken_line = escape ? line[1..-1].strip : line[2..-1].strip
      stacks.last << [:slim, :output, escape, broken_line, block]
      stacks << block
    when ?!
      # Found a directive (currently only used for doctypes)
      stacks.last << [:slim, :directive, line[1..-1].strip]
    else
      if line =~ /^(\w+):\s*$/
        # Embedded template detected. It is treated as block.
        block = [:slim, :embedded, $1]
        stacks.last << [:newline] << block
        stacks << block
        block_indent = indent
        next
      else
        # Found a HTML tag.
        tag, block, broken_line, text_indent = parse_tag(line, lineno)
        stacks.last << tag
        stacks << block if block
        if text_indent
          block_indent = indent
          text_indent += indent
        end
      end
    end
    stacks.last << [:newline]
  end

  result
end