diff --git a/assets/test_prelude.ld b/assets/test_prelude.ld index 85e2809..30ac191 100644 --- a/assets/test_prelude.ld +++ b/assets/test_prelude.ld @@ -1014,6 +1014,108 @@ fn assert! { else panic! "Assert failed: {msg} with {value}" } + +&&& processes: doing more than one thing +fn self { + "Returns the current process's pid, as a keyword." + () -> base :process (:self) +} + +fn send { + "Sends a message to the specified process and returns the message." + (pid as :keyword, msg) -> { + base :process (:send, pid, msg) + msg + } +} + +fn spawn! { + "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." + (f as :fn) -> base :process (:spawn, f) +} + +fn yield! { + "Forces a process to yield." + () -> base :process (:yield) +} + +fn alive? { + "Tells if the passed keyword is the id for a live process." + (pid as :keyword) -> base :process (:alive, pid) +} + +fn link! { + "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." + (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) + (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) + (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) +} + +fn flush! { + "Clears the current process's mailbox and returns all the messages." + () -> base :process (:flush) +} + +fn sleep! { + "Puts the current process to sleep for at least the specified number of milliseconds." + (ms as :number) -> base :process (:sleep, ms) +} + +& TODO: make this more robust, to handle multiple pending requests w/o data races +fn request_fetch! { + (pid as :keyword, url as :string) -> { + store! (fetch_outbox, url) + request_fetch! (pid) + } + (pid as :keyword) -> { + if empty? (unbox (fetch_inbox)) + then { + yield! () + request_fetch! (pid) + } + else { + send (pid, (:reply, unbox (fetch_inbox))) + store! (fetch_inbox, ()) + } + } +} + +fn fetch { + "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." + (url) -> { + let pid = self () + spawn! (fn () -> request_fetch! (pid, url)) + receive { + (:reply, response) -> response + } + } +} + +fn input_reader! { + (pid as :keyword) -> { + if do input > unbox > not + then { + yield! () + input_reader! (pid) + } + else { + send (pid, (:reply, unbox (input))) + store! (input, nil) + } + } +} + +fn read_input { + "Waits until there is input in the input buffer, and returns it once there is." + () -> { + let pid = self () + spawn! (fn () -> input_reader! (pid)) + receive { + (:reply, response) -> response + } + } +} + &&& Turtle & other graphics & some basic colors @@ -1180,6 +1282,46 @@ fn loadstate! { } } +fn turtle_listener () -> { + receive { + (:forward!, steps as :number) -> add_command! (self (), (:forward, steps)) + (:back!, steps as :number) -> add_command! (self (), (:back, steps)) + (:left!, turns as :number) -> add_command! (self (), (:left, turns)) + (:right!, turns as :number) -> add_command! (self (), (:right, turns)) + (:penup!) -> add_command!(self (), (:penup)) + (:pendown!) -> add_command! (self (), (:pendown)) + (:pencolor!, color as :keyword) -> add_command! (self (), (:pencolor, color)) + (:pencolor!, gray as :number) -> add_command! (self (), (:pencolor, (gray, gray, gray, 255))) + (:pencolor!, (r as :number, g as :number, b as :number)) -> add_command! (self (), (:pencolor, (r, g, b, 255))) + (:pencolor!, (r as :number, g as :number, b as :number, a as :number)) -> add_command! (self (), (:pencolor, (r, g, b, a))) + (:penwidth!, width as :number) -> add_command! (self (), (:penwidth, width)) + (:home!) -> add_command! (self (), (:home)) + (:goto!, x as :number, y as :number) -> add_command! (self (), (:goto, (x, y))) + (:goto!, (x as :number, y as :number)) -> add_command! (self (), (:goto, (x, y))) + (:show!) -> add_command! (self (), (:show)) + (:hide!) -> add_command! (self (), (:hide)) + (:loadstate!, state) -> { + let #{position, heading, pendown?, pencolor, penwidth, visible?} = state + add_command! (self (), (:loadstate, position, heading, visible?, pendown?, penwidth, pencolor)) + + } + (:pencolor, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :pencolor)) + (:position, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :position)) + (:penwidth, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :penwidth)) + (:heading, pid) -> send (pid, (:reply, do turtle_states > unbox > self () > :heading)) + does_not_understand -> { + let pid = self () + panic! "{pid} does not understand message: {does_not_understand}" + } + } + turtle_listener () +} + +fn spawn_turtle! { + "Spawns a new turtle in a new process. Methods on the turtle process mirror those of turtle graphics functions in prelude. Returns the pid of the new turtle." + () -> spawn! (fn () -> turtle_listener ()) +} + fn apply_command { "Takes a turtle state and a command and calculates a new state." (state, command) -> { @@ -1217,9 +1359,8 @@ fn apply_command { }} } -& position () -> (x, y) fn position { - "Returns the turtle's current position." + "Returns the turtle's current position as an `(x, y)` vector tuple." () -> do turtle_states > unbox > :turtle_0 > :position } @@ -1245,6 +1386,7 @@ fn penwidth { () -> do turtle_states > unbox > :turtle_0 > :penwidth } + &&& fake some lispisms with tuples fn cons { "Old-timey lisp `cons`. `Cons`tructs a tuple out of two arguments." @@ -1266,108 +1408,6 @@ fn llist { (...xs) -> foldr (cons, xs, nil) } -&&& processes -fn self { - "Returns the current process's pid, as a keyword." - () -> base :process (:self) -} - -fn send { - "Sends a message to the specified process and returns the message." - (pid as :keyword, msg) -> { - base :process (:send, pid, msg) - msg - } -} - -fn spawn! { - "Spawns a process. Takes a 0-argument (nullary) function that will be executed as the new process. Returns a keyword process ID (pid) of the newly spawned process." - (f as :fn) -> base :process (:spawn, f) -} - -fn yield! { - "Forces a process to yield." - () -> base :process (:yield) -} - -& TODO: implement these in the VM -fn alive? { - "Tells if the passed keyword is the id for a live process." - (pid as :keyword) -> base :process (:alive, pid) -} - -fn link! { - "Creates a link between two processes. There are two types of links: `:report`, which sends a message to pid1 when pid2 dies; and `:panic`, which causes a panic in one when the other dies. The default is `:report`." - (pid1 as :keyword, pid2 as :keyword) -> link! (pid1, pid2, :report) - (pid1 as :keyword, pid2 as :keyword, :report) -> base :process (:link_report, pid1, pid2) - (pid1 as :keyword, pid2 as :keyword, :panic) -> base :process (:link_panic, pid1, pid2) -} - -fn flush! { - "Clears the current process's mailbox and returns all the messages." - () -> base :process (:flush) -} - -fn sleep! { - "Puts the current process to sleep for at least the specified number of milliseconds." - (ms as :number) -> base :process (:sleep, ms) -} - -& TODO: make this more robust, to handle multiple pending requests w/o data races -fn request_fetch! { - (pid as :keyword, url as :string) -> { - store! (fetch_outbox, url) - request_fetch! (pid) - } - (pid as :keyword) -> { - if empty? (unbox (fetch_inbox)) - then { - yield! () - request_fetch! (pid) - } - else { - send (pid, (:reply, unbox (fetch_inbox))) - store! (fetch_inbox, ()) - } - } -} - -fn fetch { - "Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`." - (url) -> { - let pid = self () - spawn! (fn () -> request_fetch! (pid, url)) - receive { - (:reply, response) -> response - } - } -} - -fn input_reader! { - (pid as :keyword) -> { - if do input > unbox > not - then { - yield! () - input_reader! (pid) - } - else { - send (pid, (:reply, unbox (input))) - store! (input, nil) - } - } -} - -fn read_input { - "Waits until there is input in the input buffer, and returns it once there is." - () -> { - let pid = self () - spawn! (fn () -> input_reader! (pid)) - receive { - (:reply, response) -> response - } - } -} - #{ & completed actor functions self @@ -1380,6 +1420,7 @@ fn read_input { & wip actor functions & link! + spawn_turtle! & shared memory w/ rust & `box`es are actually way cool diff --git a/pkg/ludus.js b/pkg/ludus.js index f7fa38f..b215078 100644 --- a/pkg/ludus.js +++ b/pkg/ludus.js @@ -1,4 +1,4 @@ -if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, new_p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} +if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, svg, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up} const worker_url = new URL("worker.js", import.meta.url) const worker = new Worker(worker_url, {type: "module"}) @@ -440,6 +440,7 @@ function svg_render_turtle (state) { ` } +// TODO: update this to match the new `p5` function export function svg (commands) { // console.log(commands) const states = [turtle_init] @@ -509,32 +510,32 @@ function p5_render_turtle (state, calls) { return calls } -export function p5 (commands) { - const states = [turtle_init] - commands.reduce((prev_state, command) => { - const new_state = command_to_state(prev_state, command) - states.push(new_state) - return new_state - }, turtle_init) - // console.log(states) - const [r, g, b, _] = resolve_color(background_color) - if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] - const p5_calls = [...p5_call_root()] - for (let i = 1; i < states.length; ++i) { - const prev = states[i - 1] - const curr = states[i] - const calls = states_to_call(prev, curr) - for (const call of calls) { - p5_calls.push(call) - } - } - p5_calls[0] = ["background", ...resolve_color(background_color)] - p5_render_turtle(states[states.length - 1], p5_calls) - p5_calls.push(["pop"]) - return p5_calls -} +// export function p5 (commands) { +// const states = [turtle_init] +// commands.reduce((prev_state, command) => { +// const new_state = command_to_state(prev_state, command) +// states.push(new_state) +// return new_state +// }, turtle_init) +// // console.log(states) +// const [r, g, b, _] = resolve_color(background_color) +// if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] +// const p5_calls = [...p5_call_root()] +// for (let i = 1; i < states.length; ++i) { +// const prev = states[i - 1] +// const curr = states[i] +// const calls = states_to_call(prev, curr) +// for (const call of calls) { +// p5_calls.push(call) +// } +// } +// p5_calls[0] = ["background", ...resolve_color(background_color)] +// p5_render_turtle(states[states.length - 1], p5_calls) +// p5_calls.push(["pop"]) +// return p5_calls +// } -export function new_p5 (commands) { +export function p5 (commands) { const all_states = {} commands.reduce((prev_state, command) => { const [turtle_id, new_state] = command_to_state(prev_state, command) @@ -546,7 +547,6 @@ export function new_p5 (commands) { if ((r + g + b)/3 > 128) turtle_color = [0, 0, 0, 150] const p5_calls = [...p5_call_root()] for (const states of Object.values(all_states)) { - console.log(states) for (let i = 1; i < states.length; ++i) { const prev = states[i - 1] const curr = states[i] diff --git a/pkg/rudus_bg.wasm b/pkg/rudus_bg.wasm index b169fcd..fa4203e 100644 Binary files a/pkg/rudus_bg.wasm and b/pkg/rudus_bg.wasm differ