Class: Forthic::Interpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/forthic/interpreter.rb

Constant Summary collapse

TOKEN_HANDLERS =

Token handler lookup table

{
  TokenType::STRING => :handle_string_token,
  TokenType::COMMENT => :handle_comment_token,
  TokenType::START_ARRAY => :handle_start_array_token,
  TokenType::END_ARRAY => :handle_end_array_token,
  TokenType::START_MODULE => :handle_start_module_token,
  TokenType::END_MODULE => :handle_end_module_token,
  TokenType::START_DEF => :handle_start_definition_token,
  TokenType::START_MEMO => :handle_start_memo_token,
  TokenType::END_DEF => :handle_end_definition_token,
  TokenType::WORD => :handle_word_token
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeInterpreter

Returns a new instance of Interpreter.



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/forthic/interpreter.rb', line 120

def initialize
  @registered_modules = {}
  @screens = {}
  @default_module_flags = {}
  @module_flags = {}

  @global_module = GlobalModule.new(self)
  @app_module = ForthicModule.new("", self)

  @execution_state = ExecutionState.new(@app_module)
  @profiling_state = ProfilingState.new
end

Instance Attribute Details

#app_moduleObject (readonly)

Core interpreter components



100
101
102
# File 'lib/forthic/interpreter.rb', line 100

def app_module
  @app_module
end

#default_module_flagsObject

Screen and module management



102
103
104
# File 'lib/forthic/interpreter.rb', line 102

def default_module_flags
  @default_module_flags
end

#execution_stateObject (readonly)

State objects



104
105
106
# File 'lib/forthic/interpreter.rb', line 104

def execution_state
  @execution_state
end

#global_moduleObject (readonly)

Core interpreter components



100
101
102
# File 'lib/forthic/interpreter.rb', line 100

def global_module
  @global_module
end

#module_flagsObject

Screen and module management



102
103
104
# File 'lib/forthic/interpreter.rb', line 102

def module_flags
  @module_flags
end

#profiling_stateObject (readonly)

State objects



104
105
106
# File 'lib/forthic/interpreter.rb', line 104

def profiling_state
  @profiling_state
end

#registered_modulesObject (readonly)

Core interpreter components



100
101
102
# File 'lib/forthic/interpreter.rb', line 100

def registered_modules
  @registered_modules
end

#screensObject

Screen and module management



102
103
104
# File 'lib/forthic/interpreter.rb', line 102

def screens
  @screens
end

Instance Method Details

#add_timestamp(label) ⇒ Object



320
321
322
# File 'lib/forthic/interpreter.rb', line 320

def add_timestamp(label)
  @profiling_state.add_timestamp(label)
end

#count_word(word) ⇒ Object



316
317
318
# File 'lib/forthic/interpreter.rb', line 316

def count_word(word)
  @profiling_state.count_word(word)
end

#cur_definitionObject



164
165
166
# File 'lib/forthic/interpreter.rb', line 164

def cur_definition
  @execution_state.cur_definition
end

#cur_moduleForthicModule

Returns:



230
231
232
# File 'lib/forthic/interpreter.rb', line 230

def cur_module
  @execution_state.module_stack.last
end

#find_module(name) ⇒ ForthicModule

Parameters:

  • name (String)

Returns:

Raises:

  • (ArgumentError)


236
237
238
239
240
241
242
243
# File 'lib/forthic/interpreter.rb', line 236

def find_module(name)
  raise ArgumentError, "Module name cannot be nil" if name.nil?
  raise ArgumentError, "Module name cannot be empty" if name.empty?

  result = @registered_modules[name]
  raise ForthicError.new(ErrorCodes::MODULE_NOT_FOUND, "Module '#{name}' not found", "Check the module name for typos and ensure it has been properly registered.") unless result
  result
end

#find_word(name) ⇒ Word?

Parameters:

  • name (String)

Returns:



297
298
299
300
301
302
303
304
305
# File 'lib/forthic/interpreter.rb', line 297

def find_word(name)
  result = nil
  @execution_state.module_stack.reverse_each do |m|
    result = m.find_word(name)
    break if result
  end
  result ||= @global_module.find_word(name)
  result
end

#get_app_moduleForthicModule

Returns:



138
139
140
# File 'lib/forthic/interpreter.rb', line 138

def get_app_module
  @app_module
end

#get_flags(module_id) ⇒ Hash

Parameters:

  • module_id (String)

Returns:

  • (Hash)


177
178
179
180
181
182
# File 'lib/forthic/interpreter.rb', line 177

def get_flags(module_id)
  module_flags = @module_flags[module_id] || {}
  result = module_flags.dup
  @module_flags[module_id] = @default_module_flags[module_id].dup
  result
end

#get_screen_forthic(screen_name) ⇒ String

Parameters:

  • screen_name (String)

Returns:

  • (String)

Raises:



198
199
200
201
202
# File 'lib/forthic/interpreter.rb', line 198

def get_screen_forthic(screen_name)
  screen = @screens[screen_name]
  raise ForthicError.new(ErrorCodes::SCREEN_NOT_FOUND, "Unable to find screen \"#{screen_name}\"", "Screen not found. Check the screen name for typos or ensure it has been properly registered.") unless screen
  screen
end

#get_string_locationCodeLocation?

Returns:



143
144
145
# File 'lib/forthic/interpreter.rb', line 143

def get_string_location
  @execution_state.string_location
end

#haltObject



133
134
135
# File 'lib/forthic/interpreter.rb', line 133

def halt
  @execution_state.should_stop = true
end

#handle_comment_token(_token) ⇒ Object

Parameters:



377
378
379
# File 'lib/forthic/interpreter.rb', line 377

def handle_comment_token(_token)
  # Handle comment token (no-op)
end

#handle_end_array_token(_token) ⇒ Object

Parameters:



372
373
374
# File 'lib/forthic/interpreter.rb', line 372

def handle_end_array_token(_token)
  handle_word(EndArrayWord.new)
end

#handle_end_definition_token(token) ⇒ Object

Parameters:

Raises:



398
399
400
401
402
403
404
405
406
407
# File 'lib/forthic/interpreter.rb', line 398

def handle_end_definition_token(token)
  raise ForthicError.new(ErrorCodes::DEFINITION_WITHOUT_START, "Definition ended without start", "A definition was ended when none was active. Check for extra semicolons.", token.location) unless @execution_state.is_compiling
  raise ForthicError.new(ErrorCodes::MISSING_DEFINITION, "No current definition to end", "Internal error: definition state is inconsistent.", token.location) unless @execution_state.cur_definition
  if @execution_state.is_memo_definition
    cur_module.add_memo_words(@execution_state.cur_definition)
  else
    cur_module.add_word(@execution_state.cur_definition)
  end
  @execution_state.is_compiling = false
end

#handle_end_module_token(_token) ⇒ Object

Parameters:



359
360
361
362
363
364
# File 'lib/forthic/interpreter.rb', line 359

def handle_end_module_token(_token)
  word = EndModuleWord.new
  @execution_state.cur_definition.add_word(word) if @execution_state.is_compiling
  count_word(word)
  word.execute(self)
end

#handle_start_array_token(token) ⇒ Object

Parameters:



367
368
369
# File 'lib/forthic/interpreter.rb', line 367

def handle_start_array_token(token)
  handle_word(PushValueWord.new("<start_array_token>", token))
end

#handle_start_definition_token(token) ⇒ Object

Parameters:

Raises:



382
383
384
385
386
387
# File 'lib/forthic/interpreter.rb', line 382

def handle_start_definition_token(token)
  raise ForthicError.new(ErrorCodes::NESTED_DEFINITION, "Nested definition not allowed", "A definition was started while another definition is active. Ensure all definitions end with semicolons.", token.location) if @execution_state.is_compiling
  @execution_state.cur_definition = DefinitionWord.new(token.string)
  @execution_state.is_compiling = true
  @execution_state.is_memo_definition = false
end

#handle_start_memo_token(token) ⇒ Object

Parameters:

Raises:



390
391
392
393
394
395
# File 'lib/forthic/interpreter.rb', line 390

def handle_start_memo_token(token)
  raise ForthicError.new(ErrorCodes::NESTED_MEMO_DEFINITION, "Nested memo definition not allowed", "A memo definition was started while another definition is active. Ensure all definitions end with semicolons.", token.location) if @execution_state.is_compiling
  @execution_state.cur_definition = DefinitionWord.new(token.string)
  @execution_state.is_compiling = true
  @execution_state.is_memo_definition = true
end

#handle_start_module_token(token) ⇒ Object

Parameters:



351
352
353
354
355
356
# File 'lib/forthic/interpreter.rb', line 351

def handle_start_module_token(token)
  word = StartModuleWord.new(token.string)
  @execution_state.cur_definition.add_word(word) if @execution_state.is_compiling
  count_word(word)
  word.execute(self)
end

#handle_string_token(token) ⇒ Object

Parameters:



345
346
347
348
# File 'lib/forthic/interpreter.rb', line 345

def handle_string_token(token)
  value = PositionedString.new(token.string, token.location)
  handle_word(PushValueWord.new("<string>", value))
end

#handle_token(token) ⇒ Object

Parameters:



333
334
335
336
337
338
339
340
341
342
# File 'lib/forthic/interpreter.rb', line 333

def handle_token(token)
  return if token.type == TokenType::EOS

  handler = TOKEN_HANDLERS[token.type]
  if handler
    send(handler, token)
  else
    raise ForthicError.new(ErrorCodes::UNKNOWN_TOKEN, "Unknown token type '#{token.string}'", "This token type is not recognized. Check for typos or unsupported syntax.", token.location)
  end
end

#handle_word(word, location = nil) ⇒ Object

Parameters:



418
419
420
421
422
423
424
425
426
# File 'lib/forthic/interpreter.rb', line 418

def handle_word(word, location = nil)
  if @execution_state.is_compiling
    word.set_location(location)
    @execution_state.cur_definition.add_word(word)
  else
    count_word(word)
    word.execute(self)
  end
end

#handle_word_token(token) ⇒ Object

Parameters:

Raises:



410
411
412
413
414
# File 'lib/forthic/interpreter.rb', line 410

def handle_word_token(token)
  word = find_word(token.string)
  raise ForthicError.new(ErrorCodes::WORD_NOT_FOUND, "Word '#{token.string}' not found", "Check for typos in the word name or ensure the word has been defined.", token.location) unless word
  handle_word(word, token.location)
end

#import_module(mod, prefix = "") ⇒ Object

Parameters:

Raises:

  • (ArgumentError)


277
278
279
280
281
# File 'lib/forthic/interpreter.rb', line 277

def import_module(mod, prefix = "")
  raise ArgumentError, "Module cannot be nil" if mod.nil?
  register_module(mod)
  @app_module.import_module(prefix, mod, self)
end

#is_compilingObject



160
161
162
# File 'lib/forthic/interpreter.rb', line 160

def is_compiling
  @execution_state.is_compiling
end

#modify_flags(module_id, flags) ⇒ Object

Parameters:

  • module_id (String)
  • flags (Hash)


186
187
188
189
# File 'lib/forthic/interpreter.rb', line 186

def modify_flags(module_id, flags)
  module_flags = @module_flags[module_id] || {}
  @module_flags[module_id] = module_flags.merge(flags)
end

#module_stackObject



156
157
158
# File 'lib/forthic/interpreter.rb', line 156

def module_stack
  @execution_state.module_stack
end

#module_stack_popObject



264
265
266
# File 'lib/forthic/interpreter.rb', line 264

def module_stack_pop
  @execution_state.module_stack.pop
end

#module_stack_push(mod) ⇒ Object

Parameters:

Raises:

  • (ArgumentError)


259
260
261
262
# File 'lib/forthic/interpreter.rb', line 259

def module_stack_push(mod)
  raise ArgumentError, "Module cannot be nil" if mod.nil?
  @execution_state.module_stack.push(mod)
end

#profile_timestampsObject



328
329
330
# File 'lib/forthic/interpreter.rb', line 328

def profile_timestamps
  @profiling_state.timestamps
end

#register_module(mod) ⇒ Object

Parameters:

Raises:

  • (ArgumentError)


269
270
271
272
273
# File 'lib/forthic/interpreter.rb', line 269

def register_module(mod)
  raise ArgumentError, "Module cannot be nil" if mod.nil?
  raise ArgumentError, "Module must respond to :name" unless mod.respond_to?(:name)
  @registered_modules[mod.name] = mod
end

#resetObject



191
192
193
194
# File 'lib/forthic/interpreter.rb', line 191

def reset
  @app_module.variables = {}
  @execution_state.reset(@app_module)
end

#run(string, reference_location = nil) ⇒ Boolean

Parameters:

  • string (String)
  • reference_location (CodeLocation, nil) (defaults to: nil)

Returns:

  • (Boolean)


207
208
209
210
# File 'lib/forthic/interpreter.rb', line 207

def run(string, reference_location = nil)
  tokenizer = Tokenizer.new(string, reference_location)
  run_with_tokenizer(tokenizer)
end

#run_module_code(mod) ⇒ Object

Parameters:



284
285
286
287
288
289
290
291
292
293
# File 'lib/forthic/interpreter.rb', line 284

def run_module_code(mod)
  raise ArgumentError, "Module cannot be nil" if mod.nil?
  module_stack_push(mod)
  run(mod.forthic_code)
  module_stack_pop
rescue => e
  error = ForthicError.new(ErrorCodes::MODULE_EXECUTION_ERROR, "Error executing module '#{mod.name}'", "An error occurred while running the module code. Check the module implementation for syntax errors.")
  error.set_caught_error(e)
  raise error
end

#run_with_tokenizer(tokenizer) ⇒ Boolean

Parameters:

Returns:

  • (Boolean)


214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/forthic/interpreter.rb', line 214

def run_with_tokenizer(tokenizer)
  token = nil
  loop do
    token = tokenizer.next_token
    handle_token(token)
    break if token.type == TokenType::EOS || @execution_state.should_stop
    next if [TokenType::START_DEF, TokenType::END_DEF, TokenType::COMMENT].include?(token.type) || @execution_state.is_compiling
  end
  true
rescue => e
  error = ForthicError.new(ErrorCodes::EXECUTION_ERROR, "Error executing token '#{token&.string}'", "An unexpected error occurred during execution. Check the token syntax and try again.", token&.location)
  error.set_caught_error(e)
  raise error
end

#set_flags(module_id, flags) ⇒ Object

Parameters:

  • module_id (String)
  • flags (Hash)


170
171
172
173
# File 'lib/forthic/interpreter.rb', line 170

def set_flags(module_id, flags)
  @default_module_flags[module_id] = flags
  @module_flags[module_id] = flags
end

#stackObject

Delegation methods for execution state



148
149
150
# File 'lib/forthic/interpreter.rb', line 148

def stack
  @execution_state.stack
end

#stack=(new_stack) ⇒ Object



152
153
154
# File 'lib/forthic/interpreter.rb', line 152

def stack=(new_stack)
  @execution_state.stack = new_stack
end

#stack_popObject

Returns:

  • (Object)

Raises:



251
252
253
254
255
256
# File 'lib/forthic/interpreter.rb', line 251

def stack_pop
  raise ForthicError.new(ErrorCodes::STACK_UNDERFLOW, "Stack underflow", "Attempted to pop from an empty stack. This indicates a logical error in the Forthic code.") if @execution_state.stack.empty?
  result = @execution_state.stack.pop
  @execution_state.string_location = result.is_a?(PositionedString) ? result.location : nil
  result.is_a?(PositionedString) ? result.value_of : result
end

#stack_push(val) ⇒ Object

Parameters:

  • val (Object)


246
247
248
# File 'lib/forthic/interpreter.rb', line 246

def stack_push(val)
  @execution_state.stack.push(val)
end

#start_profilingObject

Delegation methods for profiling



308
309
310
# File 'lib/forthic/interpreter.rb', line 308

def start_profiling
  @profiling_state.start_profiling
end

#stop_profilingObject



312
313
314
# File 'lib/forthic/interpreter.rb', line 312

def stop_profiling
  @profiling_state.stop_profiling
end

#word_histogramObject



324
325
326
# File 'lib/forthic/interpreter.rb', line 324

def word_histogram
  @profiling_state.word_histogram
end