spawn is now a special form
This commit is contained in:
parent
8ce6a33573
commit
bbdab93cf0
|
@ -1247,10 +1247,10 @@ fn send {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn! {
|
& fn spawn! {
|
||||||
"Spawns a new process running the function passed in."
|
& "Spawns a new process running the function passed in."
|
||||||
(f as :fn) -> base :process (:spawn, f)
|
& (f as :fn) -> base :process (:spawn, f)
|
||||||
}
|
& }
|
||||||
|
|
||||||
fn yield! {
|
fn yield! {
|
||||||
"Forces a process to yield."
|
"Forces a process to yield."
|
||||||
|
@ -1303,7 +1303,7 @@ fn fetch {
|
||||||
"Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`."
|
"Requests the contents of the URL passed in. Returns a result tuple of `(:ok, {contents})` or `(:err, {status code})`."
|
||||||
(url) -> {
|
(url) -> {
|
||||||
let pid = self ()
|
let pid = self ()
|
||||||
spawn! (fn () -> request_fetch! (pid, url))
|
spawn! request_fetch! (pid, url)
|
||||||
receive {
|
receive {
|
||||||
(:reply, response) -> response
|
(:reply, response) -> response
|
||||||
}
|
}
|
||||||
|
@ -1312,7 +1312,7 @@ fn fetch {
|
||||||
|
|
||||||
fn input_reader! {
|
fn input_reader! {
|
||||||
(pid as :keyword) -> {
|
(pid as :keyword) -> {
|
||||||
if not (unbox (input))
|
if do input > unbox > not
|
||||||
then {
|
then {
|
||||||
yield! ()
|
yield! ()
|
||||||
input_reader! (pid)
|
input_reader! (pid)
|
||||||
|
@ -1328,7 +1328,7 @@ fn read_input {
|
||||||
"Waits until there is input in the input buffer, and returns it once there is."
|
"Waits until there is input in the input buffer, and returns it once there is."
|
||||||
() -> {
|
() -> {
|
||||||
let pid = self ()
|
let pid = self ()
|
||||||
spawn! (fn () -> input_reader! (pid))
|
spawn! input_reader! (pid)
|
||||||
receive {
|
receive {
|
||||||
(:reply, response) -> response
|
(:reply, response) -> response
|
||||||
}
|
}
|
||||||
|
@ -1339,7 +1339,7 @@ fn read_input {
|
||||||
& completed actor functions
|
& completed actor functions
|
||||||
self
|
self
|
||||||
send
|
send
|
||||||
spawn!
|
& spawn!
|
||||||
yield!
|
yield!
|
||||||
sleep!
|
sleep!
|
||||||
alive?
|
alive?
|
||||||
|
|
27
pkg/ludus.js
27
pkg/ludus.js
|
@ -13,18 +13,6 @@ let ready = false
|
||||||
let io_interval_id = null
|
let io_interval_id = null
|
||||||
let keys_down = new Set();
|
let keys_down = new Set();
|
||||||
|
|
||||||
function reset_ludus () {
|
|
||||||
outbox = []
|
|
||||||
ludus_console = ""
|
|
||||||
ludus_commands = []
|
|
||||||
ludus_result = null
|
|
||||||
code = null
|
|
||||||
running = false
|
|
||||||
ready = false
|
|
||||||
io_interval_id = null
|
|
||||||
keys_down = new Set()
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.onmessage = handle_messages
|
worker.onmessage = handle_messages
|
||||||
|
|
||||||
async function handle_messages (e) {
|
async function handle_messages (e) {
|
||||||
|
@ -99,17 +87,28 @@ function io_poller () {
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_io_polling () {
|
function start_io_polling () {
|
||||||
io_interval_id = setInterval(io_poller, 10)
|
io_interval_id = setInterval(io_poller, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runs a ludus script; does not return the result
|
// runs a ludus script; does not return the result
|
||||||
// the result must be explicitly polled with `result`
|
// the result must be explicitly polled with `result`
|
||||||
export function run (source) {
|
export function run (source) {
|
||||||
if (running || ready) {
|
if (running || ready) {
|
||||||
|
console.log("Main: received bouncy `run` call");
|
||||||
return "TODO: handle this? should not be running"
|
return "TODO: handle this? should not be running"
|
||||||
}
|
}
|
||||||
|
// start the vm
|
||||||
worker.postMessage([{verb: "Run", data: source}])
|
worker.postMessage([{verb: "Run", data: source}])
|
||||||
reset_ludus()
|
// reset all my state
|
||||||
|
outbox = []
|
||||||
|
ludus_console = ""
|
||||||
|
ludus_commands = []
|
||||||
|
ludus_result = null
|
||||||
|
code = null
|
||||||
|
running = true
|
||||||
|
ready = false
|
||||||
|
keys_down = new Set();
|
||||||
|
// start the polling loop loop
|
||||||
start_io_polling()
|
start_io_polling()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -403,7 +403,7 @@ function __wbg_get_imports() {
|
||||||
_assertBoolean(ret);
|
_assertBoolean(ret);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper8065 = function() { return logError(function (arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper8087 = function() { return logError(function (arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20);
|
const ret = makeMutClosure(arg0, arg1, 354, __wbg_adapter_20);
|
||||||
return ret;
|
return ret;
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
|
|
Binary file not shown.
|
@ -18,7 +18,7 @@ export function io (out) {
|
||||||
resolve(JSON.stringify(e.data))
|
resolve(JSON.stringify(e.data))
|
||||||
}
|
}
|
||||||
// cancel the response if it takes too long
|
// cancel the response if it takes too long
|
||||||
setTimeout(() => reject("io took too long"), 500)
|
setTimeout(() => reject("io took too long"), 1000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ pub enum Ast {
|
||||||
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
WhenClause(Box<Spanned<Self>>, Box<Spanned<Self>>),
|
||||||
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
Match(Box<Spanned<Self>>, Vec<Spanned<Self>>),
|
||||||
Receive(Vec<Spanned<Self>>),
|
Receive(Vec<Spanned<Self>>),
|
||||||
|
Spawn(Box<Spanned<Self>>),
|
||||||
MatchClause(
|
MatchClause(
|
||||||
Box<Spanned<Self>>,
|
Box<Spanned<Self>>,
|
||||||
Box<Option<Spanned<Self>>>,
|
Box<Option<Spanned<Self>>>,
|
||||||
|
@ -210,6 +211,7 @@ impl Ast {
|
||||||
.join(" > ")
|
.join(" > ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spawn(body) => format!("spawn! {}", body.0.show()),
|
||||||
Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
|
Repeat(times, body) => format!("repeat {} {{\n{}\n}}", times.0.show(), body.0.show()),
|
||||||
Splat(word) => format!("...{}", word),
|
Splat(word) => format!("...{}", word),
|
||||||
Splattern(pattern) => format!("...{}", pattern.0.show()),
|
Splattern(pattern) => format!("...{}", pattern.0.show()),
|
||||||
|
@ -380,6 +382,7 @@ impl fmt::Display for Ast {
|
||||||
.join(" > ")
|
.join(" > ")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spawn(body) => write!(f, "spawn: {}", body.0),
|
||||||
Repeat(_times, _body) => todo!(),
|
Repeat(_times, _body) => todo!(),
|
||||||
Splat(word) => {
|
Splat(word) => {
|
||||||
write!(f, "splat: {}", word)
|
write!(f, "splat: {}", word)
|
||||||
|
|
|
@ -43,7 +43,7 @@ impl Chunk {
|
||||||
| Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return
|
| Not | Panic | EmptyString | ConcatStrings | Stringify | MatchType | Return
|
||||||
| UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
|
| UnconditionalMatch | Print | AppendList | ConcatList | PushList | PushDict
|
||||||
| AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
|
| AppendDict | ConcatDict | Nothing | PushGlobal | SetUpvalue | LoadMessage
|
||||||
| NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee => {
|
| NextMessage | MatchMessage | ClearMessage | SendMethod | LoadScrutinee | Spawn => {
|
||||||
console_log!("{i:04}: {op}")
|
console_log!("{i:04}: {op}")
|
||||||
}
|
}
|
||||||
Constant | MatchConstant => {
|
Constant | MatchConstant => {
|
||||||
|
|
|
@ -1059,6 +1059,54 @@ impl Compiler {
|
||||||
self.emit_op(Op::Load);
|
self.emit_op(Op::Load);
|
||||||
self.stack_depth += 1;
|
self.stack_depth += 1;
|
||||||
}
|
}
|
||||||
|
Spawn(body) => {
|
||||||
|
// we compile this identically to a function
|
||||||
|
// but it's a single, nullary fn
|
||||||
|
// no match at the beginning, just go
|
||||||
|
let name = "_spawned";
|
||||||
|
let mut compiler = Compiler::new(
|
||||||
|
body,
|
||||||
|
self.input,
|
||||||
|
self.src,
|
||||||
|
self.depth + 1,
|
||||||
|
self.chunk.env.clone(),
|
||||||
|
self.debug,
|
||||||
|
);
|
||||||
|
compiler.tail_pos = true;
|
||||||
|
compiler.visit(body);
|
||||||
|
compiler.store();
|
||||||
|
compiler.scope_depth -= 1;
|
||||||
|
while let Some(binding) = compiler.bindings.last() {
|
||||||
|
if binding.depth > compiler.scope_depth {
|
||||||
|
compiler.bindings.pop();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compiler.pop_n(compiler.stack_depth);
|
||||||
|
compiler.stack_depth = 0;
|
||||||
|
compiler.emit_op(Op::Return);
|
||||||
|
let arities = vec![0];
|
||||||
|
let chunks = vec![compiler.chunk];
|
||||||
|
let lfn = crate::value::LFn::Defined {
|
||||||
|
name,
|
||||||
|
doc: None,
|
||||||
|
arities,
|
||||||
|
chunks,
|
||||||
|
splat: 0,
|
||||||
|
closed: RefCell::new(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let the_fn = Value::Fn(Rc::new(lfn));
|
||||||
|
|
||||||
|
self.emit_constant(the_fn);
|
||||||
|
for upvalue in compiler.upvalues {
|
||||||
|
self.resolve_binding(upvalue);
|
||||||
|
self.emit_op(Op::SetUpvalue);
|
||||||
|
self.stack_depth -= 1;
|
||||||
|
}
|
||||||
|
self.emit_op(Op::Spawn);
|
||||||
|
}
|
||||||
Receive(clauses) => {
|
Receive(clauses) => {
|
||||||
let tail_pos = self.tail_pos;
|
let tail_pos = self.tail_pos;
|
||||||
self.emit_op(Op::ClearMessage);
|
self.emit_op(Op::ClearMessage);
|
||||||
|
|
|
@ -82,10 +82,13 @@ pub async fn send_err_to_ludus_console(msg: String) {
|
||||||
|
|
||||||
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
|
pub async fn do_io (msgs: Vec<MsgOut>) -> Vec<MsgIn> {
|
||||||
let json = serde_json::to_string(&msgs).unwrap();
|
let json = serde_json::to_string(&msgs).unwrap();
|
||||||
let inbox = io (json).await;
|
let inbox = io(json).await;
|
||||||
let inbox = match inbox {
|
let inbox = match inbox {
|
||||||
Ok(msgs) => msgs,
|
Ok(msgs) => msgs,
|
||||||
Err(_) => return vec![]
|
Err(errs) => {
|
||||||
|
console_log!("error receiving messages in io; {:?}", errs);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let inbox = inbox.as_string().expect("response should be a string");
|
let inbox = inbox.as_string().expect("response should be a string");
|
||||||
let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
|
let inbox: Vec<MsgIn> = serde_json::from_str(inbox.as_str()).expect("response from js should be valid");
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub fn lexer(
|
||||||
"nil" => Token::Nil,
|
"nil" => Token::Nil,
|
||||||
// todo: hard code these as type constructors
|
// todo: hard code these as type constructors
|
||||||
"as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!"
|
"as" | "box" | "do" | "else" | "fn" | "if" | "let" | "loop" | "match" | "panic!"
|
||||||
| "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" => {
|
| "recur" | "repeat" | "then" | "when" | "with" | "or" | "and" | "receive" | "spawn!" => {
|
||||||
Token::Reserved(word)
|
Token::Reserved(word)
|
||||||
}
|
}
|
||||||
_ => Token::Word(word),
|
_ => Token::Word(word),
|
||||||
|
|
|
@ -106,6 +106,7 @@ pub enum Op {
|
||||||
MatchMessage,
|
MatchMessage,
|
||||||
ClearMessage,
|
ClearMessage,
|
||||||
SendMethod,
|
SendMethod,
|
||||||
|
Spawn,
|
||||||
|
|
||||||
LoadScrutinee,
|
LoadScrutinee,
|
||||||
}
|
}
|
||||||
|
@ -208,6 +209,7 @@ impl std::fmt::Display for Op {
|
||||||
MatchMessage => "match_message",
|
MatchMessage => "match_message",
|
||||||
ClearMessage => "clear_message",
|
ClearMessage => "clear_message",
|
||||||
SendMethod => "send_method",
|
SendMethod => "send_method",
|
||||||
|
Spawn => "spawn",
|
||||||
|
|
||||||
LoadScrutinee => "load_scrutinee",
|
LoadScrutinee => "load_scrutinee",
|
||||||
};
|
};
|
||||||
|
|
|
@ -512,6 +512,10 @@ where
|
||||||
.then(block.clone())
|
.then(block.clone())
|
||||||
.map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span()));
|
.map_with(|(count, body), e| (Repeat(Box::new(count), Box::new(body)), e.span()));
|
||||||
|
|
||||||
|
let spawn = just(Token::Reserved("spawn!"))
|
||||||
|
.ignore_then(nonbinding.clone())
|
||||||
|
.map_with(|body, e| (Spawn(Box::new(body)), e.span()));
|
||||||
|
|
||||||
let fn_guarded = tuple_pattern
|
let fn_guarded = tuple_pattern
|
||||||
.clone()
|
.clone()
|
||||||
.then_ignore(just(Token::Reserved("if")))
|
.then_ignore(just(Token::Reserved("if")))
|
||||||
|
@ -590,6 +594,7 @@ where
|
||||||
.or(conditional)
|
.or(conditional)
|
||||||
.or(block)
|
.or(block)
|
||||||
.or(repeat)
|
.or(repeat)
|
||||||
|
.or(spawn)
|
||||||
.or(r#loop)
|
.or(r#loop)
|
||||||
.labelled("nonbinding expression"),
|
.labelled("nonbinding expression"),
|
||||||
);
|
);
|
||||||
|
|
|
@ -164,6 +164,7 @@ impl<'a> Validator<'a> {
|
||||||
let root = self.ast;
|
let root = self.ast;
|
||||||
match root {
|
match root {
|
||||||
Error => unreachable!(),
|
Error => unreachable!(),
|
||||||
|
Spawn(body) => self.visit(body),
|
||||||
Word(name) | Splat(name) => {
|
Word(name) | Splat(name) => {
|
||||||
if !self.resolved(name) {
|
if !self.resolved(name) {
|
||||||
self.err(format!("unbound name `{name}`"))
|
self.err(format!("unbound name `{name}`"))
|
||||||
|
|
|
@ -1260,6 +1260,12 @@ impl Creature {
|
||||||
LoadScrutinee => {
|
LoadScrutinee => {
|
||||||
self.scrutinee = Some(self.peek().clone());
|
self.scrutinee = Some(self.peek().clone());
|
||||||
}
|
}
|
||||||
|
Spawn => {
|
||||||
|
let f = self.pop();
|
||||||
|
let proc = Creature::spawn(f, self.zoo.clone(), self.debug);
|
||||||
|
let id = self.zoo.borrow_mut().put(proc);
|
||||||
|
self.push(Value::Keyword(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user