xdry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +11 -0
  3. data/Rakefile +76 -0
  4. data/VERSION +1 -0
  5. data/bin/xdry +4 -0
  6. data/lib/xdry.rb +18 -0
  7. data/lib/xdry/boxing.rb +212 -0
  8. data/lib/xdry/generators/ctor_from_field.rb +91 -0
  9. data/lib/xdry/generators/dealloc.rb +53 -0
  10. data/lib/xdry/generators/dictionary_coding.rb +129 -0
  11. data/lib/xdry/generators/field_from_property.rb +20 -0
  12. data/lib/xdry/generators/property-from-field.rb +22 -0
  13. data/lib/xdry/generators/storing_constructor.rb +72 -0
  14. data/lib/xdry/generators/synthesize.rb +25 -0
  15. data/lib/xdry/generators_support.rb +42 -0
  16. data/lib/xdry/parsing/driver.rb +106 -0
  17. data/lib/xdry/parsing/model.rb +272 -0
  18. data/lib/xdry/parsing/nodes.rb +260 -0
  19. data/lib/xdry/parsing/parsers.rb +166 -0
  20. data/lib/xdry/parsing/parts/selectors.rb +95 -0
  21. data/lib/xdry/parsing/parts/var_types.rb +66 -0
  22. data/lib/xdry/parsing/pos.rb +75 -0
  23. data/lib/xdry/parsing/scope_stack.rb +68 -0
  24. data/lib/xdry/parsing/scopes.rb +61 -0
  25. data/lib/xdry/parsing/scopes_support.rb +143 -0
  26. data/lib/xdry/patching/emitter.rb +60 -0
  27. data/lib/xdry/patching/insertion_points.rb +209 -0
  28. data/lib/xdry/patching/item_patchers.rb +74 -0
  29. data/lib/xdry/patching/patcher.rb +139 -0
  30. data/lib/xdry/run.rb +227 -0
  31. data/lib/xdry/support/enumerable_additions.rb +35 -0
  32. data/lib/xdry/support/string_additions.rb +27 -0
  33. data/lib/xdry/support/symbol_additions.rb +14 -0
  34. data/site/_config.yml +3 -0
  35. data/site/_example +9 -0
  36. data/site/_layouts/default.html +30 -0
  37. data/site/_plugins/example.rb +16 -0
  38. data/site/_plugins/highlight_unindent.rb +17 -0
  39. data/site/index.md +417 -0
  40. data/site/master.css +94 -0
  41. data/spec/boxing_spec.rb +80 -0
  42. data/spec/ctor_from_field_spec.rb +251 -0
  43. data/spec/dealloc_spec.rb +103 -0
  44. data/spec/dictionary_coding_spec.rb +132 -0
  45. data/spec/field_from_prop_spec.rb +72 -0
  46. data/spec/prop_from_field_spec.rb +39 -0
  47. data/spec/readme_samples_spec.rb +76 -0
  48. data/spec/spec.opts +3 -0
  49. data/spec/spec_helper.rb +53 -0
  50. data/spec/synthesize_spec.rb +94 -0
  51. data/xdry.gemspec +103 -0
  52. metadata +141 -0
@@ -0,0 +1,129 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class DictionaryCoding < Generator
6
+ id "dict-coding"
7
+
8
+ def process_class oclass
9
+ return unless oclass.attributes.any? { |a| a.persistent? }
10
+
11
+ dictionary_var = "dictionary"
12
+
13
+ defines_emitter = Emitter.new
14
+ init_out = Emitter.new
15
+ repr_out = Emitter.new
16
+
17
+ dictionary_var = "dictionary"
18
+
19
+ oclass.attributes.select { |a| a.persistent? }.each do |oattr|
20
+ name, type = oattr.name, oattr.type
21
+ field_name = oattr.field_name
22
+ raw_name = "#{name}Raw"
23
+ capitalized_name = name.capitalized_identifier
24
+ key_const = "#{capitalized_name}Key"
25
+
26
+ type_boxer = Boxing.converter_for type
27
+ if type_boxer.nil?
28
+ puts "Persistence not (yet) supported for type #{type}"
29
+ next
30
+ end
31
+
32
+ defines_emitter << %Q`\#define #{key_const} @"#{capitalized_name}"`
33
+
34
+ init_out << %Q`id #{raw_name} = [#{dictionary_var} objectForKey:#{key_const}];`
35
+ init_out.if "#{raw_name} != nil" do
36
+ unboxed = type_boxer.unbox_retained(init_out, raw_name, name)
37
+ init_out << "#{field_name} = #{unboxed};"
38
+ end
39
+
40
+ boxed = type_boxer.box(repr_out, field_name, name)
41
+ repr_out << %Q`[dictionary setObject:#{boxed} forKey:#{key_const}];`
42
+ end
43
+
44
+ init_code = Emitter.capture do |o|
45
+ o.method "(id)initWithDictionary:(NSDictionary *)#{dictionary_var}" do
46
+ o.if "self = [super init]" do
47
+ end
48
+ o << "return self;"
49
+ end
50
+ end
51
+
52
+ repr_code = Emitter.capture do |o|
53
+ o.method "(NSDictionary *)dictionaryRepresentation" do
54
+ o << "NSMutableDictionary *#{dictionary_var} = [NSMutableDictionary dictionary];"
55
+ o << "return #{dictionary_var};"
56
+ end
57
+ end
58
+
59
+ file_scope = oclass.main_implementation.parent_scope
60
+ define_ip = MultiIP.new(AfterDefineIP.new(file_scope), BeforeImplementationStartIP.new(oclass))
61
+ define_lines = Emitter.capture do |o|
62
+ each_persistent_attr(oclass) do |oattr, capitalized_name, key_const, type_boxer|
63
+ unless file_scope.children.any? { |n| NDefine === n && n.word == key_const }
64
+ o << %Q`\#define #{key_const} @"#{capitalized_name}"`
65
+ end
66
+ end
67
+ end
68
+ define_ip.wrap_with_empty_lines_if_last!
69
+ define_ip.insert @patcher, define_lines
70
+
71
+ MethodPatcher.new(patcher, oclass, 'dictionaryRepresentation', ImplementationStartIP.new(oclass), repr_code) do |omethod|
72
+ impl = omethod.impl
73
+ ip = BeforeReturnIP.new(impl)
74
+
75
+ lines = Emitter.capture do |o|
76
+ each_persistent_attr(oclass) do |oattr, capitalized_name, key_const, type_boxer|
77
+ unless impl.children.any? { |n| NLine === n && n.line =~ /\bsetObject:.*(?:\b#{oattr.name}|\b#{oattr.field_name}\b)/ }
78
+ boxed = type_boxer.box(o, oattr.field_name, oattr.name)
79
+ o << %Q`[dictionary setObject:#{boxed} forKey:#{key_const}];`
80
+ end
81
+ end
82
+ end
83
+
84
+ ip.insert @patcher, lines unless lines.empty?
85
+ end
86
+
87
+ MethodPatcher.new(patcher, oclass, 'initWithDictionary:', ImplementationStartIP.new(oclass), init_code) do |omethod|
88
+ impl = omethod.impl
89
+ ip = InsideConstructorIfSuperIP.new(impl)
90
+ var_name = impl.start_node.selector_def.var_name_after_keyword('initWithDictionary:')
91
+
92
+ lines = Emitter.capture do |o|
93
+ each_persistent_attr(oclass) do |oattr, capitalized_name, key_const, type_boxer|
94
+ unless impl.children.any? { |n| NLine === n && n.line =~ /^(?:self\s*.\s*#{oattr.name}|#{oattr.field_name})\s*=/ }
95
+ raw_name = "#{oattr.name}Raw"
96
+ o << %Q`id #{raw_name} = [#{var_name} objectForKey:#{key_const}];`
97
+ o.if "#{raw_name} != nil" do
98
+ unboxed = type_boxer.unbox_retained(o, raw_name, oattr.name)
99
+ o << "#{oattr.field_name} = #{unboxed};"
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ ip.insert @patcher, lines unless lines.empty?
106
+ end
107
+ end
108
+
109
+ def each_persistent_attr oclass
110
+ oclass.attributes.select { |a| a.persistent? }.each do |oattr|
111
+ name, type = oattr.name, oattr.type
112
+ field_name = oattr.field_name
113
+ capitalized_name = name.capitalized_identifier
114
+ key_const = "#{capitalized_name}Key"
115
+
116
+ type_boxer = Boxing.converter_for type
117
+ if type_boxer.nil?
118
+ puts "Persistence not (yet) supported for type #{type}"
119
+ next
120
+ end
121
+
122
+ yield oattr, capitalized_name, key_const, type_boxer
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,20 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class FieldFromProperty < Generator
6
+ id "field-from-prop"
7
+
8
+ def process_attribute oclass, oattr
9
+ if !oattr.has_field_def? && oattr.type_known? && !oclass.has_method_impl?(oattr.getter_selector)
10
+ lines = [oattr.new_field_def.to_source]
11
+ if oclass.main_interface # && oclass.main_interface.fields_scope
12
+ node = oclass.main_interface.fields_scope.end_node
13
+ patcher.insert_before node.pos, lines, node.indent + INDENT_STEP
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class PropertyFromField < Generator
6
+ id "prop-from-field"
7
+
8
+ def process_attribute oclass, oattr
9
+ if !oattr.has_property_def? && oattr.wants_property? && oattr.type_known?
10
+ pd = oattr.new_property_def
11
+
12
+ ip = BeforeInterfaceEndIP.new(oclass)
13
+ lines = Emitter.capture do |o|
14
+ o << pd.to_source
15
+ end
16
+ ip.insert patcher, [""] + lines + [""]
17
+ end
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,72 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class StoringConstructor < Generator
6
+ id "store-ctor"
7
+
8
+ def process_class oclass
9
+ oclass.methods.each do |omethod|
10
+ case omethod.selector
11
+ when /^initWith/
12
+ process_method oclass, omethod
13
+ end
14
+ end
15
+ end
16
+
17
+ def process_method oclass, omethod
18
+ components = omethod.header_or_impl.selector_def.components
19
+ mapping = {}
20
+ components.each do |comp|
21
+ kw = comp.keyword_without_colon
22
+ oattr = oclass.find_attribute(kw)
23
+ if oattr.nil? and comp == components.first
24
+ stripped_kw = kw.gsub(/^initWith/, '')
25
+ [stripped_kw.downcase, stripped_kw[0..0].downcase + stripped_kw[1..-1]].each do |alt_kw|
26
+ oattr = oclass.find_attribute(alt_kw)
27
+ break unless oattr.nil?
28
+ end
29
+ end
30
+ mapping[kw] = oattr unless oattr.nil?
31
+ end
32
+
33
+ unless mapping.empty?
34
+ new_selector_def = CompoundSelectorDef.new(components.collect do |comp|
35
+ if oattr = mapping[kw = comp.keyword_without_colon]
36
+ arg_name = comp.arg_name
37
+ arg_name = oattr.name if arg_name.empty?
38
+ SelectorComponent.new(comp.keyword, arg_name, comp.type || oattr.type)
39
+ else
40
+ comp
41
+ end
42
+ end)
43
+ method_header = NMethodHeader.new(new_selector_def, omethod.ret_type)
44
+
45
+ init_out = Emitter.new
46
+
47
+ new_selector_def.components.each do |comp|
48
+ if oattr = mapping[kw = comp.keyword_without_colon]
49
+ field_name = oattr.field_name
50
+ arg_name = comp.arg_name
51
+ type = comp.type
52
+ retain_policy = Boxing.retain_policy_of(type)
53
+
54
+ retained = retain_policy.retain(arg_name)
55
+ init_out << "#{field_name} = #{retained};"
56
+ end
57
+ end
58
+
59
+ # out << "#{method_header};"
60
+ # out.block "#{method_header}" do
61
+ # out.if "self = [super init]" do
62
+ # out << init_out
63
+ # end
64
+ # out << "return self;"
65
+ # end
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class Synthesize < Generator
6
+ id "synth"
7
+
8
+ def process_attribute oclass, oattr
9
+ if oattr.has_property_def? && !oattr.has_synthesize? && !oclass.has_method_impl?(oattr.getter_selector)
10
+ synthesize = oattr.new_synthesize
11
+ impl = oclass.main_implementation
12
+ new_lines = [synthesize.to_s]
13
+ if impl.synthesizes.empty?
14
+ pos = impl.start_node.pos
15
+ new_lines = [""] + new_lines + [""]
16
+ else
17
+ pos = impl.synthesizes.sort { |a, b| a.pos.line_no <=> b.pos.line_no }.last.pos
18
+ end
19
+ patcher.insert_after pos, new_lines
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module XDry::Generators
3
+
4
+ ALL = []
5
+
6
+ Emitter = XDry::Emitter
7
+ Boxing = XDry::Boxing
8
+
9
+ class Generator
10
+
11
+ attr_reader :patcher
12
+
13
+ def initialize config, patcher
14
+ @config = config
15
+ @patcher = patcher
16
+ end
17
+
18
+ def verbose?
19
+ @config.verbose
20
+ end
21
+
22
+ def process_class oclass
23
+ oclass.attributes.each do |oattr|
24
+ process_attribute oclass, oattr
25
+ end
26
+ end
27
+
28
+ def process_attribute oclass, oattr
29
+ end
30
+
31
+ def self.id(value=nil)
32
+ @id = value if value
33
+ @id
34
+ end
35
+
36
+ def self.inherited subclass
37
+ ALL << subclass
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,106 @@
1
+ require 'generator'
2
+ require 'stringio'
3
+
4
+ module XDry
5
+
6
+ class ParsingDriver
7
+ attr_accessor :verbose
8
+ attr_reader :markers
9
+
10
+ def initialize oglobal
11
+ @oglobal = oglobal
12
+ @verbose = false
13
+ @markers = []
14
+ end
15
+
16
+ def parse_file file_name
17
+ gen = Generator.new { |g|
18
+ file_ref = FileRef.new(file_name)
19
+ File.open(file_name) do |file|
20
+ new_lines_generator g, file_ref, file
21
+ end
22
+ }
23
+ parse_data_in_file_scope gen
24
+ end
25
+
26
+ def parse_string file_name, source
27
+ gen = Generator.new { |g|
28
+ file_ref = TestFileRef.new(file_name, source)
29
+ new_lines_generator g, file_ref, StringIO.new(source)
30
+ }
31
+ parse_data_in_file_scope gen
32
+ end
33
+
34
+ def parse_fragment file_ref, lines, start_lineno, start_scope
35
+ gen = Generator.new { |g|
36
+ new_lines_from_array_generator g, file_ref, start_lineno, lines
37
+ }
38
+ parse_data_in_scopes gen, start_scope.all_scopes
39
+ end
40
+
41
+ private
42
+
43
+ def parse_data_in_file_scope gen
44
+ parse_data_in_scopes gen, [@oglobal.new_file_scope]
45
+ end
46
+
47
+ def parse_data_in_scopes gen, scopes
48
+ scope_stack = ScopeStack.new(scopes)
49
+ scope_stack.verbose = @verbose
50
+ while gen.next?
51
+ orig_line, line, pos, eol_comments, indent = gen.next
52
+ puts " #{pos} #{orig_line}" if @verbose
53
+ pos.scope_before = scope_stack.current_scope
54
+ scope_stack.parse_line line, eol_comments, indent do |scope, child|
55
+ # child is a Node or a Scope
56
+ if child.is_a? Node
57
+ child.pos = pos
58
+ child.indent = indent
59
+ end
60
+ if child.is_a? NMarker
61
+ @markers << child
62
+ end
63
+
64
+ puts "#{scope} << #{child}" if @verbose
65
+ scope << child
66
+ end
67
+ pos.scope_after = scope_stack.current_scope
68
+ end
69
+ end
70
+
71
+ def new_lines_generator g, file_ref, io
72
+ line_no = 0
73
+ io.each_line do |line|
74
+ line_no += 1
75
+ orig_line, line, eol_comments, indent = split_line(line)
76
+ g.yield [orig_line, line, Pos.new(file_ref, line_no), eol_comments, indent]
77
+ end
78
+ end
79
+
80
+ def new_lines_from_array_generator g, file_ref, start_lineno, lines
81
+ line_no = start_lineno - 1
82
+ lines.each do |line|
83
+ line_no += 1
84
+ orig_line, line, eol_comments, indent = split_line(line.dup)
85
+ g.yield [orig_line, line, Pos.new(file_ref, line_no), eol_comments, indent]
86
+ end
87
+ end
88
+
89
+ def split_line line
90
+ orig_line = line.dup
91
+ line.strip!
92
+
93
+ # strip end-of-line comments, but keep comment lines
94
+ eol_comments = ''
95
+ line_without_comments = line.sub(%r`//.*$`) { eol_comments = $&; '' }
96
+ unless line_without_comments.empty?
97
+ line = line_without_comments
98
+ end
99
+
100
+ indent = if orig_line =~ /^(\s+)/ then $1 else '' end
101
+ return [orig_line, line, eol_comments, indent]
102
+ end
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,272 @@
1
+
2
+ module XDry
3
+
4
+ class OGlobal
5
+
6
+ def initialize
7
+ @names_to_classes = {}
8
+ end
9
+
10
+ def classes
11
+ @names_to_classes.values
12
+ end
13
+
14
+ def new_file_scope
15
+ SFile.new.bind(self)
16
+ end
17
+
18
+ def << child
19
+ case child
20
+ when SInterface
21
+ lookup_class(child.class_name).add_interface child
22
+ when SImplementation
23
+ lookup_class(child.class_name).add_implementation child
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def lookup_class name
30
+ @names_to_classes[name] ||= OClass.new(self, name)
31
+ end
32
+ end
33
+
34
+ class OClass
35
+ attr_reader :oglobal
36
+ attr_reader :name, :field_defs, :attributes, :methods
37
+ attr_reader :interfaces
38
+ attr_reader :implementations
39
+
40
+ def initialize oglobal, name
41
+ @oglobal, @name = oglobal, name
42
+
43
+ @names_to_attributes = {}
44
+ @attributes = []
45
+
46
+ @selectors_to_methods = {}
47
+ @methods = []
48
+
49
+ @field_defs = []
50
+ @property_defs = []
51
+ @interfaces = []
52
+ @implementations = []
53
+ end
54
+
55
+ def add_interface child
56
+ @interfaces << child.bind(self)
57
+ end
58
+
59
+ def add_implementation child
60
+ @implementations << child.bind(self)
61
+ end
62
+
63
+ def main_interface
64
+ # FIXME
65
+ @interfaces.first
66
+ end
67
+
68
+ def main_implementation
69
+ # FIXME
70
+ @implementations.first
71
+ end
72
+
73
+ def << node
74
+ case node
75
+ when NFieldDef
76
+ @field_defs << node
77
+ attr_name = node.name.gsub /^_/, ''
78
+ lookup_attribute(attr_name).add_field_def! node
79
+ when NPropertyDef
80
+ @property_defs << node
81
+ attr_name = node.name
82
+ lookup_attribute(attr_name).add_property_def! node
83
+ when NMethodHeader
84
+ selector = node.selector
85
+ lookup_method(selector).add_method_header! node
86
+ when NSynthesize
87
+ node.items.each do |item|
88
+ lookup_attribute(item.property_name).add_synthesize! node
89
+ end
90
+ when SInterfaceFields
91
+ node.bind(self)
92
+ when SMethodImpl
93
+ lookup_method(node.selector).add_method_impl! node
94
+ end
95
+ end
96
+
97
+ def to_s
98
+ "class #{name}"
99
+ end
100
+
101
+ def find_attribute name
102
+ @names_to_attributes[name]
103
+ end
104
+
105
+ def find_method selector
106
+ @selectors_to_methods[selector]
107
+ end
108
+
109
+ def has_method_impl? selector
110
+ m = find_method(selector)
111
+ return m && m.has_impl?
112
+ end
113
+
114
+ private
115
+
116
+ def lookup_attribute name
117
+ @names_to_attributes[name] ||= create_attribute(name)
118
+ end
119
+
120
+ def lookup_method selector
121
+ @selectors_to_methods[selector] ||= create_method(selector)
122
+ end
123
+
124
+ def create_attribute name
125
+ a = OAttribute.new(self, name)
126
+ @attributes << a
127
+ return a
128
+ end
129
+
130
+ def create_method selector
131
+ a = OMethod.new(selector)
132
+ @methods << a
133
+ return a
134
+ end
135
+
136
+ end
137
+
138
+ class OAttribute
139
+ attr_reader :name
140
+ attr_reader :field_def
141
+
142
+ def initialize oclass, name
143
+ @oclass, @name = oclass, name
144
+
145
+ @field_def = nil
146
+ @property_def = nil
147
+ @synthesizes = []
148
+ end
149
+
150
+ def field_name
151
+ if @field_def
152
+ @field_def.name
153
+ else
154
+ FIELD_PREFIX + name
155
+ end
156
+ end
157
+
158
+ def add_field_def! field_def
159
+ @field_def = field_def
160
+ end
161
+
162
+ def add_property_def! property_def
163
+ @property_def = property_def
164
+ end
165
+
166
+ def add_synthesize! synthesize
167
+ @synthesizes << synthesize
168
+ end
169
+
170
+ def has_field_def?
171
+ not @field_def.nil?
172
+ end
173
+
174
+ def has_property_def?
175
+ not @property_def.nil?
176
+ end
177
+
178
+ def has_synthesize?
179
+ not @synthesizes.empty?
180
+ end
181
+
182
+ def new_property_def
183
+ NPropertyDef.new(name, type)
184
+ end
185
+
186
+ def new_field_def
187
+ NFieldDef.new(field_name, type)
188
+ end
189
+
190
+ def new_synthesize
191
+ NSynthesize.new([SynthesizeItem.new(name, (field_name == name ? nil : field_name) )])
192
+ end
193
+
194
+ def persistent?
195
+ @field_def && @field_def.persistent?
196
+ end
197
+
198
+ def type_known?
199
+ not type.nil?
200
+ end
201
+
202
+ def wants_property?
203
+ has_field_def? && field_def.wants_property?
204
+ end
205
+
206
+ def wants_constructor?
207
+ has_field_def? && field_def.wants_constructor?
208
+ end
209
+
210
+ def getter_selector
211
+ # FIXME: need to account for a possible selector override declared in @property
212
+ @name
213
+ end
214
+
215
+ def type
216
+ if @property_def
217
+ @property_def.type
218
+ elsif @field_def
219
+ @field_def.type
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ def to_s
226
+ traits = []
227
+ traits << field_name if has_field_def?
228
+ "#{type} #{name}" + (traits.empty? ? "" : " (" + traits.join(", ") + ")")
229
+ end
230
+ end
231
+
232
+ class OMethod
233
+ attr_reader :selector, :header, :impl
234
+
235
+ def initialize selector
236
+ @selector = selector
237
+ end
238
+
239
+ def add_method_header! method_header
240
+ @header = method_header
241
+ end
242
+
243
+ def add_method_impl! method_impl
244
+ @impl = method_impl.bind(self)
245
+ end
246
+
247
+ def has_header?
248
+ not @header.nil?
249
+ end
250
+
251
+ def has_impl?
252
+ not @impl.nil?
253
+ end
254
+
255
+ def ret_type
256
+ header_or_impl.ret_type
257
+ end
258
+
259
+ def << child
260
+ end
261
+
262
+ def to_s
263
+ header_or_impl.to_s
264
+ end
265
+
266
+ def header_or_impl
267
+ @header || @impl.start_node
268
+ end
269
+
270
+ end
271
+
272
+ end