orb_template 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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/LICENSE.txt +21 -0
  5. data/Makefile +45 -0
  6. data/README.md +429 -0
  7. data/Rakefile +15 -0
  8. data/lib/orb/ast/abstract_node.rb +27 -0
  9. data/lib/orb/ast/attribute.rb +51 -0
  10. data/lib/orb/ast/block_node.rb +26 -0
  11. data/lib/orb/ast/control_expression_node.rb +27 -0
  12. data/lib/orb/ast/newline_node.rb +22 -0
  13. data/lib/orb/ast/printing_expression_node.rb +29 -0
  14. data/lib/orb/ast/private_comment_node.rb +22 -0
  15. data/lib/orb/ast/public_comment_node.rb +22 -0
  16. data/lib/orb/ast/root_node.rb +11 -0
  17. data/lib/orb/ast/tag_node.rb +208 -0
  18. data/lib/orb/ast/text_node.rb +22 -0
  19. data/lib/orb/ast.rb +19 -0
  20. data/lib/orb/document.rb +19 -0
  21. data/lib/orb/errors.rb +40 -0
  22. data/lib/orb/parser.rb +182 -0
  23. data/lib/orb/patterns.rb +40 -0
  24. data/lib/orb/rails_derp.rb +138 -0
  25. data/lib/orb/rails_template.rb +101 -0
  26. data/lib/orb/railtie.rb +9 -0
  27. data/lib/orb/render_context.rb +36 -0
  28. data/lib/orb/template.rb +72 -0
  29. data/lib/orb/temple/attributes_compiler.rb +114 -0
  30. data/lib/orb/temple/compiler.rb +204 -0
  31. data/lib/orb/temple/engine.rb +40 -0
  32. data/lib/orb/temple/filters.rb +132 -0
  33. data/lib/orb/temple/generators.rb +108 -0
  34. data/lib/orb/temple/identity.rb +16 -0
  35. data/lib/orb/temple/parser.rb +46 -0
  36. data/lib/orb/temple.rb +16 -0
  37. data/lib/orb/token.rb +47 -0
  38. data/lib/orb/tokenizer.rb +757 -0
  39. data/lib/orb/tokenizer2.rb +591 -0
  40. data/lib/orb/utils/erb.rb +40 -0
  41. data/lib/orb/utils/orb.rb +12 -0
  42. data/lib/orb/version.rb +5 -0
  43. data/lib/orb.rb +50 -0
  44. metadata +89 -0
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Temple
5
+ # The Compiler is used in the ORB::Engine to compile an input document string
6
+ # into a Temple expression that gets passed on to the next step in the pipeline.
7
+ class Compiler
8
+ # The Compiler is initialized by the Engine pipeline with the options passed to the Engine
9
+ def initialize(options = {})
10
+ @options = options
11
+ @identity = Identity.new
12
+ end
13
+
14
+ # Then the pipeline executes the #call method with an ORB::AST::*Node input
15
+ def call(ast)
16
+ return runtime_error(ast) if ast.is_a?(ORB::Error)
17
+
18
+ transform(ast)
19
+ rescue CompilerError => e
20
+ raise e
21
+ rescue ORB::Error => e
22
+ runtime_error(e)
23
+ end
24
+
25
+ private
26
+
27
+ # Entry point for the compiler, dispatches to the appropriate method based on the node type
28
+ # The `context` argument is used to pass information down the tree of nodes
29
+ # The compile method is usually called on a root node, which then calls `compile` on its children
30
+ # even though it can be called on any node in the AST to compile the subtree under that node.
31
+ #
32
+ # rubocop:disable Metrics/CyclomaticComplexity
33
+ # rubocop:disable Metrics/PerceivedComplexity
34
+ def transform(node, context = {})
35
+ if node.is_a?(ORB::AST::RootNode)
36
+ transform_children(node, context)
37
+ elsif node.is_a?(ORB::AST::TextNode)
38
+ transform_text_node(node, context)
39
+ elsif node.is_a?(ORB::AST::PrintingExpressionNode)
40
+ transform_printing_expression_node(node, context)
41
+ elsif node.is_a?(ORB::AST::ControlExpressionNode)
42
+ transform_control_expression_node(node, context)
43
+ elsif node.is_a?(ORB::AST::TagNode) && node.compiler_directives?
44
+ transform_directives_for_tag_node(node, context)
45
+ elsif node.is_a?(ORB::AST::TagNode) && node.dynamic?
46
+ transform_dynamic_tag_node(node, context)
47
+ elsif node.is_a?(ORB::AST::TagNode) && node.html_tag?
48
+ transform_html_tag_node(node, context)
49
+ elsif node.is_a?(ORB::AST::TagNode) && node.component_tag?
50
+ transform_component_tag_node(node, context)
51
+ elsif node.is_a?(ORB::AST::TagNode) && node.component_slot_tag?
52
+ transform_component_slot_tag_node(node, context)
53
+ elsif node.is_a?(ORB::AST::BlockNode)
54
+ transform_block_node(node, context)
55
+ elsif node.is_a?(ORB::AST::PublicCommentNode)
56
+ transform_public_comment_node(node, context)
57
+ elsif node.is_a?(ORB::AST::PrivateCommentNode)
58
+ transform_private_comment_node(node, context)
59
+ elsif node.is_a?(ORB::AST::NewlineNode)
60
+ transform_newline_node(node, context)
61
+ elsif node.is_a?(ORB::Error)
62
+ runtime_error(node)
63
+ else
64
+ raise ORB::CompilerError, "Unknown node type: #{node.class} for #{node.inspect}"
65
+ end
66
+ end
67
+ # rubocop:enable Metrics/CyclomaticComplexity
68
+ # rubocop:enable Metrics/PerceivedComplexity
69
+
70
+ # Compile the children of a node and collect the result into a Temple expression
71
+ def transform_children(node, context)
72
+ [:multi, *node.children.map { |child| transform(child, context) }]
73
+ end
74
+
75
+ # Compile a TextNode into a Temple expression
76
+ def transform_text_node(node, _context)
77
+ [:static, node.text]
78
+ end
79
+
80
+ # Compile an PExpressionNode into a Temple expression
81
+ def transform_printing_expression_node(node, context)
82
+ if node.block?
83
+ tmp = @identity.generate(:variable)
84
+ [:multi,
85
+ # Capture the result of the code in a variable. We can't do
86
+ # `[:dynamic, code]` because it's probably not a complete
87
+ # expression (which is a requirement for Temple).
88
+ [
89
+ :block, "#{tmp} = #{node.expression}",
90
+
91
+ # Capture the content of a block in a separate buffer. This means
92
+ # that `yield` will not output the content to the current buffer,
93
+ # but rather return the output.
94
+ #
95
+ # The capturing can be disabled with the option :disable_capture.
96
+ # Output code in the block writes directly to the output buffer then.
97
+ # Rails handles this by replacing the output buffer for helpers.
98
+ if @options.fetch(:disable_capture, false)
99
+ transform_children(node, context)
100
+ else
101
+ [:capture, @identity.generate(:variable), transform_children(node, context)]
102
+ end
103
+ ],
104
+ # Output the content.
105
+ [:escape, true, [:dynamic, tmp]]]
106
+ elsif node.children.any?
107
+ [:multi, [:escape, true, [:dynamic, node.expression]], transform_children(node, context)]
108
+ else
109
+ [:escape, true, [:dynamic, node.expression]]
110
+ end
111
+ end
112
+
113
+ # Compile an NPExpressionNode into a Temple expression
114
+ def transform_control_expression_node(node, context)
115
+ if node.block?
116
+ tmp = @identity.generate(:variable)
117
+ [:multi,
118
+ [:block, "#{tmp} = #{node.expression}", transform_children(node, context)]]
119
+ elsif node.children.any?
120
+ [:multi, [:code, node.expression], transform_children(node, context)]
121
+ else
122
+ [:code, node.expression]
123
+ end
124
+ end
125
+
126
+ # Compile an HTML TagNode into a Temple expression
127
+ def transform_html_tag_node(node, context)
128
+ [:orb, :tag, node.tag, node.attributes, transform_children(node, context)]
129
+ end
130
+
131
+ # Compile a component TagNode into a Temple expression
132
+ def transform_component_tag_node(node, context)
133
+ [:orb, :component, node, transform_children(node, context)]
134
+ end
135
+
136
+ # Compile a component slot TagNode into a Temple expression
137
+ def transform_component_slot_tag_node(node, context)
138
+ [:orb, :slot, node, transform_children(node, context)]
139
+ end
140
+
141
+ # Compile a block node into a Temple expression
142
+ def transform_block_node(node, context)
143
+ case node.name
144
+ when :if
145
+ [:orb, :if, node.expression, transform_children(node, context)]
146
+ when :for
147
+ [:orb, :for, node.expression, transform_children(node, context)]
148
+ else
149
+ [:static, 'Unknown block node']
150
+ end
151
+ end
152
+
153
+ # Compile a comment node into a Temple expression
154
+ def transform_public_comment_node(node, _context)
155
+ [:html, :comment, [:static, node.text]]
156
+ end
157
+
158
+ # Compile a private_comment node into a Temple expression
159
+ def transform_private_comment_node(_node, _context)
160
+ [:static, ""]
161
+ end
162
+
163
+ # Compile a newline node into a Temple expression
164
+ def transform_newline_node(_node, _context)
165
+ [:newline]
166
+ end
167
+
168
+ # Compile a tag node with directives
169
+ def transform_directives_for_tag_node(node, context)
170
+ # First, process any :if directives
171
+ if_directive = node.directives.fetch(:if, false)
172
+ if if_directive
173
+ node.remove_directive(:if)
174
+ return [:if,
175
+ if_directive,
176
+ transform(node, context)]
177
+ end
178
+
179
+ # Second, process any :for directives
180
+ for_directive = node.directives.fetch(:for, false)
181
+ if for_directive
182
+ node.remove_directive(:for)
183
+ return [:orb, :for, for_directive, transform(node, context)]
184
+ end
185
+
186
+ # Last, render as a dynamic node expression
187
+ transform(node, context)
188
+ end
189
+
190
+ # Compile a dynamic tag node
191
+ def transform_dynamic_tag_node(node, context)
192
+ [:orb, :dynamic, node, transform_children(node, context)]
193
+ end
194
+
195
+ # Helper or raising exceptions during compilation
196
+ def runtime_error(error)
197
+ [:multi].tap do |temple|
198
+ (error.line - 1).times { temple << [:newline] } if error.line
199
+ temple << [:code, %[raise ORB::Error.new(%q[#{error.message}], #{error.line.inspect})]]
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'temple'
4
+
5
+ module ORB
6
+ module Temple
7
+ class Engine < ::Temple::Engine
8
+ # This overwrites some Temple default options or sets default options for ORB specific filters.
9
+ # It is recommended to set the default settings only once in the code and avoid duplication. Only use
10
+ # `define_options` when you have to override some default settings.
11
+ define_options generator: ::Temple::Generators::StringBuffer,
12
+ buffer_class: 'ActionView::OutputBuffer',
13
+ format: :xhtml,
14
+ default_tag: 'div',
15
+ pretty: false,
16
+ attr_quote: '"',
17
+ sort_attrs: true,
18
+ merge_attrs: { 'class' => ' ' },
19
+ streaming: true,
20
+ use_html_safe: true,
21
+ disable_capture: false
22
+ filter :Encoding
23
+ filter :RemoveBOM
24
+ use ORB::Temple::Parser
25
+ use ORB::Temple::Compiler
26
+ use ORB::Temple::Filters
27
+ html :AttributeSorter
28
+ html :AttributeMerger
29
+ use(:AttributeRemover) { ::Temple::HTML::AttributeRemover.new(remove_empty_attrs: options[:merge_attrs].keys) }
30
+ html :Fast
31
+ filter :Ambles
32
+ filter :Escapable
33
+ filter :StaticAnalyzer
34
+ filter :ControlFlow
35
+ filter :MultiFlattener
36
+ filter :StaticMerger
37
+ use(:Generator) { options[:generator] }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Temple
5
+ class Filters < ::Temple::Filter
6
+ def initialize(options = {})
7
+ @options = options
8
+ @attributes_compiler = AttributesCompiler.new
9
+ end
10
+
11
+ # Handle an HTML tag expression `[:orb, :tag, name, attributes, content]`
12
+ #
13
+ # @param [String] name The name of the tag
14
+ # @param [Array] attributes The attributes to be passed in to the tag
15
+ # @param [Array] content (optional) child nodes of the tag
16
+ # @return [Array] compiled Temple core expression
17
+ def on_orb_tag(name, attributes, content = nil)
18
+ [:html, :tag, name, @attributes_compiler.compile_attributes(attributes), compile(content)]
19
+ end
20
+
21
+ # Handle a component tag expression `[:orb, :component, name, attributes, content]`
22
+ #
23
+ # @param [String] name The name of the component
24
+ # @param [Array<ORB::AST::Attribute>] attributes The attributes to be passed in to the component
25
+ # @param [Array] content (optional) Temple expression
26
+ # @return [Array] compiled Temple core expression
27
+ def on_orb_component(node, content = [])
28
+ tmp = unique_name
29
+
30
+ # Lookup the component class name using the ORB lookup mechanism
31
+ # that traverses the configured namespaces
32
+ name = node.tag.gsub('.', '::')
33
+ komponent = ORB.lookup_component(name)
34
+ komponent_name = komponent || name
35
+
36
+ block_name = "__orb__#{komponent_name.rpartition('::').last.underscore}"
37
+ block_name = node.directives.fetch(:with, block_name)
38
+
39
+ # We need to compile the attributes into a set of captures and a set of arguments
40
+ # since arguments passed to the view component constructor may be defined as
41
+ # dynamic expressions in our template, and we need to first capture their results.
42
+ arg_captures = @attributes_compiler.compile_captures(node.attributes, tmp)
43
+ args = @attributes_compiler.compile_komponent_args(node.attributes, tmp)
44
+
45
+ # Construct the render call for the view component
46
+ code = "render #{komponent_name}.new(#{args}) do |#{block_name}|"
47
+
48
+ # Return a compiled Temple expression that captures the component render call
49
+ # and then evaluates the result into the OutputBuffer.
50
+ [:multi,
51
+ *arg_captures,
52
+ # Capture the result of the component render call into a variable
53
+ # we can't do :dynamic here because it's probably not a complete expression
54
+ [:block, "#{tmp} = #{code}",
55
+ # Capture the content of the block into a separate buffer
56
+ # [:capture, unique_name, compile(content)]
57
+ compile(content)],
58
+ # Output the content
59
+ [:escape, true, [:dynamic, tmp.to_s]]]
60
+ end
61
+
62
+ # Handle a component slot tag expression `[:orb, :slot, name, attributes, content]`
63
+ #
64
+ # @param [String] name The name of the slot
65
+ # @param [Array<ORB::AST::Attribute>] attributes the attributes to be passed in to the slot
66
+ # @param [Array] content (optional) Temple expression for the slot content
67
+ # @return [Array] compiled Temple expression
68
+ def on_orb_slot(node, content = [])
69
+ tmp = unique_name
70
+
71
+ # We need to compile the attributes into a set of captures and a set of arguments
72
+ # since arguments passed to the view component constructor may be defined as
73
+ # dynamic expressions in our template, and we need to first capture their results.
74
+ arg_captures = @attributes_compiler.compile_captures(node.attributes, tmp)
75
+ args = @attributes_compiler.compile_komponent_args(node.attributes, tmp)
76
+
77
+ # Prepare the slot name, parent name, and block name
78
+ slot_name = node.slot
79
+ parent_name = "__orb__#{node.component.underscore}"
80
+ block_name = node.directives.fetch(:with, "__orb__#{slot_name}")
81
+
82
+ # Construct the code to call the slot on the parent component
83
+ code = "#{parent_name}.with_#{slot_name}(#{args}) do |#{block_name}|"
84
+
85
+ # Return a compiled Temple expression that captures the slot call
86
+ [:multi,
87
+ *arg_captures,
88
+ [:code, code], compile(content),
89
+ [:code, "end"]]
90
+ end
91
+
92
+ # Handle an if block expression `[:orb, :if, condition, yes, no]`
93
+ #
94
+ # @param [String] condition The condition to be evaluated
95
+ # @param [Array] yes The content to be rendered if the condition is true
96
+ # @param [Array] no (optional) The content to be rendered if the condition is false
97
+ # @return [Array] compiled Temple expression
98
+ def on_orb_if(condition, yes, no = nil)
99
+ result = [:if, condition, compile(yes)]
100
+ result << compile(no) if no
101
+ result
102
+ end
103
+
104
+ # Handle a for block expression `[:orb, :for, expression, content]`
105
+ #
106
+ # @param [String] expression The iterator expression to be evaluated
107
+ # @param [Array] content The content to be rendered for each iteration
108
+ # @return [Array] compiled Temple expression
109
+ def on_orb_for(expression, content)
110
+ enumerator, collection = expression.split(' in ')
111
+ code = "#{collection}.each do |#{enumerator}|"
112
+
113
+ [:multi,
114
+ [:code, code], compile(content),
115
+ [:code, "end"]]
116
+ end
117
+
118
+ # Handle a dynamic node expression `[:orb, :dynamic, node, content]`
119
+ #
120
+ def on_orb_dynamic(node, content)
121
+ # TODO: Determine whether the node is an html_tag, component, or slot node
122
+ tmp = unique_name
123
+ splats = @attributes_compiler.compile_splat_attributes(node.splat_attributes)
124
+ code = "content_tag('#{node.tag}', #{splats}) do"
125
+
126
+ [:multi,
127
+ [:block, "#{tmp} = #{code}", compile(content)],
128
+ [:escape, true, [:dynamic, tmp]]]
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: This class is a WIP and not used in production code, yet.
4
+ #
5
+ # Eventually this class will be used to generate the Ruby code from the AST.
6
+ # It tries to match the behaviour of the rails ERB Template handler as close as possible.
7
+ module ORB
8
+ module Temple
9
+ module Generators
10
+ class Generator
11
+ include Utils
12
+ include Mixins::CompiledDispatcher
13
+ include Mixins::Options
14
+
15
+ define_options :save_buffer,
16
+ :streaming,
17
+ capture_generator: 'Generator',
18
+ buffer_class: 'ActionView::OutputBuffer',
19
+ buffer: '@output_buffer',
20
+ freeze_static: true
21
+
22
+ def call(exp)
23
+ [preamble, compile(exp), postamble].flatten.compact.join('; ')
24
+ end
25
+
26
+ def preamble
27
+ [save_buffer, create_buffer]
28
+ end
29
+
30
+ def postamble
31
+ [return_buffer, restore_buffer]
32
+ end
33
+
34
+ def save_buffer
35
+ "begin; #{@original_buffer = unique_name} = #{buffer} if defined?(#{buffer})" if options[:save_buffer]
36
+ end
37
+
38
+ def restore_buffer
39
+ "ensure; #{buffer} = #{@original_buffer}; end" if options[:save_buffer]
40
+ end
41
+
42
+ def create_buffer
43
+ if buffer == '@output_buffer'
44
+ "#{buffer} = output_buffer || #{options[:buffer_class]}.new"
45
+ else
46
+ "#{buffer} = #{options[:buffer_class]}.new"
47
+ end
48
+ end
49
+
50
+ def return_buffer
51
+ 'nil'
52
+ end
53
+
54
+ def on(*exp)
55
+ raise InvalidExpression, "Generator supports only core expressions - found #{exp.inspect}"
56
+ end
57
+
58
+ def on_multi(*exp)
59
+ exp.map { |e| compile(e) }.join('; ')
60
+ end
61
+
62
+ def on_newline
63
+ "\n"
64
+ end
65
+
66
+ def on_capture(name, exp)
67
+ capture_generator.new(capture_generator: options[:capture_generator],
68
+ freeze_static: options[:freeze_static],
69
+ buffer: name).call(exp)
70
+ end
71
+
72
+ def on_static(text)
73
+ concat(options[:freeze_static] ? "#{text.inspect}.freeze" : text.inspect)
74
+ end
75
+
76
+ def on_dynamic(code)
77
+ concat(code)
78
+ end
79
+
80
+ def on_code(code)
81
+ code
82
+ end
83
+
84
+ protected
85
+
86
+ def buffer
87
+ options[:buffer]
88
+ end
89
+
90
+ def capture_generator
91
+ @capture_generator ||= if Class === options[:capture_generator]
92
+ options[:capture_generator]
93
+ else
94
+ Generators.const_get(options[:capture_generator])
95
+ end
96
+ end
97
+
98
+ def safe_concat(str)
99
+ "#{buffer}.safe_append=#{str}"
100
+ end
101
+
102
+ def concat(str)
103
+ "#{buffer}.append=#{str}"
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Temple
5
+ class Identity
6
+ def initialize
7
+ @unique_id = 0
8
+ end
9
+
10
+ def generate(prefix = nil)
11
+ @unique_id += 1
12
+ ["_orb_compiler", prefix, @unique_id].compact.join('_')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Temple
5
+ class Parser
6
+ def initialize(options = {})
7
+ @options = options
8
+
9
+ # Only raise errors when is used as a standalone library
10
+ # and not within the Temple engine
11
+ @raise_errors = !options.key?(:generator)
12
+ end
13
+
14
+ def call(template)
15
+ tokenizer = ORB::Tokenizer2.new(template, **@options)
16
+ tokens = tokenizer.tokenize!
17
+
18
+ parser = ORB::Parser.new(tokens, **@options)
19
+ @root = parser.parse!
20
+
21
+ @root
22
+ rescue ORB::Error => e
23
+ e.set_backtrace(e.backtrace.unshift("#{@options.fetch(:filename, :nofile)}:#{e.line || 42}"))
24
+
25
+ # Within the Temple engine, tokenizer and parser errors shouldn't be raised
26
+ # but instead passed on to the compiler, so we can raise them there and
27
+ # render nice error templates.
28
+ raise if @raise_errors
29
+
30
+ error_with_lineno(e)
31
+ end
32
+
33
+ private
34
+
35
+ def error_with_lineno(error)
36
+ return error if error.line
37
+
38
+ trace = error.backtrace.first
39
+ return error unless trace
40
+
41
+ line = trace.match(/\d+\z/).to_s.to_i
42
+ Error.new(error.message, line)
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/orb/temple.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ module Temple
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Filters
8
+ autoload :ComponentFilter
9
+ autoload :AttributesCompiler
10
+ autoload :Parser
11
+ autoload :Compiler
12
+ autoload :Identity
13
+ autoload :Engine
14
+ autoload :Generator, 'orb/temple/generators'
15
+ end
16
+ end
data/lib/orb/token.rb ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ORB
4
+ # A simple PORO to represent a token.
5
+ class Token
6
+ attr_accessor :type, :value, :meta
7
+
8
+ def initialize(type, value, meta = {})
9
+ @type = type
10
+ @value = value
11
+ @meta = meta
12
+ @line = line || 0
13
+ end
14
+
15
+ def set_meta(key, value)
16
+ meta[key.to_sym] = value
17
+ end
18
+
19
+ def respond_to_missing?(method, _include_private = false)
20
+ meta.has_key?(method.to_sym)
21
+ end
22
+
23
+ def method_missing(method, *args, &block)
24
+ if meta.has_key?(method.to_sym)
25
+ meta[method.to_sym]
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def ==(other)
32
+ type == other.type &&
33
+ value == other.value &&
34
+ meta == other.meta
35
+ end
36
+
37
+ attr_reader :line
38
+
39
+ def inspect
40
+ to_s
41
+ end
42
+
43
+ def to_s
44
+ "#<ORB::Token[#{type}] meta=#{meta.inspect} value=#{value.inspect}>"
45
+ end
46
+ end
47
+ end