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
38
# 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).



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

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).



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

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

  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.



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

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.



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

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