diff --git a/Cargo.toml b/Cargo.toml index 702bb4e5..9f8c4c61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ piston_meta = "0.26.1" range = "0.3.1" rand = "0.3.13" read_color = "0.1.0" +hyper = "0.9.4" diff --git a/assets/syntax.txt b/assets/syntax.txt index d10c8e80..949f2e7c 100644 --- a/assets/syntax.txt +++ b/assets/syntax.txt @@ -7,7 +7,10 @@ _seps: "(){}[],.:;=<>*·+-/%^?|&∧∨!¬∑∃∀\n" 202 w = .r!({.w! comment}) 0 fn = { - ["fn" .w! .."("!:"name" ?w "(" ?w args ?w ")" ?w ?["->":"returns" ?w ?type:"ret_type"] ?w block:"block"] + ["fn" .w! .."("!:"name" ?w "(" ?w args ?w ")" ?w { + ["->":"returns" ?w ?type:"ret_type"] + !"->":!"returns" + } ?w block:"block"] [.."("!:"name" ?w "(" ?w args ?w ")" ?w "=" ?w expr:"expr"] } 1 args = .s?.(, arg:"arg") @@ -84,10 +87,11 @@ _seps: "(){}[],.:;=<>*·+-/%^?|&∧∨!¬∑∃∀\n" 25 call = [.._seps!:"name" ?w "(" .s?.(, arg_expr:"call_arg") ?w ")"] 26 named_call = [.._seps!:"word" ?w "(" ?w .s?.(, [.._seps!:"word" ?w ":" ?w arg_expr:"call_arg" ?w]) ")"] -27 assign = [lexpr:"left" ?w assign_op ?w expr:"right"] -28 assign_op = {":=":":=" "=":"=" "+=":"+=" "-=":"-=" "*=":"*=" "/=":"/=" "%=":"%="} -29 compare = [lexpr:"left" ?w compare_op ?w expr:"right"] -30 compare_op = {"==":"==" "!=":"!=" "¬=":"!=" "<=":"<=" "<":"<" ">=":">=" ">":">"} +27 go = ["go" ?w {call:"call" named_call:"named_call"}] +28 assign = [lexpr:"left" ?w assign_op ?w expr:"right"] +29 assign_op = {":=":":=" "=":"=" "+=":"+=" "-=":"-=" "*=":"*=" "/=":"/=" "%=":"%="} +30 compare = [lexpr:"left" ?w compare_op ?w expr:"right"] +31 compare_op = {"==":"==" "!=":"!=" "¬=":"!=" "<=":"<=" "<":"<" ">=":">=" ">":">"} 40 label = ?["'" .._seps!:"label" ?w ":" ?w] 41 short_body = [.w! .._seps!:"name" ?w {["[" ?w expr:"start" , expr:"end" ?w ")"] @@ -96,7 +100,7 @@ _seps: "(){}[],.:;=<>*·+-/%^?|&∧∨!¬∑∃∀\n" 43 , = [?w "," ?w] 44 arr = {array:"array" array_fill:"array_fill"} 45 items = {vec4:"vec4" ["(" ?w expr ?w ")"] unop:"unop" - text call:"call" named_call:"named_call" + text go:"go" call:"call" named_call:"named_call" num bool color item:"item"} 50 short_loops = {sum:"sum" min:"min" max:"max" sift:"sift" any:"any" all:"all"} @@ -119,6 +123,8 @@ _seps: "(){}[],.:;=<>*·+-/%^?|&∧∨!¬∑∃∀\n" "[]":"arr_any" ["[" ?w type:"arr" ?w "]"] "{}":"obj_any" + ["thr" ?w "[" ?w type:"thr" ?w "]"] + "thr":"thr_any" } 101 + = [?w {"+":"+" "||":"+" "∨":"+" "or":"+"} ?w] diff --git a/editor-plugins/atom/README.md b/editor-plugins/atom/README.md index 6fa7020f..8c8875bb 100644 --- a/editor-plugins/atom/README.md +++ b/editor-plugins/atom/README.md @@ -10,3 +10,10 @@ Dyon syntax highlighting for the Atom editor 3. Create a new folder `~/.atom/packages/language-dyon` 4. Copy the content of `editor-plugins/atom/` into the new folder 5. Restart Atom + +Alternative to restart: + +1. In Atom, go to the menu `Packages -> Command Palette -> Toggle` +2. Type "Window: Reload" and hit Enter + +The window will flash for a moment and reload the packages. diff --git a/editor-plugins/atom/grammars/dyon.cson b/editor-plugins/atom/grammars/dyon.cson index d2808369..f277ef8b 100644 --- a/editor-plugins/atom/grammars/dyon.cson +++ b/editor-plugins/atom/grammars/dyon.cson @@ -74,7 +74,7 @@ 'core_types': { 'comment': 'Built-in/core type' 'name': 'storage.type.core.dyon' - 'match': '\\b(bool|f64|vec4|str|opt|res)\\b' + 'match': '\\b(bool|f64|vec4|str|opt|res|thr)\\b' } 'core_vars': { 'comment': 'Core type variant' @@ -140,7 +140,7 @@ { 'comment': 'Keyword' 'name': 'keyword.other.dyon' - 'match': '\\b(sum|min|max|any|all|sift|and|or)\\b' + 'match': '\\b(sum|min|max|any|all|sift|and|or|go)\\b' } { 'include': '#self' } { 'include': '#mut' } diff --git a/interactive/examples/common/functions.dyon b/interactive/examples/common/functions.dyon index 0da9a861..2f59f083 100644 --- a/interactive/examples/common/functions.dyon +++ b/interactive/examples/common/functions.dyon @@ -2,26 +2,36 @@ fn print_function(f: {}) { print(f.name) print("(") n := len(f.arguments) - for i := 0; i < n; i += 1 { + for i n { print(f.arguments[i].name) if f.arguments[i].lifetime != none() { print(": '" + unwrap(f.arguments[i].lifetime)) + if f.arguments[i].takes != "any" { + print(" " + f.argument[i].takes) + } + } else { + if f.arguments[i].takes != "any" { + print(": " + f.arguments[i].takes) + } } if (i + 1) < n { print(", ") } } print(")") - if f.returns { - print(" ->") + if f.returns != "void" { + if f.returns == "any" { + print(" ->") + } else { + print(" -> " + f.returns) + } } println("") } -fn print_functions(functions: [{}]) { - n := len(functions) - for i := 0; i < n; i += 1 { - if functions[i].type != "external" { continue } +fn print_functions_type(functions: [{}], type) { + for i len(functions) { + if functions[i].type != type { continue } print_function(functions[i]) } } diff --git a/interactive/examples/piston_window/loader.dyon b/interactive/examples/piston_window/loader.dyon index 77a5aa3d..3581376a 100644 --- a/interactive/examples/piston_window/loader.dyon +++ b/interactive/examples/piston_window/loader.dyon @@ -4,18 +4,10 @@ fn main() { println("Reset with R.") println("You can modify \"source/piston_window/snake.rs\" while running.") - { - println("External functions:") - println("===================") - functions := unwrap(load("examples/common/functions.dyon")) - list := functions() - call(functions, "print_functions", [list]) - println("===================") - } - render := unwrap(load("examples/piston_window/render.dyon")) source := "examples/piston_window/snake.dyon" m := unwrap(load(source: source, imports: [render])) + call(m, "init", []) settings := call_ret(m, "settings", []) data := call_ret(m, "init_data", [settings]) diff --git a/interactive/examples/piston_window/snake.dyon b/interactive/examples/piston_window/snake.dyon index 8fccf690..f55af209 100644 --- a/interactive/examples/piston_window/snake.dyon +++ b/interactive/examples/piston_window/snake.dyon @@ -1,5 +1,19 @@ title() = "Snake!" +fn init() { + functions := unwrap(load("examples/common/functions.dyon")) + list := functions() + println("===================") + println("External functions:") + println("===================") + call(functions, "print_functions_type", [list, "external"]) + println("===================") + println("Loaded functions:") + println("===================") + call(functions, "print_functions_type", [list, "loaded"]) + println("===================") +} + settings() = { background_color: #e5d6a1, reload_interval: 0.25, diff --git a/source/bench/threads_go.dyon b/source/bench/threads_go.dyon new file mode 100644 index 00000000..21cda893 --- /dev/null +++ b/source/bench/threads_go.dyon @@ -0,0 +1,15 @@ +sum_n(i: f64, n: f64) = ∑ j n { i * n + j + 1 } + +fn results(mut threads: [thr[f64]]) -> res[f64] { + return ok(∑ _ len(threads) { + join(thread: pop(mut threads))? + }) +} + +fn main() { + n := 100_000 + + t := 2 + threads := sift i t { go sum_n(i, n / t) } + x := unwrap(results(mut threads)) +} diff --git a/source/bench/threads_no_go.dyon b/source/bench/threads_no_go.dyon new file mode 100644 index 00000000..41b98acf --- /dev/null +++ b/source/bench/threads_no_go.dyon @@ -0,0 +1,6 @@ +sum_n(i: f64, n: f64) = ∑ j n { i * n + j + 1 } + +fn main() { + n := 100_000 + x := sum_n(0, n) +} diff --git a/source/functions/functions.dyon b/source/functions/functions.dyon index 33e10eec..d4bb9717 100644 --- a/source/functions/functions.dyon +++ b/source/functions/functions.dyon @@ -10,18 +10,29 @@ fn print_function(f) { print(f.name) print("(") n := len(f.arguments) - for i := 0; i < n; i += 1 { + for i n { print(f.arguments[i].name) if f.arguments[i].lifetime != none() { print(": '" + unwrap(f.arguments[i].lifetime)) + if f.arguments[i].takes != "any" { + print(" " + f.argument[i].takes) + } + } else { + if f.arguments[i].takes != "any" { + print(": " + f.arguments[i].takes) + } } if (i + 1) < n { print(", ") } } print(")") - if f.returns { - print(" ->") + if f.returns != "void" { + if f.returns == "any" { + print(" ->") + } else { + print(" -> " + f.returns) + } } print(" ... ") print(f.type) diff --git a/source/syntax/lifetime_9.dyon b/source/syntax/lifetime_9.dyon new file mode 100644 index 00000000..56b0678e --- /dev/null +++ b/source/syntax/lifetime_9.dyon @@ -0,0 +1,13 @@ +fn foo(a: 'b f64, mut b: []) -> { + b[0] = {a: a} + debug() + return 5 +} + +fn main() { + y := 5 + x := [{}, 2, 3] + foo_thread := go foo(y, mut x) + r := unwrap(join(thread: foo_thread)) + println("r: " + to_string(r)) +} diff --git a/source/syntax/new_pos.dyon b/source/syntax/new_pos.dyon index 6d1ce5a9..6c968dbf 100644 --- a/source/syntax/new_pos.dyon +++ b/source/syntax/new_pos.dyon @@ -1,3 +1,3 @@ fn new() { - {x: 0, y: 0} + _ := {x: 0, y: 0} } diff --git a/source/test.dyon b/source/test.dyon index 8954eea8..3b617876 100644 --- a/source/test.dyon +++ b/source/test.dyon @@ -1,4 +1,17 @@ fn main() { - data := unwrap(load(meta: "assets/test_meta.txt", file: "assets/test_data.txt")) - println(data) + list := [ + "https://raw.githubusercontent.com/PistonDevelopers/dyon/master/assets/test_data.txt", + "https://raw.githubusercontent.com/PistonDevelopers/dyon/master/assets/test_meta.txt" + ] + names := [ + "1.txt", + "2.txt" + ] + threads := sift i len(list) { + go download(url: list[i], file: names[i]) + } + results := sift i len(threads) { + unwrap(join(thread: pop(mut threads))) + } + println(results) } diff --git a/source/typechk/go.dyon b/source/typechk/go.dyon new file mode 100644 index 00000000..2adba174 --- /dev/null +++ b/source/typechk/go.dyon @@ -0,0 +1,11 @@ +fn bar() {} + +fn main() { + data_thread := go bar() + for i 10 { + sleep(0.1) + } + println("Done here") + data := unwrap(join(thread: data_thread)) + println(data) +} diff --git a/source/typechk/threads.dyon b/source/typechk/threads.dyon new file mode 100644 index 00000000..dda742ef --- /dev/null +++ b/source/typechk/threads.dyon @@ -0,0 +1,17 @@ +fn sum_n(i: f64, n: f64) -> f64 { + return ∑ j n { i * n + j + 1 } +} + +fn results(mut threads: [f64]) -> res[f64] { + return ok(∑ _ len(threads) { + join(thread: pop(mut threads))? + }) +} + +fn main() { + n := 100 + + t := 2 + threads := sift i t { go sum_n(i, n / t) } + println(unwrap(results(mut threads))) +} diff --git a/source/typechk/unused_result.dyon b/source/typechk/unused_result.dyon new file mode 100644 index 00000000..8879129f --- /dev/null +++ b/source/typechk/unused_result.dyon @@ -0,0 +1,8 @@ +fn foo() -> f64 { + 3 + 4 + return 7 +} + +fn main() { + println(foo()) +} diff --git a/source/typechk/unused_result_2.dyon b/source/typechk/unused_result_2.dyon new file mode 100644 index 00000000..5191bd4f --- /dev/null +++ b/source/typechk/unused_result_2.dyon @@ -0,0 +1,7 @@ +fn foo() { + 3 + 4 +} + +fn main() { + foo() +} diff --git a/src/ast.rs b/src/ast.rs index 803e2d5d..6944bbb4 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -53,7 +53,7 @@ impl Function { let mut args: Vec = vec![]; let mut block: Option = None; let mut expr: Option = None; - let mut ret = Type::Void; + let mut ret: Option = None; loop { if let Ok(range) = convert.end_node(node) { convert.update(range); @@ -65,15 +65,13 @@ impl Function { convert, ignored) { convert.update(range); args.push(val); - } else if let Ok((range, _val)) = convert.meta_bool("returns") { + } else if let Ok((range, val)) = convert.meta_bool("returns") { convert.update(range); - if let Type::Void = ret { - ret = Type::Any; - } + ret = Some(if val { Type::Any } else { Type::Void }) } else if let Ok((range, val)) = Type::from_meta_data( "ret_type", convert, ignored) { convert.update(range); - ret = val; + ret = Some(val); } else if let Ok((range, val)) = Block::from_meta_data( "block", convert, ignored) { convert.update(range); @@ -82,6 +80,7 @@ impl Function { "expr", convert, ignored) { convert.update(range); expr = Some(val); + ret = Some(Type::Any); } else { let range = convert.ignore(); convert.update(range); @@ -93,7 +92,6 @@ impl Function { let block = match expr { None => try!(block.ok_or(())), Some(expr) => { - ret = Type::Any; let source_range = expr.source_range(); Block { expressions: vec![Expression::Return(Box::new(expr))], @@ -114,6 +112,7 @@ impl Function { name_plus_args.push(')'); name = Arc::new(name_plus_args); } + let ret = try!(ret.ok_or(())); Ok((convert.subtract(start), Function { name: name, file: file, @@ -233,6 +232,7 @@ pub enum Expression { Break(Break), Continue(Continue), Block(Block), + Go(Box), Call(Call), Item(Item), BinOp(Box), @@ -256,6 +256,9 @@ pub enum Expression { Try(Box), } +// Required because the `Sync` impl of `Variable` is unsafe. +unsafe impl Sync for Expression {} + impl Expression { pub fn from_meta_data( node: &str, @@ -347,6 +350,10 @@ impl Expression { } else { return Err(()); } + } else if let Ok((range, val)) = Go::from_meta_data( + convert, ignored) { + convert.update(range); + result = Some(Expression::Go(Box::new(val))); } else if let Ok((range, val)) = Call::from_meta_data( convert, ignored) { convert.update(range); @@ -433,6 +440,7 @@ impl Expression { Break(ref br) => br.source_range, Continue(ref c) => c.source_range, Block(ref bl) => bl.source_range, + Go(ref go) => go.source_range, Call(ref call) => call.source_range, Item(ref it) => it.source_range, BinOp(ref binop) => binop.source_range, @@ -985,6 +993,48 @@ impl Item { } } +#[derive(Debug, Clone)] +pub struct Go { + pub call: Call, + pub source_range: Range, +} + +impl Go { + pub fn from_meta_data( + mut convert: Convert, + ignored: &mut Vec + ) -> Result<(Range, Go), ()> { + let start = convert.clone(); + let node = "go"; + let start_range = try!(convert.start_node(node)); + convert.update(start_range); + + let mut call: Option = None; + loop { + if let Ok(range) = convert.end_node(node) { + convert.update(range); + break; + } else if let Ok((range, val)) = Call::from_meta_data(convert, ignored) { + convert.update(range); + call = Some(val); + } else if let Ok((range, val)) = Call::named_from_meta_data(convert, ignored) { + convert.update(range); + call = Some(val); + } else { + let range = convert.ignore(); + convert.update(range); + ignored.push(range); + } + } + + let call = try!(call.ok_or(())); + Ok((convert.subtract(start), Go { + call: call, + source_range: convert.source(start).unwrap(), + })) + } +} + #[derive(Debug, Clone)] pub struct Call { pub name: Arc, diff --git a/src/intrinsics/meta.rs b/src/intrinsics/meta.rs index fd1d804d..ff05a2d2 100644 --- a/src/intrinsics/meta.rs +++ b/src/intrinsics/meta.rs @@ -14,16 +14,9 @@ fn io_error(action: &str, file: &str, err: &io::Error) -> String { }) } -/// Loads a file using a meta file as syntax. -pub fn load_meta_file(meta: &str, file: &str) -> Result, String> { - let mut syntax_file = try!(File::open(meta).map_err(|err| io_error("open", meta, &err))); - let mut s = String::new(); - try!(syntax_file.read_to_string(&mut s).map_err(|err| io_error("read", meta, &err))); +fn load_metarules_data(meta: &str, s: &str, file: &str, d: &str) -> Result, String> { let rules = try!(syntax_errstr(&s).map_err(|err| format!("When parsing meta syntax in `{}`:\n{}", meta, err))); - let mut data_file = try!(File::open(file).map_err(|err| io_error("open", file, &err))); - let mut d = String::new(); - try!(data_file.read_to_string(&mut d).map_err(|err| io_error("read", file, &err))); let mut tokens = vec![]; try!(parse_errstr(&rules, &d, &mut tokens).map_err(|err| format!("When parsing data in `{}`:\n{}", file, err))); @@ -66,3 +59,78 @@ pub fn load_meta_file(meta: &str, file: &str) -> Result, String> { } Ok(res) } + +/// Loads a file using a meta file as syntax. +pub fn load_meta_file(meta: &str, file: &str) -> Result, String> { + let mut syntax_file = try!(File::open(meta).map_err(|err| io_error("open", meta, &err))); + let mut s = String::new(); + try!(syntax_file.read_to_string(&mut s).map_err(|err| io_error("read", meta, &err))); + let mut data_file = try!(File::open(file).map_err(|err| io_error("open", file, &err))); + let mut d = String::new(); + try!(data_file.read_to_string(&mut d).map_err(|err| io_error("read", file, &err))); + load_metarules_data(meta, &s, file, &d) +} + +/// Loads a text file from url. +fn load_text_file_from_url(url: &str) -> Result { + use hyper::client::Client; + use hyper::{Url}; + use hyper::status::StatusCode; + use std::io::Read; + + let url_address = try!(Url::parse(url) + .map_err(|e| format!("Error parsing url:\n`{}`\n", e))); + let client = Client::new(); + let request = client.get(url_address); + let mut response = try!(request.send() + .map_err(|e| format!("Error fetching file over http `{}`:\n{}\n", + url, e.to_string()))); + if response.status == StatusCode::Ok { + let mut data = String::new(); + try!(response.read_to_string(&mut data) + .map_err(|e| format!("Error fetching file over http `{}`:\n{}\n", + url, e.to_string()))); + Ok(data) + } else { + Err(format!("Error fetching file over http `{}:\n{}\n", + url, response.status)) + } +} + +/// Loads an url using a meta file as syntax. +pub fn load_meta_url(meta: &str, url: &str) -> Result, String> { + let mut syntax_file = try!(File::open(meta).map_err(|err| io_error("open", meta, &err))); + let mut s = String::new(); + try!(syntax_file.read_to_string(&mut s).map_err(|err| io_error("read", meta, &err))); + let d = try!(load_text_file_from_url(url)); + load_metarules_data(meta, &s, url, &d) +} + +// Downloads a file from url. +pub fn download_url_to_file(url: &str, file: &str) -> Result { + use hyper::client::Client; + use hyper::{Url}; + use hyper::status::StatusCode; + use std::io::copy; + use std::fs::File; + + let url_address = try!(Url::parse(url) + .map_err(|e| format!("Error parsing url:\n`{}`\n", e))); + let client = Client::new(); + let request = client.get(url_address); + let mut response = try!(request.send() + .map_err(|e| format!("Error fetching file over http `{}`:\n{}\n", + url, e.to_string()))); + if response.status == StatusCode::Ok { + let mut f = try!(File::create(file).map_err(|err| { + format!("Could not create file `{}`:\n{}", file, err.description()) + })); + try!(copy(&mut response, &mut f) + .map_err(|e| format!("Error fetching file over http `{}`:\n{}\n", + url, e.to_string()))); + Ok(file.into()) + } else { + Err(format!("Error fetching file over http `{}:\n{}\n", + url, response.status)) + } +} diff --git a/src/intrinsics/mod.rs b/src/intrinsics/mod.rs index a95ce6a5..87189345 100644 --- a/src/intrinsics/mod.rs +++ b/src/intrinsics/mod.rs @@ -43,7 +43,7 @@ pub fn standard(f: &mut HashMap, PreludeFunction>) { f.insert(Arc::new("random".into()), PreludeFunction { lts: vec![], tys: vec![], - ret: Type::Void + ret: Type::F64 }); sarg(f, "read_number", Type::Text, Type::Any); f.insert(Arc::new("read_line".into()), PreludeFunction { @@ -99,7 +99,7 @@ pub fn standard(f: &mut HashMap, PreludeFunction>) { f.insert(Arc::new("call".into()), PreludeFunction { lts: vec![Lt::Default; 3], tys: vec![Type::Any, Type::Text, Type::array()], - ret: Type::Any + ret: Type::Void }); f.insert(Arc::new("call_ret".into()), PreludeFunction { lts: vec![Lt::Default; 3], @@ -132,6 +132,17 @@ pub fn standard(f: &mut HashMap, PreludeFunction>) { tys: vec![Type::Text; 2], ret: Type::Result(Box::new(Type::array())) }); + f.insert(Arc::new("load_meta_url".into()), PreludeFunction { + lts: vec![Lt::Default; 2], + tys: vec![Type::Text; 2], + ret: Type::Result(Box::new(Type::array())) + }); + f.insert(Arc::new("download_url_file".into()), PreludeFunction { + lts: vec![Lt::Default; 2], + tys: vec![Type::Text; 2], + ret: Type::Result(Box::new(Type::Text)) + }); + sarg(f, "join_thread", Type::thread(), Type::Result(Box::new(Type::Any))); } enum EscapeString { @@ -231,7 +242,11 @@ fn write_variable( } } } - ref x => panic!("Could not print out `{:?}`", x) + Variable::Thread(_) => try!(write!(w, "_thread")), + Variable::Return => try!(write!(w, "_return")), + Variable::UnsafeRef(_) => try!(write!(w, "_unsafe_ref")), + Variable::RustObject(_) => try!(write!(w, "_rust_object")), + // ref x => panic!("Could not print out `{:?}`", x) } Ok(()) } @@ -636,6 +651,7 @@ pub fn call_standard( &Variable::RustObject(_) => rt.rust_object_type.clone(), &Variable::Option(_) => rt.option_type.clone(), &Variable::Result(_) => rt.result_type.clone(), + &Variable::Thread(_) => rt.thread_type.clone(), }; rt.stack.push(v); rt.pop_fn(call.name.clone()); @@ -662,7 +678,7 @@ pub fn call_standard( let v = match rt.resolve(&v) { &Variable::Text(ref text) => { let mut m = Module::new(); - for (key, &(ref f, ref ext)) in &module.ext_prelude { + for (key, &(ref f, ref ext)) in &*module.ext_prelude { m.add(key.clone(), *f, ext.clone()); } if let Err(err) = load(text, &mut m) { @@ -692,7 +708,7 @@ pub fn call_standard( let modules = rt.stack.pop().expect(TINVOTS); let source = rt.stack.pop().expect(TINVOTS); let mut new_module = Module::new(); - for (key, &(ref f, ref ext)) in &module.ext_prelude { + for (key, &(ref f, ref ext)) in &*module.ext_prelude { new_module.add(key.clone(), *f, ext.clone()); } match rt.resolve(&modules) { @@ -870,6 +886,7 @@ pub fn call_standard( let name: Arc = Arc::new("name".into()); let arguments: Arc = Arc::new("arguments".into()); let returns: Arc = Arc::new("returns".into()); + let takes: Arc = Arc::new("takes".into()); let lifetime: Arc = Arc::new("lifetime".into()); let ret_lifetime: Arc = Arc::new("return".into()); let ty: Arc = Arc::new("type".into()); @@ -881,7 +898,7 @@ pub fn call_standard( for (f_name, f) in &intrinsics { let mut obj = HashMap::new(); obj.insert(name.clone(), Variable::Text(f_name.clone())); - obj.insert(returns.clone(), Variable::Bool(f.returns())); + obj.insert(returns.clone(), Variable::Text(Arc::new(f.ret.description()))); obj.insert(ty.clone(), Variable::Text(intrinsic.clone())); let mut args = vec![]; for (i, lt) in f.lts.iter().enumerate() { @@ -899,15 +916,17 @@ pub fn call_standard( Box::new(Variable::Text(ret_lifetime.clone())) )), }); + obj_arg.insert(takes.clone(), + Variable::Text(Arc::new(f.tys[i].description()))); args.push(Variable::Object(Arc::new(obj_arg))); } obj.insert(arguments.clone(), Variable::Array(Arc::new(args))); functions.push(Variable::Object(Arc::new(obj))); } - for (f_name, &(_, ref f)) in &module.ext_prelude { + for (f_name, &(_, ref f)) in &*module.ext_prelude { let mut obj = HashMap::new(); obj.insert(name.clone(), Variable::Text(f_name.clone())); - obj.insert(returns.clone(), Variable::Bool(f.returns())); + obj.insert(returns.clone(), Variable::Text(Arc::new(f.ret.description()))); obj.insert(ty.clone(), Variable::Text(external.clone())); let mut args = vec![]; for (i, lt) in f.lts.iter().enumerate() { @@ -925,6 +944,8 @@ pub fn call_standard( Box::new(Variable::Text(ret_lifetime.clone())) )), }); + obj_arg.insert(takes.clone(), + Variable::Text(Arc::new(f.tys[i].description()))); args.push(Variable::Object(Arc::new(obj_arg))); } obj.insert(arguments.clone(), Variable::Array(Arc::new(args))); @@ -933,7 +954,7 @@ pub fn call_standard( for f in module.functions.values() { let mut obj = HashMap::new(); obj.insert(name.clone(), Variable::Text(f.name.clone())); - obj.insert(returns.clone(), Variable::Bool(f.returns())); + obj.insert(returns.clone(), Variable::Text(Arc::new(f.ret.description()))); obj.insert(ty.clone(), Variable::Text(loaded.clone())); let mut args = vec![]; for arg in &f.args { @@ -948,6 +969,8 @@ pub fn call_standard( ))) } ); + obj_arg.insert(takes.clone(), + Variable::Text(Arc::new(arg.ty.description()))); args.push(Variable::Object(Arc::new(obj_arg))); } obj.insert(arguments.clone(), Variable::Array(Arc::new(args))); @@ -1106,7 +1129,92 @@ pub fn call_standard( })); rt.pop_fn(call.name.clone()); Expect::Something + } + "load_meta_url" => { + rt.push_fn(call.name.clone(), None, st + 1, lc); + let url = rt.stack.pop().expect(TINVOTS); + let meta = rt.stack.pop().expect(TINVOTS); + let url = match rt.resolve(&url) { + &Variable::Text(ref url) => url.clone(), + x => return Err(module.error(call.args[1].source_range(), + &rt.expected(x, "str"))) + }; + let meta = match rt.resolve(&meta) { + &Variable::Text(ref meta) => meta.clone(), + x => return Err(module.error(call.args[0].source_range(), + &rt.expected(x, "str"))) + }; + let res = meta::load_meta_url(&**meta, &**url); + rt.stack.push(Variable::Result(match res { + Ok(res) => Ok(Box::new(Variable::Array(Arc::new(res)))), + Err(err) => Err(Box::new(Error { + message: Variable::Text(Arc::new(err)), + trace: vec![] + })) + })); + rt.pop_fn(call.name.clone()); + Expect::Something + } + "download_url_file" => { + rt.push_fn(call.name.clone(), None, st + 1, lc); + let file = rt.stack.pop().expect(TINVOTS); + let url = rt.stack.pop().expect(TINVOTS); + let file = match rt.resolve(&file) { + &Variable::Text(ref file) => file.clone(), + x => return Err(module.error(call.args[1].source_range(), + &rt.expected(x, "str"))) + }; + let url = match rt.resolve(&url) { + &Variable::Text(ref url) => url.clone(), + x => return Err(module.error(call.args[0].source_range(), + &rt.expected(x, "str"))) + }; + + let res = meta::download_url_to_file(&**url, &**file); + rt.stack.push(Variable::Result(match res { + Ok(res) => Ok(Box::new(Variable::Text(Arc::new(res)))), + Err(err) => Err(Box::new(Error { + message: Variable::Text(Arc::new(err)), + trace: vec![] + })) + })); + rt.pop_fn(call.name.clone()); + Expect::Something + } + "join_thread" => { + use Thread; + rt.push_fn(call.name.clone(), None, st + 1, lc); + let thread = rt.stack.pop().expect(TINVOTS); + let handle_res = Thread::invalidate_handle(rt, thread); + rt.stack.push(Variable::Result({ + match handle_res { + Ok(handle) => { + match handle.join() { + Ok(res) => match res { + Ok(res) => Ok(Box::new(res)), + Err(err) => Err(Box::new(Error { + message: Variable::Text(Arc::new(err)), + trace: vec![] + })) + }, + Err(_err) => Err(Box::new(Error { + message: Variable::Text(Arc::new( + "Thread did not exit successfully".into())), + trace: vec![] + })) + } + } + Err(err) => { + Err(Box::new(Error { + message: Variable::Text(Arc::new(err)), + trace: vec![] + })) + } + } + })); + rt.pop_fn(call.name.clone()); + Expect::Something } _ => return Err(module.error(call.source_range, &format!("{}\nUnknown function `{}`", rt.stack_trace(), call.name))) diff --git a/src/lib.rs b/src/lib.rs index b1ad1fde..b7a05ad2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,11 @@ extern crate piston_meta; extern crate rand; extern crate range; extern crate read_color; +extern crate hyper; use std::any::Any; +use std::fmt; +use std::thread::JoinHandle; use std::sync::{Arc, Mutex}; use std::collections::HashMap; use range::Range; @@ -33,6 +36,56 @@ pub struct Error { pub trace: Vec, } +#[derive(Clone)] +pub struct Thread { + pub handle: Option>>>>, +} + +impl Thread { + pub fn new(handle: JoinHandle>) -> Thread { + Thread { + handle: Some(Arc::new(Mutex::new(handle))) + } + } + + /// Removes the thread handle from the stack. + /// This is to prevent an extra reference when resolving the variable. + pub fn invalidate_handle( + rt: &mut Runtime, + var: Variable + ) -> Result>, String> { + use std::error::Error; + + let thread = match var { + Variable::Ref(ind) => { + use std::mem::replace; + + match replace(&mut rt.stack[ind], Variable::Thread(Thread { handle: None })) { + Variable::Thread(th) => th, + x => return Err(rt.expected(&x, "Thread")) + } + } + Variable::Thread(thread) => thread, + x => return Err(rt.expected(&x, "Thread")) + }; + let handle = match thread.handle { + None => return Err("The Thread has already been invalidated".into()), + Some(thread) => thread + }; + let mutex = try!(Arc::try_unwrap(handle).map_err(|_| + format!("{}\nCan not access Thread because there is \ + more than one reference to it", rt.stack_trace()))); + mutex.into_inner().map_err(|err| + format!("{}\nCan not lock Thread mutex:\n{}", rt.stack_trace(), err.description())) + } +} + +impl fmt::Debug for Thread { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "thread") + } +} + #[derive(Debug, Clone)] pub enum Variable { Ref(usize), @@ -47,8 +100,16 @@ pub enum Variable { RustObject(RustObject), Option(Option>), Result(Result, Box>), + Thread(Thread), } +/* +This is requires because `UnsafeRef(*mut Variable)` can not be sent across threads. +The lack of `UnsafeRef` variant when sending across threads is guaranteed at language level. +TODO: Make the interior of `UnsafeRef` inaccessible outside the library. +*/ +unsafe impl Send for Variable {} + impl Variable { fn deep_clone(&self, stack: &Vec) -> Variable { use Variable::*; @@ -85,6 +146,7 @@ impl Variable { Result(Ok(ref ok)) => Result(Ok(ok.clone())), // `err(x)` always uses deep clone, so it does not contain references. Result(Err(ref err)) => Result(Err(err.clone())), + Thread(_) => self.clone(), } } } @@ -106,11 +168,12 @@ impl PartialEq for Variable { } } +#[derive(Clone)] pub struct Module { pub source: Option, pub functions: HashMap, Arc>, - pub ext_prelude: HashMap, - (fn(&mut Runtime) -> Result<(), String>, PreludeFunction)>, + pub ext_prelude: Arc, + (fn(&mut Runtime) -> Result<(), String>, PreludeFunction)>>, } impl Module { @@ -118,7 +181,7 @@ impl Module { Module { source: None, functions: HashMap::new(), - ext_prelude: HashMap::new(), + ext_prelude: Arc::new(HashMap::new()), } } @@ -143,7 +206,10 @@ impl Module { f: fn(&mut Runtime) -> Result<(), String>, prelude_function: PreludeFunction ) { - self.ext_prelude.insert(name.clone(), (f, prelude_function)); + Arc::get_mut(&mut self.ext_prelude) + .expect("Can not add prelude function when there is \ + more than one reference to the module") + .insert(name.clone(), (f, prelude_function)); } } @@ -192,7 +258,13 @@ pub fn load(source: &str, module: &mut Module) -> Result<(), String> { // Check that lifetime checking succeeded. match handle.join().unwrap() { - Ok(()) => {} + Ok(refined_rets) => { + for (name, ty) in &refined_rets { + module.functions.get_mut(name).map(|f| { + Arc::make_mut(f).ret = ty.clone(); + }); + } + } Err(err_msg) => { let (range, msg) = err_msg.decouple(); return Err(format!("In `{}`:\n{}", source, module.error(range, &msg))) @@ -303,4 +375,14 @@ mod tests { fn bench_primes_trad(b: &mut Bencher) { b.iter(|| run_bench("source/bench/primes_trad.dyon")); } + + #[bench] + fn bench_threads_no_go(b: &mut Bencher) { + b.iter(|| run_bench("source/bench/threads_no_go.dyon")); + } + + #[bench] + fn bench_threads_go(b: &mut Bencher) { + b.iter(|| run_bench("source/bench/threads_go.dyon")); + } } diff --git a/src/lifetime/kind.rs b/src/lifetime/kind.rs index 33e6eec4..cbf070e3 100644 --- a/src/lifetime/kind.rs +++ b/src/lifetime/kind.rs @@ -61,6 +61,7 @@ pub enum Kind { Res, RetType, ReturnVoid, + Go, } impl Kind { @@ -127,6 +128,7 @@ impl Kind { "res" => Kind::Res, "ret_type" => Kind::RetType, "return_void" => Kind::ReturnVoid, + "go" => Kind::Go, _ => return None }) } diff --git a/src/lifetime/mod.rs b/src/lifetime/mod.rs index cb800663..36dc4d12 100644 --- a/src/lifetime/mod.rs +++ b/src/lifetime/mod.rs @@ -11,15 +11,19 @@ use self::lt::{arg_lifetime, compare_lifetimes, Lifetime}; use prelude::{Lt, Prelude}; +use Type; + mod kind; pub mod node; mod lt; mod typecheck; +/// Checks lifetime constraints and does type checking. +/// Returns refined return types of functions to put in AST. pub fn check( data: &[Range], prelude: &Prelude -) -> Result<(), Range> { +) -> Result, Type>, Range> { let mut nodes: Vec = vec![]; try!(convert_meta_data(&mut nodes, data)); @@ -348,6 +352,42 @@ pub fn check( } } + // Check that `go` functions does not have lifetime constraints. + for &c in &calls { + let call = &nodes[c]; + if let Some(parent) = call.parent { + if nodes[parent].kind != Kind::Go { continue } + } else { + continue; + } + if let Some(declaration) = call.declaration { + let function = &nodes[declaration]; + for (i, &a) in function.children.iter() + .enumerate() + .filter(|&(_, &i)| nodes[i].kind == Kind::Arg) { + let arg = &nodes[a]; + if arg.lifetime.is_some() { + return Err(nodes[call.children[i]].source.wrap( + format!("Can not use `go` because this argument has a lifetime constraint"))); + } + } + } else { + // Check that call to intrinsic satisfy the declared constraints. + for ((i, <), _) in + call.lts.iter().enumerate() + .zip(call.children.iter() + .filter(|&&n| nodes[n].kind == Kind::CallArg)) { + match lt { + Lt::Default => {} + _ => { + return Err(nodes[call.children[i]].source.wrap( + format!("Can not use `go` because this argument has a lifetime constraint"))); + } + } + } + } + } + // Check that calls satisfy the lifetime constraints of arguments. for &c in &calls { let call = &nodes[c]; @@ -486,7 +526,15 @@ pub fn check( try!(typecheck::run(&mut nodes, prelude)); - Ok(()) + // Copy refined return types to use in AST. + let mut refined_rets: HashMap, Type> = HashMap::new(); + for (name, &ind) in &function_lookup { + if let Some(ref ty) = nodes[functions[ind]].ty { + refined_rets.insert(name.clone(), ty.clone()); + } + } + + Ok(refined_rets) } // Search for suggestions using matching function signature. diff --git a/src/lifetime/node.rs b/src/lifetime/node.rs index 75917628..dd284747 100644 --- a/src/lifetime/node.rs +++ b/src/lifetime/node.rs @@ -133,6 +133,7 @@ impl Node { let mut call_arg_ind = 0; for &c in &self.children { match (self.kind, nodes[c].kind) { + (_, Kind::Go) => {} (_, Kind::ForN) => {} (_, Kind::Continue) => {} (_, Kind::Sift) => {} @@ -255,19 +256,11 @@ pub fn convert_meta_data( } } - if kind == Kind::Expr { - let parent = *parents.last().unwrap(); - if nodes[parent].kind == Kind::Fn { - // Function returns a value. - nodes[parent].ty = Some(Type::Any); - } - } - let ty = match kind { - Kind::Fn => Some(Type::Void), Kind::Array | Kind::ArrayFill => Some(Type::array()), Kind::Vec4 => Some(Type::Vec4), Kind::Object => Some(Type::object()), + Kind::Sum => Some(Type::F64), _ => None }; @@ -364,7 +357,11 @@ pub fn convert_meta_data( // Assuming this will be overwritten when // type is parsed or inferred. let i = *parents.last().unwrap(); - nodes[i].ty = Some(Type::Any); + if _val { + nodes[i].ty = Some(Type::Any); + } else { + nodes[i].ty = Some(Type::Void); + } } "return_void" => { // There is no sub node, so we need change kind of parent. diff --git a/src/lifetime/typecheck.rs b/src/lifetime/typecheck.rs index a6b340fa..2a185162 100644 --- a/src/lifetime/typecheck.rs +++ b/src/lifetime/typecheck.rs @@ -13,53 +13,63 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range let kind = nodes[i].kind; let mut this_ty = None; match kind { - Kind::Call => { - if let Some(decl) = nodes[i].declaration { - let mut missing = false; - for j in 0..nodes[i].children.len() { - let ch = nodes[i].children[j]; - if nodes[ch].item_try_or_ids() { continue 'node; } + Kind::Go => { + // Infer thread type from function. + if nodes[i].children.len() > 0 { + let ch = nodes[i].children[0]; + if let Some(ref ty) = nodes[ch].ty { + this_ty = Some(Type::Thread(Box::new(ty.clone()))) + } + } + } + Kind::Fn => { + if let Some(ch) = nodes[i].find_child_by_kind(nodes, Kind::Expr) { + // Infer return type from body of function. + this_ty = nodes[ch].ty.clone(); + } + } + Kind::CallArg => { + if nodes[i].children.len() == 0 || nodes[i].item_try_or_ids() { + continue 'node; + } + let expr_type = nodes[nodes[i].children[0]].ty.clone(); + if let Some(parent) = nodes[i].parent { + let j = nodes[parent].children.iter().position(|&ch| ch == i); + let j = if let Some(j) = j { j } else { continue 'node }; + if let Some(decl) = nodes[parent].declaration { let arg = nodes[decl].children[j]; - match (&nodes[ch].ty, &nodes[arg].ty) { + match (&expr_type, &nodes[arg].ty) { (&Some(ref ch_ty), &Some(ref arg_ty)) => { if !ch_ty.goes_with(arg_ty) { - return Err(nodes[ch].source.wrap( + return Err(nodes[i].source.wrap( format!("Type mismatch: Expected `{}`, found `{}`", arg_ty.description(), ch_ty.description()))); } } - (&None, _) | (_, &None) => { - missing = true; - } + (&None, _) | (_, &None) => {} } - } - if !missing { - if let Some(ref ty) = nodes[decl].ty { - this_ty = Some(ty.clone()); + } else if let Some(ref f) = prelude.functions.get( + nodes[parent].name.as_ref().unwrap()) { + if let Some(ref ty) = expr_type { + if !ty.goes_with(&f.tys[j]) { + return Err(nodes[i].source.wrap( + format!("Type mismatch: Expected `{}`, found `{}`", + f.tys[j].description(), ty.description()) + )) + } } } } - if this_ty.is_none() { - if let Some(ref f) = prelude.functions.get( - nodes[i].name.as_ref().unwrap()) { - let mut missing = false; - for j in 0..nodes[i].children.len() { - let ch = nodes[i].children[j]; - if nodes[ch].item_try_or_ids() { continue 'node; } - if let &Some(ref ch_ty) = &nodes[ch].ty { - if !ch_ty.goes_with(&f.tys[j]) { - return Err(nodes[ch].source.wrap( - format!("Type mismatch: Expected `{}`, found `{}`", - f.tys[j].description(), ch_ty.description()))); - } - } else { - missing = true; - } - } - if !missing { - this_ty = Some(f.ret.clone()); - } + this_ty = expr_type; + } + Kind::Call => { + if let Some(decl) = nodes[i].declaration { + if let Some(ref ty) = nodes[decl].ty { + this_ty = Some(ty.clone()); } + } else if let Some(ref f) = prelude.functions.get( + nodes[i].name.as_ref().unwrap()) { + this_ty = Some(f.ret.clone()); } } Kind::Assign => { @@ -85,9 +95,21 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range Kind::Item => { if nodes[i].item_try_or_ids() { continue 'node; } if let Some(decl) = nodes[i].declaration { - // No member identifiers. - if let Some(ref ty) = nodes[decl].ty { - this_ty = Some(ty.clone()); + match nodes[decl].kind { + Kind::Sum | Kind::Min | Kind::Max | + Kind::Any | Kind::All | Kind::Sift => { + // All indices are numbers. + this_ty = Some(Type::F64); + } + Kind::Arg => { + this_ty = Some(nodes[decl].ty.as_ref() + .unwrap_or(&Type::Any).clone()); + } + _ => { + if let Some(ref ty) = nodes[decl].ty { + this_ty = Some(ty.clone()); + } + } } } if let Some(parent) = nodes[i].parent { @@ -99,14 +121,15 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range } } } - Kind::Return | Kind::Val | Kind::CallArg | Kind::Expr + Kind::Return | Kind::Val | Kind::Expr | Kind::Cond | Kind::Exp | Kind::Base | Kind::Right => { - for &ch in &nodes[i].children { - if nodes[ch].item_try_or_ids() { continue 'node; } - if let Some(ref ty) = nodes[ch].ty { - this_ty = Some(ty.clone()); - break; - } + if nodes[i].children.len() == 0 { continue 'node; } + let ch = nodes[i].children[0]; + if nodes[ch].item_try_or_ids() { continue 'node; } + if nodes[ch].kind == Kind::Return { + this_ty = Some(Type::Void); + } else if let Some(ref ty) = nodes[ch].ty { + this_ty = Some(ty.clone()); } } Kind::Add => { @@ -188,6 +211,17 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range } } } + Kind::Sift => { + // Infer type from body. + let ch = if let Some(ch) = nodes[i].find_child_by_kind(nodes, Kind::Block) { + ch + } else { + continue 'node; + }; + if let Some(ref ty) = nodes[ch].ty { + this_ty = Some(Type::Array(Box::new(ty.clone()))); + } + } _ => {} } if this_ty.is_some() { @@ -201,7 +235,6 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range let kind = nodes[i].kind; match kind { Kind::Fn => { - // TODO: Infer type from body when written as mathematical expression. if let Some(ref ty) = nodes[i].ty { try!(check_fn(i, nodes, ty)) } else { @@ -211,9 +244,59 @@ pub fn run(nodes: &mut Vec, prelude: &Prelude) -> Result<(), Range )); } } + Kind::Go => { + if nodes[i].children.len() > 0 { + if let Some(decl) = nodes[nodes[i].children[0]].declaration { + match nodes[decl].ty { + None | Some(Type::Void) => { + return Err(nodes[i].source.wrap( + format!("Requires `->` on `{}`", + nodes[decl].name.as_ref().unwrap()) + )); + } + _ => {} + } + } + } + } Kind::If => { try!(check_if(i, nodes)) } + Kind::Block => { + // Make sure all results are used. + // TODO: If the block is the body of a for loop, + // then the last child node should be checked too. + let n = nodes[i].children.len(); + if n == 0 { continue; } + let children = if let Some(parent) = nodes[i].parent { + match nodes[parent].kind { + Kind::Fn => { + match &nodes[parent].ty { + &Some(Type::Void) => &nodes[i].children, + &None => continue, + _ => &nodes[i].children[0..n - 1] + } + } + _ => &nodes[i].children[0..n - 1] + } + } else { + &nodes[i].children[0..n - 1] + }; + for j in 0..children.len() { + let ch = children[j]; + match nodes[ch].kind { + Kind::Return => continue, + _ => {} + }; + if let Some(ref ty) = nodes[ch].ty { + if ty != &Type::Void { + return Err(nodes[ch].source.wrap( + format!("Unused result `{}`", ty.description()) + )); + } + } + } + } _ => {} } } diff --git a/src/prelude.rs b/src/prelude.rs index 64ccf63c..0f874228 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -63,7 +63,7 @@ impl Prelude { pub fn from_module(module: &Module) -> Prelude { let mut functions = HashMap::new(); intrinsics::standard(&mut functions); - for (key, &(_, ref val)) in &module.ext_prelude { + for (key, &(_, ref val)) in &*module.ext_prelude { functions.insert(key.clone(), val.clone()); } for f in module.functions.values() { diff --git a/src/runtime.rs b/src/runtime.rs index 00352095..5337e617 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -54,7 +54,7 @@ pub struct Runtime { pub call_stack: Vec, pub local_stack: Vec<(Arc, usize)>, pub ret: Arc, - pub rng: rand::ThreadRng, + pub rng: rand::StdRng, pub text_type: Variable, pub f64_type: Variable, pub vec4_type: Variable, @@ -67,6 +67,7 @@ pub struct Runtime { pub rust_object_type: Variable, pub option_type: Variable, pub result_type: Variable, + pub thread_type: Variable, } fn resolve<'a>(stack: &'a Vec, var: &'a Variable) -> &'a Variable { @@ -202,7 +203,7 @@ impl Runtime { call_stack: vec![], local_stack: vec![], ret: Arc::new("return".into()), - rng: rand::thread_rng(), + rng: rand::StdRng::new().unwrap(), text_type: Variable::Text(Arc::new("string".into())), f64_type: Variable::Text(Arc::new("number".into())), vec4_type: Variable::Text(Arc::new("vec4".into())), @@ -215,6 +216,7 @@ impl Runtime { rust_object_type: Variable::Text(Arc::new("rust_object".into())), option_type: Variable::Text(Arc::new("option".into())), result_type: Variable::Text(Arc::new("result".into())), + thread_type: Variable::Text(Arc::new("thread".into())), } } @@ -352,6 +354,7 @@ impl Runtime { Break(ref b) => Ok((Expect::Nothing, Flow::Break(b.label.clone()))), Continue(ref b) => Ok((Expect::Nothing, Flow::ContinueLoop(b.label.clone()))), + Go(ref go) => self.go(go, module), Call(ref call) => self.call(call, module), Item(ref item) => { let flow = try!(self.item(item, side, module)); @@ -516,6 +519,71 @@ impl Runtime { Ok((expect, Flow::Continue)) } + pub fn go(&mut self, go: &ast::Go, module: &Module) -> Result<(Expect, Flow), String> { + use std::thread::{self, JoinHandle}; + use Thread; + + let n = go.call.args.len(); + let mut stack = vec![]; + let mut fake_call = ast::Call { + name: go.call.name.clone(), + args: Vec::with_capacity(n), + source_range: go.call.source_range, + }; + // Evaluate the arguments and put a deep clone on the new stack. + // This prevents the arguments from containing any reference to other variables. + for (i, arg) in go.call.args.iter().enumerate() { + match try!(self.expression(arg, Side::Right, module)) { + (x, Flow::Return) => { return Ok((x, Flow::Return)); } + (Expect::Something, Flow::Continue) => {} + _ => return Err(module.error(arg.source_range(), + &format!("{}\nExpected something. \ + Expression did not return a value.", + self.stack_trace()))) + }; + let v = self.stack.pop().expect("There is no value on the stack"); + stack.push(v.deep_clone(&self.stack)); + fake_call.args.push(ast::Expression::Variable( + go.call.args[i].source_range(), Variable::Ref(n-i-1))); + } + stack.reverse(); + let new_rt = Runtime { + stack: stack, + local_stack: vec![], + call_stack: vec![], + rng: self.rng.clone(), + ret: self.ret.clone(), + ref_type: self.ref_type.clone(), + option_type: self.option_type.clone(), + array_type: self.array_type.clone(), + bool_type: self.bool_type.clone(), + object_type: self.object_type.clone(), + text_type: self.text_type.clone(), + f64_type: self.f64_type.clone(), + thread_type: self.thread_type.clone(), + unsafe_ref_type: self.unsafe_ref_type.clone(), + return_type: self.return_type.clone(), + rust_object_type: self.rust_object_type.clone(), + vec4_type: self.vec4_type.clone(), + result_type: self.result_type.clone(), + }; + let new_module: Module = module.clone(); + let handle: JoinHandle> = thread::spawn(move || { + let mut new_rt = new_rt; + let new_module = new_module; + let fake_call = fake_call; + match new_rt.call(&fake_call, &new_module) { + Err(err) => return Err(err), + Ok(_) => {} + }; + + let v = new_rt.stack.pop().expect("There is no value on the stack"); + Ok(v.deep_clone(&new_rt.stack)) + }); + self.stack.push(Variable::Thread(Thread::new(handle))); + Ok((Expect::Something, Flow::Continue)) + } + pub fn call( &mut self, call: &ast::Call, @@ -840,7 +908,7 @@ impl Runtime { } r } - x => panic!("Expected reference, found `{:?}`", x) + x => panic!("Expected reference, found `{}`", self.typeof_var(&x)) }; match self.resolve(&b) { @@ -1435,6 +1503,7 @@ impl Runtime { &Variable::RustObject(_) => self.rust_object_type.clone(), &Variable::Option(_) => self.option_type.clone(), &Variable::Result(_) => self.result_type.clone(), + &Variable::Thread(_) => self.thread_type.clone(), }; match v { Variable::Text(v) => v, diff --git a/src/typecheck.rs b/src/typecheck.rs index 08a020b0..68bbd15f 100644 --- a/src/typecheck.rs +++ b/src/typecheck.rs @@ -16,6 +16,7 @@ pub enum Type { // Rust(Arc), Option(Box), Result(Box), + Thread(Box), } impl Type { @@ -60,6 +61,16 @@ impl Type { res } } + &Thread(ref ty) => { + if let Any = **ty { + "thr".into() + } else { + let mut res = String::from("thr["); + res.push_str(&ty.description()); + res.push(']'); + res + } + } } } @@ -79,6 +90,10 @@ impl Type { Type::Result(Box::new(Type::Any)) } + pub fn thread() -> Type { + Type::Thread(Box::new(Type::Any)) + } + pub fn goes_with(&self, other: &Type) -> bool { use self::Type::*; @@ -120,6 +135,15 @@ impl Type { false } } + &Thread(ref thr) => { + if let &Thread(ref other_thr) = other { + thr.goes_with(other_thr) + } else if let &Any = other { + true + } else { + false + } + } // Void, Bool, F64, Text, Vec4. x if x == other => { true } _ if *other == Type::Any => { true } @@ -214,6 +238,9 @@ impl Type { } else if let Ok((range, _)) = convert.meta_bool("obj_any") { convert.update(range); ty = Some(Type::Object); + } else if let Ok((range, _)) = convert.meta_bool("thr_any") { + convert.update(range); + ty = Some(Type::Thread(Box::new(Type::Any))); } else if let Ok((range, val)) = Type::from_meta_data( "opt", convert, ignored) { convert.update(range); @@ -226,6 +253,10 @@ impl Type { "arr", convert, ignored) { convert.update(range); ty = Some(Type::Array(Box::new(val))); + } else if let Ok((range, val)) = Type::from_meta_data( + "thr", convert, ignored) { + convert.update(range); + ty = Some(Type::Thread(Box::new(val))); } else { let range = convert.ignore(); convert.update(range); diff --git a/tests/lib.rs b/tests/lib.rs index 1bf858a5..3886b2f4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -14,7 +14,11 @@ pub fn test_fail_src(source: &str) { let mut module = Module::new(); match load(source, &mut module) { Ok(_) => panic!("`{}` should fail", source), - Err(_) => {} + Err(err) => { + if err.starts_with(&format!("Could not open `{}`", source)) { + panic!("{}", err) + } + } }; } @@ -54,6 +58,7 @@ fn test_syntax() { test_src("source/syntax/lifetime_6.dyon"); test_src("source/syntax/lifetime_7.dyon"); test_src("source/syntax/lifetime_8.dyon"); + test_fail_src("source/syntax/lifetime_9.dyon"); test_src("source/syntax/insert.dyon"); test_src("source/syntax/named_call.dyon"); test_src("source/syntax/max_min.dyon"); @@ -92,7 +97,10 @@ fn test_typechk() { test_fail_src("source/typechk/pow_3.dyon"); test_fail_src("source/typechk/call.dyon"); test_src("source/typechk/obj.dyon"); - test_fail_src("source/typechk/obj_2.dyon"); + test_fail_src("source/typechk/go.dyon"); + test_fail_src("source/typechk/threads.dyon"); + test_fail_src("source/typechk/unused_result.dyon"); + test_fail_src("source/typechk/unused_result_2.dyon"); } #[test]