From 806d9f0d9ab381500318f3e106b9c20c5eabccb7 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 26 May 2022 17:14:44 +0200 Subject: [PATCH] Pure functions! --- Cargo.lock | 83 --------------------- Cargo.toml | 1 - src/eval/capture.rs | 16 ++-- src/eval/func.rs | 4 +- src/eval/mod.rs | 100 ++++++++----------------- src/eval/scope.rs | 140 +++++++++++++++++++++-------------- src/library/mod.rs | 60 +++++++-------- tests/typ/code/closure.typ | 13 ++-- tests/typ/code/return.typ | 24 +++--- tests/typ/code/spread.typ | 10 +-- tests/typ/layout/stack-1.typ | 11 +-- tests/typeset.rs | 6 +- 12 files changed, 187 insertions(+), 281 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f54d5f78..2f35c121b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,16 +433,6 @@ dependencies = [ "rand_chacha 0.3.1", ] -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.17" @@ -568,29 +558,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" -[[package]] -name = "parking_lot" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - [[package]] name = "pathfinder_color" version = "0.5.0" @@ -943,12 +910,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "semver" version = "0.11.0" @@ -1177,7 +1138,6 @@ dependencies = [ "memmap2", "miniz_oxide 0.4.4", "once_cell", - "parking_lot", "pdf-writer", "pico-args", "pixglyph", @@ -1420,49 +1380,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "xi-unicode" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 585e85ee0..494a912f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ lipsum = { git = "https://github.com/reknih/lipsum", default-features = false } once_cell = "1" serde = { version = "1", features = ["derive"] } typed-arena = "2" -parking_lot = "0.12" unscanny = { git = "https://github.com/typst/unscanny" } regex = "1" diff --git a/src/eval/capture.rs b/src/eval/capture.rs index 24fc7abcd..1192eaa78 100644 --- a/src/eval/capture.rs +++ b/src/eval/capture.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use super::{Scope, Scopes, Value}; use crate::syntax::ast::{ClosureParam, Expr, Ident, Imports, TypedNode}; use crate::syntax::RedRef; @@ -28,14 +26,14 @@ impl<'a> CapturesVisitor<'a> { /// Bind a new internal variable. pub fn bind(&mut self, ident: Ident) { - self.internal.top.def_mut(ident.take(), Value::None); + self.internal.top.define(ident.take(), Value::None); } /// Capture a variable if it isn't internal. pub fn capture(&mut self, ident: Ident) { - if self.internal.get(&ident).is_none() { - if let Some(slot) = self.external.get(&ident) { - self.captures.def_slot(ident.take(), Arc::clone(slot)); + if self.internal.get(&ident).is_err() { + if let Ok(value) = self.external.get(&ident) { + self.captures.define_captured(ident.take(), value.clone()); } } } @@ -145,9 +143,9 @@ mod tests { let red = RedNode::from_root(green, SourceId::from_raw(0)); let mut scopes = Scopes::new(None); - scopes.top.def_const("x", 0); - scopes.top.def_const("y", 0); - scopes.top.def_const("z", 0); + scopes.top.define("x", 0); + scopes.top.define("y", 0); + scopes.top.define("z", 0); let mut visitor = CapturesVisitor::new(&scopes); visitor.visit(red.as_ref()); diff --git a/src/eval/func.rs b/src/eval/func.rs index f15549dce..12dbfb2ee 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -206,7 +206,7 @@ impl Closure { // Parse the arguments according to the parameter list. for (param, default) in &self.params { - scopes.top.def_mut(param, match default { + scopes.top.define(param, match default { None => args.expect::(param)?, Some(default) => { args.named::(param)?.unwrap_or_else(|| default.clone()) @@ -216,7 +216,7 @@ impl Closure { // Put the remaining arguments into the sink. if let Some(sink) = &self.sink { - scopes.top.def_mut(sink, args.take()); + scopes.top.define(sink, args.take()); } // Determine the route inside the closure. diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d76257fa9..db7595f9c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -31,7 +31,6 @@ pub use value::*; use std::collections::BTreeMap; -use parking_lot::{MappedRwLockWriteGuard, RwLockWriteGuard}; use unicode_segmentation::UnicodeSegmentation; use crate::diag::{At, StrResult, Trace, Tracepoint, TypResult}; @@ -165,7 +164,7 @@ fn eval_markup( } MarkupNode::Expr(Expr::Wrap(wrap)) => { let tail = eval_markup(vm, nodes)?; - vm.scopes.top.def_mut(wrap.binding().take(), tail); + vm.scopes.top.define(wrap.binding().take(), tail); wrap.body().eval(vm)?.display() } @@ -354,10 +353,7 @@ impl Eval for Ident { type Output = Value; fn eval(&self, vm: &mut Machine) -> TypResult { - match vm.scopes.get(self) { - Some(slot) => Ok(slot.read().clone()), - None => bail!(self.span(), "unknown variable"), - } + vm.scopes.get(self).cloned().at(self.span()) } } @@ -404,7 +400,7 @@ fn eval_code( } Expr::Wrap(wrap) => { let tail = eval_code(vm, exprs)?; - vm.scopes.top.def_mut(wrap.binding().take(), tail); + vm.scopes.top.define(wrap.binding().take(), tail); wrap.body().eval(vm)? } @@ -565,8 +561,7 @@ impl BinaryExpr { op: fn(Value, Value) -> StrResult, ) -> TypResult { let rhs = self.rhs().eval(vm)?; - let lhs = self.lhs(); - let mut location = lhs.access(vm)?; + let location = self.lhs().access(vm)?; let lhs = std::mem::take(&mut *location); *location = op(lhs, rhs).at(self.span())?; Ok(Value::None) @@ -748,7 +743,7 @@ impl Eval for LetExpr { Some(expr) => expr.eval(vm)?, None => Value::None, }; - vm.scopes.top.def_mut(self.binding().take(), value); + vm.scopes.top.define(self.binding().take(), value); Ok(Value::None) } } @@ -860,7 +855,7 @@ impl Eval for ForExpr { (for ($($binding:ident => $value:ident),*) in $iter:expr) => {{ #[allow(unused_parens)] for ($($value),*) in $iter { - $(vm.scopes.top.def_mut(&$binding, $value);)* + $(vm.scopes.top.define(&$binding, $value);)* let body = self.body(); let value = body.eval(vm)?; @@ -937,14 +932,14 @@ impl Eval for ImportExpr { match self.imports() { Imports::Wildcard => { - for (var, slot) in module.scope.iter() { - vm.scopes.top.def_mut(var, slot.read().clone()); + for (var, value) in module.scope.iter() { + vm.scopes.top.define(var, value.clone()); } } Imports::Items(idents) => { for ident in idents { - if let Some(slot) = module.scope.get(&ident) { - vm.scopes.top.def_mut(ident.take(), slot.read().clone()); + if let Some(value) = module.scope.get(&ident) { + vm.scopes.top.define(ident.take(), value.clone()); } else { bail!(ident.span(), "unresolved import"); } @@ -1028,11 +1023,11 @@ impl Eval for ReturnExpr { /// Access an expression mutably. pub trait Access { /// Access the value. - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult>; + fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value>; } impl Access for Expr { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult> { + fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { match self { Expr::Ident(v) => v.access(vm), Expr::FieldAccess(v) => v.access(vm), @@ -1043,68 +1038,35 @@ impl Access for Expr { } impl Access for Ident { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult> { - match vm.scopes.get(self) { - Some(slot) => match slot.try_write() { - Some(guard) => Ok(RwLockWriteGuard::map(guard, |v| v)), - None => bail!(self.span(), "cannot mutate a constant"), - }, - None => bail!(self.span(), "unknown variable"), - } + fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + vm.scopes.get_mut(self).at(self.span()) } } impl Access for FieldAccess { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult> { - let guard = self.object().access(vm)?; - try_map(guard, |value| { - Ok(match value { - Value::Dict(dict) => dict.get_mut(self.field().take()), - v => bail!( - self.object().span(), - "expected dictionary, found {}", - v.type_name(), - ), - }) + fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { + Ok(match self.object().access(vm)? { + Value::Dict(dict) => dict.get_mut(self.field().take()), + v => bail!( + self.object().span(), + "expected dictionary, found {}", + v.type_name(), + ), }) } } impl Access for FuncCall { - fn access<'a>(&self, vm: &'a mut Machine) -> TypResult> { + fn access<'a>(&self, vm: &'a mut Machine) -> TypResult<&'a mut Value> { let args = self.args().eval(vm)?; - let guard = self.callee().access(vm)?; - try_map(guard, |value| { - Ok(match value { - Value::Array(array) => { - array.get_mut(args.into_index()?).at(self.span())? - } - Value::Dict(dict) => dict.get_mut(args.into_key()?), - v => bail!( - self.callee().span(), - "expected collection, found {}", - v.type_name(), - ), - }) + Ok(match self.callee().access(vm)? { + Value::Array(array) => array.get_mut(args.into_index()?).at(self.span())?, + Value::Dict(dict) => dict.get_mut(args.into_key()?), + v => bail!( + self.callee().span(), + "expected collection, found {}", + v.type_name(), + ), }) } } - -/// A mutable location. -type Location<'a> = MappedRwLockWriteGuard<'a, Value>; - -/// Map a reader-writer lock with a function. -fn try_map(location: Location, f: F) -> TypResult -where - F: FnOnce(&mut Value) -> TypResult<&mut Value>, -{ - let mut error = None; - MappedRwLockWriteGuard::try_map(location, |value| match f(value) { - Ok(value) => Some(value), - Err(err) => { - error = Some(err); - None - } - }) - .map_err(|_| error.unwrap()) -} diff --git a/src/eval/scope.rs b/src/eval/scope.rs index 8a0b81659..29778a903 100644 --- a/src/eval/scope.rs +++ b/src/eval/scope.rs @@ -1,18 +1,11 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; -use std::hash::{Hash, Hasher}; -use std::iter; -use std::sync::Arc; - -use parking_lot::RwLock; +use std::hash::Hash; use super::{Args, Func, Machine, Node, Value}; -use crate::diag::TypResult; +use crate::diag::{StrResult, TypResult}; use crate::util::EcoString; -/// A slot where a variable is stored. -pub type Slot = Arc>; - /// A stack of scopes. #[derive(Debug, Default, Clone)] pub struct Scopes<'a> { @@ -42,21 +35,33 @@ impl<'a> Scopes<'a> { self.top = self.scopes.pop().expect("no pushed scope"); } - /// Look up the slot of a variable. - pub fn get(&self, var: &str) -> Option<&Slot> { - iter::once(&self.top) + /// Try to access a variable immutably. + pub fn get(&self, var: &str) -> StrResult<&Value> { + Ok(std::iter::once(&self.top) .chain(self.scopes.iter().rev()) .chain(self.base.into_iter()) .find_map(|scope| scope.get(var)) + .ok_or("unknown variable")?) + } + + /// Try to access a variable mutably. + pub fn get_mut(&mut self, var: &str) -> StrResult<&mut Value> { + std::iter::once(&mut self.top) + .chain(&mut self.scopes.iter_mut().rev()) + .find_map(|scope| scope.get_mut(var)) + .ok_or_else(|| { + if self.base.map_or(false, |base| base.get(var).is_some()) { + "cannot mutate a constant" + } else { + "unknown variable" + } + })? } } -/// A map from variable names to variable slots. -#[derive(Default, Clone)] -pub struct Scope { - /// The mapping from names to slots. - values: BTreeMap, -} +/// A map from binding names to values. +#[derive(Default, Clone, Hash)] +pub struct Scope(BTreeMap); impl Scope { /// Create a new empty scope. @@ -64,24 +69,9 @@ impl Scope { Self::default() } - /// Define a constant variable with a value. - pub fn def_const(&mut self, var: impl Into, value: impl Into) { - let cell = RwLock::new(value.into()); - - // Make it impossible to write to this value again. - std::mem::forget(cell.read()); - - self.values.insert(var.into(), Arc::new(cell)); - } - - /// Define a mutable variable with a value. - pub fn def_mut(&mut self, var: impl Into, value: impl Into) { - self.values.insert(var.into(), Arc::new(RwLock::new(value.into()))); - } - - /// Define a variable with a slot. - pub fn def_slot(&mut self, var: impl Into, slot: Slot) { - self.values.insert(var.into(), slot); + /// Bind a value to a name. + pub fn define(&mut self, name: impl Into, value: impl Into) { + self.0.insert(name.into(), Slot::new(value.into(), Kind::Normal)); } /// Define a function through a native rust function. @@ -90,32 +80,36 @@ impl Scope { name: &'static str, func: fn(&mut Machine, &mut Args) -> TypResult, ) { - self.def_const(name, Func::from_fn(name, func)); + self.define(name, Func::from_fn(name, func)); } /// Define a function through a native rust node. pub fn def_node(&mut self, name: &'static str) { - self.def_const(name, Func::from_node::(name)); + self.define(name, Func::from_node::(name)); } - /// Look up the value of a variable. - pub fn get(&self, var: &str) -> Option<&Slot> { - self.values.get(var) + /// Define a captured, immutable binding. + pub fn define_captured( + &mut self, + var: impl Into, + value: impl Into, + ) { + self.0.insert(var.into(), Slot::new(value.into(), Kind::Captured)); + } + + /// Try to access a variable immutably. + pub fn get(&self, var: &str) -> Option<&Value> { + self.0.get(var).map(Slot::read) + } + + /// Try to access a variable mutably. + pub fn get_mut(&mut self, var: &str) -> Option> { + self.0.get_mut(var).map(Slot::write) } /// Iterate over all definitions. - pub fn iter(&self) -> impl Iterator { - self.values.iter().map(|(k, v)| (k.as_str(), v)) - } -} - -impl Hash for Scope { - fn hash(&self, state: &mut H) { - self.values.len().hash(state); - for (name, value) in self.values.iter() { - name.hash(state); - value.read().hash(state); - } + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|(k, v)| (k.as_str(), v.read())) } } @@ -123,7 +117,45 @@ impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.write_str("Scope ")?; f.debug_map() - .entries(self.values.iter().map(|(k, v)| (k, v.read()))) + .entries(self.0.iter().map(|(k, v)| (k, v.read()))) .finish() } } + +/// A slot where a variable is stored. +#[derive(Clone, Hash)] +struct Slot { + /// The stored value. + value: Value, + /// The kind of slot, determines how the value can be accessed. + kind: Kind, +} + +/// The different kinds of slots. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +enum Kind { + /// A normal, mutable binding. + Normal, + /// A captured copy of another variable. + Captured, +} + +impl Slot { + /// Create a new constant slot. + fn new(value: Value, kind: Kind) -> Self { + Self { value, kind } + } + + /// Read the variable. + fn read(&self) -> &Value { + &self.value + } + + /// Try to write to the variable. + fn write(&mut self) -> StrResult<&mut Value> { + match self.kind { + Kind::Normal => Ok(&mut self.value), + Kind::Captured => Err("cannot mutate a captured variable")?, + } + } +} diff --git a/src/library/mod.rs b/src/library/mod.rs index cf5d6e64f..27658189b 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -96,38 +96,38 @@ pub fn new() -> Scope { std.def_fn("lorem", utility::lorem); // Predefined colors. - std.def_const("black", Color::BLACK); - std.def_const("gray", Color::GRAY); - std.def_const("silver", Color::SILVER); - std.def_const("white", Color::WHITE); - std.def_const("navy", Color::NAVY); - std.def_const("blue", Color::BLUE); - std.def_const("aqua", Color::AQUA); - std.def_const("teal", Color::TEAL); - std.def_const("eastern", Color::EASTERN); - std.def_const("purple", Color::PURPLE); - std.def_const("fuchsia", Color::FUCHSIA); - std.def_const("maroon", Color::MAROON); - std.def_const("red", Color::RED); - std.def_const("orange", Color::ORANGE); - std.def_const("yellow", Color::YELLOW); - std.def_const("olive", Color::OLIVE); - std.def_const("green", Color::GREEN); - std.def_const("lime", Color::LIME); + std.define("black", Color::BLACK); + std.define("gray", Color::GRAY); + std.define("silver", Color::SILVER); + std.define("white", Color::WHITE); + std.define("navy", Color::NAVY); + std.define("blue", Color::BLUE); + std.define("aqua", Color::AQUA); + std.define("teal", Color::TEAL); + std.define("eastern", Color::EASTERN); + std.define("purple", Color::PURPLE); + std.define("fuchsia", Color::FUCHSIA); + std.define("maroon", Color::MAROON); + std.define("red", Color::RED); + std.define("orange", Color::ORANGE); + std.define("yellow", Color::YELLOW); + std.define("olive", Color::OLIVE); + std.define("green", Color::GREEN); + std.define("lime", Color::LIME); // Other constants. - std.def_const("ltr", Dir::LTR); - std.def_const("rtl", Dir::RTL); - std.def_const("ttb", Dir::TTB); - std.def_const("btt", Dir::BTT); - std.def_const("start", RawAlign::Start); - std.def_const("end", RawAlign::End); - std.def_const("left", RawAlign::Specific(Align::Left)); - std.def_const("center", RawAlign::Specific(Align::Center)); - std.def_const("right", RawAlign::Specific(Align::Right)); - std.def_const("top", RawAlign::Specific(Align::Top)); - std.def_const("horizon", RawAlign::Specific(Align::Horizon)); - std.def_const("bottom", RawAlign::Specific(Align::Bottom)); + std.define("ltr", Dir::LTR); + std.define("rtl", Dir::RTL); + std.define("ttb", Dir::TTB); + std.define("btt", Dir::BTT); + std.define("start", RawAlign::Start); + std.define("end", RawAlign::End); + std.define("left", RawAlign::Specific(Align::Left)); + std.define("center", RawAlign::Specific(Align::Center)); + std.define("right", RawAlign::Specific(Align::Right)); + std.define("top", RawAlign::Specific(Align::Top)); + std.define("horizon", RawAlign::Specific(Align::Horizon)); + std.define("bottom", RawAlign::Specific(Align::Bottom)); std } diff --git a/tests/typ/code/closure.typ b/tests/typ/code/closure.typ index aa7bc5b92..e9389e131 100644 --- a/tests/typ/code/closure.typ +++ b/tests/typ/code/closure.typ @@ -31,7 +31,7 @@ --- // Capture environment. { - let mark = "?" + let mark = "!" let greet = { let hi = "Hi" name => { @@ -39,9 +39,10 @@ } } - test(greet("Typst"), "Hi, Typst?") + test(greet("Typst"), "Hi, Typst!") - mark = "!" + // Changing the captured variable after the closure definition has no effect. + mark = "?" test(greet("Typst"), "Hi, Typst!") } @@ -71,12 +72,12 @@ // For loop bindings. { let v = (1, 2, 3) - let s = 0 let f() = { + let s = 0 for v in v { s += v } + s } - f() - test(s, 6) + test(f(), 6) } --- diff --git a/tests/typ/code/return.typ b/tests/typ/code/return.typ index 8db99a813..0eea394e2 100644 --- a/tests/typ/code/return.typ +++ b/tests/typ/code/return.typ @@ -55,30 +55,28 @@ --- // Test that the expression is evaluated to the end. -#let y = 1 -#let identity(x, ..rest) = x -#let f(x) = { - identity( - ..return, - x + 1, - y = 2, - ) +#let sum(..args) = { + let s = 0 + for v in args.positional() { + s += v + } + s +} + +#let f() = { + sum(..return, 1, 2, 3) "nope" } -#test(f(1), 2) -#test(y, 2) +#test(f(), 6) --- // Test value return from content. #let x = 3 #let f() = [ Hello 😀 - { x = 1 } #return "nope" - { x = 2 } World ] #test(f(), "nope") -#test(x, 1) diff --git a/tests/typ/code/spread.typ b/tests/typ/code/spread.typ index 86dbfd989..ff661eadb 100644 --- a/tests/typ/code/spread.typ +++ b/tests/typ/code/spread.typ @@ -23,16 +23,14 @@ } --- -// Test storing arguments in a variable. +// Test doing things with arguments. { - let args - let save(..sink) = { - args = sink + let save(..args) = { + test(type(args), "arguments") + test(repr(args), "(1, 2, three: true)") } save(1, 2, three: true) - test(type(args), "arguments") - test(repr(args), "(1, 2, three: true)") } --- diff --git a/tests/typ/layout/stack-1.typ b/tests/typ/layout/stack-1.typ index 19a00de59..0ecbe246d 100644 --- a/tests/typ/layout/stack-1.typ +++ b/tests/typ/layout/stack-1.typ @@ -7,13 +7,14 @@ 30pt, 50%, 20pt, 100%, ) -#let shaded = { - let v = 0% - let next() = { v += 10%; rgb(v, v, v) } - w => rect(width: w, height: 10pt, fill: next()) +#let shaded(i, w) = { + let v = (i + 1) * 10% + rect(width: w, height: 10pt, fill: rgb(v, v, v)) } -#let items = for w in widths { (align(right, shaded(w)),) } +#let items = for i, w in widths { + (align(right, shaded(i, w)),) +} #set page(width: 50pt, margins: 0pt) #stack(dir: btt, ..items) diff --git a/tests/typeset.rs b/tests/typeset.rs index b334ae9a0..72bdb4313 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -70,10 +70,10 @@ fn main() { ); styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into())); - // Hook up an assert function into the global scope. + // Hook up two more colors and an assert function into the global scope. let mut std = typst::library::new(); - std.def_const("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); - std.def_const("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); + std.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); + std.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); std.def_fn("test", move |_, args| { let lhs = args.expect::("left-hand side")?; let rhs = args.expect::("right-hand side")?;