Class: Token

Inherits:
Object
  • Object
show all
Defined in:
lib/raka/token.rb

Overview

A raka expression is a list of linked tokens. The Token class store current token, info of previous tokens, and context. It plays rule of both token and expr

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(compiler, context, chain, inline_scope, input_exts: nil) ⇒ Token

Returns a new instance of Token.



31
32
33
34
35
36
37
38
39
# File 'lib/raka/token.rb', line 31

def initialize(compiler, context, chain, inline_scope, input_exts: nil)
  @compiler = compiler
  @context = context
  @chain = chain
  @inline_scope = inline_scope
  @options = {}
  @options[:input_exts] = input_exts
  @compiler_options = @compiler.instance_variable_get(:@options)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args) ⇒ Object

rubocop:disable Style/MissingRespondToMissing # for DSL not essential



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/raka/token.rb', line 87

def method_missing(sym, *args)
  # if ends with '=' then is to compile;
  # if not but has a arg then it is template token, push template;
  # else is inconclusive so just push symbol
  super if internal(sym)

  if sym.to_s.end_with? '='
    @compiler.compile(_attach_(sym.to_s.chomp('=')), args.first)
  elsif !args.empty?
    _attach_ args.first.to_s
  else
    _attach_ sym.to_s
  end
end

Instance Attribute Details

#chainObject (readonly)

Returns the value of attribute chain.



29
30
31
# File 'lib/raka/token.rb', line 29

def chain
  @chain
end

Instance Method Details

#[](pattern, options = {}) ⇒ Object

These two methods indicate that this is a pattern token



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/raka/token.rb', line 156

def [](pattern, options = {})
  symbol = @chain.pop.to_s
  # if the pattern contains child pattern like percent_(\d+), we change the capture to
  # named capture so that it can be captured later. The name is symbol with the index, like func0
  pattern = pattern.to_s.gsub(/\(\S+?\)/).with_index { |m, i| "(?<#{symbol}#{i}>#{m})" }

  # merge global pattern defaults with provided options
  options = @compiler_options&.pattern_options&.merge(options)

  # build the pattern based on match mode
  refined_pattern = case options[:match_mode]
                    when 'exact'
                      pattern.to_s
                    when 'prefix'
                      "#{pattern}\\w*"
                    else
                      raise "Invalid match_mode: #{match_mode}. Use 'exact' or 'prefix'"
                    end

  # if the symbol is _, \S+ will be put in chain, it indicates not to capture,
  # so just replace it with the refined pattern
  if symbol == Pattern::ANY # match-everything and not bound
    @chain.push refined_pattern
  else
    @chain.push "(?<#{symbol}>(#{refined_pattern}))"
  end
  self
end

#[]=(pattern, *args) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/raka/token.rb', line 185

def []=(pattern, *args)
  case args.length
  when 1
    # Standard: txt._[:pattern] = value
    value = args[0]
    @compiler.compile(self[pattern], value)
  when 2
    # With options: txt._[:pattern, {match_mode: 'exact'}] = value
    options, value = args
    @compiler.compile(self[pattern, options], value)
  else
    raise ArgumentError, "wrong number of arguments (given #{args.length + 1}, expected 2 or 3)"
  end
end

#_(*args) ⇒ Object

non capture matching anything



104
105
106
107
108
109
110
# File 'lib/raka/token.rb', line 104

def _(*args)
  if !args.empty?
    _attach_ args.first.to_s
  else
    _attach_ Pattern::ANY
  end
end

#_=(rhs) ⇒ Object



112
113
114
# File 'lib/raka/token.rb', line 112

def _=(rhs)
  @compiler.compile(_attach_(Pattern::ANY), rhs)
end

#_attach_(item) ⇒ Object

attach a new item to the chain



82
83
84
# File 'lib/raka/token.rb', line 82

def _attach_(item)
  Token.new(@compiler, @context, @chain + [item], @inline_scope, **@options)
end

#_captures_(target) ⇒ Object



45
46
47
48
49
# File 'lib/raka/token.rb', line 45

def _captures_(target)
  matched = _pattern_.match(target)
  keys = matched.names.map(&:to_sym)
  Hash[keys.zip(matched.captures)]
end

#_input_?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/raka/token.rb', line 116

def _input_?
  @chain.length > 1
end

#_inputs_(output, ext) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/raka/token.rb', line 120

def _inputs_(output, ext)
  # no input
  return [] if @chain.length == 1

  # match the body part besides the scope (if not scoped), leading xxx__ and .ext of output
  info = _parse_output_(output)
  input_stem = /^\S+?__(\S+)$/.match(info.stem)[1]
  auto_input = "#{input_stem}.#{ext}"
  auto_input = "#{info.target_scope}/" + auto_input if info.target_scope
  auto_input = "#{info.scope}/" + auto_input if info.scope
  [auto_input]
end

#_options_Object



41
42
43
# File 'lib/raka/token.rb', line 41

def _options_
  @options
end

#_parse_output_(output) ⇒ Object

rubocop:disable Style/MethodLength # long but straightforward



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
# File 'lib/raka/token.rb', line 52

def _parse_output_(output)
  # xxx? is for minimal match
  out_pattern = %r{^((?<scope>\S+)/)?}.source
  out_pattern += %r{(?<target_scope>#{@inline_scope})/}.source unless @inline_scope.nil?
  out_pattern += /(?<stem>(\S+))(?<ext>\.[^.]+)$/.source
  info = Regexp.new(out_pattern).match(output)
  res = Hash[info.names.zip(info.captures)]
  unless info[:scope].nil?
    rule_scopes = Regexp.new(_scope_pattern_).match(info[:scope]).captures
    res[:rule_scopes] = rule_scopes[2..-1].reverse
  end
  if !@inline_scope.nil? && !info[:target_scope].nil?
    segs = Regexp.new(@inline_scope).match(info[:target_scope]).captures
    res[:target_scope_captures] = segs
  end
  name_details = /^(\S+?)__(\S+)$/.match(info[:stem])
  res = if name_details
          res.merge(func: name_details[1], input_stem: name_details[2])
        else
          res.merge(func: nil, input_stem: nil)
        end
  res = res.merge(captures: OpenStruct.new(_captures_(output)))
  res[:name] = output
  res[:output] = output
  res[:output_stem] = info[:stem]
  OpenStruct.new res
end

#_pattern_Object



141
142
143
144
145
146
147
# File 'lib/raka/token.rb', line 141

def _pattern_
  # scopes as leading
  leading = !@context.scopes.empty? ? _scope_pattern_ + '/' : _scope_pattern_
  leading += "(#{@inline_scope})/" unless @inline_scope.nil?
  body = @chain.reverse.map { |s| "(#{s})" }.join('__')
  Regexp.new('^' + leading + body + '\.' + @context.ext.to_s + '$')
end

#_rule_scope_pattern_Object



133
134
135
# File 'lib/raka/token.rb', line 133

def _rule_scope_pattern_
  (@context.scopes.map { |layer| "(#{layer.join('|')})" }).join('/') + ')'
end

#_scope_pattern_Object



137
138
139
# File 'lib/raka/token.rb', line 137

def _scope_pattern_
  '((?:(\S+)/)?' + _rule_scope_pattern_
end

#_template_(scope = nil) ⇒ Object



149
150
151
152
153
# File 'lib/raka/token.rb', line 149

def _template_(scope = nil)
  (scope.nil? ? '' : scope + '/') + (@inline_scope.nil? ? '' : @inline_scope + '/') +
    @chain.reverse.join('__') + '.' +
    @context.ext.to_s
end