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: CycleError, Error

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeExecutor

Returns a new instance of Executor.



85
86
87
88
89
# File 'lib/sevgi/executor.rb', line 85

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:

  • (Sevgi::Executor::Scope, nil)

    execution scope, or nil for empty source



53
54
55
# File 'lib/sevgi/executor.rb', line 53

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:

  • (Sevgi::Executor::Scope, nil)

    execution scope, or nil for an empty file



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

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:

  • (Sevgi::Executor::Scope)

    current execution scope

Raises:

  • (Sevgi::PanicError)

    when there is no active executor scope



35
36
37
38
39
# File 'lib/sevgi/executor.rb', line 35

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:

  • (Sevgi::Executor::Scope, nil)

    removed scope



81
82
83
# File 'lib/sevgi/executor.rb', line 81

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:

  • (Sevgi::Executor::Scope)

    created scope



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

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:

  • (Sevgi::Executor::Scope, nil)

    active scope, if any



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

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.



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

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:

  • (Sevgi::Executor::Scope, nil)

    removed scope



121
122
123
124
125
126
# File 'lib/sevgi/executor.rb', line 121

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.



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

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

    @signal_count += 1
  end
end