Class: DMark::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/d-mark/parser.rb

Defined Under Namespace

Classes: ElementNode, ParserError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input) ⇒ Parser

Returns a new instance of Parser.



53
54
55
56
57
58
59
60
# File 'lib/d-mark/parser.rb', line 53

def initialize(input)
  @input = input
  @input_chars = @input.chars

  @pos = 0
  @col_nr = 0
  @line_nr = 0
end

Instance Attribute Details

#posObject (readonly)

Returns the value of attribute pos.



51
52
53
# File 'lib/d-mark/parser.rb', line 51

def pos
  @pos
end

Instance Method Details

#advanceObject



87
88
89
90
91
92
93
94
95
# File 'lib/d-mark/parser.rb', line 87

def advance
  if !eof? && @input_chars[@pos] == "\n"
    @line_nr += 1
    @col_nr = 0
  end

  @pos += 1
  @col_nr += 1
end

#detect_indentationObject



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/d-mark/parser.rb', line 207

def detect_indentation
  indentation_chars = 0
  pos = @pos

  loop do
    case peek_char(pos)
    when ' '
      pos += 1
      indentation_chars += 1
    else
      break
    end
  end

  indentation_chars / 2
end

#eof?(pos = @pos) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
# File 'lib/d-mark/parser.rb', line 83

def eof?(pos = @pos)
  pos >= @input_chars.size
end

#parseObject



62
63
64
65
66
67
68
69
70
71
# File 'lib/d-mark/parser.rb', line 62

def parse
  res = []

  loop do
    break if eof?
    res << read_block_with_children
  end

  res
end

#peek_char(pos = @pos) ⇒ Object



75
76
77
78
79
80
81
# File 'lib/d-mark/parser.rb', line 75

def peek_char(pos = @pos)
  if eof?
    nil
  else
    @input_chars[pos]
  end
end

#raise_parse_error(msg) ⇒ Object

Raises:



456
457
458
# File 'lib/d-mark/parser.rb', line 456

def raise_parse_error(msg)
  raise ParserError.new(@line_nr, @col_nr, msg)
end

#read_attribute_keyObject



353
354
355
# File 'lib/d-mark/parser.rb', line 353

def read_attribute_key
  read_identifier
end

#read_attribute_valueObject



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/d-mark/parser.rb', line 357

def read_attribute_value
  res = ''

  is_escaping = false
  loop do
    char = peek_char

    if is_escaping
      case char
      when nil, "\n"
        break
      else
        advance
        res << char
        is_escaping = false
      end
    else
      case char
      when nil, "\n", ']', ','
        break
      when '%'
        advance
        is_escaping = true
      else
        advance
        res << char
      end
    end
  end

  res.to_s
end

#read_attributesObject



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/d-mark/parser.rb', line 321

def read_attributes
  read_char('[')

  res = {}

  at_start = true
  loop do
    char = peek_char
    case char
    when ']'
      advance
      break
    else
      read_char(',') unless at_start

      key = read_attribute_key
      if peek_char == '='
        read_char('=')
        value = read_attribute_value
      else
        value = key
      end

      res[key] = value

      at_start = false
    end
  end

  res
end

#read_block_with_children(indentation = 0) ⇒ Object



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
# File 'lib/d-mark/parser.rb', line 109

def read_block_with_children(indentation = 0)
  res = read_single_block

  pending_blanks = 0
  until eof?
    blank_pos = try_read_blank_line
    if blank_pos
      @pos = blank_pos
      @line_nr += 1
      @col_nr = 0
      pending_blanks += 1
    else
      sub_indentation = detect_indentation
      break if sub_indentation < indentation + 1

      read_indentation(indentation + 1)
      if try_read_block_start
        res.children << read_block_with_children(indentation + 1)
      else
        res.children << "\n" unless res.children.empty?
        pending_blanks.times { res.children << "\n" }
        pending_blanks = 0

        res.children.concat(read_inline_content)
        read_end_of_inline_content
      end
    end
  end

  res
end

#read_char(c) ⇒ Object



97
98
99
100
101
102
103
104
105
# File 'lib/d-mark/parser.rb', line 97

def read_char(c)
  char = peek_char
  if char != c
    raise_parse_error("expected #{c.inspect}, but got #{char.nil? ? 'EOF' : char.inspect}")
  else
    advance
    char
  end
end

#read_end_of_inline_contentObject



275
276
277
278
279
280
281
282
283
284
285
# File 'lib/d-mark/parser.rb', line 275

def read_end_of_inline_content
  char = peek_char
  case char
  when "\n", nil
    advance
  when '}'
    raise_parse_error('unexpected } -- try escaping it as "%}"')
  else
    raise_parse_error('unexpected content')
  end
end

#read_identifierObject



287
288
289
290
291
# File 'lib/d-mark/parser.rb', line 287

def read_identifier
  a = read_identifier_head
  b = read_identifier_tail
  "#{a}#{b}"
end

#read_identifier_headObject



293
294
295
296
297
298
299
300
301
302
# File 'lib/d-mark/parser.rb', line 293

def read_identifier_head
  char = peek_char
  case char
  when 'a'..'z'
    advance
    char
  else
    raise_parse_error("expected an identifier, but got #{char.inspect}")
  end
end

#read_identifier_tailObject



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/d-mark/parser.rb', line 304

def read_identifier_tail
  res = ''

  loop do
    char = peek_char
    case char
    when 'a'..'z', '-', '0'..'9'
      advance
      res << char
    else
      break
    end
  end

  res.to_s
end

#read_indentation(indentation) ⇒ Object



244
245
246
247
248
249
# File 'lib/d-mark/parser.rb', line 244

def read_indentation(indentation)
  indentation.times do
    read_char(' ')
    read_char(' ')
  end
end

#read_inline_contentObject



390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/d-mark/parser.rb', line 390

def read_inline_content
  res = []

  loop do
    char = peek_char
    case char
    when "\n", nil
      break
    when '}'
      break
    when '%'
      advance
      res << read_percent_body
    else
      res << read_string
    end
  end

  res
end

#read_inline_elementObject



441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/d-mark/parser.rb', line 441

def read_inline_element
  name = read_identifier
  attributes =
    if peek_char == '['
      read_attributes
    else
      {}
    end
  read_char('{')
  contents = read_inline_content
  read_char('}')

  ElementNode.new(name, attributes, contents)
end

#read_percent_bodyObject



428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/d-mark/parser.rb', line 428

def read_percent_body
  char = peek_char
  case char
  when '%', '}'
    advance
    char.to_s
  when nil, "\n"
    raise_parse_error("expected something after %")
  else
    read_inline_element
  end
end

#read_single_blockObject



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/d-mark/parser.rb', line 251

def read_single_block
  identifier = read_identifier

  attributes =
    if peek_char == '['
      read_attributes
    else
      {}
    end

  read_char('.')

  case peek_char
  when nil, "\n"
    advance
    ElementNode.new(identifier, attributes, [])
  else
    read_char(' ')
    content = read_inline_content
    read_end_of_inline_content
    ElementNode.new(identifier, attributes, content)
  end
end

#read_stringObject



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/d-mark/parser.rb', line 411

def read_string
  res = ''

  loop do
    char = peek_char
    case char
    when nil, "\n", '%', '}'
      break
    else
      advance
      res << char
    end
  end

  res.to_s
end

#read_until_eol_or_eofObject



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/d-mark/parser.rb', line 224

def read_until_eol_or_eof
  res = ''

  loop do
    char = peek_char
    case char
    when "\n"
      advance
      break
    when nil
      break
    else
      advance
      res << char
    end
  end

  res.to_s
end

#try_read_blank_lineObject



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/d-mark/parser.rb', line 141

def try_read_blank_line
  pos = @pos

  loop do
    case peek_char(pos)
    when ' '
      pos += 1
    when nil
      break pos + 1
    when "\n"
      break pos + 1
    else
      break nil
    end
  end
end

#try_read_block_startObject

FIXME: ugly and duplicated



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/d-mark/parser.rb', line 159

def try_read_block_start
  old_pos = @pos

  success =
    if try_read_identifier_head
      if try_read_identifier_tail
        case peek_char
        when '['
          true
        when '.'
          advance
          [' ', "\n", nil].include?(peek_char)
        end
      end
    end

  @pos = old_pos
  success
end

#try_read_identifier_headObject

FIXME: ugly and duplicated



180
181
182
183
184
185
186
187
# File 'lib/d-mark/parser.rb', line 180

def try_read_identifier_head
  char = peek_char
  case char
  when 'a'..'z'
    advance
    char
  end
end

#try_read_identifier_tailObject

FIXME: ugly and duplicated



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/d-mark/parser.rb', line 190

def try_read_identifier_tail
  res = ''

  loop do
    char = peek_char
    case char
    when 'a'..'z', '-', '0'..'9'
      advance
      res << char
    else
      break
    end
  end

  res.to_s
end