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
-
.evaluate_condition(expression, env) ⇒ Object
Evaluates an expression string against the given environment and returns a boolean result.
-
.evaluate_expression(expression, env) ⇒ Object
Evaluates an expression and returns the raw result value.
-
.evaluate_expressions(expressions, env) ⇒ Object
Evaluates multiple expressions against the given environment.
-
.sdk_evaluable?(expression) ⇒ Boolean
Returns true if the expression can be evaluated locally by the SDK.
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.
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 |