mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
More utility functions
- join("a", "b", "c", sep: ", ") - int("12") - float("31.4e-1") - str(10) - sorted((3, 2, 1))
This commit is contained in:
parent
ee84bf7408
commit
3481d8cc81
@ -1,3 +1,4 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
@ -80,6 +81,26 @@ impl Array {
|
|||||||
self.0.iter()
|
self.0.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a sorted version of this array.
|
||||||
|
///
|
||||||
|
/// Returns an error if two values could not be compared.
|
||||||
|
pub fn sorted(mut self) -> StrResult<Self> {
|
||||||
|
let mut result = Ok(());
|
||||||
|
Rc::make_mut(&mut self.0).sort_by(|a, b| {
|
||||||
|
a.partial_cmp(b).unwrap_or_else(|| {
|
||||||
|
if result.is_ok() {
|
||||||
|
result = Err(format!(
|
||||||
|
"cannot compare {} with {}",
|
||||||
|
a.type_name(),
|
||||||
|
b.type_name(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ordering::Equal
|
||||||
|
})
|
||||||
|
});
|
||||||
|
result.map(|_| self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Repeat this array `n` times.
|
/// Repeat this array `n` times.
|
||||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
let count = usize::try_from(n)
|
let count = usize::try_from(n)
|
||||||
|
@ -5,13 +5,14 @@ mod array;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod dict;
|
mod dict;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
mod str;
|
||||||
|
#[macro_use]
|
||||||
mod value;
|
mod value;
|
||||||
mod capture;
|
mod capture;
|
||||||
mod function;
|
mod function;
|
||||||
mod ops;
|
mod ops;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod state;
|
mod state;
|
||||||
mod str;
|
|
||||||
mod template;
|
mod template;
|
||||||
mod walk;
|
mod walk;
|
||||||
|
|
||||||
|
@ -291,12 +291,21 @@ pub fn compare(lhs: &Value, rhs: &Value) -> Option<Ordering> {
|
|||||||
match (lhs, rhs) {
|
match (lhs, rhs) {
|
||||||
(Bool(a), Bool(b)) => a.partial_cmp(b),
|
(Bool(a), Bool(b)) => a.partial_cmp(b),
|
||||||
(Int(a), Int(b)) => a.partial_cmp(b),
|
(Int(a), Int(b)) => a.partial_cmp(b),
|
||||||
(Int(a), Float(b)) => (*a as f64).partial_cmp(b),
|
|
||||||
(Float(a), Int(b)) => a.partial_cmp(&(*b as f64)),
|
|
||||||
(Float(a), Float(b)) => a.partial_cmp(b),
|
(Float(a), Float(b)) => a.partial_cmp(b),
|
||||||
(Angle(a), Angle(b)) => a.partial_cmp(b),
|
(Angle(a), Angle(b)) => a.partial_cmp(b),
|
||||||
(Length(a), Length(b)) => a.partial_cmp(b),
|
(Length(a), Length(b)) => a.partial_cmp(b),
|
||||||
|
(Relative(a), Relative(b)) => a.partial_cmp(b),
|
||||||
|
(Fractional(a), Fractional(b)) => a.partial_cmp(b),
|
||||||
(Str(a), Str(b)) => a.partial_cmp(b),
|
(Str(a), Str(b)) => a.partial_cmp(b),
|
||||||
|
|
||||||
|
// Some technically different things should be comparable.
|
||||||
|
(&Int(a), &Float(b)) => (a as f64).partial_cmp(&b),
|
||||||
|
(&Float(a), &Int(b)) => a.partial_cmp(&(b as f64)),
|
||||||
|
(&Length(a), &Linear(b)) if b.rel.is_zero() => a.partial_cmp(&b.abs),
|
||||||
|
(&Relative(a), &Linear(b)) if b.abs.is_zero() => a.partial_cmp(&b.rel),
|
||||||
|
(&Linear(a), &Length(b)) if a.rel.is_zero() => a.abs.partial_cmp(&b),
|
||||||
|
(&Linear(a), &Relative(b)) if a.abs.is_zero() => a.rel.partial_cmp(&b),
|
||||||
|
|
||||||
_ => Option::None,
|
_ => Option::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::ops::{Add, AddAssign, Deref};
|
use std::ops::{Add, AddAssign, Deref};
|
||||||
@ -5,6 +6,16 @@ use std::ops::{Add, AddAssign, Deref};
|
|||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
|
/// Create a new [`Str`] from a format string.
|
||||||
|
macro_rules! format_str {
|
||||||
|
($($tts:tt)*) => {{
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut s = $crate::util::EcoString::new();
|
||||||
|
write!(s, $($tts)*).unwrap();
|
||||||
|
$crate::eval::Str::from(s)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
/// A string value with inline storage and clone-on-write semantics.
|
/// A string value with inline storage and clone-on-write semantics.
|
||||||
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub struct Str(EcoString);
|
pub struct Str(EcoString);
|
||||||
@ -92,6 +103,18 @@ impl AddAssign for Str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Str {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Borrow<str> for Str {
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<char> for Str {
|
impl From<char> for Str {
|
||||||
fn from(c: char) -> Self {
|
fn from(c: char) -> Self {
|
||||||
Self(c.into())
|
Self(c.into())
|
||||||
|
@ -88,6 +88,11 @@ impl Value {
|
|||||||
{
|
{
|
||||||
T::cast(self)
|
T::cast(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Join the value with another value.
|
||||||
|
pub fn join(self, rhs: Self) -> StrResult<Self> {
|
||||||
|
ops::join(self, rhs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Value {
|
impl Default for Value {
|
||||||
|
@ -17,8 +17,8 @@ use std::convert::TryFrom;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::color::{Color, RgbaColor};
|
use crate::color::{Color, RgbaColor};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::{At, TypResult};
|
||||||
use crate::eval::{Arguments, EvalContext, Scope, State, Str, Template, Value};
|
use crate::eval::{Arguments, Array, EvalContext, Scope, State, Str, Template, Value};
|
||||||
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
use crate::font::{FontFamily, FontStretch, FontStyle, FontWeight, VerticalFontMetric};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::layout::LayoutNode;
|
use crate::layout::LayoutNode;
|
||||||
@ -59,13 +59,18 @@ pub fn new() -> Scope {
|
|||||||
// Utility.
|
// Utility.
|
||||||
std.def_func("type", type_);
|
std.def_func("type", type_);
|
||||||
std.def_func("repr", repr);
|
std.def_func("repr", repr);
|
||||||
std.def_func("len", len);
|
std.def_func("join", join);
|
||||||
std.def_func("rgb", rgb);
|
std.def_func("int", int);
|
||||||
|
std.def_func("float", float);
|
||||||
|
std.def_func("str", str);
|
||||||
std.def_func("abs", abs);
|
std.def_func("abs", abs);
|
||||||
std.def_func("min", min);
|
std.def_func("min", min);
|
||||||
std.def_func("max", max);
|
std.def_func("max", max);
|
||||||
|
std.def_func("rgb", rgb);
|
||||||
std.def_func("lower", lower);
|
std.def_func("lower", lower);
|
||||||
std.def_func("upper", upper);
|
std.def_func("upper", upper);
|
||||||
|
std.def_func("len", len);
|
||||||
|
std.def_func("sorted", sorted);
|
||||||
|
|
||||||
// Colors.
|
// Colors.
|
||||||
std.def_const("white", RgbaColor::WHITE);
|
std.def_const("white", RgbaColor::WHITE);
|
||||||
|
@ -15,34 +15,65 @@ pub fn repr(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
|||||||
Ok(args.expect::<Value>("value")?.to_string().into())
|
Ok(args.expect::<Value>("value")?.to_string().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `len`: The length of a string, an array or a dictionary.
|
/// `join`: Join a sequence of values, optionally interspersing it with another
|
||||||
pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
/// value.
|
||||||
let Spanned { v, span } = args.expect("collection")?;
|
pub fn join(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
let span = args.span;
|
||||||
|
let sep = args.named::<Value>("sep")?.unwrap_or(Value::None);
|
||||||
|
|
||||||
|
let mut result = Value::None;
|
||||||
|
let mut iter = args.all::<Value>();
|
||||||
|
|
||||||
|
if let Some(first) = iter.next() {
|
||||||
|
result = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in iter {
|
||||||
|
result = result.join(sep.clone()).at(span)?;
|
||||||
|
result = result.join(value).at(span)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `int`: Try to convert a value to a integer.
|
||||||
|
pub fn int(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
Ok(Value::Int(match v {
|
Ok(Value::Int(match v {
|
||||||
Value::Str(v) => v.len(),
|
Value::Bool(v) => v as i64,
|
||||||
Value::Array(v) => v.len(),
|
Value::Int(v) => v,
|
||||||
Value::Dict(v) => v.len(),
|
Value::Float(v) => v as i64,
|
||||||
_ => bail!(span, "expected string, array or dictionary"),
|
Value::Str(v) => match v.parse() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => bail!(span, "invalid integer"),
|
||||||
|
},
|
||||||
|
v => bail!(span, "cannot convert {} to integer", v.type_name()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `rgb`: Create an RGB(A) color.
|
/// `float`: Try to convert a value to a float.
|
||||||
pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
pub fn float(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
Ok(Value::Color(Color::Rgba(
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
if let Some(string) = args.eat::<Spanned<Str>>() {
|
Ok(Value::Float(match v {
|
||||||
match RgbaColor::from_str(&string.v) {
|
Value::Int(v) => v as f64,
|
||||||
Ok(color) => color,
|
Value::Float(v) => v,
|
||||||
Err(_) => bail!(string.span, "invalid color"),
|
Value::Str(v) => match v.parse() {
|
||||||
}
|
Ok(v) => v,
|
||||||
} else {
|
Err(_) => bail!(span, "invalid float"),
|
||||||
let r = args.expect("red component")?;
|
|
||||||
let g = args.expect("green component")?;
|
|
||||||
let b = args.expect("blue component")?;
|
|
||||||
let a = args.eat().unwrap_or(1.0);
|
|
||||||
let f = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
|
||||||
RgbaColor::new(f(r), f(g), f(b), f(a))
|
|
||||||
},
|
},
|
||||||
)))
|
v => bail!(span, "cannot convert {} to float", v.type_name()),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `str`: Try to convert a value to a string.
|
||||||
|
pub fn str(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect("value")?;
|
||||||
|
Ok(Value::Str(match v {
|
||||||
|
Value::Int(v) => format_str!("{}", v),
|
||||||
|
Value::Float(v) => format_str!("{}", v),
|
||||||
|
Value::Str(v) => v,
|
||||||
|
v => bail!(span, "cannot convert {} to string", v.type_name()),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `abs`: The absolute value of a numeric value.
|
/// `abs`: The absolute value of a numeric value.
|
||||||
@ -91,6 +122,25 @@ fn minmax(args: &mut Arguments, goal: Ordering) -> TypResult<Value> {
|
|||||||
Ok(extremum)
|
Ok(extremum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `rgb`: Create an RGB(A) color.
|
||||||
|
pub fn rgb(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
Ok(Value::Color(Color::Rgba(
|
||||||
|
if let Some(string) = args.eat::<Spanned<Str>>() {
|
||||||
|
match RgbaColor::from_str(&string.v) {
|
||||||
|
Ok(color) => color,
|
||||||
|
Err(_) => bail!(string.span, "invalid color"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let r = args.expect("red component")?;
|
||||||
|
let g = args.expect("green component")?;
|
||||||
|
let b = args.expect("blue component")?;
|
||||||
|
let a = args.eat().unwrap_or(1.0);
|
||||||
|
let f = |v: f64| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||||
|
RgbaColor::new(f(r), f(g), f(b), f(a))
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
/// `lower`: Convert a string to lowercase.
|
/// `lower`: Convert a string to lowercase.
|
||||||
pub fn lower(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
pub fn lower(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Str>("string")?.to_lowercase().into())
|
Ok(args.expect::<Str>("string")?.to_lowercase().into())
|
||||||
@ -100,3 +150,20 @@ pub fn lower(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
|||||||
pub fn upper(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
pub fn upper(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
Ok(args.expect::<Str>("string")?.to_uppercase().into())
|
Ok(args.expect::<Str>("string")?.to_uppercase().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `len`: The length of a string, an array or a dictionary.
|
||||||
|
pub fn len(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect("collection")?;
|
||||||
|
Ok(Value::Int(match v {
|
||||||
|
Value::Str(v) => v.len(),
|
||||||
|
Value::Array(v) => v.len(),
|
||||||
|
Value::Dict(v) => v.len(),
|
||||||
|
_ => bail!(span, "expected string, array or dictionary"),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `sorted`: The sorted version of an array.
|
||||||
|
pub fn sorted(_: &mut EvalContext, args: &mut Arguments) -> TypResult<Value> {
|
||||||
|
let Spanned { v, span } = args.expect::<Spanned<Array>>("array")?;
|
||||||
|
Ok(Value::Array(v.sorted().at(span)?))
|
||||||
|
}
|
||||||
|
BIN
tests/ref/utility/basics.png
Normal file
BIN
tests/ref/utility/basics.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 873 B |
@ -26,8 +26,8 @@
|
|||||||
{not ()}
|
{not ()}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 2-12 cannot apply '<=' to relative and relative
|
// Error: 2-18 cannot apply '<=' to linear and relative
|
||||||
{30% <= 40%}
|
{30% + 1pt <= 40%}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Special messages for +, -, * and /.
|
// Special messages for +, -, * and /.
|
||||||
|
@ -146,6 +146,8 @@
|
|||||||
#test(5 <= 5, true)
|
#test(5 <= 5, true)
|
||||||
#test(5 <= 4, false)
|
#test(5 <= 4, false)
|
||||||
#test(45deg < 1rad, true)
|
#test(45deg < 1rad, true)
|
||||||
|
#test(10% < 20%, true)
|
||||||
|
#test(50% < 40% + 0pt, false)
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test assignment operators.
|
// Test assignment operators.
|
||||||
|
@ -51,10 +51,5 @@
|
|||||||
{() => none}
|
{() => none}
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test using the `repr` function.
|
// When using the `repr` function it's not in monospace.
|
||||||
|
|
||||||
// Returns a string.
|
|
||||||
#test(repr((1, 2, false, )), "(1, 2, false)")
|
|
||||||
|
|
||||||
// Not in monospace
|
|
||||||
#repr(23deg)
|
#repr(23deg)
|
||||||
|
@ -2,16 +2,67 @@
|
|||||||
// Ref: false
|
// Ref: false
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test the `len` function.
|
// Test the `type` function.
|
||||||
#test(len(()), 0)
|
#test(type(1), "integer")
|
||||||
#test(len(("A", "B", "C")), 3)
|
#test(type(ltr), "direction")
|
||||||
#test(len("Hello World!"), 12)
|
|
||||||
#test(len((a: 1, b: 2)), 2)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 5-7 missing argument: collection
|
// Test the `repr` function.
|
||||||
#len()
|
#test(repr(ltr), "ltr")
|
||||||
|
#test(repr((1, 2, false, )), "(1, 2, false)")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 6-10 expected string, array or dictionary
|
// Test the `join` function.
|
||||||
#len(12pt)
|
#test(join(), none)
|
||||||
|
#test(join(sep: false), none)
|
||||||
|
#test(join(1), 1)
|
||||||
|
#test(join("a", "b", "c"), "abc")
|
||||||
|
#test("(" + join("a", "b", "c", sep: ", ") + ")", "(a, b, c)")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test joining templates.
|
||||||
|
// Ref: true
|
||||||
|
#join([One], [Two], [Three], sep: [, ]).
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 11-24 cannot join boolean with boolean
|
||||||
|
#test(join(true, false))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 11-29 cannot join string with integer
|
||||||
|
#test(join("a", "b", sep: 1))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test conversion functions.
|
||||||
|
#test(int(false), 0)
|
||||||
|
#test(int(true), 1)
|
||||||
|
#test(int(10), 10)
|
||||||
|
#test(int("150"), 150)
|
||||||
|
#test(type(10 / 3), "float")
|
||||||
|
#test(int(10 / 3), 3)
|
||||||
|
#test(float(10), 10.0)
|
||||||
|
#test(float("31.4e-1"), 3.14)
|
||||||
|
#test(type(float(10)), "float")
|
||||||
|
#test(str(123), "123")
|
||||||
|
#test(str(50.14), "50.14")
|
||||||
|
#test(len(str(10 / 3)) > 10, true)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-10 cannot convert length to integer
|
||||||
|
#int(10pt)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 8-13 cannot convert function to float
|
||||||
|
#float(float)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-8 cannot convert template to string
|
||||||
|
#str([])
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-12 invalid integer
|
||||||
|
#int("nope")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 8-15 invalid float
|
||||||
|
#float("1.2.3")
|
||||||
|
38
tests/typ/utility/collection.typ
Normal file
38
tests/typ/utility/collection.typ
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Test collection functions.
|
||||||
|
// Ref: false
|
||||||
|
|
||||||
|
---
|
||||||
|
#let memes = "ArE mEmEs gReAt?";
|
||||||
|
#test(lower(memes), "are memes great?")
|
||||||
|
#test(upper(memes), "ARE MEMES GREAT?")
|
||||||
|
#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `len` function.
|
||||||
|
#test(len(()), 0)
|
||||||
|
#test(len(("A", "B", "C")), 3)
|
||||||
|
#test(len("Hello World!"), 12)
|
||||||
|
#test(len((a: 1, b: 2)), 2)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 5-7 missing argument: collection
|
||||||
|
#len()
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 6-10 expected string, array or dictionary
|
||||||
|
#len(12pt)
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test the `sorted` function.
|
||||||
|
#test(sorted(()), ())
|
||||||
|
#test(sorted((true, false) * 10), (false,) * 10 + (true,) * 10)
|
||||||
|
#test(sorted(("it", "the", "hi", "text")), ("hi", "it", "text", "the"))
|
||||||
|
#test(sorted((2, 1, 3, 10, 5, 8, 6, -7, 2)), (-7, 1, 2, 2, 3, 5, 6, 8, 10))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 9-21 cannot compare string with integer
|
||||||
|
#sorted((1, 2, "ab"))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Error: 9-24 cannot compare template with template
|
||||||
|
#sorted(([Hi], [There]))
|
@ -1,8 +0,0 @@
|
|||||||
// Test string functions.
|
|
||||||
// Ref: false
|
|
||||||
|
|
||||||
---
|
|
||||||
#let memes = "ArE mEmEs gReAt?";
|
|
||||||
#test(lower(memes), "are memes great?")
|
|
||||||
#test(upper(memes), "ARE MEMES GREAT?")
|
|
||||||
#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
|
|
Loading…
x
Reference in New Issue
Block a user