diff --git a/base/src/ast.rs b/base/src/ast.rs index 868e6972cd..886b3b0342 100644 --- a/base/src/ast.rs +++ b/base/src/ast.rs @@ -16,7 +16,7 @@ use crate::pos::{self, BytePos, HasSpan, Span, Spanned}; use crate::resolve::remove_aliases_cow; use crate::symbol::Symbol; use crate::types::{ - self, Alias, AliasData, ArcType, ArgType, NullInterner, Type, TypeEnv, TypeExt, + self, Alias, AliasData, ArcType, ArgType, Flags, NullInterner, Type, TypeEnv, TypeExt, }; use ordered_float::NotNan; @@ -112,6 +112,12 @@ impl From>> for AstType { } } +impl From<(Type>, Flags)> for AstType { + fn from((typ, _): (Type>, Flags)) -> AstType { + Self::from(typ) + } +} + impl HasSpan for AstType { fn span(&self) -> Span { self._typ.typ.span @@ -423,6 +429,18 @@ impl Default for Expr { } impl Expr { + pub fn app(func: SpannedExpr, args: Vec>) -> Self { + if args.is_empty() { + func.value + } else { + Expr::App { + func: func.into(), + implicit_args: Vec::new(), + args, + } + } + } + // TODO Use impl Trait pub fn field_iter<'a>( &'a self, @@ -902,6 +920,7 @@ impl Typed for Expr { fn try_type_of(&self, env: &dyn TypeEnv) -> Result { match *self { Expr::Ident(ref id) => Ok(id.typ.clone()), + Expr::Tuple { ref elems, .. } if elems.len() == 1 => elems[0].try_type_of(env), Expr::Projection(_, _, ref typ) | Expr::Record { ref typ, .. } | Expr::Tuple { ref typ, .. } => Ok(typ.clone()), diff --git a/base/src/types/mod.rs b/base/src/types/mod.rs index 6ada30a301..611039acea 100644 --- a/base/src/types/mod.rs +++ b/base/src/types/mod.rs @@ -1501,6 +1501,14 @@ pub trait TypeExt: Deref::Id, Self>> + Clone + S type_field_iter(self) } + fn arg_iter(&self) -> ArgIterator { + arg_iter(self) + } + + fn implicit_arg_iter(&self) -> ImplicitArgIterator { + implicit_arg_iter(self) + } + /// Returns an iterator over all fields in a record. /// `{ Test, Test2, x, y } => [x, y]` fn row_iter(&self) -> RowIterator { diff --git a/examples/http/server.glu b/examples/http/server.glu index 3780acd88b..a2c0813380 100644 --- a/examples/http/server.glu +++ b/examples/http/server.glu @@ -1,5 +1,6 @@ let prelude = import! std.prelude -let io @ { ? } = import! std.io +let io = import! std.effect.io +let { ? } = import! std.io let string = import! std.string let { (<>) } = import! std.prelude let { map } = import! std.functor @@ -10,11 +11,11 @@ let { ? } = import! std.array let { Result } = import! std.result let { foldl } = import! std.foldable let { Eff, ? } = import! std.effect +let { run_lift } = import! std.effect.lift let http @ { Request, Response, HttpEffect, StatusCode, handle, - io_handler, empty_response, get, post, @@ -38,7 +39,7 @@ let hello_world : Eff (HttpEffect r) Response = *> (wrap { status = status.ok, .. http.response }) let echo_body request : Request -> Eff (HttpEffect r) () = - do chunk = io_handler (read_chunk request.body) + do chunk = read_chunk request.body match chunk with | Some chunk -> write_response chunk *> echo_body request | None -> wrap () @@ -48,7 +49,7 @@ let echo : Eff (HttpEffect r) Response = *> wrap { status = status.ok, .. http.response } let array_body request : Request -> Eff (HttpEffect r) (Array Byte) = - do chunk = io_handler (read_chunk request.body) + do chunk = read_chunk request.body match chunk with | Some chunk -> do rest = array_body request @@ -80,8 +81,10 @@ let handler : Eff (HttpEffect r) Response = <|> (post *> path "/sum" *> sum) <|> (get *> path "/error" *> (wrap { status = status.internal_server_error, .. http.response })) -let print_error h = catch_error h (\msg -> io_handler (io.println msg)) +let print_error h = catch_error h (\msg -> io.println msg) \port -> - io.println ("Opened server on port " <> show port) - *> listen { port, .. http.default_listen_settings } handler + let action = + seq io.println ("Opened server on port " <> show port) + listen { port, .. http.default_listen_settings } handler + run_lift action diff --git a/repl/src/repl.glu b/repl/src/repl.glu index 16c4f611a2..b130d6ca58 100644 --- a/repl/src/repl.glu +++ b/repl/src/repl.glu @@ -1,5 +1,6 @@ let prelude = import! std.prelude -let io @ { ? } = import! std.io +let io = import! std.effect.io +let mio @ { ? } = import! std.io let map @ { Map, empty, singleton, find, insert, ? } = import! std.map let { Bool } = import! std.bool let { Option } = import! std.option @@ -43,8 +44,7 @@ let run_interruptible_io action : do cpu_pool = asks (\r -> r.cpu_pool) do eval_thread = lift <| thread.new_thread () let interruptible_action = repl_prim.finish_or_interrupt cpu_pool eval_thread action - lift - <| io.catch (io.functor.map Ok interruptible_action) (wrap << Err) + io.catch (mio.functor.map Ok interruptible_action) (wrap << Err) let load_file filename : String -> Eff (ReplEffect r) String = @@ -54,8 +54,8 @@ let load_file filename : String -> Eff (ReplEffect r) String = | Some i -> i + 1 let modulename = string.slice filename last_slash (string.len filename - 3) let action = - do expr = io.read_file_to_string filename - do result = io.load_script modulename expr + do expr = mio.read_file_to_string filename + do result = mio.load_script modulename expr wrap result do result = run_interruptible_io action @@ -65,22 +65,20 @@ let load_file filename : String -> Eff (ReplEffect r) String = let run_file filename : String -> Eff (ReplEffect r) () = let action = - do expr = io.read_file_to_string filename - do result = io.run_expr expr + do expr = mio.read_file_to_string filename + do result = mio.run_expr expr wrap (result.value ++ " : " ++ result.typ) do result = run_interruptible_io action - lift - <| (match result with - | Ok _ -> io.println "" - | Err x -> io.println x) + match result with + | Ok _ -> io.println "" + | Err x -> io.println x let commands : Commands = let print_result result = - lift - <| (match result with - | Ok x -> io.println x - | Err x -> io.println x) + match result with + | Ok x -> io.println x + | Err x -> io.println x let commands = ref [] let cmds : Array Cmd = [{ @@ -124,7 +122,7 @@ let commands : Commands = info = "Loads the file at \'folder/module.ext\' and stores it at \'module\'", action = \arg -> - (load_file arg >>= (lift << io.println)) + (load_file arg >>= io.println) *> wrap Continue, }, { @@ -160,7 +158,7 @@ let commands : Commands = { color, .. settings }) - | Err msg -> lift <| io.println msg + | Err msg -> io.println msg wrap Continue, }, { @@ -177,10 +175,9 @@ let commands : Commands = alias = "h", info = "Print this help", action = \_ -> - let print_header = lift <| io.println "Available commands\n" + let print_header = io.println "Available commands\n" let print_cmd cmd : Cmd -> Eff (ReplEffect r) () = - lift - (io.println (" :" ++ cmd.name ++ " (" ++ cmd.alias ++ ") " ++ cmd.info)) + io.println (" :" ++ cmd.name ++ " (" ++ cmd.alias ++ ") " ++ cmd.info) print_header *> array.traversable.traverse effect.applicative print_cmd (load commands) @@ -218,10 +215,9 @@ let do_command commands line : Commands -> String -> Eff (ReplEffect r) ReplActi | Ok { cmd, arg } -> match find cmd commands with | Some command -> command.action arg - | None -> lift <| io.println ("Unknown command \'" ++ cmd ++ "\'") *> wrap Continue + | None -> io.println ("Unknown command \'" ++ cmd ++ "\'") *> wrap Continue | Err err -> - lift - <| io.println "Expected a command such as `:h`" *> wrap Continue + io.println "Expected a command such as `:h`" *> wrap Continue let loop _ : () -> Eff (ReplEffect r) () = do repl = ask @@ -238,8 +234,7 @@ let loop _ : () -> Eff (ReplEffect r) () = do eval_thread = thread.new_thread () let eval_action = repl_prim.eval_line settings.color line repl_prim.finish_or_interrupt cpu_pool eval_thread eval_action - lift - <| io.catch action io.println *> wrap Continue + io.catch action mio.println *> wrap Continue do line_result = lift <| rustyline.readline repl.editor settings.prompt match line_result with @@ -253,11 +248,11 @@ let loop _ : () -> Eff (ReplEffect r) () = seq lift <| rustyline.save_history repl.editor wrap () -let run settings : Settings -> IO () = +let run settings : Settings -> Eff [| lift : Lift IO |] () = seq io.println "gluon (:h for help, :q to quit)" - do editor = rustyline.new_editor () - do cpu_pool = repl_prim.new_cpu_pool 1 + do editor = lift <| rustyline.new_editor () + do cpu_pool = lift <| repl_prim.new_cpu_pool 1 let repl = { commands, editor, cpu_pool } - run_lift (run_reader repl (eval_state settings (loop ()))) + run_reader repl (eval_state settings (loop ())) -run +run_lift << run diff --git a/src/import.rs b/src/import.rs index b6fcb992f6..8d250e00df 100644 --- a/src/import.rs +++ b/src/import.rs @@ -79,7 +79,7 @@ pub trait Importer: Any + Clone + Sync + Send { compiler: &mut ModuleCompiler, vm: &Thread, modulename: &str, - ) -> Result<(), (Option, crate::Error)>; + ) -> Result, crate::Error)>; } #[derive(Clone)] @@ -90,12 +90,12 @@ impl Importer for DefaultImporter { compiler: &mut ModuleCompiler, _vm: &Thread, modulename: &str, - ) -> Result<(), (Option, crate::Error)> { - compiler + ) -> Result, crate::Error)> { + let value = compiler .database .global(modulename.to_string()) .map_err(|err| (None, err))?; - Ok(()) + Ok(value.typ) } } @@ -285,7 +285,7 @@ impl Import { compiler: &mut ModuleCompiler, vm: &Thread, module_id: &Symbol, - ) -> Result<(), (Option, MacroError)> + ) -> Result, MacroError)> where I: Importer, { @@ -297,22 +297,26 @@ impl Import { .get_unloaded_module(vm, &modulename) .map_err(|err| (None, MacroError::new(err)))?; - match unloaded_module { + Ok(match unloaded_module { UnloadedModule::Extern(ExternModule { value, typ, metadata, }) => { - vm.set_global(module_id.clone(), typ, metadata.into(), value.get_value()) - .map_err(|err| (None, MacroError::new(err)))?; - } - UnloadedModule::Source => { - self.importer - .import(compiler, vm, &modulename) - .map_err(|(t, err)| (t, MacroError::new(err)))?; + vm.set_global( + module_id.clone(), + typ.clone(), + metadata.into(), + value.get_value(), + ) + .map_err(|err| (None, MacroError::new(err)))?; + typ } - } - Ok(()) + UnloadedModule::Source => self + .importer + .import(compiler, vm, &modulename) + .map_err(|(t, err)| (t, MacroError::new(err)))?, + }) } } diff --git a/src/lib.rs b/src/lib.rs index b0ae2f59c1..6093926b89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ macro_rules! try_future { pub mod compiler_pipeline; #[macro_use] pub mod import; +pub mod lift_io; #[doc(hidden)] pub mod query; pub mod std_lib; @@ -594,6 +595,7 @@ pub trait ThreadExt { &module_name, ) .map_err(|(_, err)| err.into()) + .map(|_| ()) .into_future(), ) } @@ -831,15 +833,23 @@ impl VmBuilder { let vm = RootedThread::with_global_state(crate::vm::vm::GlobalVmStateBuilder::new().build()); - let import = Import::new(DefaultImporter); - if let Some(import_paths) = self.import_paths { - import.set_paths(import_paths); - } + { + let macros = vm.get_macros(); + + { + let import = Import::new(DefaultImporter); + if let Some(import_paths) = self.import_paths { + import.set_paths(import_paths); + } + + if let Ok(gluon_path) = env::var("GLUON_PATH") { + import.add_path(gluon_path); + } + macros.insert(String::from("import"), import); + } - if let Ok(gluon_path) = env::var("GLUON_PATH") { - import.add_path(gluon_path); + macros.insert(String::from("lift_io"), lift_io::LiftIo); } - vm.get_macros().insert(String::from("import"), import); add_extern_module(&vm, "std.prim", crate::vm::primitives::load); diff --git a/src/lift_io.rs b/src/lift_io.rs new file mode 100644 index 0000000000..17d0171d42 --- /dev/null +++ b/src/lift_io.rs @@ -0,0 +1,160 @@ +use futures::future; + +use gluon_codegen::Trace; +use { + base::{ + ast::{ + Argument, AstType, EmptyEnv, Expr, ExprField, Pattern, SpannedExpr, Typed, TypedIdent, + ValueBinding, + }, + pos::{self, BytePos, Span}, + symbol::{Symbol, Symbols}, + types::{self, ArcType, Type, TypeExt}, + }, + check::check_signature, + vm::{ + api::{generic::A, VmType, IO}, + macros::{self, Macro, MacroExpander, MacroFuture}, + }, +}; + +#[derive(Trace)] +#[gluon(crate_name = "vm")] +pub(crate) struct LiftIo; + +impl Macro for LiftIo { + fn expand(&self, env: &mut MacroExpander, mut args: Vec>) -> MacroFuture { + if args.len() != 2 { + return Box::new(future::err(macros::Error::message(format!( + "`lift_io!` expects 1 argument" + )))); + } + let mut module = args.pop().unwrap(); + let lift = args.pop().unwrap(); + let mut symbols = Symbols::new(); + env.run(&mut symbols, &mut module); + let typ = module.env_type_of(&EmptyEnv::default()); + + let span = module.span; + + let vm = env.vm; + + let out = pos::spanned( + span, + Expr::Record { + typ: typ.clone(), + types: Vec::new(), + exprs: typ + .row_iter() + .filter_map(|field| { + let mut arg_iter = field.typ.remove_forall_and_implicit_args().arg_iter(); + let args = arg_iter.by_ref().count(); + + let action = pos::spanned( + span, + Expr::Projection( + Box::new(module.clone()), + field.name.clone(), + field.typ.clone(), + ), + ); + + if check_signature( + &vm.get_env(), + &arg_iter.typ, + &IO::::make_forall_type(&vm), + ) { + let action = lift_action( + &mut symbols, + lift.clone(), + action, + args, + &field.typ, + span, + ); + Some(ExprField { + metadata: Default::default(), + name: pos::spanned(span, field.name.clone()), + value: Some(action), + }) + } else { + None + } + }) + .collect(), + base: Some(Box::new(module)), + }, + ); + Box::new(future::ok(out)) + } +} + +fn lift_action( + symbols: &mut Symbols, + lift: SpannedExpr, + action: SpannedExpr, + args: usize, + original_type: &ArcType, + span: Span, +) -> SpannedExpr { + let args: Vec<_> = (0..args) + .map(|i| { + Argument::explicit(pos::spanned( + span, + TypedIdent::new(symbols.simple_symbol(format!("x{}", i))), + )) + }) + .collect(); + if args.is_empty() { + pos::spanned(span, Expr::app(lift, vec![action])) + } else { + let translate_type = |t| types::translate_type(&mut types::NullInterner::default(), t); + // If there are any implicit arguments we need to forward them to the lambda so implicits + // get resolved correctly + let typ: AstType<_> = { + let mut iter = original_type.forall_params(); + let forall_params: Vec<_> = iter.by_ref().cloned().collect(); + let mut iter = original_type.remove_forall().implicit_arg_iter(); + let implicit_args: Vec<_> = iter.by_ref().map(translate_type).collect(); + let mut iter = iter.typ.arg_iter(); + let args: Vec<_> = iter.by_ref().map(translate_type).collect(); + + Type::forall( + forall_params, + Type::function_implicit(implicit_args, Type::function(args, Type::hole())), + ) + }; + + let lambda = TypedIdent::new(symbols.simple_symbol("lambda")); + pos::spanned( + span, + Expr::rec_let_bindings( + vec![ValueBinding { + name: pos::spanned(span, Pattern::Ident(lambda.clone())), + expr: pos::spanned( + span, + Expr::app( + lift, + vec![pos::spanned( + span, + Expr::app( + action, + args.iter() + .map(|arg| { + pos::spanned(span, Expr::Ident(arg.name.value.clone())) + }) + .collect(), + ), + )], + ), + ), + args, + typ: Some(typ), + resolved_type: Default::default(), + metadata: Default::default(), + }], + pos::spanned(span, Expr::Ident(lambda)), + ), + ) + } +} diff --git a/src/query.rs b/src/query.rs index 8f82456e7e..567752876c 100644 --- a/src/query.rs +++ b/src/query.rs @@ -343,15 +343,18 @@ fn typechecked_module( let text = db.module_text(module.clone())?; let thread = db.thread(); - text.typecheck_expected( - &mut thread.module_compiler(db.compiler()), - thread, - &module, - &text, - expected_type.as_ref(), - ) - .map(|value| value.map(Arc::new)) - .map_err(|(_, err)| err) + let mut compiler = thread.module_compiler(db.compiler()); + let value = text + .typecheck_expected( + &mut compiler, + thread, + &module, + &text, + expected_type.as_ref(), + ) + .map_err(|(_, err)| err)?; + + Ok(value.map(Arc::new)) } fn core_expr( @@ -439,9 +442,9 @@ fn import(db: &impl Compilation, modulename: String) -> StdResult, compiler.collect_garbage(); - result?; + let typ = result?; - Ok(Expr::Ident(TypedIdent::new(name))) + Ok(Expr::Ident(TypedIdent { name, typ })) } fn global_(db: &impl Compilation, name: String) -> Result { diff --git a/std/effect/io.glu b/std/effect/io.glu new file mode 100644 index 0000000000..2593573cf0 --- /dev/null +++ b/std/effect/io.glu @@ -0,0 +1,2 @@ +let { lift } = import! std.effect.lift +lift_io! lift (import! std.io) diff --git a/std/effect/io/read.glu b/std/effect/io/read.glu new file mode 100644 index 0000000000..baeb0924ff --- /dev/null +++ b/std/effect/io/read.glu @@ -0,0 +1,3 @@ +let { lift } = import! std.effect.lift +let { Read } = import! std.io.read +lift_io! lift (import! std.io.read) diff --git a/std/effect/io/write.glu b/std/effect/io/write.glu new file mode 100644 index 0000000000..d0ec3fea46 --- /dev/null +++ b/std/effect/io/write.glu @@ -0,0 +1,3 @@ +let { lift } = import! std.effect.lift +let { Write } = import! std.io.write +lift_io! lift (import! std.io.write) diff --git a/std/http.glu b/std/http.glu index fe29291852..4d5d05941c 100644 --- a/std/http.glu +++ b/std/http.glu @@ -1,6 +1,6 @@ let prelude = import! std.prelude let function = import! std.function -let io @ { ? } = import! std.io +let { ? } = import! std.io let { (<<), (<|), ? } = import! std.function let string = import! std.string let { Bool } = import! std.bool @@ -26,7 +26,7 @@ let { HttpEffect, HttpState, Uri, } = import! std.http.types -let http_prim = import! std.http.prim +let http_prim = lift_io! lift (import! std.http.prim) let status = let code : Int -> StatusCode = id @@ -91,14 +91,10 @@ let get_response_body : Eff (HttpEffect r) ResponseBody = /// Returns `OK` with an empty body let empty_response = { status = status.ok } -/// Converts an `IO` into a `Handler` -let io_handler action : IO a -> Eff (HttpEffect r) a = - lift action - /// Write `bytes` to the http response let write_response bytes : Array Byte -> Eff (HttpEffect r) () = do response = get_response_body - io_handler (http_prim.write_response response bytes) + http_prim.write_response response bytes /// Throws an exception which aborts the current handler. Can be caught with `catch_error` let fail msg : String -> Eff (HttpEffect r) a = @@ -119,7 +115,7 @@ let handle handler state : Eff (HttpEffect r) Response -> HttpState -> IO Respon run_alt handler empty match opt with | None -> - http_prim.write_response state.response (string.as_bytes "Page not found") *> wrap { status = status.not_found, .. response } + run_lift (http_prim.write_response state.response (string.as_bytes "Page not found") *> wrap { status = status.not_found, .. response }) | Some response -> wrap response let show_uri: Show Uri = { @@ -148,7 +144,6 @@ let default_listen_settings = { port = 80, tls_cert = None } post, path, is_match, - io_handler, fail, catch_error,