defmodule ForthVM do @moduledoc """ A toy Forth-like virtual machine. I have written it to experiment implementing a stack-based preemtive multitasking interpreter (and to play) with Elixir. """ @doc """ Starts a new VM supervisor, initializin `num_cores` cores. ## Examples ForthVM.start(num_cores: 2) {:ok, #PID<0.375.0>} """ def start(num_cores: num_cores) do children = [ {ForthVM.Supervisor, num_cores: num_cores} ] opts = [strategy: :one_for_one, name: ForthVM] Supervisor.start_link(children, opts) end @doc """ Returns a map with cores' id as keys and cores' pid as values. ## Examples ForthVM.Supervisor.cores() %{"core_1" => #PID<0.407.0>, "core_2" => #PID<0.408.0>} """ defdelegate cores(), to: ForthVM.Supervisor @doc """ Returns the PID for the Core with the given `core_id` string. ## Examples ForthVM.core_pid("core_2") #PID<0.408.0> """ defdelegate core_pid(core_id), to: ForthVM.Supervisor @doc """ Executes Forth code in `source` string using `process_id` Process managed by the `core_id` Core. Optionally, a custom Forth `dictionary` can be passed. Returns the updated Core state. ## Examples ForthVM.execute("core_2", "p_1", "40 2 +") %ForthVM.Core{ id: 2, io: :stdio, processes: [ %ForthVM.Process{ context: {[], '*', [], %{ "dup" => {:word, &ForthVM.Words.Stack.dup/5, %{doc: "duplicate element from top of stack", stack: "( x -- x x )"}}, ... }, %{ core_id: 2, debug: false, io: %{ device: :stdio, devices: %{"core_io" => :stdio, "stdio" => :stdio} }, messages: [], process_id: "p_1", reductions: 997, sleep: 0 }}, core_id: nil, exit_value: 42, id: "p_1", status: :exit } ] } """ defdelegate execute(core_id, process_id, source, dictionary \\ nil), to: ForthVM.Supervisor @doc """ Loads Forth code from the `source` string into `process_id` Process managed by `core_id` Core, replacing all code currrently stored into the process. The loaded code is executed right away. If the `process_id` does not exist, a message will be logged, but no error raised. Returns the updated Core state. """ defdelegate load(core_id, process_id, source), to: ForthVM.Supervisor @doc """ Spawns a new process with given `process_id` that will be managed by the `core_id` Core, If `process_id` is `nil`, an new id will be automatically generated using `System.unique_integer()`. Returns the newly spawned Process' state. ## Examples ForthVM.spawn("core_2", "p_new") %ForthVM.Process{ context: {[], [], [], %{ "<<" => {:word, &ForthVM.Words.Logic.b_shift_left/5, %{doc: "bitwise shift left", stack: "( x y -- v )"}}, ... }, %{ core_id: 2, debug: false, io: %{device: :stdio, devices: %{"core_io" => :stdio, "stdio" => :stdio}}, messages: [], process_id: "p_new", reductions: 0, sleep: 0 }}, core_id: nil, exit_value: nil, id: "p_new", status: nil } """ defdelegate spawn(core_id, process_id, dictionary \\ nil), to: ForthVM.Supervisor @doc """ Sends a message to `process_id` Process managed by `core_id` Core: `word_name` is the name of the dictionary's word that will handle the message, `message_data` is a list containing the data to be placed on top of the data stack. The message will place `{word_name, message_data}` into the Process' messages FIFO queue. Messages are handled when the Process has no more tokens to process: - `word_name` is placed into the list of tokens to execute - `message_data` list is joined with the data stack - the message is removed from the `messages` queue This is a cast call, so nothing is returned. ## Examples ForthVM.send_message("core_2", "p_new", ".", ["hello world"]) :ok hello world """ defdelegate send_message(core_id, process_id, word_name, message_data), to: ForthVM.Supervisor end