Class: NNQ::CLI::ExpressionEvaluator

Inherits:
Object
  • Object
show all
Defined in:
lib/nnq/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).

nnq has no multipart, so ‘$F` is always a 1-element array and `$_` is the body string.

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)

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

  • format (Symbol)

    active format, used to normalise results

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

    registered NNQ.outgoing/incoming handler; used only when src is nil



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/nnq/cli/expression_evaluator.rb', line 26

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 # rubocop:disable Security/Eval
    @end_proc   = eval("proc { #{end_body} }")   if end_body   # rubocop:disable Security/Eval
    if expr && !expr.strip.empty?
      @eval_proc = eval("proc { $_ = $F&.first; #{expr} }") # rubocop:disable Security/Eval
    end
  elsif fallback_proc
    @eval_proc = proc { |msg|
      body = msg&.first
      $_ = body
      fallback_proc.call(body)
    }
  end
end

Instance Attribute Details

#begin_procObject (readonly)

Returns the value of attribute begin_proc.



15
16
17
# File 'lib/nnq/cli/expression_evaluator.rb', line 15

def begin_proc
  @begin_proc
end

#end_procObject (readonly)

Returns the value of attribute end_proc.



15
16
17
# File 'lib/nnq/cli/expression_evaluator.rb', line 15

def end_proc
  @end_proc
end

#eval_procObject (readonly)

Returns the value of attribute eval_proc.



15
16
17
# File 'lib/nnq/cli/expression_evaluator.rb', line 15

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.



77
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
# File 'lib/nnq/cli/expression_evaluator.rb', line 77

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

  extract = ->(expr, kw) {
    s = expr.index(/#{kw}\s*\{/)
    return [expr, nil] unless s
    ci = expr.index("{", s)
    depth = 1
    j = ci + 1
    while j < expr.length && depth > 0
      depth += 1 if expr[j] == "{"
      depth -= 1 if expr[j] == "}"
      j += 1
    end
    [expr[0...s] + expr[j..], expr[(ci + 1)..(j - 2)]]
  }

  expr, begin_body = extract.(src, "BEGIN")
  expr, end_body   = extract.(expr, "END")

  begin_proc = eval("proc { #{begin_body} }") if begin_body # rubocop:disable Security/Eval
  end_proc   = eval("proc { #{end_body} }")   if end_body   # rubocop:disable Security/Eval
  eval_proc  = nil
  if expr && !expr.strip.empty?
    ractor_expr = expr.gsub(/\$F\b/, "__F")
    eval_proc   = eval("proc { |__F| $_ = __F&.first; #{ractor_expr} }") # rubocop:disable Security/Eval
  end

  [begin_proc, end_proc, eval_proc]
end

.normalize_result(result) ⇒ Object

Normalises an eval result to nil (skip) or a 1-element Array of strings. Used inside Ractor worker blocks.



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

def self.normalize_result(result)
  return nil if result.nil?
  result = result.is_a?(Array) ? result.first(1) : [result]
  result.map!(&:to_s)
end

Instance Method Details

#call(msg, context) ⇒ Object

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



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/nnq/cli/expression_evaluator.rb', line 48

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

  $F     = msg
  result = context.instance_exec(msg, &@eval_proc)
  return nil  if result.nil?
  return SENT if result.equal?(context)
  return [result] if @format == :marshal

  result = result.is_a?(Array) ? result.first(1) : [result]
  result.map!(&:to_s)
rescue => e
  $stderr.puts "nnq: eval error: #{e.message} (#{e.class})"
  exit 3
end