diff --git a/crates/core/src/ir/lowerer.rs b/crates/core/src/ir/lowerer.rs index d363696..cceb985 100644 --- a/crates/core/src/ir/lowerer.rs +++ b/crates/core/src/ir/lowerer.rs @@ -7,6 +7,7 @@ use crate::{ diagnostic::{Diagnostic, DiagnosticCode, Diagnostics, Label}, labels::{FunctionLabelMap, call_argument_order, function_label_map, use_callback_placement}, resolve::ReferenceTarget, + stdlib::{MemberStrategy, StdlibRegistry}, types::{ConstructorInfo, TypedModule}, }; @@ -120,7 +121,7 @@ impl Lowerer { } } - let mut functions = Vec::new(); + let mut functions = self.lower_stdlib_host_imports(&ast); for function in ast.functions { if let Some(function) = self.lower_function(&function) { functions.push(function); @@ -888,6 +889,42 @@ impl Lowerer { } fn lower_call(&mut self, context: &mut FunctionContext, call: &ast::Call) -> Option { + if let Some(stdlib_call) = stdlib_call(&self.module.resolved.ast, &call.function) { + if !matches!( + stdlib_call.strategy, + MemberStrategy::Intrinsic | MemberStrategy::HostImport + ) { + self.diagnostics.push( + Diagnostic::new( + DiagnosticCode::LoweringError, + format!( + "stdlib member `{}` from `{}` cannot be lowered for this target yet", + stdlib_call.member, stdlib_call.module + ), + ) + .with_label(Label::primary(call.span, "unsupported stdlib call")), + ); + return None; + } + let function_type = stdlib_call.type_.clone(); + let Type::Function { return_type, .. } = function_type.clone() else { + return None; + }; + let type_ = self.typed_expression_type(call.span).unwrap_or(*return_type); + return Some(Expression { + type_, + span: call.span, + kind: ExpressionKind::DirectCall(DirectCall { + function: stdlib_lowered_name(&stdlib_call.module, &stdlib_call.member), + arguments: self.lower_ordered_call_arguments(context, call)?, + abi: call_abi( + &function_type, + stdlib_boundary(stdlib_call.strategy, &stdlib_call.member), + ), + }), + }); + } + if let AstExpression::Variable(function_name) = call.function.as_ref() && let Some(function_type) = self.function_types.get(&function_name.text).cloned() { @@ -1097,6 +1134,55 @@ impl Lowerer { } } + fn lower_stdlib_host_imports(&self, ast: &ast::Module) -> Vec { + let mut imports = Vec::new(); + for import in &ast.imports { + let Some(module) = StdlibRegistry::new().module(&import.module.text).cloned() else { + continue; + }; + for member in module.members { + if member.strategy != MemberStrategy::HostImport { + continue; + } + let Some(type_) = module.interface.functions.get(member.name).cloned() else { + continue; + }; + let Type::Function { params, return_type } = type_.clone() else { + continue; + }; + let locals = params + .iter() + .enumerate() + .map(|(index, type_)| Local { + id: LocalId(index as u32), + name: format!("arg{index}"), + type_: type_.clone(), + span: import.span, + }) + .collect::>(); + imports.push(Function { + name: stdlib_lowered_name(module.name, member.name), + public: false, + closure_captures: Vec::new(), + params: locals.clone(), + locals, + return_type: *return_type, + abi: call_abi( + &type_, + CallBoundary::HostImport { module: "env".into(), name: member.name.into() }, + ), + body: Block { + instructions: Vec::new(), + result: Box::new(self.nil_expression(import.span)), + span: import.span, + }, + span: import.span, + }); + } + } + imports + } + fn lower_constant(&self, id: ConstantId, constant: &ast::Constant) -> Constant { Constant { id, @@ -1117,6 +1203,56 @@ impl Lowerer { } } +#[derive(Debug, Clone)] +struct StdlibCall { + module: String, + member: String, + strategy: MemberStrategy, + type_: Type, +} + +fn stdlib_call(module: &ast::Module, function: &AstExpression) -> Option { + let registry = StdlibRegistry::new(); + let (module_name, member_name) = match function { + AstExpression::FieldAccess(access) => { + let AstExpression::Variable(module_alias) = access.record.as_ref() else { + return None; + }; + let import = module.imports.iter().find(|import| { + let local = import + .alias + .as_ref() + .map(|alias| alias.text.as_str()) + .unwrap_or_else(|| import.module.text.rsplit('/').next().unwrap_or(&import.module.text)); + local == module_alias.text + })?; + (import.module.text.clone(), access.field.text.clone()) + } + AstExpression::Variable(name) => module.imports.iter().find_map(|import| { + import.unqualified.iter().find_map(|unqualified| { + let local = unqualified.alias.as_ref().unwrap_or(&unqualified.name).text.as_str(); + (local == name.text).then(|| (import.module.text.clone(), unqualified.name.text.clone())) + }) + })?, + _ => return None, + }; + let module = registry.module(&module_name)?; + let strategy = registry.member_strategy(&module_name, &member_name)?; + let type_ = module.interface.functions.get(&member_name)?.clone(); + Some(StdlibCall { module: module_name, member: member_name, strategy, type_ }) +} + +fn stdlib_lowered_name(module: &str, member: &str) -> String { + format!("__stdlib_{}_{}", module.replace('/', "_"), member) +} + +fn stdlib_boundary(strategy: MemberStrategy, member: &str) -> CallBoundary { + match strategy { + MemberStrategy::HostImport => CallBoundary::HostImport { module: "env".into(), name: member.into() }, + _ => CallBoundary::Internal, + } +} + fn type_has_generic(type_: &Type) -> bool { match type_ { Type::Generic(_) => true, diff --git a/crates/core/src/resolve.rs b/crates/core/src/resolve.rs index 994c384..0e468da 100644 --- a/crates/core/src/resolve.rs +++ b/crates/core/src/resolve.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::ast::{self, Declaration, Expression, Pattern, Statement, UnqualifiedImportKind}; use crate::diagnostic::{Diagnostic, DiagnosticCode, Diagnostics, Label}; -use crate::{parse, project::Project, source::Span}; +use crate::{parse, project::Project, source::Span, stdlib::StdlibRegistry}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SymbolId(pub u32); @@ -105,7 +105,7 @@ struct ModuleMember { } pub fn resolve(module: ast::Module) -> Result { - Resolver::new(module).resolve() + Resolver::new(module).with_stdlib_interfaces().resolve() } pub fn resolve_project(project: &Project) -> Result { @@ -123,10 +123,12 @@ pub fn resolve_project(project: &Project) -> Result>(); + let mut interfaces = stdlib_resolve_interfaces(); + interfaces.extend( + ast_modules + .iter() + .map(|(name, module)| (name.clone(), module_interface(module))), + ); let mut modules = Vec::new(); for (name, module) in ast_modules { @@ -168,6 +170,11 @@ impl Resolver { Self { module_name: Some(module_name), project_modules, ..Self::new(module) } } + fn with_stdlib_interfaces(mut self) -> Self { + self.project_modules.extend(stdlib_resolve_interfaces()); + self + } + fn resolve(mut self) -> Result { let module_scope = self.new_scope(None); self.collect_prelude(module_scope); @@ -190,7 +197,9 @@ impl Resolver { } fn collect_prelude(&mut self, scope: ScopeId) { - for name in ["Int", "Float", "String", "BitArray", "Bool", "Nil", "List", "Result"] { + for name in [ + "Int", "Float", "String", "BitArray", "Bool", "Nil", "List", "Result", "Option", "Order", + ] { let name = ast::Name { span: self.module.span, text: name.into() }; self.define(scope, &name, Namespace::Type, SymbolKind::Prelude); } @@ -891,6 +900,38 @@ fn type_annotation_names(type_: &ast::TypeAnnotation) -> Vec { .collect() } +fn stdlib_resolve_interfaces() -> HashMap { + StdlibRegistry::new() + .modules() + .map(|module| { + let mut members = HashMap::new(); + for name in module.interface.functions.keys() { + members.insert( + (Namespace::Value, name.clone()), + ModuleMember { public: true, span: module_span() }, + ); + } + for name in module.interface.types.keys() { + members.insert( + (Namespace::Type, name.clone()), + ModuleMember { public: true, span: module_span() }, + ); + } + for name in module.interface.constructors.keys() { + members.insert( + (Namespace::Constructor, name.clone()), + ModuleMember { public: true, span: module_span() }, + ); + } + (module.name.to_string(), ModuleInterface { members }) + }) + .collect() +} + +fn module_span() -> Span { + Span { file_id: crate::source::SourceFileId(u32::MAX), start: 0, end: 0 } +} + fn module_interface(module: &ast::Module) -> ModuleInterface { let mut members = HashMap::new(); diff --git a/crates/core/src/stdlib.rs b/crates/core/src/stdlib.rs index 4c60a5e..1819593 100644 --- a/crates/core/src/stdlib.rs +++ b/crates/core/src/stdlib.rs @@ -114,7 +114,7 @@ impl StdlibModule { "debug", vec![Type::generic("a")], Type::generic("a"), - MemberStrategy::HostImport, + MemberStrategy::InterfaceOnly, ), ], &[], diff --git a/crates/core/src/types.rs b/crates/core/src/types.rs index 80daba0..0cdd0e3 100644 --- a/crates/core/src/types.rs +++ b/crates/core/src/types.rs @@ -22,6 +22,7 @@ use crate::{ project::Project, resolve::{self, ResolvedModule}, source::Span, + stdlib::StdlibRegistry, }; /// A type known to the compiler. @@ -152,15 +153,27 @@ pub fn check_project(project: &Project) -> Result { let mut interfaces = HashMap::new(); let mut diagnostics = Vec::new(); + let stdlib_interfaces = StdlibRegistry::new() + .modules() + .map(|module| (module.name.to_string(), module.interface.clone())) + .collect::>(); + interfaces.extend(stdlib_interfaces.clone()); + let external_constructors = resolved .modules .iter() .flat_map(|module| constructors_from_ast(&module.ast)) + .chain(stdlib_interfaces.values().flat_map(interface_constructors)) + .collect::>(); + let stdlib_values = stdlib_interfaces + .iter() + .flat_map(|(module, interface)| qualified_values_from_interface(module, interface)) .collect::>(); let mut external_values = resolved .modules .iter() .flat_map(|module| values_from_ast(&module.ast)) + .chain(stdlib_values) .collect::>(); let mut external_function_labels = resolved .modules @@ -251,6 +264,7 @@ impl TypeChecker { fn check(mut self) -> Result { self.collect_type_declarations(); + self.collect_imported_stdlib_interfaces(); self.collect_annotated_function_types(); self.collect_external_function_types(); self.collect_constant_types(); @@ -315,6 +329,51 @@ impl TypeChecker { } } + fn collect_imported_stdlib_interfaces(&mut self) { + let registry = StdlibRegistry::new(); + for import in &self.module.ast.imports { + let Some(interface) = registry.interface(&import.module.text) else { + continue; + }; + self.interface.types.extend(interface.types.clone()); + self.interface.constructors.extend(interface.constructors.clone()); + self.constructors.extend(interface.constructors.clone()); + + let module_name = import + .alias + .as_ref() + .map(|alias| alias.text.clone()) + .unwrap_or_else(|| { + import + .module + .text + .rsplit('/') + .next() + .unwrap_or(&import.module.text) + .to_string() + }); + for (name, type_) in &interface.functions { + self.external_values + .insert(format!("{module_name}.{name}"), type_.clone()); + } + for imported in &import.unqualified { + let local_name = imported.alias.as_ref().unwrap_or(&imported.name).text.clone(); + match imported.kind { + ast::UnqualifiedImportKind::Value => { + if let Some(type_) = interface.functions.get(&imported.name.text) { + self.external_values.insert(local_name, type_.clone()); + } + } + ast::UnqualifiedImportKind::TypeOrConstructor => { + if let Some(constructor) = interface.constructors.get(&imported.name.text) { + self.constructors.insert(local_name, constructor.clone()); + } + } + } + } + } + } + fn collect_annotated_function_types(&mut self) { for function in self.module.ast.functions.clone() { if let Some(type_) = self.function_type_from_annotations(&function) { @@ -872,7 +931,7 @@ impl TypeChecker { } } - Some(*return_type) + Some(self.resolve_inference_type(&return_type)) } fn check_field_access(&mut self, field_access: &ast::FieldAccess) -> Option { @@ -2211,6 +2270,28 @@ fn module_name(module: &ast::Module) -> String { .unwrap_or_else(|| "module".into()) } +fn interface_constructors(interface: &ModuleInterface) -> Vec<(String, ConstructorInfo)> { + interface + .constructors + .iter() + .map(|(name, constructor)| (name.clone(), constructor.clone())) + .collect() +} + +fn qualified_values_from_interface(module: &str, interface: &ModuleInterface) -> Vec<(String, Type)> { + let short = module.rsplit('/').next().unwrap_or(module); + interface + .functions + .iter() + .flat_map(|(name, type_)| { + [ + (format!("{module}.{name}"), type_.clone()), + (format!("{short}.{name}"), type_.clone()), + ] + }) + .collect() +} + fn constructors_from_ast(module: &ast::Module) -> Vec<(String, ConstructorInfo)> { module .declarations @@ -2305,6 +2386,27 @@ mod tests { assert!(typed.expressions.iter().any(|expression| expression.type_ == Type::Int)); } + #[test] + fn checks_initial_stdlib_interfaces() { + let typed = check_source( + r#"import gleam/result.{Ok, Error} +import gleam/option.{Some, None} +import gleam/order.{Lt, Eq, Gt} + +fn ok() -> Result(Int, String) { Ok(1) } +fn err() -> Result(Int, String) { Error("no") } +fn some() -> Option(Int) { Some(1) } +fn none() -> Option(Int) { None } +fn order(x: Int) -> Order { case x { 0 -> Eq 1 -> Gt _ -> Lt } } +"#, + ) + .expect("type check stdlib interfaces"); + + assert!(typed.interface.types.contains_key("Result")); + assert!(typed.interface.types.contains_key("Option")); + assert!(typed.interface.types.contains_key("Order")); + } + #[test] fn checks_direct_function_calls() { let typed = check_source("fn id(x: Int) -> Int { x }\nfn main() { id(1) }").expect("type check source"); diff --git a/crates/core/src/wasm.rs b/crates/core/src/wasm.rs index 733aa7c..9f23bc6 100644 --- a/crates/core/src/wasm.rs +++ b/crates/core/src/wasm.rs @@ -134,11 +134,11 @@ pub fn emit_wat_with_options(module: &ir::Module, options: EmitOptions) -> Resul } let mut wat = String::from("(module\n"); + wat.push_str(&emitter.imports); if emitter.uses_runtime { wat.push_str(&runtime_prelude(emitter.config)); } - wat.push_str(&emitter.imports); wat.push_str(&emitter.functions); for object in emitter.data { @@ -423,12 +423,44 @@ impl Emitter { } "__op_and" => self.short_circuit_bool(call, false), "__op_or" => self.short_circuit_bool(call, true), - "__op_string_concat" => { + "__op_string_concat" | "__stdlib_gleam_string_append" => { self.expression(&call.arguments[0].value); self.expression(&call.arguments[1].value); writeln!(self.functions, " call $__string_concat").expect("write WAT"); self.uses_runtime = true; } + "__stdlib_gleam_string_concat" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__string_concat_list").expect("write WAT"); + self.uses_runtime = true; + } + "__stdlib_gleam_string_length" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__string_len").expect("write WAT"); + writeln!(self.functions, " i64.extend_i32_u").expect("write WAT"); + self.uses_runtime = true; + } + "__stdlib_gleam_string_is_empty" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__string_len").expect("write WAT"); + writeln!(self.functions, " i32.eqz").expect("write WAT"); + self.uses_runtime = true; + } + "__stdlib_gleam_int_to_string" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__int_to_string").expect("write WAT"); + self.uses_runtime = true; + } + "__stdlib_gleam_list_length" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__list_length").expect("write WAT"); + self.uses_runtime = true; + } + "__stdlib_gleam_list_reverse" => { + self.expression(&call.arguments[0].value); + writeln!(self.functions, " call $__list_reverse").expect("write WAT"); + self.uses_runtime = true; + } _ => { for argument in &call.arguments { self.expression(&argument.value); @@ -2614,6 +2646,92 @@ pub fn choose(left: Bool, right: Bool) -> Bool { left || right } assert_eq!(choose.call(&mut store, (0, 1)).expect("call choose"), 1); } + #[test] + fn imports_initial_stdlib_io_host_calls() { + let wasm = compile_wasm( + r#"import gleam/io + +pub fn main() { + io.print("hi") + io.println("!") +} +"#, + ); + let engine = Engine::default(); + let module = Module::new(&engine, &wasm.bytes).expect("compile wasm module"); + let mut linker = Linker::new(&engine); + linker.func_wrap("env", "print", |_ptr: i32| {}).expect("define print"); + linker + .func_wrap("env", "println", |_ptr: i32| {}) + .expect("define println"); + let mut store = Store::new(&engine, ()); + let instance = linker.instantiate(&mut store, &module).expect("instantiate module"); + let main = instance + .get_typed_func::<(), ()>(&mut store, "main") + .expect("get main export"); + main.call(&mut store, ()).expect("call main"); + } + + #[test] + fn runs_initial_stdlib_intrinsics() { + let wasm = compile_wasm( + r#"import gleam/int +import gleam/string +import gleam/list + +pub fn number() { int.to_string(-42) } +pub fn text() { string.append("a", "b") } +pub fn text_len() -> Int { string.length(string.concat(["a", "bc"])) } +pub fn empty() -> Bool { string.is_empty("") } +pub fn item_count() -> Int { list.length([1, 2, 3]) } +pub fn reversed_head() -> Int { + case list.reverse([1, 2, 3]) { + [head, ..] -> head + _ -> 0 + } +} +"#, + ); + let engine = Engine::default(); + let module = Module::new(&engine, &wasm.bytes).expect("compile wasm module"); + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[]).expect("instantiate module"); + + let memory = instance.get_memory(&mut store, "memory").expect("memory export"); + let number = instance + .get_typed_func::<(), i32>(&mut store, "number") + .expect("get number export"); + let pointer = number.call(&mut store, ()).expect("call number") as usize; + let mut bytes = [0; 16]; + memory.read(&store, pointer, &mut bytes).expect("read number string"); + assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), 3); + assert_eq!(&bytes[8..11], b"-42"); + + let text = instance + .get_typed_func::<(), i32>(&mut store, "text") + .expect("get text export"); + let pointer = text.call(&mut store, ()).expect("call text") as usize; + memory.read(&store, pointer, &mut bytes).expect("read text string"); + assert_eq!(&bytes[8..10], b"ab"); + + let text_len = instance + .get_typed_func::<(), i64>(&mut store, "text_len") + .expect("get text_len export"); + assert_eq!(text_len.call(&mut store, ()).expect("call text_len"), 3); + let empty = instance + .get_typed_func::<(), i32>(&mut store, "empty") + .expect("get empty export"); + assert_eq!(empty.call(&mut store, ()).expect("call empty"), 1); + let item_count = instance + .get_typed_func::<(), i64>(&mut store, "item_count") + .expect("get item_count export"); + assert_eq!(item_count.call(&mut store, ()).expect("call item_count"), 3); + let reversed_head = instance + .get_typed_func::<(), i64>(&mut store, "reversed_head") + .expect("get reversed_head export"); + assert_eq!(reversed_head.call(&mut store, ()).expect("call reversed_head"), 3); + } + #[test] fn runs_string_concat_and_value_equality_codegen() { let wasm = compile_wasm( diff --git a/crates/core/src/wasm/helpers.rs b/crates/core/src/wasm/helpers.rs index 7c3b55d..a62a60a 100644 --- a/crates/core/src/wasm/helpers.rs +++ b/crates/core/src/wasm/helpers.rs @@ -309,6 +309,120 @@ pub const STRING_HELPERS: &str = r#" end end ) + (func $__string_concat_list (param $list i32) (result i32) + (local $result i32) + i32.const 0 + i32.const 0 + call $__string_new + local.set $result + block $done + loop $loop + local.get $list + i32.eqz + br_if $done + local.get $result + local.get $list + call $__list_head + i32.wrap_i64 + call $__string_concat + local.set $result + local.get $list + call $__list_tail + local.set $list + br $loop + end + end + local.get $result + ) + (func $__int_to_string (param $value i64) (result i32) + (local $n i64) (local $temp i64) (local $digits i32) (local $len i32) (local $ptr i32) (local $pos i32) + local.get $value + local.set $n + local.get $value + i64.const 0 + i64.lt_s + if + local.get $value + i64.const -1 + i64.mul + local.set $n + i32.const 1 + local.set $len + end + local.get $n + local.set $temp + i32.const 1 + local.set $digits + block $digits_done + local.get $temp + i64.const 10 + i64.lt_u + br_if $digits_done + i32.const 0 + local.set $digits + loop $digits_loop + local.get $temp + i64.eqz + br_if $digits_done + local.get $temp + i64.const 10 + i64.div_u + local.set $temp + local.get $digits + i32.const 1 + i32.add + local.set $digits + br $digits_loop + end + end + local.get $len + local.get $digits + i32.add + local.set $len + i32.const 0 + local.get $len + call $__string_new + local.set $ptr + local.get $ptr + call $__string_data + local.get $len + i32.add + local.set $pos + block $done + loop $loop + local.get $pos + i32.const 1 + i32.sub + local.set $pos + local.get $pos + local.get $n + i64.const 10 + i64.rem_u + i32.wrap_i64 + i32.const 48 + i32.add + i32.store8 + local.get $n + i64.const 10 + i64.div_u + local.set $n + local.get $n + i64.eqz + br_if $done + br $loop + end + end + local.get $value + i64.const 0 + i64.lt_s + if + local.get $ptr + call $__string_data + i32.const 45 + i32.store8 + end + local.get $ptr + ) (func $__string_inspect (param $ptr i32) (result i32) local.get $ptr ) @@ -1257,6 +1371,45 @@ pub const MANAGED_VALUE_HELPERS: &str = r#" i32.add i32.load ) + (func $__list_length (param $ptr i32) (result i64) + (local $count i64) + block $done + loop $loop + local.get $ptr + i32.eqz + br_if $done + local.get $count + i64.const 1 + i64.add + local.set $count + local.get $ptr + call $__list_tail + local.set $ptr + br $loop + end + end + local.get $count + ) + (func $__list_reverse (param $ptr i32) (result i32) + (local $result i32) + block $done + loop $loop + local.get $ptr + i32.eqz + br_if $done + local.get $ptr + call $__list_head + local.get $result + call $__list_cons + local.set $result + local.get $ptr + call $__list_tail + local.set $ptr + br $loop + end + end + local.get $result + ) (func $__tuple_new (param $arity i32) (param $fields i32) (result i32) (local $ptr i32) local.get $arity diff --git a/docs/src/internal/tasks/14_stdlib_and_host_interop.md b/docs/src/internal/tasks/14_stdlib_and_host_interop.md index 208fdf2..cf15624 100644 --- a/docs/src/internal/tasks/14_stdlib_and_host_interop.md +++ b/docs/src/internal/tasks/14_stdlib_and_host_interop.md @@ -12,30 +12,30 @@ Support useful standard library modules and host calls. host imports, or implemented as compiler/runtime intrinsics. - [x] Define a table-driven stdlib registry for module interfaces and lowering strategies. -- [ ] Load or model dependency package metadata needed for stdlib modules. -- [ ] Resolve stdlib module interfaces consistently with project module +- [x] Load or model dependency package metadata needed for stdlib modules. +- [x] Resolve stdlib module interfaces consistently with project module interfaces. -- [ ] Load external module interfaces from dependency metadata where available. -- [ ] Type-check selected stdlib functions using the same interface data as user +- [x] Load external module interfaces from dependency metadata where available. +- [x] Type-check selected stdlib functions using the same interface data as user modules. -- [ ] Type-check dependency functions, constructors, and types through imported +- [x] Type-check dependency functions, constructors, and types through imported module interface schemes. -- [ ] Add diagnostics for unsupported stdlib modules, functions, types, or +- [x] Add diagnostics for unsupported stdlib modules, functions, types, or target combinations. ### Group 1: initial useful stdlib -- [ ] Model interfaces for `gleam/io`, `gleam/int`, `gleam/string`, +- [x] Model interfaces for `gleam/io`, `gleam/int`, `gleam/string`, `gleam/list`, `gleam/result`, `gleam/option`, and `gleam/order`. -- [ ] Implement `gleam/io.println` and `gleam/io.print` as host calls. -- [ ] Implement `gleam/int.to_string` as an intrinsic or runtime helper. -- [ ] Implement `gleam/string.append`, `concat`, `length`, and `is_empty`. -- [ ] Implement `gleam/list.length` and `gleam/list.reverse`. -- [ ] Support `gleam/result.Result`, `Ok`, and `Error` in interfaces and +- [x] Implement `gleam/io.println` and `gleam/io.print` as host calls. +- [x] Implement `gleam/int.to_string` as an intrinsic or runtime helper. +- [x] Implement `gleam/string.append`, `concat`, `length`, and `is_empty`. +- [x] Implement `gleam/list.length` and `gleam/list.reverse`. +- [x] Support `gleam/result.Result`, `Ok`, and `Error` in interfaces and lowering. -- [ ] Support `gleam/option.Option`, `Some`, and `None` in interfaces and +- [x] Support `gleam/option.Option`, `Some`, and `None` in interfaces and lowering. -- [ ] Support `gleam/order.Order`, `Lt`, `Eq`, and `Gt` in interfaces and +- [x] Support `gleam/order.Order`, `Lt`, `Eq`, and `Gt` in interfaces and lowering. ### Host ABI