Class: OMQ::CLI::ExpressionEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/omq/cli/expression_evaluator.rb

Overview

Compiles and evaluates a single Ruby expression string for use in –recv-eval / –send-eval. Handles BEGIN{}/END{} block extraction, proc compilation, and result normalisation.

One instance per direction (send or recv).

Constant Summary collapse

SENT =

Sentinel: eval proc returned the context object, meaning it already sent the reply itself.

Object.new.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(src, format:, fallback_proc: nil) ⇒ ExpressionEvaluator

Returns a new instance of ExpressionEvaluator.

Parameters:

  • src (String, nil)

    the raw expression string (may include BEGIN{}/END{})

  • format (Symbol)

    the active format, used to normalise results

  • fallback_proc (Proc, nil) (defaults to: nil)

    registered OMQ.outgoing/incoming handler; used only when src is nil (no inline expression)



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/omq/cli/expression_evaluator.rb', line 24

def initialize(src, format:, fallback_proc: nil)
  @format = format

  if src
    expr, begin_body, end_body = extract_blocks(src)
    @begin_proc = eval("proc { #{begin_body} }") if begin_body
    @end_proc   = eval("proc { #{end_body} }")   if end_body
    if expr && !expr.strip.empty?
      @eval_proc = eval("proc { #{expr} }")
    end
  elsif fallback_proc
    @eval_proc = proc { fallback_proc.call(it) }
  end
end

Instance Attribute Details

#begin_procObject (readonly)

Returns the value of attribute begin_proc.



12
13
14
# File 'lib/omq/cli/expression_evaluator.rb', line 12

def begin_proc
  @begin_proc
end

#end_procObject (readonly)

Returns the value of attribute end_proc.



12
13
14
# File 'lib/omq/cli/expression_evaluator.rb', line 12

def end_proc
  @end_proc
end

#eval_procObject (readonly)

Returns the value of attribute eval_proc.



12
13
14
# File 'lib/omq/cli/expression_evaluator.rb', line 12

def eval_proc
  @eval_proc
end

Class Method Details

.compile_inside_ractor(src) ⇒ Object

Compiles begin/end/eval procs inside a Ractor from a raw expression string. Returns [begin_proc, end_proc, eval_proc], any may be nil.

Must be called inside the Ractor block (Procs are not Ractor-shareable).



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/omq/cli/expression_evaluator.rb', line 79

def self.compile_inside_ractor(src)
  return [nil, nil, nil] unless src

  expr, begin_body = extract_block(src,  "BEGIN")
  expr, end_body   = extract_block(expr, "END")

  begin_proc = eval("proc { #{begin_body} }") if begin_body
  end_proc   = eval("proc { #{end_body} }")   if end_body
  eval_proc  = nil
  if expr && !expr.strip.empty?
    eval_proc = eval("proc { #{expr} }")
  end

  [begin_proc, end_proc, eval_proc]
end

.extract_block(expr, keyword) ⇒ Object

Strips a BEGIN {…} or END {…} block from expr and returns [trimmed_expr, block_body_or_nil]. Brace-matched scan, so nested ‘{}` inside the block body are handled. Shared by instance and Ractor compile paths, so must be a class method (Ractors cannot call back into instance state).



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/omq/cli/expression_evaluator.rb', line 102

def self.extract_block(expr, keyword)
  start = expr.index(/#{keyword}\s*\{/)
  return [expr, nil] unless start

  i     = expr.index("{", start)
  depth = 1
  j     = i + 1
  while j < expr.length && depth > 0
    case expr[j]
    when "{"
      depth += 1
    when "}"
      depth -= 1
    end
    j += 1
  end

  body    = expr[(i + 1)..(j - 2)]
  trimmed = expr[0...start] + expr[j..]
  [trimmed, body]
end

.normalize_result(result, format: nil) ⇒ Object

Normalises an eval result to nil (skip), an Array (text formats), or an arbitrary Ruby object (:marshal).

Used inside Ractor worker blocks where instance methods are unavailable. When format is :marshal, the raw result is passed through — the wire path will Marshal.dump it into a single frame.



66
67
68
69
70
71
# File 'lib/omq/cli/expression_evaluator.rb', line 66

def self.normalize_result(result, format: nil)
  return nil if result.nil?
  return result if format == :marshal
  result = result.is_a?(Array) ? result : [result]
  result.map { |part| part.to_s }
end

Instance Method Details

#call(parts, context) ⇒ Object

Runs the eval proc against parts using context as self. Returns the normalised result Array, nil (filter/skip), or SENT.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/omq/cli/expression_evaluator.rb', line 43

def call(parts, context)
  return parts unless @eval_proc

  result = context.instance_exec(parts, &@eval_proc)
  return nil  if result.nil?
  return SENT if result.equal?(context)
  return result if @format == :marshal

  result = result.is_a?(Array) ? result : [result]
  result.map { |part| part.to_s }
rescue => e
  $stderr.puts "omq: eval error: #{e.message} (#{e.class})"
  exit 3
end