Skip to content

Commit

Permalink
Implement events, running each event handler on a separate thread and…
Browse files Browse the repository at this point in the history
… copying the data they use (#666)
  • Loading branch information
dfellis authored Mar 2, 2024
1 parent e1585b6 commit 3c6a856
Show file tree
Hide file tree
Showing 8 changed files with 288 additions and 32 deletions.
25 changes: 12 additions & 13 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ macro_rules! status {
// The only test that works for now
test!(hello_world => r#"
export fn main {
print("Hello, World!");
print('Hello, World!');
}"#;
stdout "Hello, World!\n";
status 0;
Expand Down Expand Up @@ -157,50 +157,49 @@ test!(print_function => r#"
status 0;
);
test!(stdout_event => r#"
from @std/app import start, stdout, exit
on start {
export fn main(): ExitCode {
emit stdout 'Hello, World';
wait(10);
emit exit 0;
wait(10); // Because emits run on another thread, we need to wait to be sure it actually runs
return ExitCode(0);
}"#;
stdout "Hello, World";
);

// Basic Math Tests

test!(int8_add => r#"
export fn main(): ExitCode = ExitCode(getOrExit(add(toI8(1), toI8(2))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(add(i8(1), i8(2))));"#;
status 3;
);
test!(int8_sub => r#"
export fn main(): ExitCode = ExitCode(getOrExit(sub(toI8(2), toI8(1))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(sub(i8(2), i8(1))));"#;
status 1;
);
test!(int8_mul => r#"
export fn main(): ExitCode = ExitCode(getOrExit(mul(toI8(2), toI8(1))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(mul(i8(2), i8(1))));"#;
status 2;
);
test!(int8_div => r#"
export fn main(): ExitCode = ExitCode(getOrExit(div(toI8(6), toI8(2))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(div(i8(6), i8(2))));"#;
status 3;
);
test!(int8_mod => r#"
export fn main(): ExitCode = ExitCode(getOrExit(mod(toI8(6), toI8(4))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(mod(i8(6), i8(4))));"#;
status 2;
);
test!(int8_pow => r#"
export fn main(): ExitCode = ExitCode(getOrExit(pow(toI8(6), toI8(2))));"#;
export fn main(): ExitCode = ExitCode(getOrExit(pow(i8(6), i8(2))));"#;
status 36;
);
test!(int8_min => r#"
export fn main() {
print(min(toI8(3), toI8(5)));
print(min(i8(3), i8(5)));
}"#;
stdout "3\n";
);
test!(int8_max => r#"
export fn main() {
print(max(toI8(3), toI8(5)));
print(max(i8(3), i8(5)));
}"#;
stdout "5\n";
);
Expand Down
122 changes: 122 additions & 0 deletions src/lntors/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Builds the event functions using the code from their handlers. Currently an O(n^2) algo for
// simplicity.

use crate::program::{Function, Microstatement, Program};
use crate::lntors::function::from_microstatement;

pub fn generate(
program: &Program,
) -> Result<String, Box<dyn std::error::Error>> {
// The events will all go into an `event` sub-module with every function marked public. These
// event functions are what the `emit <eventName> <optionalValue>;` statements will call, so
// something like:
// emit foo "bar";
// becomes something like:
// event::foo("bar");
//
// The event functions copy their input argument for each handler, which is run within a
// thread, looking something like:
// let arg_copy = arg.clone();
// std::thread::spawn(move || {
// <generated code that references arg_copy>
// });
//
// With the generated code coming from the handler functions, in the order they're discovered
// across the scopes.
let mut out = "mod event {\n".to_string();
// First scan through all scopes for each defined event
for (_, (_, _, eventscope)) in program.scopes_by_file.iter() {
for (eventname, event) in eventscope.events.iter() {
// Define the function for this event
out = format!("{} pub fn {}{} {{\n",
out,
eventname,
match event.typename.as_str() {
"void" => "".to_string(),
x => format!("(arg: {})", x).to_string(),
},
);
let expected_arg_type = match event.typename.as_str() {
"void" => None,
x => Some(x.to_string()),
};
// Next scan through all scopes for handlers that bind to this particular event
for (_, (_, _, handlerscope)) in program.scopes_by_file.iter() {
for (handlereventname, handler) in handlerscope.handlers.iter() {
if eventname == handlereventname {
// We have a match, grab the function(s) from this scope
let fns: &Vec<Function> = match handlerscope.functions.get(&handler.functionname) {
Some(res) => Ok(res),
None => Err("Somehow unable to find function for handler"),
}?;
// Determine the correct function from the vector by finding the last one
// that implements the correct interface (if the event has an argument,
// then exactly one argument of the same type and no return type, if no
// argument then no arguments to the function)
let mut handler_pos = None;
for (i, possible_fn) in fns.iter().enumerate() {
if let Some(_) = &possible_fn.rettype {
continue;
}
match expected_arg_type {
None => {
if possible_fn.args.len() == 0 {
handler_pos = Some(i);
}
},
Some(ref arg_type) => {
if possible_fn.args.len() == 1 && &possible_fn.args[0].1 == arg_type {
handler_pos = Some(i);
}
},
}
}
let handler_fn = match handler_pos {
None => Err("No function properly matches the event signature"),
Some(i) => Ok(&fns[i]),
}?;
// Because of what we validated above, we *know* that if this event takes
// an argument, then the first microstatement is an Arg microstatement that
// the normal code generation path is going to ignore. We're going to peek
// at the first microstatement of the function and decide if we need to
// insert the special `let <argname> = arg.clone();` statement or not
match &handler_fn.microstatements[0] {
Microstatement::Arg { name, .. } => {
// TODO: guard against valid alan variable names that are no valid
// rust variable names
out = format!("{} let {} = arg.clone();\n", out, name).to_string();
},
_ => {},
}
// Now we generate the thread to run this event handler on
out = format!("{} std::thread::spawn(move || {{\n", out).to_string();
if let Some(b) = &handler_fn.bind {
// If it's a bound function, just call it with the argument, if there
// is one
let arg_str = match &handler_fn.microstatements[0] {
Microstatement::Arg { name, .. } => name.clone(),
_ => "".to_string(),
};
out = format!("{} super::{}({});\n", out, b, arg_str);
} else {
// Inline the microstatements if it's an Alan function
for microstatement in &handler_fn.microstatements {
let stmt = from_microstatement(microstatement, handlerscope, program)?;
if stmt != "" {
out = format!("{} {};\n", out, stmt);
}
}
}
// And close out the thread
out = format!("{} }});\n", out).to_string();
}
}
}
// Now we finally close out this function
out = format!("{} }}\n", out).to_string();
}
}
// And close out the event module
out = format!("{}}}\n", out).to_string();
Ok(out)
}
14 changes: 13 additions & 1 deletion src/lntors/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ pub fn from_microstatement(
program: &Program,
) -> Result<String, Box<dyn std::error::Error>> {
match microstatement {
Microstatement::Arg { .. } => Ok("".to_string()), // Skip arg microstatements that are just used for discovery during generation
Microstatement::Assignment { name, value } => Ok(format!(
"let {} = {}",
name,
from_microstatement(value, scope, program)?
)
.to_string()),
Microstatement::Value { representation, .. } => Ok(representation.clone()),
Microstatement::Value { typen, representation} => match typen.as_str() {
"String" => Ok(format!("{}.to_string()", representation).to_string()),
_ => Ok(representation.clone())
},
Microstatement::FnCall { function, args } => {
let mut arg_types = Vec::new();
for arg in args {
Expand Down Expand Up @@ -44,6 +48,14 @@ pub fn from_microstatement(
}
None => Ok("return".to_string()),
},
Microstatement::Emit { event, value } => match value {
Some(val) => {
Ok(format!("event::{}({})", event, from_microstatement(val, scope, program)?).to_string())
}
None => {
Ok(format!("event::{}()", event).to_string())
}
},
}
}

Expand Down
13 changes: 9 additions & 4 deletions src/lntors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use crate::lntors::function::generate;
use crate::lntors::event::generate as evt_generate;
use crate::lntors::function::generate as fn_generate;
use crate::program::Program;

mod event;
mod function;
mod typen;

pub fn lntors(entry_file: String) -> Result<String, Box<dyn std::error::Error>> {
// TODO: Figure out a better way to include custom Rust functions that we may then bind
let preamble = include_str!("../std/root.rs").to_string();
let program = Program::new(entry_file)?;
// Assuming a single scope for now
// Generate all of the events and their handlers defined across all scopes
// TODO: Pruning unused events should be pursued eventually
let event_fns = evt_generate(&program)?;
// Getting the entry scope, where the `main` function is expected
let scope = match program.scopes_by_file.get(&program.entry_file.clone()) {
Some((_, _, s)) => s,
None => {
Expand Down Expand Up @@ -41,6 +46,6 @@ pub fn lntors(entry_file: String) -> Result<String, Box<dyn std::error::Error>>
assert_eq!(func.len(), 1);
assert_eq!(func[0].args.len(), 0);
// Assertion proven, start emitting the Rust `main` function
let main_fn = generate(&func[0], &scope, &program)?;
Ok(format!("{}\n{}", preamble, main_fn).to_string())
let main_fn = fn_generate(&func[0], &scope, &program)?;
Ok(format!("{}\n{}\n{}", preamble, event_fns, main_fn).to_string())
}
2 changes: 2 additions & 0 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,7 @@ named_and!(events: Events =>
colon: String as colon,
c: String as optwhitespace,
fulltypename: FullTypename as fulltypename,
optsemicolon: String as optsemicolon,
);
named_and!(propertytypeline: PropertyTypeline =>
variable: String as variable,
Expand Down Expand Up @@ -1454,6 +1455,7 @@ named_and!(handlers: Handlers =>
eventname: Vec<VarSegment> as var,
b: String as whitespace,
handler: Handler as handler,
optsemicolon: String as optsemicolon,
);
named_or!(rootelements: RootElements =>
Whitespace: String as whitespace,
Expand Down
Loading

0 comments on commit 3c6a856

Please sign in to comment.