Skip to content

Commit d89ae4e

Browse files
committed
feat: Add format! macro
1 parent 1a5489c commit d89ae4e

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

src/format_macro.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//! Implementation of the `format!` macro
2+
use gluon_codegen::Trace;
3+
4+
use crate::{
5+
base::{
6+
ast::{self, AstClone, SpannedExpr},
7+
pos,
8+
symbol::{Symbol, Symbols},
9+
types::TypeCache,
10+
},
11+
parser::parse_expr,
12+
vm::macros::{self, Macro, MacroExpander, MacroFuture},
13+
};
14+
15+
/**
16+
* Format macro with expressions embedded in the format string
17+
*
18+
* ```
19+
* let x = 1
20+
* format! "x is {x}, x + 1 is {x+1}"
21+
* ```
22+
*/
23+
#[derive(Trace)]
24+
#[gluon(crate_name = "vm")]
25+
pub struct Format;
26+
27+
impl Macro for Format {
28+
fn expand<'r, 'a: 'r, 'b: 'r, 'c: 'r, 'ast: 'r>(
29+
&self,
30+
_env: &'b mut MacroExpander<'a>,
31+
symbols: &'c mut Symbols,
32+
arena: &'b mut ast::OwnedArena<'ast, Symbol>,
33+
args: &'b mut [SpannedExpr<'ast, Symbol>],
34+
) -> MacroFuture<'r, 'ast> {
35+
Box::pin(async move {
36+
let arg = match args {
37+
[arg] => (arg),
38+
_ => {
39+
return Err(macros::Error::message(format!(
40+
"format! expects 1 argument"
41+
)))
42+
}
43+
};
44+
45+
let expr: &ast::Expr<Symbol> = &arg.value;
46+
47+
let format_string = match expr {
48+
ast::Expr::Literal(ast::Literal::String(text)) => text,
49+
_ => {
50+
return Err(macros::Error::message(format!(
51+
"format! expects a string argument"
52+
)))
53+
}
54+
};
55+
56+
let span = arg.span;
57+
58+
let sp = |e: ast::Expr<'ast, Symbol>| pos::spanned(span, e);
59+
60+
let show_ident = ast::TypedIdent::new(symbols.simple_symbol("show"));
61+
let show_func = arena.alloc(sp(ast::Expr::Ident(show_ident)));
62+
63+
let append_ident = ast::TypedIdent::new(symbols.simple_symbol("(++)"));
64+
let append_func = arena.alloc(sp(ast::Expr::Ident(append_ident)));
65+
66+
let show_expr = |e: ast::SpannedExpr<'ast, Symbol>| {
67+
let func = show_func.ast_clone(arena.borrow());
68+
69+
sp(ast::Expr::App {
70+
func,
71+
implicit_args: arena.alloc_extend(vec![]),
72+
args: arena.alloc_extend(vec![e]),
73+
})
74+
};
75+
76+
let app_exprs = |lhs, rhs| {
77+
let func = append_func.ast_clone(arena.borrow());
78+
79+
sp(ast::Expr::App {
80+
func,
81+
implicit_args: arena.alloc_extend(vec![]),
82+
args: arena.alloc_extend(vec![lhs, rhs]),
83+
})
84+
};
85+
86+
let literal_expr = |val: String| sp(ast::Expr::Literal(ast::Literal::String(val)));
87+
88+
let type_cache = TypeCache::new();
89+
90+
let mut remaining = format_string.as_str();
91+
92+
let mut result_expr = None;
93+
94+
while let Some(find_result) = find_expr(remaining)? {
95+
remaining = find_result.remaining;
96+
97+
let sub_expr =
98+
parse_expr(arena.borrow(), symbols, &type_cache, find_result.expr)
99+
.map_err(|err| {
100+
macros::Error::message(format!(
101+
"format! could not parse subexpression: {}",
102+
err
103+
))
104+
})?;
105+
106+
let part_expr = app_exprs(
107+
literal_expr(find_result.prefix.to_owned()),
108+
show_expr(sub_expr),
109+
);
110+
111+
result_expr = match result_expr.take() {
112+
None => Some(part_expr),
113+
Some(prev_expr) => Some(app_exprs(prev_expr, part_expr)),
114+
};
115+
}
116+
117+
let result_expr = match result_expr.take() {
118+
None => literal_expr(remaining.to_owned()),
119+
Some(result_expr) => {
120+
if remaining.is_empty() {
121+
result_expr
122+
} else {
123+
app_exprs(result_expr, literal_expr(remaining.to_owned()))
124+
}
125+
}
126+
};
127+
128+
Ok(result_expr.into())
129+
})
130+
}
131+
}
132+
133+
const OPEN_BRACE: &'static str = "{";
134+
const CLOSE_BRACE: &'static str = "}";
135+
136+
struct FindExprResult<'a> {
137+
prefix: &'a str,
138+
expr: &'a str,
139+
remaining: &'a str,
140+
}
141+
142+
fn find_expr<'a>(format_str: &'a str) -> Result<Option<FindExprResult<'a>>, macros::Error> {
143+
let prefix_end_ix = match format_str.find(OPEN_BRACE) {
144+
None => return Ok(None),
145+
Some(ix) => ix,
146+
};
147+
148+
let prefix = &format_str[..prefix_end_ix];
149+
let expr_start = OPEN_BRACE.len() + prefix_end_ix;
150+
151+
let mut brace_depth = 1;
152+
153+
let mut expr_end = expr_start;
154+
let mut brace_end = expr_start;
155+
156+
while brace_depth != 0 {
157+
let next_open = format_str[brace_end..].find(OPEN_BRACE);
158+
let next_close = format_str[brace_end..].find(CLOSE_BRACE);
159+
160+
let (brace_ix, is_close) = match (next_open, next_close) {
161+
(None, None) => break,
162+
(None, Some(ix)) => (ix, true),
163+
(Some(ix), None) => (ix, false),
164+
(Some(open_ix), Some(close_ix)) => {
165+
if open_ix < close_ix {
166+
(open_ix, false)
167+
} else {
168+
(close_ix, true)
169+
}
170+
}
171+
};
172+
173+
expr_end = brace_end + brace_ix;
174+
brace_end = if is_close {
175+
brace_depth -= 1;
176+
expr_end + OPEN_BRACE.len()
177+
} else {
178+
brace_depth += 1;
179+
expr_end + CLOSE_BRACE.len()
180+
};
181+
}
182+
183+
if brace_depth != 0 {
184+
return Err(macros::Error::message(format!("mismatched braces")));
185+
}
186+
187+
let expr = &format_str[expr_start..expr_end];
188+
189+
let remaining = &format_str[brace_end..];
190+
191+
Ok(Some(FindExprResult {
192+
prefix,
193+
expr,
194+
remaining,
195+
}))
196+
}
197+

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub mod compiler_pipeline;
4949
#[macro_use]
5050
pub mod import;
5151
pub mod lift_io;
52+
pub mod format_macro;
5253
#[doc(hidden)]
5354
pub mod query;
5455
pub mod std_lib;
@@ -958,6 +959,8 @@ impl VmBuilder {
958959
}
959960

960961
macros.insert(String::from("lift_io"), lift_io::LiftIo);
962+
963+
macros.insert(String::from("format"), format_macro::Format);
961964
}
962965

963966
add_extern_module_with_deps(

0 commit comments

Comments
 (0)