diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 67f710c31..cf02d2a5f 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -327,7 +327,8 @@ impl Eval for BlockExpr { let mut output = Value::None; for expr in &self.exprs { - output = expr.eval(ctx); + let value = expr.eval(ctx); + output = output.join(ctx, value, expr.span()); } if self.scoping { @@ -584,19 +585,15 @@ impl Eval for WhileExpr { type Output = Value; fn eval(&self, ctx: &mut EvalContext) -> Self::Output { - let mut output = vec![]; + let mut output = Value::None; loop { let condition = self.condition.eval(ctx); if let Some(condition) = ctx.cast(condition, self.condition.span()) { if condition { - match self.body.eval(ctx) { - Value::Template(v) => output.extend(v), - Value::Str(v) => output.push(TemplateNode::Str(v)), - Value::Error => return Value::Error, - _ => {} - } + let value = self.body.eval(ctx); + output = output.join(ctx, value, self.body.span()); } else { - return Value::Template(output); + return output; } } else { return Value::Error; @@ -611,26 +608,19 @@ impl Eval for ForExpr { fn eval(&self, ctx: &mut EvalContext) -> Self::Output { macro_rules! iter { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ - let mut output = vec![]; + let mut output = Value::None; ctx.scopes.enter(); #[allow(unused_parens)] for ($($value),*) in $iter { $(ctx.scopes.def_mut($binding.as_str(), $value);)* - match self.body.eval(ctx) { - Value::Template(v) => output.extend(v), - Value::Str(v) => output.push(TemplateNode::Str(v)), - Value::Error => { - ctx.scopes.exit(); - return Value::Error; - } - _ => {} - } + let value = self.body.eval(ctx); + output = output.join(ctx, value, self.body.span()); } ctx.scopes.exit(); - Value::Template(output) + output }}; } diff --git a/src/eval/ops.rs b/src/eval/ops.rs index b6bd5402c..2537f4a5f 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -3,6 +3,33 @@ use std::cmp::Ordering::*; use super::{TemplateNode, Value}; use Value::*; +/// Join a value with another value. +pub fn join(lhs: Value, rhs: Value) -> Result { + Ok(match (lhs, rhs) { + (_, Error) => Error, + (Error, _) => Error, + + (a, None) => a, + (None, b) => b, + + (Str(a), Str(b)) => Str(a + &b), + (Array(a), Array(b)) => Array(concat(a, b)), + (Dict(a), Dict(b)) => Dict(concat(a, b)), + + (Template(a), Template(b)) => Template(concat(a, b)), + (Template(mut a), Str(b)) => Template({ + a.push(TemplateNode::Str(b)); + a + }), + (Str(a), Template(mut b)) => Template({ + b.insert(0, TemplateNode::Str(a)); + b + }), + + (a, _) => return Err(a), + }) +} + /// Apply the plus operator to a value. pub fn pos(value: Value) -> Value { match value { @@ -60,8 +87,6 @@ pub fn add(lhs: Value, rhs: Value) -> Value { (Dict(a), Dict(b)) => Dict(concat(a, b)), (Template(a), Template(b)) => Template(concat(a, b)), - (Template(a), None) => Template(a), - (None, Template(b)) => Template(b), (Template(mut a), Str(b)) => Template({ a.push(TemplateNode::Str(b)); a diff --git a/src/eval/value.rs b/src/eval/value.rs index f2bc4fd66..b29d01f32 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::Deref; use std::rc::Rc; +use super::ops; use super::EvalContext; use crate::color::{Color, RgbaColor}; use crate::exec::ExecContext; @@ -61,14 +62,6 @@ impl Value { Self::Template(vec![TemplateNode::Func(TemplateFunc::new(name, f))]) } - /// Try to cast the value into a specific type. - pub fn cast(self) -> CastResult - where - T: Cast, - { - T::cast(self) - } - /// The name of the stored value's type. pub fn type_name(&self) -> &'static str { match self { @@ -125,6 +118,26 @@ impl Value { _ => None, } } + + /// Try to cast the value into a specific type. + pub fn cast(self) -> CastResult + where + T: Cast, + { + T::cast(self) + } + + /// Join with another value. + pub fn join(self, ctx: &mut EvalContext, other: Self, span: Span) -> Self { + let (lhs, rhs) = (self.type_name(), other.type_name()); + match ops::join(self, other) { + Ok(joined) => joined, + Err(prev) => { + ctx.diag(error!(span, "cannot join {} with {}", lhs, rhs)); + prev + } + } + } } impl Default for Value { diff --git a/tests/ref/code/block.png b/tests/ref/code/block.png index 6b0dd540a..3e64d82c5 100644 Binary files a/tests/ref/code/block.png and b/tests/ref/code/block.png differ diff --git a/tests/typ/code/block-invalid.typ b/tests/typ/code/block-invalid.typ index d98bf06bf..ba3f02d36 100644 --- a/tests/typ/code/block-invalid.typ +++ b/tests/typ/code/block-invalid.typ @@ -7,8 +7,9 @@ {1u} // Should output `1`. -// Error: 3 expected semicolon or line break -{0 1} +// Error: 2:3 expected semicolon or line break +// Error: 1:4-1:5 cannot join integer with integer +{1 2} // Should output `2`. // Error: 2:12 expected semicolon or line break diff --git a/tests/typ/code/block.typ b/tests/typ/code/block.typ index 196e6c145..8c30fa648 100644 --- a/tests/typ/code/block.typ +++ b/tests/typ/code/block.typ @@ -9,7 +9,7 @@ All none // Let evaluates to none. { let v = 0 } -// Trailing none evaluates to none. +// Type is joined with trailing none, evaluates to string. { type("") none @@ -19,15 +19,35 @@ All none // Evaluates to single expression. { "Hello" } -// Evaluates to trailing expression. +// Evaluates to string. { let x = "Hel"; x + "lo" } -// Evaluates to concatenation of for loop bodies. +// Evaluates to join of none, [He] and the two loop bodies. { - let parts = ("Hel", "lo") + let parts = ("l", "lo") + [He] for s in parts [{s}] } +--- +// Evaluates to join of the templates and strings. +{ + [Hey, ] + if true { + "there!" + } + [ ] + if false [Nope] + [How are ] + "you?" +} + +{ + [A] + // Error: 5-6 cannot join template with integer + 1 + [B] +} + --- // Works the same way in code environment. // Ref: false diff --git a/tests/typ/code/for.typ b/tests/typ/code/for.typ index 321b08cf5..e6bcf2693 100644 --- a/tests/typ/code/for.typ +++ b/tests/typ/code/for.typ @@ -20,14 +20,13 @@ // String. { - let out = "" let first = true - for c in "abc" { + let out = for c in "abc" { if not first { - out += ", " + ", " } + c first = false - out += c } test(out, "a, b, c") } @@ -36,14 +35,16 @@ // Block body. // Should output `[1st, 2nd, 3rd, 4th, 5th, 6th]`. { - "[" + for v in (1, 2, 3, 4, 5, 6) { - (if v > 1 [, ] - + [{v}] - + if v == 1 [st] - + if v == 2 [nd] - + if v == 3 [rd] - + if v >= 4 [th]) - } + "]" + "[" + for v in (1, 2, 3, 4, 5, 6) { + if v > 1 [, ] + [#v] + if v == 1 [st] + if v == 2 [nd] + if v == 3 [rd] + if v >= 4 [th] + } + "]" } // Template body. @@ -53,8 +54,8 @@ --- // Value of for loops. // Ref: false -#test(type(for v in () {}), "template") -#test(type(for v in () []), "template") +#test(for v in "" [], none) +#test(type(for v in "1" []), "template") --- // Ref: false @@ -67,7 +68,7 @@ // Error: 11-18 cannot add integer and string #for v in 1 + "2" {} -// A single error stops iteration. +// Errors taint everything. #test(error, for v in (1, 2, 3) { if v < 2 [Ok] else {error} }) diff --git a/tests/typ/code/while.typ b/tests/typ/code/while.typ index e55f8f108..306c1e450 100644 --- a/tests/typ/code/while.typ +++ b/tests/typ/code/while.typ @@ -23,8 +23,10 @@ // Value of while loops. // Ref: false -#test(type(while false {}), "template") -#test(type(while false []), "template") +#test(while false {}, none) + +#let i = 0 +#test(type(while i < 1 [{ i += 1 }]), "template") --- // Ref: false @@ -37,13 +39,13 @@ // Error: 8-15 unknown variable #while nothing {} -// A single error stops iteration. +// Errors taint everything. #let i = 0 #test(error, while i < 10 { i += 1 if i < 5 [nope] else { error } }) -#test(i, 5) +#test(i, 10) --- // Error: 7 expected expression diff --git a/tests/typeset.rs b/tests/typeset.rs index 42151ac60..f4e74bc10 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -327,10 +327,8 @@ fn register_helpers(scope: &mut Scope, panics: Rc>>) { let rhs = args.expect::(ctx, "right-hand side"); if lhs != rhs { panics.borrow_mut().push(Panic { pos: args.span.start, lhs, rhs }); - Value::Str(format!("(panic)")) - } else { - Value::None } + Value::None }; scope.def_const("error", Value::Error);