Class: Sevgi::Executor

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/sevgi/executor.rb,
lib/sevgi/executor/error.rb,
lib/sevgi/executor/scope.rb,
lib/sevgi/executor/source.rb

Overview

Executes Sevgi script source inside an isolated module scope.

The executor is used by script mode and by the Load DSL word to preserve a useful load stack while keeping DSL methods out of the caller's global object whenever possible. Active scope stacks are isolated per Ruby fiber, so concurrent executions can perform nested Load calls without sharing scope state. The process SIGINT handler is shared by Ruby, so executor runs guard it with a reference-counted critical section and restore the previous handler after the last active execution finishes.

Defined Under Namespace

Classes: Error, Scope

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeExecutor

Returns a new instance of Executor.



83
84
85
86
87
# File 'lib/sevgi/executor.rb', line 83

def initialize
  @signal_count = 0
  @signal_mutex = Mutex.new
  @signal_previous = nil
end

Class Method Details

.execute(string, file: nil, line: nil, require: nil, receiver: nil) { ... } ⇒ Sevgi::Executor::Scope?

Note:

Required-library load failures are captured as Error on the returned scope.

Note:

Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains process-global while any execution is active.

Executes Ruby source inside a managed Sevgi script scope.

Parameters:

  • string (String)

    source to evaluate

  • file (String, nil) (defaults to: nil)

    source file name used for errors and backtraces

  • line (Integer, nil) (defaults to: nil)

    starting source line used for errors and backtraces

  • require (String, nil) (defaults to: nil)

    optional Ruby library to require before execution

  • receiver (Object, nil) (defaults to: nil)

    receiver used while booting the DSL

Yields:

  • optional boot block that installs DSL methods before evaluation

Yield Returns:

  • (void)

Returns:



51
52
53
# File 'lib/sevgi/executor.rb', line 51

def self.execute(string, file: nil, line: nil, require: nil, receiver: nil, &block)
  execute_source(Source.new(string:, file:, line:), require:, receiver:, &block)
end

.execute_file(file, require: nil, receiver: nil) { ... } ⇒ Sevgi::Executor::Scope?

Note:

File-read and required-library load failures are captured as Error on the returned scope.

Note:

Reentrant and concurrent calls keep independent scope stacks per fiber. The temporary SIGINT handler remains process-global while any execution is active.

Executes a file inside a managed Sevgi script scope.

Parameters:

  • file (String)

    source file to read and execute

  • require (String, nil) (defaults to: nil)

    optional Ruby library to require before execution

  • receiver (Object, nil) (defaults to: nil)

    receiver used while booting the DSL

Yields:

  • optional boot block that installs DSL methods before evaluation

Yield Returns:

  • (void)

Returns:



65
66
67
68
69
70
71
72
73
74
# File 'lib/sevgi/executor.rb', line 65

def self.execute_file(file, require: nil, receiver: nil, &block)
  source = nil
  begin
    source = Source.load(file)
  rescue ::SystemCallError => e
    return capture_error(Source.new(string: "", file:, line: 1), e)
  end

  execute_source(source, require:, receiver:, &block)
end

.load(file) ⇒ Sevgi::Executor::Scope

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

Uses the active executor scope from the current fiber.

Loads a script file inside the current executor scope.

Parameters:

  • file (String)

    path to a Sevgi script file

Returns:

Raises:

  • (Sevgi::PanicError)

    when there is no active executor scope



33
34
35
36
37
# File 'lib/sevgi/executor.rb', line 33

def self.load(file, ...)
  PanicError.("box stack empty; create a box first") unless instance.current

  instance.current.load(file, ...)
end

.shutdownSevgi::Executor::Scope?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Removes the current executor scope.

Returns:



79
80
81
# File 'lib/sevgi/executor.rb', line 79

def self.shutdown
  instance.shutdown
end

Instance Method Details

#create(scope = nil) ⇒ Sevgi::Executor::Scope

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates and pushes a new executor scope.

Parameters:

  • scope (Module, nil) (defaults to: nil)

    existing module scope to reuse

Returns:



93
# File 'lib/sevgi/executor.rb', line 93

def create(scope = nil) = Scope.new(scope).tap { scopes << it }

#currentSevgi::Executor::Scope?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the active executor scope.

Returns:



98
# File 'lib/sevgi/executor.rb', line 98

def current = scopes.last

#restorevoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Restores the process SIGINT handler when the last active execution ends.



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/sevgi/executor.rb', line 103

def restore
  @signal_mutex.synchronize do
    next if @signal_count.zero?

    @signal_count -= 1
    next unless @signal_count.zero?

    Signal.trap("INT", @signal_previous)
    @signal_previous = nil
  end
end

#shutdown(scope = nil) ⇒ Sevgi::Executor::Scope?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Removes the active executor scope.

Parameters:

  • scope (Sevgi::Executor::Scope, nil) (defaults to: nil)

    exact scope to remove, or nil to pop the current scope

Returns:



119
120
121
122
123
124
# File 'lib/sevgi/executor.rb', line 119

def shutdown(scope = nil)
  return scopes.pop unless scope
  return scopes.pop if scopes.last.equal?(scope)

  scopes.delete(scope)
end

#trapvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Installs the process SIGINT handler while one or more executions are active.



129
130
131
132
133
134
135
# File 'lib/sevgi/executor.rb', line 129

def trap
  @signal_mutex.synchronize do
    @signal_previous = Signal.trap("INT") { Kernel.abort("") } if @signal_count.zero?

    @signal_count += 1
  end
end