Module: Tracekit::Evaluator

Defined in:
lib/tracekit/evaluator.rb

Overview

Expression evaluator for the TraceKit portable expression subset. Evaluates breakpoint conditions locally to avoid server round-trips.

Uses a custom recursive-descent parser – never eval()/instance_eval. Supports: comparison, logical, arithmetic, string concat, property access, bracket notation, membership (in), null safety, and grouping.

Defined Under Namespace

Modules: Lexer, TokenType Classes: Parser, Token, UnsupportedExpressionError

Class Method Summary collapse

Class Method Details

.evaluate_condition(expression, env) ⇒ Object

Evaluates an expression string against the given environment and returns a boolean result. Empty expressions return true. Raises UnsupportedExpressionError for server-only expressions.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/tracekit/evaluator.rb', line 85

def self.evaluate_condition(expression, env)
  return true if expression.nil? || expression.strip.empty?

  unless sdk_evaluable?(expression)
    raise UnsupportedExpressionError, "unsupported expression: requires server-side evaluation"
  end

  result = evaluate_expression(expression, env)

  case result
  when true, false
    result
  when nil
    false
  else
    # Non-boolean result from a condition -- treat as truthy
    true
  end
end

.evaluate_expression(expression, env) ⇒ Object

Evaluates an expression and returns the raw result value. Raises UnsupportedExpressionError for server-only expressions.



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/tracekit/evaluator.rb', line 107

def self.evaluate_expression(expression, env)
  return nil if expression.nil? || expression.strip.empty?

  unless sdk_evaluable?(expression)
    raise UnsupportedExpressionError, "unsupported expression: requires server-side evaluation"
  end

  tokens = Lexer.tokenize(expression)
  parser = Parser.new(tokens, env)
  parser.parse_expression
end

.evaluate_expressions(expressions, env) ⇒ Object

Evaluates multiple expressions against the given environment. Results are keyed by expression string. On error, nil is stored.



121
122
123
124
125
126
127
128
129
# File 'lib/tracekit/evaluator.rb', line 121

def self.evaluate_expressions(expressions, env)
  results = {}
  expressions.each do |expr|
    results[expr] = evaluate_expression(expr, env)
  rescue StandardError
    results[expr] = nil
  end
  results
end

.sdk_evaluable?(expression) ⇒ Boolean

Returns true if the expression can be evaluated locally by the SDK. Returns false for expressions containing function calls, regex operators, assignment, array indexing, ternary, range, template literals, or bitwise operators.

Returns:

  • (Boolean)


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
# File 'lib/tracekit/evaluator.rb', line 16

def self.sdk_evaluable?(expression)
  return true if expression.nil? || expression.strip.empty?

  # Function calls: word followed by opening paren
  return false if expression.match?(/\b[a-zA-Z_]\w*\s*\(/)

  # Regex match keyword
  return false if expression.match?(/\bmatches\b/)

  # Regex operator =~
  return false if expression.include?("=~")

  # Bitwise NOT ~ (but not inside =~, already handled above)
  expression.each_char.with_index do |ch, i|
    if ch == "~" && (i == 0 || expression[i - 1] != "=")
      return false
    end
  end

  # Bitwise AND: single & not part of &&
  i = 0
  while i < expression.length
    if expression[i] == "&"
      if i + 1 < expression.length && expression[i + 1] == "&"
        i += 2
        next
      end
      return false
    end
    i += 1
  end

  # Bitwise OR: single | not part of ||
  i = 0
  while i < expression.length
    if expression[i] == "|"
      if i + 1 < expression.length && expression[i + 1] == "|"
        i += 2
        next
      end
      return false
    end
    i += 1
  end

  # Bit shift
  return false if expression.include?("<<") || expression.include?(">>")

  # Template literals
  return false if expression.include?("${")

  # Range operator
  return false if expression.include?("..")

  # Ternary
  return false if expression.include?("?")

  # Array indexing [N]
  return false if expression.match?(/\[\d/)

  # Compound assignment
  return false if expression.match?(/[+\-*\/]=/)

  true
end