From 1d2df72af75a24f7507ecadd60e1913064ae1378 Mon Sep 17 00:00:00 2001 From: bch29 Date: Tue, 15 Dec 2020 11:51:07 +0000 Subject: [PATCH 1/2] feat: Add `Display` typeclass --- std/display.glu | 22 ++++++++++++++++++++++ std/float.glu | 7 ++++++- std/int.glu | 6 ++++++ std/prelude.glu | 4 ++++ std/string.glu | 4 ++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 std/display.glu diff --git a/std/display.glu b/std/display.glu new file mode 100644 index 0000000000..ed44cea1e7 --- /dev/null +++ b/std/display.glu @@ -0,0 +1,22 @@ +//@NO-IMPLICIT-PRELUDE +//! Value to string conversion, intended for strings passed to users. + +/// `Display a` represents a conversion function from `a` to a pretty string. +#[implicit] +type Display a = { display : a -> String } + +/// Converts a value into a string. +/// ``` +/// let { ? } = import! std.effect +/// let { assert_eq, ? } = import! std.test +/// +/// seq assert_eq (display 123) "123" +/// seq assert_eq (display 3.14) "3.14" +/// assert_eq (display "abc") "abc" +/// ``` +let display ?d : [Display a] -> a -> String = d.display + +{ + Display, + display, +} diff --git a/std/float.glu b/std/float.glu index 35248f3c2b..762ecfa75f 100644 --- a/std/float.glu +++ b/std/float.glu @@ -1,7 +1,7 @@ //@NO-IMPLICIT-PRELUDE //! The 64-bit floating point type. -let { Semigroup, Monoid, Group, Eq, Ord, Ordering, Num, Show } = import! std.prelude +let { Semigroup, Monoid, Group, Eq, Ord, Ordering, Num, Show, Display } = import! std.prelude let additive = let semigroup : Semigroup Float = { append = \x y -> x #Float+ y } @@ -55,6 +55,10 @@ let show : Show Float = { show = (import! std.prim).show_float, } +let display : Display Float = { + display = (import! std.prim).show_float, +} + { additive, multiplicative, @@ -62,6 +66,7 @@ let show : Show Float = { ord, num, show, + display, .. import! std.float.prim } diff --git a/std/int.glu b/std/int.glu index 09affe7ac9..4ab81dcbdf 100644 --- a/std/int.glu +++ b/std/int.glu @@ -7,6 +7,7 @@ let { Group } = import! std.group let { Eq, Ord, Ordering } = import! std.cmp let { Num } = import! std.num let { Show } = import! std.show +let { Display } = import! std.display let additive = let semigroup : Semigroup Int = { @@ -59,6 +60,10 @@ let show : Show Int = { show = (import! std.prim).show_int, } +let display : Display Int = { + display = (import! std.prim).show_int, +} + { additive, multiplicative, @@ -66,6 +71,7 @@ let show : Show Int = { ord, num, show, + display, .. import! std.int.prim } diff --git a/std/prelude.glu b/std/prelude.glu index 5e584019fc..5553757b24 100644 --- a/std/prelude.glu +++ b/std/prelude.glu @@ -13,6 +13,7 @@ let { Monoid, empty } = import! std.monoid let { Group } = import! std.group let { Eq, Ord, Bool, Ordering, (==), (/=), (<), (<=), (>=), (>) } = import! std.cmp let { Show, show } = import! std.show +let { Display, display } = import! std.display let { Category, id, compose } = import! std.category let { Num, (+), (-), (*), (/), negate } = import! std.num let { Bool, not } = import! std.bool @@ -64,6 +65,9 @@ let { flat_map } = import! std.monad Show, show, + Display, + display, + Option, Bool, diff --git a/std/string.glu b/std/string.glu index d26068f4d0..6d65cbab9b 100644 --- a/std/string.glu +++ b/std/string.glu @@ -6,6 +6,7 @@ let prim = import! std.prim let { Semigroup, (<>) } = import! std.semigroup let { Monoid } = import! std.monoid let { Show } = import! std.show +let { Display } = import! std.display let { Eq, Ord, Ordering } = import! std.cmp let function = import! std.function @@ -25,10 +26,13 @@ let ord : Ord String = { eq, compare = prim.string_compare } let show : Show String = { show = \s -> "\"" ++ s ++ "\"" } +let display : Display String = { display = \s -> s } + { eq, ord, show, + display, semigroup, monoid, (++), From c8ed76fb1b8a08d59341a559bf50fc5e1e6742a2 Mon Sep 17 00:00:00 2001 From: Bradley Hardy Date: Mon, 14 Dec 2020 19:19:41 +0000 Subject: [PATCH 2/2] feat: Add `format!` macro --- src/format_macro.rs | 300 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 2 files changed, 303 insertions(+) create mode 100644 src/format_macro.rs diff --git a/src/format_macro.rs b/src/format_macro.rs new file mode 100644 index 0000000000..20ec02c3c2 --- /dev/null +++ b/src/format_macro.rs @@ -0,0 +1,300 @@ +//! Implementation of the `format!` macro +use gluon_codegen::Trace; + +use crate::{ + base::{ + ast::{self, AstClone, SpannedExpr}, + pos, + symbol::{Symbol, Symbols}, + types::TypeCache, + }, + parser::parse_expr, + vm::macros::{self, Error, Macro, MacroExpander, MacroFuture, MacroResult}, +}; + +/** + * Format macro with expressions embedded in the format string. + * + * ```ignore + * let x = 1 + * let foo = "foo" + * format! "x is {x}, x + 1 is {x+1}, foo is {foo} and {foo:?}" + * ``` + * + * ==> x is 1, x + 1 is 2, foo is foo and "foo" + */ +#[derive(Trace)] +#[gluon(crate_name = "vm")] +pub struct Format; + +fn expr_from_path<'ast>( + arena: &mut ast::OwnedArena<'ast, Symbol>, + symbols: &mut Symbols, + span: pos::Span, + path_head: &str, + path_tail: &[&str], +) -> SpannedExpr<'ast, Symbol> { + let mut head = pos::spanned( + span, + ast::Expr::Ident(ast::TypedIdent::new(symbols.simple_symbol(path_head))), + ); + + for &item in path_tail { + head = pos::spanned( + span, + ast::Expr::Projection( + arena.alloc(head), + symbols.simple_symbol(item), + Default::default(), + ), + ); + } + + head +} + +async fn run_import<'a, 'ast>( + importer: &dyn Macro, + env: &mut MacroExpander<'a>, + symbols: &mut Symbols, + arena: &mut ast::OwnedArena<'ast, Symbol>, + span: pos::Span, + path: &[&str], +) -> MacroResult<'ast> { + let (path_head, path_tail) = match path { + [head, tail @ ..] => (head, tail), + _ => return Err(Error::message("run_import for empty path")), + }; + + let path = expr_from_path(arena, symbols, span, path_head, path_tail); + let args = arena.alloc_extend(vec![path]); + + match importer.expand(env, symbols, arena, args).await? { + macros::LazyMacroResult::Done(res) => Ok(res), + macros::LazyMacroResult::Lazy(f) => f().await, + } +} + +impl Macro for Format { + fn expand<'r, 'a: 'r, 'b: 'r, 'c: 'r, 'ast: 'r>( + &self, + env: &'b mut MacroExpander<'a>, + symbols: &'c mut Symbols, + arena: &'b mut ast::OwnedArena<'ast, Symbol>, + args: &'b mut [SpannedExpr<'ast, Symbol>], + ) -> MacroFuture<'r, 'ast> { + Box::pin(async move { + let arg = match args { + [arg] => (arg), + _ => return Err(Error::message(format!("format! expects 1 argument"))), + }; + + let span = arg.span; + let sp = |e: ast::Expr<'ast, Symbol>| pos::spanned(span, e); + + let import = env + .vm + .get_macros() + .get("import") + .ok_or_else(|| Error::message(format!("format! cannot find import macro")))?; + + let show_module = + run_import(&*import, env, symbols, arena, span, &["std", "show"]).await?; + let show_module = arena.alloc(show_module); + let show_func = arena.alloc(sp(ast::Expr::Projection( + show_module, + symbols.simple_symbol("show"), + Default::default(), + ))); + + let display_module = + run_import(&*import, env, symbols, arena, span, &["std", "display"]).await?; + let display_module = arena.alloc(display_module); + let display_func = sp(ast::Expr::Projection( + display_module, + symbols.simple_symbol("display"), + Default::default(), + )); + + let semigroup_module = + run_import(&*import, env, symbols, arena, span, &["std", "semigroup"]).await?; + let semigroup_module = arena.alloc(semigroup_module); + let append_func = sp(ast::Expr::Projection( + semigroup_module, + symbols.simple_symbol("append"), + Default::default(), + )); + + let format_string = match &arg.value { + ast::Expr::Literal(ast::Literal::String(text)) => text, + _ => return Err(Error::message(format!("format! expects a string argument"))), + }; + + let app1 = |func: &ast::SpannedExpr<'ast, Symbol>, e: ast::SpannedExpr<'ast, Symbol>| { + let func = arena.alloc(func.ast_clone(arena.borrow())); + + sp(ast::Expr::App { + func, + implicit_args: arena.alloc_extend(vec![]), + args: arena.alloc_extend(vec![e]), + }) + }; + + let app_exprs = |lhs, rhs| { + let func = arena.alloc(append_func.ast_clone(arena.borrow())); + + sp(ast::Expr::App { + func, + implicit_args: arena.alloc_extend(vec![]), + args: arena.alloc_extend(vec![lhs, rhs]), + }) + }; + + let literal_expr = |val: String| sp(ast::Expr::Literal(ast::Literal::String(val))); + + let type_cache = TypeCache::new(); + + let mut remaining = format_string.as_str(); + + let mut result_expr = None; + + while let Some(find_result) = find_expr(remaining)? { + remaining = find_result.remaining; + + let sub_expr = { + let value = parse_expr( + arena.borrow(), + symbols, + &type_cache, + find_result.expr.expr, + ) + .map_err(|err| { + Error::message(format!("format! could not parse subexpression: {}", err)) + })? + .value; + + pos::spanned( + span.subspan( + pos::ByteOffset(find_result.expr_start as i64), + pos::ByteOffset(find_result.expr_end as i64), + ), + value, + ) + }; + + let formatted_sub_expr = match find_result.expr.modifier { + FormatModifier::Display => app1(&display_func, sub_expr), + FormatModifier::Debug => app1(&show_func, sub_expr), + }; + + let part_expr = app_exprs( + literal_expr(find_result.prefix.to_owned()), + formatted_sub_expr, + ); + + result_expr = match result_expr.take() { + None => Some(part_expr), + Some(prev_expr) => Some(app_exprs(prev_expr, part_expr)), + }; + } + + let result_expr = match result_expr.take() { + None => literal_expr(remaining.to_owned()), + Some(result_expr) => { + if remaining.is_empty() { + result_expr + } else { + app_exprs(result_expr, literal_expr(remaining.to_owned())) + } + } + }; + + Ok(result_expr.into()) + }) + } +} + +const OPEN_BRACE: &'static str = "{"; +const CLOSE_BRACE: &'static str = "}"; + +enum FormatModifier { + Display, + Debug, +} + +struct FormatExpr<'a> { + expr: &'a str, + modifier: FormatModifier, +} + +struct FindExprResult<'a> { + prefix: &'a str, + expr: FormatExpr<'a>, + remaining: &'a str, + expr_start: usize, + expr_end: usize, +} + +fn parse_format_expr<'a>(contents: &'a str) -> Result, Error> { + let mut split_iter = contents.split(":"); + + match (split_iter.next(), split_iter.next(), split_iter.next()) { + (Some(expr), Some(fmt), None) => { + if fmt == "?" { + Ok(FormatExpr { + expr, + modifier: FormatModifier::Debug, + }) + } else { + Err(Error::message(format!( + "Unrecognized format specifier: {}", + fmt + ))) + } + } + (Some(expr), None, None) => Ok(FormatExpr { + expr, + modifier: FormatModifier::Display, + }), + _ => Err(Error::message(format!( + "At most one colon allowed in format subexpression", + ))), + } +} + +fn find_expr<'a>(format_str: &'a str) -> Result>, Error> { + let open_ix = match format_str.find(OPEN_BRACE) { + None => return Ok(None), + Some(ix) => ix, + }; + + let close_ix = match format_str.find(CLOSE_BRACE) { + Some(ix) if ix < open_ix => { + return Err(Error::message(format!( + "Mismatched braces in format! string" + ))) + } + None => + return Err(Error::message(format!( + "Mismatched braces in format! string" + ))), + Some(ix) => ix, + }; + + let prefix = &format_str[..open_ix]; + let expr_start = OPEN_BRACE.len() + open_ix; + let expr_end = close_ix; + let brace_end = close_ix + CLOSE_BRACE.len(); + + let expr = parse_format_expr(&format_str[expr_start..expr_end])?; + + let remaining = &format_str[brace_end..]; + + Ok(Some(FindExprResult { + prefix, + expr, + remaining, + expr_start, + expr_end, + })) +} diff --git a/src/lib.rs b/src/lib.rs index a244db7726..48dd2710d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,7 @@ pub mod compiler_pipeline; #[macro_use] pub mod import; pub mod lift_io; +pub mod format_macro; #[doc(hidden)] pub mod query; pub mod std_lib; @@ -958,6 +959,8 @@ impl VmBuilder { } macros.insert(String::from("lift_io"), lift_io::LiftIo); + + macros.insert(String::from("format"), format_macro::Format); } add_extern_module_with_deps(