File: heredoc.rb

package info (click to toggle)
ruby-parslet 2.0.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,264 kB
  • sloc: ruby: 6,157; sh: 6; javascript: 3; makefile: 3
file content (126 lines) | stat: -rw-r--r-- 2,914 bytes parent folder | download | duplicates (2)
1
2
3
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
# An example that demonstrates how you would parse Ruby heredocs.
# This is just a hack, however it toys around with some concepts that we 
# just might use later on. 

$:.unshift File.dirname(__FILE__) + "/../lib"

require 'pp'
require 'parslet'
require 'parslet/convenience'

class Parslet::Atoms::Base
  def space
    self >> Parslet.str(' ').repeat
  end
  
  def bind(name)
    BoundParslet.new(self, name)
  end
  
  def matches(name)
    BindCompare.new(self, name)
  end
end

class BoundParslet < Parslet::Atoms::Base
  attr_reader :parslet, :name
  def initialize(parslet, name)
    super()
    @parslet, @name = parslet, name
  end
  
  def try(source, context)
    parslet.try(source, context).tap { |result| 
      set_binding(context, name, 
        flatten(result.result))
    }
  end
  
  def set_binding(context, name, value)
    b = context.instance_variable_get('@bindings') || {}
    b.store name, value
    p b
    context.instance_variable_set('@bindings', b)
  end
    
  def to_s_inner(prec)
    parslet.to_s(prec)
  end
end

class BindCompare < Parslet::Atoms::Base
  attr_reader :parslet, :name
  def initialize(parslet, name)
    super()
    @parslet, @name = parslet, name
  end
  
  def try(source, context)
    parslet.try(source, context).tap { |result| 
      unless result.error?
        value = flatten(result.result)
      
        p [value, bound_value(context, name), value == bound_value(context, name)]
        unless value == bound_value(context, name)
          p :error_return
          return error(source, "Bound value doesn't match.")
        end
      end
    }
  end
  
  def bound_value(context, name)
    b = context.instance_variable_get('@bindings') || {}
    b[name]
  end
    
  def to_s_inner(prec)
    parslet.to_s(prec)
  end
end

class HereDocs < Parslet::Parser
  root(:document)
  
  # a document of heredocs
  rule(:document) { heredoc.repeat }
  # a whole heredoc led by 'a' or 'b'
  rule(:heredoc)  { 
    space? >> 
      intro.as(:intro) >> 
      backticks >> 
      tag.bind(:tag) >> doc.as(:doc) | 
    space? >> eol
  }
  # essentially just 'a' or 'b'
  rule(:intro) { (str('a') | str('b')).space }
  # the tag that delimits the heredoc
  rule(:tag) { match['A-Z'].repeat(1) }
  # the doc itself, ends when tag is found at start of line
  rule(:doc) { gobble_eol >> doc_line }
  # a doc_line is either the stop tag followed by nothing 
  # or just any kind of line.
  rule(:doc_line) { 
    (end_tag.absent? >> gobble_eol).repeat >> end_tag
  }
  rule(:end_tag) { tag.matches(:tag) >> space? >> eol }
  # eats anything until an end of line is found
  rule(:gobble_eol) { (eol.absent? >> any).repeat >> eol }

  rule(:eol) { match['\n\r'].repeat(1) }
  rule(:space?) { str(' ').repeat }
  rule(:backticks) { str('<<').space }
end

code = %q(
  a <<HERE
  b <<THERE
HERE
  b <<THEN
  a <<HERE
THE
HERE
THEN
)

pp HereDocs.new.parse_with_debug(code)