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

The expression sees the message body via the default block variable ‘it`, or callers can declare an explicit parameter with block-literal syntax: `-e ’|msg| msg.upcase’‘ compiles to `proc { |msg| msg.upcase }`.

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



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

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 { |msg| fallback_proc.call(msg) }
  end
end

Instance Attribute Details

#begin_procObject (readonly)

Returns the value of attribute begin_proc.



17
18
19
# File 'lib/nnq/cli/expression_evaluator.rb', line 17

def begin_proc
  @begin_proc
end

#end_procObject (readonly)

Returns the value of attribute end_proc.



17
18
19
# File 'lib/nnq/cli/expression_evaluator.rb', line 17

def end_proc
  @end_proc
end

#eval_procObject (readonly)

Returns the value of attribute eval_proc.



17
18
19
# File 'lib/nnq/cli/expression_evaluator.rb', line 17

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.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/nnq/cli/expression_evaluator.rb', line 74

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



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/nnq/cli/expression_evaluator.rb', line 98

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) ⇒ Object

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



65
66
67
68
# File 'lib/nnq/cli/expression_evaluator.rb', line 65

def self.normalize_result(result)
  return nil if result.nil?
  result.to_s
end

Instance Method Details

#call(msg, context) ⇒ Object

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



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

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

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

  result.to_s
rescue => e
  $stderr.puts "nnq: eval error: #{e.message} (#{e.class})"
  exit 3
end