Interpolate strings by desugaring to Str.concat

We could definitely make this more efficent by
allocating enough space for the final string
and then copying the contents of each of the pieces
into it one by one. We don't do that yet though!
This commit is contained in:
Richard Feldman 2020-08-31 23:09:57 -04:00
parent 2e15443c8c
commit 37a254cef3
5 changed files with 60 additions and 96 deletions

View file

@ -45,12 +45,6 @@ impl Output {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum StrSegment {
Interpolation(Located<Expr>),
Plaintext(InlinableString),
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr { pub enum Expr {
// Literals // Literals
@ -62,7 +56,7 @@ pub enum Expr {
// Int and Float store a variable to generate better error messages // Int and Float store a variable to generate better error messages
Int(Variable, i64), Int(Variable, i64),
Float(Variable, f64), Float(Variable, f64),
Str(Vec<StrSegment>), Str(InlinableString),
List { List {
list_var: Variable, // required for uniqueness of the list list_var: Variable, // required for uniqueness of the list
elem_var: Variable, elem_var: Variable,
@ -1333,10 +1327,7 @@ fn flatten_str_literal<'a>(
use ast::StrLiteral::*; use ast::StrLiteral::*;
match literal { match literal {
PlainLine(str_slice) => ( PlainLine(str_slice) => (Expr::Str((*str_slice).into()), Output::default()),
Expr::Str(vec![StrSegment::Plaintext((*str_slice).into())]),
Output::default(),
),
Line(segments) => flatten_str_lines(env, var_store, scope, &[segments]), Line(segments) => flatten_str_lines(env, var_store, scope, &[segments]),
Block(lines) => flatten_str_lines(env, var_store, scope, lines), Block(lines) => flatten_str_lines(env, var_store, scope, lines),
} }
@ -1350,6 +1341,11 @@ fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
} }
} }
enum StrSegment {
Interpolation(Located<Expr>),
Plaintext(InlinableString),
}
fn flatten_str_lines<'a>( fn flatten_str_lines<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
@ -1397,6 +1393,9 @@ fn flatten_str_lines<'a>(
}, },
Interpolated(loc_expr) => { Interpolated(loc_expr) => {
if is_valid_interpolation(loc_expr.value) { if is_valid_interpolation(loc_expr.value) {
// Interpolations desugar to Str.concat calls
output.references.calls.insert(Symbol::STR_CONCAT);
if !buf.is_empty() { if !buf.is_empty() {
segments.push(StrSegment::Plaintext(buf.into())); segments.push(StrSegment::Plaintext(buf.into()));
@ -1432,7 +1431,45 @@ fn flatten_str_lines<'a>(
segments.push(StrSegment::Plaintext(buf.into())); segments.push(StrSegment::Plaintext(buf.into()));
} }
(Expr::Str(segments), output) (desugar_str_segments(var_store, segments), output)
}
/// Resolve stirng interpolations by desugaring a sequence of StrSegments
/// into nested calls to Str.concat
fn desugar_str_segments<'a>(var_store: &mut VarStore, segments: Vec<StrSegment>) -> Expr {
use StrSegment::*;
let mut iter = segments.into_iter().rev();
let mut loc_expr = match iter.next() {
Some(Plaintext(string)) => Located::new(0, 0, 0, 0, Expr::Str(string.into())),
Some(Interpolation(loc_expr)) => loc_expr,
None => {
// No segments? Empty string!
Located::new(0, 0, 0, 0, Expr::Str("".into()))
}
};
for seg in iter {
let loc_new_expr = match seg {
Plaintext(string) => Located::new(0, 0, 0, 0, Expr::Str(string.into())),
Interpolation(loc_interpolated_expr) => loc_interpolated_expr,
};
let fn_expr = Located::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT));
let expr = Expr::Call(
Box::new((var_store.fresh(), fn_expr, var_store.fresh())),
vec![
(var_store.fresh(), loc_new_expr),
(var_store.fresh(), loc_expr),
],
CalledVia::Space,
);
loc_expr = Located::new(0, 0, 0, 0, expr);
}
loc_expr.value
} }
/// Returns the char that would have been originally parsed to /// Returns the char that would have been originally parsed to

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut}; use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Recursive, StrSegment}; use roc_can::expr::Recursive;
use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError}; use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region; use roc_region::all::Region;
use std::{f64, i64}; use std::{f64, i64};
@ -70,7 +70,7 @@ mod test_can {
} }
fn expr_str(contents: &str) -> Expr { fn expr_str(contents: &str) -> Expr {
Expr::Str(vec![StrSegment::Plaintext(contents.into())]) Expr::Str(contents.into())
} }
// NUMBER LITERALS // NUMBER LITERALS

View file

@ -1,4 +1,4 @@
use crate::builtins::{empty_list_type, float_literal, int_literal, list_type}; use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type};
use crate::pattern::{constrain_pattern, PatternState}; use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables; use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
@ -199,35 +199,7 @@ pub fn constrain_expr(
exists(vars, And(cons)) exists(vars, And(cons))
} }
Str(segments) => { Str(_) => Eq(str_type(), expected, Category::Str, region),
use crate::builtins::str_type;
use roc_can::expr::StrSegment::*;
let mut cons = Vec::with_capacity(segments.len() + 1);
let expect_interpolated =
|region| Expected::ForReason(Reason::StrInterpolation, str_type(), region);
for segment in segments {
match segment {
Plaintext(_) => {
// Plaintext strings add no constraints
}
Interpolation(loc_expr) => {
cons.push(constrain_expr(
env,
loc_expr.region,
&loc_expr.value,
expect_interpolated(loc_expr.region),
));
}
}
}
// The expression as a whole should have the type Str.
cons.push(Eq(str_type(), expected, Category::Str, region));
And(cons)
}
List { List {
elem_var, elem_var,
loc_elems, loc_elems,

View file

@ -503,14 +503,7 @@ pub fn constrain_expr(
]), ]),
) )
} }
Str(segments) => { Str(_) => {
if segments
.iter()
.any(|seg| matches!(seg, roc_can::expr::StrSegment::Interpolation(_)))
{
todo!("TODO support unique constraints for interpolated strings.");
}
let uniq_type = var_store.fresh(); let uniq_type = var_store.fresh();
let inferred = str_type(Bool::variable(uniq_type)); let inferred = str_type(Bool::variable(uniq_type));

View file

@ -586,12 +586,6 @@ pub enum Stmt<'a> {
RuntimeError(&'a str), RuntimeError(&'a str),
} }
#[derive(Clone, Debug, PartialEq)]
pub enum StrSegment<'a> {
Interpolation(Expr<'a>),
Plaintext(&'a str),
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Literal<'a> { pub enum Literal<'a> {
// Literals // Literals
@ -1249,44 +1243,12 @@ pub fn with_hole<'a>(
hole, hole,
), ),
Str(segments) => { Str(string) => Stmt::Let(
use roc_can::expr::StrSegment::*; assigned,
Expr::Literal(Literal::Str(arena.alloc(string))),
let iter = &mut segments.into_iter().rev(); Layout::Builtin(Builtin::Str),
let /* mut */ stmt = match iter.next() { hole,
Some(Plaintext(string)) => Stmt::Let( ),
assigned,
Expr::Literal(Literal::Str(arena.alloc(string))),
Layout::Builtin(Builtin::Str),
hole,
),
Some(Interpolation(loc_expr)) => {
with_hole(env, loc_expr.value, procs, layout_cache, assigned, hole)
}
None => {
// No segments? Empty string!
return Stmt::Let(
assigned,
Expr::Literal(Literal::Str("")),
Layout::Builtin(Builtin::Str),
hole,
);
}
};
while let Some(seg) = iter.next() {
match seg {
Plaintext(string) => {
todo!("Str.concat plaintext str with previous: {:?}", string);
}
Interpolation(loc_expr) => {
todo!("Str.concat interplation with previous: {:?}", loc_expr);
}
}
}
stmt
}
Num(var, num) => match num_argument_to_int_or_float(env.subs, var) { Num(var, num) => match num_argument_to_int_or_float(env.subs, var) {
IntOrFloat::IntType => Stmt::Let( IntOrFloat::IntType => Stmt::Let(