Module: Kapusta::Compiler::EmitterModules::SimpleExpression

Defined in:
lib/kapusta/compiler/emitter/simple_expression.rb

Constant Summary collapse

KEYWORDS =
%w[nil true false self].freeze
OPENERS =
{ '(' => ')', '[' => ']', '{' => '}' }.freeze
CLOSERS =
OPENERS.values.freeze
PRIMARY_OPENERS =

‘…` is intentionally excluded — a top-level hash or block literal shouldn’t be treated as a bare primary even though balanced.

{ '(' => ')', '[' => ']' }.freeze
PRIMARY_PATTERNS =
[
  /\A-?\d+(?:\.\d+)?/, # number (incl. negative literal)
  /\A:[a-zA-Z_]\w*[!?=]?/, # :symbol
  /\A"(?:[^"\\]|\\.)*"/, # "string"
  /\A'(?:[^'\\]|\\.)*'/, # 'string'
  /\A@@?[a-z_]\w*/, # @ivar / @@cvar
  /\A\$[a-zA-Z_]\w*/, # $gvar
  /\A[A-Z]\w*(?:::[A-Z]\w*)*/, # Constant / A::B::C
  /\A[a-z_]\w*[!?=]?/ # local or bare call head
].freeze
CHAIN_METHOD =
/\A\.[a-zA-Z_]\w*[!?=]?/

Class Method Summary collapse

Class Method Details

.consume(code, pos) ⇒ Object



46
47
48
49
50
51
52
53
54
55
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 46

def consume(code, pos)
  pos = consume_primary(code, pos)
  while pos && pos < code.length
    advanced = consume_segment(code, pos)
    break unless advanced

    pos = advanced
  end
  pos
end

.consume_group(code, pos) ⇒ Object



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
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 78

def consume_group(code, pos)
  return unless OPENERS.key?(code[pos])

  stack = [OPENERS[code[pos]]]
  pos += 1
  quote = nil
  while pos < code.length
    ch = code[pos]
    if quote
      if ch == '\\' && pos + 1 < code.length
        pos += 2
      else
        quote = nil if ch == quote
        pos += 1
      end
    elsif ['"', "'"].include?(ch)
      quote = ch
      pos += 1
    elsif OPENERS.key?(ch)
      stack.push(OPENERS[ch])
      pos += 1
    elsif CLOSERS.include?(ch)
      return unless stack.last == ch

      stack.pop
      pos += 1
      return pos if stack.empty?
    else
      pos += 1
    end
  end
  nil
end

.consume_primary(code, pos) ⇒ Object



57
58
59
60
61
62
63
64
65
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 57

def consume_primary(code, pos)
  return consume_group(code, pos) if PRIMARY_OPENERS.key?(code[pos])

  regex = PRIMARY_PATTERNS.find { |re| code[pos..].match?(re) }
  return unless regex

  after = pos + regex.match(code[pos..]).end(0)
  code[after] == '(' ? consume_group(code, after) : after
end

.consume_segment(code, pos) ⇒ Object



67
68
69
70
71
72
73
74
75
76
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 67

def consume_segment(code, pos)
  return consume_group(code, pos) if code[pos] == '['
  return unless code[pos] == '.'

  match = CHAIN_METHOD.match(code[pos..])
  return unless match

  after = pos + match.end(0)
  code[after] == '(' ? consume_group(code, after) : after
end

.match?(code) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
34
35
36
37
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 31

def match?(code)
  return false if code.empty? || code.include?("\n")
  return true if KEYWORDS.include?(code)
  return negation?(code) if code.start_with?('!')

  consume(code, 0) == code.length
end

.negation?(code) ⇒ Boolean

Returns:

  • (Boolean)


39
40
41
42
43
44
# File 'lib/kapusta/compiler/emitter/simple_expression.rb', line 39

def negation?(code)
  return false if code.length < 2

  rest = code[1..]
  match?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
end