Class: Tache::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/tache/parser.rb

Constant Summary collapse

ALLOWED =
/[\w\?!\/\.\-]/

Instance Method Summary collapse

Instance Method Details

#parse(source, tags = nil) ⇒ Object

Raises:

  • (ArgumentError)


4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
# File 'lib/tache/parser.rb', line 4

def parse(source, tags = nil)
  return [] if source == ''
  
  tags ||= ['{{', '}}']
  raise ArgumentError, "Invalid tags: '#{tags.join(', ')}'" unless tags.size == 2
  
  state = :text
  index = 0
  start = 0
  finish = 0
  line = 0
  tokens = [['indent']]
  sections = []
  left = tags.first
  right = tags.last
  type = nil
  before = nil
  indent = ''
  standalone = nil
  within = nil
  
  while char = source[index]
    case state
    when :text
      case char
      when left[0]
        if source[index, left.size] == left
          # TODO: Handle indents better.
          if start == line && source[start...index] =~ /\A([\ \t]*)\Z/
            indent = $1
            standalone = true
          else
            tokens << ['text', source[start...index]] if index > start
            indent = ''
            standalone = false
          end
          before = index
          index += left.size - 1
          start = index + 1
          state = :seek
        end
      when "\r"
      when "\n"
        feed = (source[index - 1] == "\r" ? "\r\n" : "\n")
        tokens << ['text', source[start..index - feed.size] << feed]
        tokens << ['indent'] unless index + 1 == source.size
        start = index + 1
        line = index + 1
      end
    when :seek
      case char
      when '#', '^', '/', '&', '>', '{', '<', '$'
        start = index + 1
        state = :pre
        type = char
      when '!', '='
        start = index + 1
        state = :special
        type = char
      when ALLOWED
        state = :name
        type = 'name'
        start = index
      end
    when :pre
      case char
      when ALLOWED
        state = :name
        start = index
      when ' '
        # Valid space.
      else
        error "Invalid character in tag name: #{char.inspect}", source, index
      end
    when :name, :special, :post
      case char
      when right[0]
        if source[index, right.size] == right
          content = source[start...(state == :post ? finish : index)]

          if type == '{' && source[index, 1 + right.size] == ('}' + right)
            index += 1
            type = '&'
          end

          index += right.size - 1

          tail = ''
          if standalone
            carriage = source[index + 1] == "\r"
            index += 1 if carriage
            if source[index + 1] == "\n"
              index += 1
              line = index + 1
              tail = carriage ? "\r\n" : "\n"
            end
          end
   
          within = sections.last && sections.last[0]
   
          case type
          when 'name', '&'
            if within == '<'
              error "Illegal tag inside a partial layout tag", source, before
            end
            tokens << [type, content, indent, tail]
          when '#', '^', '<', '$'
            if within == '<' && type != '$'
              error "Illegal tag inside a partial layout tag", source, before
            end
            nested = []
            tokens << ['text', indent] if !indent.empty? && tail.empty?
            tokens << [type, content, nested]
            sections << [type, content, before, index + 1, tokens]
            tokens = nested
          when '>'
            if within == '<'
              error "Illegal tag inside a partial layout tag", source, before
            end
            tokens << ['>', content, indent]
          when '!'
          when '='
            if content[-1] == '='
              tags = content[0..-2].strip.split(' ')
              error "Invalid tags '#{tags.join(', ')}'", source, before unless tags.size == 2
              left, right = *tags
            end
          when '/'
            tokens << ['text', indent] if !indent.empty? && tail.empty? && index + 1 != source.size
            opener, name, at, pos, tokens = sections.pop
            error "Closing unopened '#{content}'", source, before unless name
            error "Unclosed section '#{name}'", source, at if name != content
            tokens.last << source[pos...before] + indent << tags if opener == '#'
          end
  
          start = index + 1
          state = :text
          tokens << ['indent'] unless tail.empty? || index + 1 == source.size
        end
      when ALLOWED
        # Valid char.
      when '}'
        if type == '{'
          state = :post
          type = '&'
          finish = index
        end
      when ' ', "\t"
        if state == :name
          state = :post
          finish = index
        end
      else
        unless state == :special
          error "Invalid character in tag name: #{char.inspect}", source, index
        end
      end
    end
    index += 1
  end
  
  unless sections.empty?
    opener, name, at = sections.pop
    error "Unclosed section '#{name}'", source, at
  end
  
  case state
  when :text
    tokens << ['text', source[start...index]] if start < index
  when :name
    error "Unclosed tag", source, before
  end
  
  tokens
end