mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Constrain string interpolation
This commit is contained in:
parent
5080a7e24b
commit
274e7e786d
11 changed files with 150 additions and 180 deletions
|
@ -9,12 +9,13 @@ use crate::num::{
|
|||
use crate::pattern::{canonicalize_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
use crate::scope::Scope;
|
||||
use inlinable_string::InlinableString;
|
||||
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, StrLiteral, StrSegment};
|
||||
use roc_parse::ast::{self, StrLiteral};
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||
use roc_region::all::{Located, Region};
|
||||
|
@ -44,6 +45,12 @@ impl Output {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum StrSegment {
|
||||
Interpolation(Located<Expr>),
|
||||
Plaintext(InlinableString),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expr {
|
||||
// Literals
|
||||
|
@ -55,10 +62,7 @@ pub enum Expr {
|
|||
// Int and Float store a variable to generate better error messages
|
||||
Int(Variable, i64),
|
||||
Float(Variable, f64),
|
||||
Str {
|
||||
interpolations: Vec<(Box<str>, Symbol)>,
|
||||
suffix: Box<str>,
|
||||
},
|
||||
Str(Vec<StrSegment>),
|
||||
List {
|
||||
list_var: Variable, // required for uniqueness of the list
|
||||
elem_var: Variable,
|
||||
|
@ -249,7 +253,7 @@ pub fn canonicalize_expr<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, scope, literal),
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
ast::Expr::List(loc_elems) => {
|
||||
if loc_elems.is_empty() {
|
||||
(
|
||||
|
@ -1320,32 +1324,39 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
}
|
||||
}
|
||||
|
||||
fn flatten_str_literal(
|
||||
env: &mut Env<'_>,
|
||||
fn flatten_str_literal<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
literal: &StrLiteral<'_>,
|
||||
literal: &StrLiteral<'a>,
|
||||
) -> (Expr, Output) {
|
||||
use ast::StrLiteral::*;
|
||||
|
||||
match literal {
|
||||
PlainLine(str_slice) => (
|
||||
Expr::Str {
|
||||
interpolations: Vec::new(),
|
||||
suffix: (*str_slice).into(),
|
||||
},
|
||||
Expr::Str(vec![StrSegment::Plaintext((*str_slice).into())]),
|
||||
Output::default(),
|
||||
),
|
||||
LineWithEscapes(segments) => flatten_str_lines(env, scope, &[segments]),
|
||||
Block(lines) => flatten_str_lines(env, scope, lines),
|
||||
Line(segments) => flatten_str_lines(env, var_store, scope, &[segments]),
|
||||
Block(lines) => flatten_str_lines(env, var_store, scope, lines),
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_str_lines(
|
||||
env: &mut Env<'_>,
|
||||
fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
||||
match expr {
|
||||
ast::Expr::Var { .. } => true,
|
||||
ast::Expr::Access(sub_expr, _) => is_valid_interpolation(sub_expr),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten_str_lines<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
lines: &[&[StrSegment<'_>]],
|
||||
lines: &[&[ast::StrSegment<'a>]],
|
||||
) -> (Expr, Output) {
|
||||
use StrSegment::*;
|
||||
use ast::StrSegment::*;
|
||||
|
||||
let mut buf = String::new();
|
||||
let mut interpolations = Vec::new();
|
||||
|
@ -1360,37 +1371,32 @@ fn flatten_str_lines(
|
|||
Unicode(loc_digits) => {
|
||||
todo!("parse unicode digits {:?}", loc_digits);
|
||||
}
|
||||
Interpolated {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
} => {
|
||||
let (expr, new_output) =
|
||||
canonicalize_lookup(env, scope, module_name, ident, region.clone());
|
||||
Interpolated(loc_expr) => {
|
||||
if is_valid_interpolation(loc_expr.value) {
|
||||
let (loc_expr, new_output) = canonicalize_expr(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
loc_expr.region,
|
||||
loc_expr.value,
|
||||
);
|
||||
|
||||
output.union(new_output);
|
||||
output.union(new_output);
|
||||
|
||||
match expr {
|
||||
Expr::Var(symbol) => {
|
||||
interpolations.push((buf.into(), symbol));
|
||||
}
|
||||
_ => {
|
||||
todo!("TODO gracefully handle non-ident in string interpolation.");
|
||||
}
|
||||
interpolations.push(StrSegment::Interpolation(loc_expr));
|
||||
} else {
|
||||
env.problem(Problem::InvalidInterpolation(loc_expr.region));
|
||||
|
||||
return (
|
||||
Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)),
|
||||
output,
|
||||
);
|
||||
}
|
||||
|
||||
buf = String::new();
|
||||
}
|
||||
EscapedChar(ch) => buf.push(*ch),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Expr::Str {
|
||||
interpolations,
|
||||
suffix: buf.into(),
|
||||
},
|
||||
output,
|
||||
)
|
||||
(Expr::Str(interpolations), output)
|
||||
}
|
||||
|
|
|
@ -471,7 +471,7 @@ fn flatten_str_literal(literal: &StrLiteral<'_>) -> Pattern {
|
|||
|
||||
match literal {
|
||||
PlainLine(str_slice) => Pattern::StrLiteral((*str_slice).into()),
|
||||
LineWithEscapes(segments) => flatten_str_lines(&[segments]),
|
||||
Line(segments) => flatten_str_lines(&[segments]),
|
||||
Block(lines) => flatten_str_lines(lines),
|
||||
}
|
||||
}
|
||||
|
@ -490,8 +490,8 @@ fn flatten_str_lines(lines: &[&[StrSegment<'_>]]) -> Pattern {
|
|||
Unicode(loc_digits) => {
|
||||
todo!("parse unicode digits {:?}", loc_digits);
|
||||
}
|
||||
Interpolated { region, .. } => {
|
||||
return Pattern::UnsupportedPattern(region.clone());
|
||||
Interpolated(loc_expr) => {
|
||||
return Pattern::UnsupportedPattern(loc_expr.region);
|
||||
}
|
||||
EscapedChar(ch) => buf.push(*ch),
|
||||
}
|
||||
|
|
|
@ -1236,113 +1236,6 @@ mod test_can {
|
|||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_at_start() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
"\(abc)defg"
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
assert_eq!(problems, Vec::new());
|
||||
// let (args, ret) = (vec![("", Located::new(0, 2, 0, 4, Var("abc")))], "defg");
|
||||
// let arena = Bump::new();
|
||||
// let actual = parse_with(&arena, input);
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(Expr::InterpolatedStr(&(
|
||||
// arena.alloc_slice_clone(&args),
|
||||
// ret
|
||||
// ))),
|
||||
// actual
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_at_end() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
"abcd\(efg)"
|
||||
"#
|
||||
);
|
||||
// let (args, ret) = (vec![("abcd", Located::new(0, 6, 0, 8, Var("efg")))], "");
|
||||
// let arena = Bump::new();
|
||||
// let actual = parse_with(&arena, input);
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
||||
// actual
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_interpolation_in_middle() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
"abc\(defg)hij"
|
||||
"#
|
||||
);
|
||||
// let (args, ret) = (vec![("abc", Located::new(0, 5, 0, 8, Var("defg")))], "hij");
|
||||
// let arena = Bump::new();
|
||||
// let actual = parse_with(&arena, input);
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
||||
// actual
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_two_interpolations_in_middle() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
"abc\(defg)hi\(jkl)mn"
|
||||
"#
|
||||
);
|
||||
// let (args, ret) = (
|
||||
// vec![
|
||||
// ("abc", Located::new(0, 5, 0, 8, Var("defg"))),
|
||||
// ("hi", Located::new(0, 14, 0, 16, Var("jkl"))),
|
||||
// ],
|
||||
// "mn",
|
||||
// );
|
||||
// let arena = Bump::new();
|
||||
// let actual = parse_with(&arena, input);
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
||||
// actual
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_with_four_interpolations() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
"\(abc)def\(ghi)jkl\(mno)pqrs\(tuv)"
|
||||
"#
|
||||
);
|
||||
// let (args, ret) = (
|
||||
// vec![
|
||||
// ("", Located::new(0, 2, 0, 4, Var("abc"))),
|
||||
// ("def", Located::new(0, 11, 0, 13, Var("ghi"))),
|
||||
// ("jkl", Located::new(0, 20, 0, 22, Var("mno"))),
|
||||
// ("pqrs", Located::new(0, 30, 0, 32, Var("tuv"))),
|
||||
// ],
|
||||
// "",
|
||||
// );
|
||||
// let arena = Bump::new();
|
||||
// let actual = parse_with(&arena, input);
|
||||
|
||||
// assert_eq!(
|
||||
// Ok(InterpolatedStr(&(arena.alloc_slice_clone(&args), ret))),
|
||||
// actual
|
||||
// );
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn string_with_escaped_interpolation() {
|
||||
// assert_parses_to(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue