From beca01c826ee51c9ee6d5eadd7e5ef10f7fb9f58 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Fri, 18 Mar 2022 23:36:18 +0100 Subject: Methods --- src/library/mod.rs | 9 +- src/library/utility/color.rs | 58 +++++++++++++ src/library/utility/math.rs | 29 +++++++ src/library/utility/mod.rs | 180 ++------------------------------------- src/library/utility/numbering.rs | 111 ------------------------ src/library/utility/string.rs | 148 ++++++++++++++++++++++++++++++++ 6 files changed, 247 insertions(+), 288 deletions(-) create mode 100644 src/library/utility/color.rs delete mode 100644 src/library/utility/numbering.rs create mode 100644 src/library/utility/string.rs (limited to 'src/library') diff --git a/src/library/mod.rs b/src/library/mod.rs index 087ff7ea..528a2ce7 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -67,13 +67,10 @@ pub fn new() -> Scope { std.def_node::("math"); // Utility functions. - std.def_fn("assert", utility::assert); std.def_fn("type", utility::type_); - std.def_fn("repr", utility::repr); - std.def_fn("join", utility::join); + std.def_fn("assert", utility::assert); std.def_fn("int", utility::int); std.def_fn("float", utility::float); - std.def_fn("str", utility::str); std.def_fn("abs", utility::abs); std.def_fn("min", utility::min); std.def_fn("max", utility::max); @@ -83,13 +80,13 @@ pub fn new() -> Scope { std.def_fn("range", utility::range); std.def_fn("rgb", utility::rgb); std.def_fn("cmyk", utility::cmyk); + std.def_fn("repr", utility::repr); + std.def_fn("str", utility::str); std.def_fn("lower", utility::lower); std.def_fn("upper", utility::upper); std.def_fn("letter", utility::letter); std.def_fn("roman", utility::roman); std.def_fn("symbol", utility::symbol); - std.def_fn("len", utility::len); - std.def_fn("sorted", utility::sorted); // Predefined colors. std.def_const("black", Color::BLACK); diff --git a/src/library/utility/color.rs b/src/library/utility/color.rs new file mode 100644 index 00000000..df24f615 --- /dev/null +++ b/src/library/utility/color.rs @@ -0,0 +1,58 @@ +use std::str::FromStr; + +use crate::library::prelude::*; + +/// Create an RGB(A) color. +pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult { + Ok(Value::from( + if let Some(string) = args.find::>()? { + match RgbaColor::from_str(&string.v) { + Ok(color) => color, + Err(_) => bail!(string.span, "invalid hex string"), + } + } else { + struct Component(u8); + + castable! { + Component, + Expected: "integer or relative", + Value::Int(v) => match v { + 0 ..= 255 => Self(v as u8), + _ => Err("must be between 0 and 255")?, + }, + Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, + } + + let Component(r) = args.expect("red component")?; + let Component(g) = args.expect("green component")?; + let Component(b) = args.expect("blue component")?; + let Component(a) = args.eat()?.unwrap_or(Component(255)); + RgbaColor::new(r, g, b, a) + }, + )) +} + +/// Create a CMYK color. +pub fn cmyk(_: &mut Context, args: &mut Args) -> TypResult { + struct Component(u8); + + castable! { + Component, + Expected: "relative", + Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { + Self((v.get() * 255.0).round() as u8) + } else { + Err("must be between 0% and 100%")? + }, + } + + let Component(c) = args.expect("cyan component")?; + let Component(m) = args.expect("magenta component")?; + let Component(y) = args.expect("yellow component")?; + let Component(k) = args.expect("key component")?; + Ok(Value::Color(CmykColor::new(c, m, y, k).into())) +} diff --git a/src/library/utility/math.rs b/src/library/utility/math.rs index e48af426..0aebc573 100644 --- a/src/library/utility/math.rs +++ b/src/library/utility/math.rs @@ -2,6 +2,35 @@ use std::cmp::Ordering; use crate::library::prelude::*; +/// Convert a value to a integer. +pub fn int(_: &mut Context, args: &mut Args) -> TypResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Int(match v { + Value::Bool(v) => v as i64, + Value::Int(v) => v, + Value::Float(v) => v as i64, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid integer"), + }, + v => bail!(span, "cannot convert {} to integer", v.type_name()), + })) +} + +/// Convert a value to a float. +pub fn float(_: &mut Context, args: &mut Args) -> TypResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Float(match v { + Value::Int(v) => v as f64, + Value::Float(v) => v, + Value::Str(v) => match v.parse() { + Ok(v) => v, + Err(_) => bail!(span, "invalid float"), + }, + v => bail!(span, "cannot convert {} to float", v.type_name()), + })) +} + /// The absolute value of a numeric value. pub fn abs(_: &mut Context, args: &mut Args) -> TypResult { let Spanned { v, span } = args.expect("numeric value")?; diff --git a/src/library/utility/mod.rs b/src/library/utility/mod.rs index d85c3f12..13220242 100644 --- a/src/library/utility/mod.rs +++ b/src/library/utility/mod.rs @@ -1,15 +1,19 @@ //! Computational utility functions. +mod color; mod math; -mod numbering; +mod string; +pub use color::*; pub use math::*; -pub use numbering::*; - -use std::str::FromStr; +pub use string::*; use crate::library::prelude::*; -use crate::library::text::{Case, TextNode}; + +/// The name of a value's type. +pub fn type_(_: &mut Context, args: &mut Args) -> TypResult { + Ok(args.expect::("value")?.type_name().into()) +} /// Ensure that a condition is fulfilled. pub fn assert(_: &mut Context, args: &mut Args) -> TypResult { @@ -19,169 +23,3 @@ pub fn assert(_: &mut Context, args: &mut Args) -> TypResult { } Ok(Value::None) } - -/// The name of a value's type. -pub fn type_(_: &mut Context, args: &mut Args) -> TypResult { - Ok(args.expect::("value")?.type_name().into()) -} - -/// The string representation of a value. -pub fn repr(_: &mut Context, args: &mut Args) -> TypResult { - Ok(args.expect::("value")?.repr().into()) -} - -/// Join a sequence of values, optionally interspersing it with another value. -pub fn join(_: &mut Context, args: &mut Args) -> TypResult { - let span = args.span; - let sep = args.named::("sep")?.unwrap_or(Value::None); - - let mut result = Value::None; - let mut iter = args.all::()?.into_iter(); - - 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) -} - -/// Convert a value to a integer. -pub fn int(_: &mut Context, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Int(match v { - Value::Bool(v) => v as i64, - Value::Int(v) => v, - Value::Float(v) => v as i64, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid integer"), - }, - v => bail!(span, "cannot convert {} to integer", v.type_name()), - })) -} - -/// Convert a value to a float. -pub fn float(_: &mut Context, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Float(match v { - Value::Int(v) => v as f64, - Value::Float(v) => v, - Value::Str(v) => match v.parse() { - Ok(v) => v, - Err(_) => bail!(span, "invalid float"), - }, - v => bail!(span, "cannot convert {} to float", v.type_name()), - })) -} - -/// Cconvert a value to a string. -pub fn str(_: &mut Context, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect("value")?; - Ok(Value::Str(match v { - Value::Int(v) => format_eco!("{}", v), - Value::Float(v) => format_eco!("{}", v), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) -} - -/// Create an RGB(A) color. -pub fn rgb(_: &mut Context, args: &mut Args) -> TypResult { - Ok(Value::from( - if let Some(string) = args.find::>()? { - match RgbaColor::from_str(&string.v) { - Ok(color) => color, - Err(_) => bail!(string.span, "invalid hex string"), - } - } else { - struct Component(u8); - - castable! { - Component, - Expected: "integer or relative", - Value::Int(v) => match v { - 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, - }, - Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, - } - - let Component(r) = args.expect("red component")?; - let Component(g) = args.expect("green component")?; - let Component(b) = args.expect("blue component")?; - let Component(a) = args.eat()?.unwrap_or(Component(255)); - RgbaColor::new(r, g, b, a) - }, - )) -} - -/// Create a CMYK color. -pub fn cmyk(_: &mut Context, args: &mut Args) -> TypResult { - struct Component(u8); - - castable! { - Component, - Expected: "relative", - Value::Relative(v) => if (0.0 ..= 1.0).contains(&v.get()) { - Self((v.get() * 255.0).round() as u8) - } else { - Err("must be between 0% and 100%")? - }, - } - - let Component(c) = args.expect("cyan component")?; - let Component(m) = args.expect("magenta component")?; - let Component(y) = args.expect("yellow component")?; - let Component(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - -/// The length of a string, an array or a dictionary. -pub fn len(_: &mut Context, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect("collection")?; - Ok(Value::Int(match v { - Value::Str(v) => v.len() as i64, - Value::Array(v) => v.len(), - Value::Dict(v) => v.len(), - v => bail!( - span, - "expected string, array or dictionary, found {}", - v.type_name(), - ), - })) -} - -/// Convert a string to lowercase. -pub fn lower(_: &mut Context, args: &mut Args) -> TypResult { - case(Case::Lower, args) -} - -/// Convert a string to uppercase. -pub fn upper(_: &mut Context, args: &mut Args) -> TypResult { - case(Case::Upper, args) -} - -/// Change the case of a string or content. -fn case(case: Case, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect("string or content")?; - Ok(match v { - Value::Str(v) => Value::Str(case.apply(&v).into()), - Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), - v => bail!(span, "expected string or content, found {}", v.type_name()), - }) -} - -/// The sorted version of an array. -pub fn sorted(_: &mut Context, args: &mut Args) -> TypResult { - let Spanned { v, span } = args.expect::>("array")?; - Ok(Value::Array(v.sorted().at(span)?)) -} diff --git a/src/library/utility/numbering.rs b/src/library/utility/numbering.rs deleted file mode 100644 index 0070873f..00000000 --- a/src/library/utility/numbering.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::library::prelude::*; - -/// Converts an integer into one or multiple letters. -pub fn letter(_: &mut Context, args: &mut Args) -> TypResult { - convert(Numbering::Letter, args) -} - -/// Converts an integer into a roman numeral. -pub fn roman(_: &mut Context, args: &mut Args) -> TypResult { - convert(Numbering::Roman, args) -} - -/// Convert a number into a symbol. -pub fn symbol(_: &mut Context, args: &mut Args) -> TypResult { - convert(Numbering::Symbol, args) -} - -fn convert(numbering: Numbering, args: &mut Args) -> TypResult { - let n = args.expect::("non-negative integer")?; - Ok(Value::Str(numbering.apply(n))) -} - -/// Allows to convert a number into letters, roman numerals and symbols. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum Numbering { - Arabic, - Letter, - Roman, - Symbol, -} - -impl Numbering { - /// Apply the numbering to the given number. - pub fn apply(self, mut n: usize) -> EcoString { - match self { - Self::Arabic => { - format_eco!("{}", n) - } - Self::Letter => { - if n == 0 { - return '-'.into(); - } - - n -= 1; - - let mut letters = vec![]; - loop { - letters.push(b'a' + (n % 26) as u8); - n /= 26; - if n == 0 { - break; - } - } - - letters.reverse(); - String::from_utf8(letters).unwrap().into() - } - Self::Roman => { - if n == 0 { - return 'N'.into(); - } - - // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at - // https://github.com/linfir/roman.rs/ - let mut fmt = EcoString::new(); - for &(name, value) in ROMANS { - while n >= value { - n -= value; - fmt.push_str(name); - } - } - - fmt - } - Self::Symbol => { - if n == 0 { - return '-'.into(); - } - - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() - } - } - } -} - -static ROMANS: &'static [(&'static str, usize)] = &[ - ("M̅", 1000000), - ("D̅", 500000), - ("C̅", 100000), - ("L̅", 50000), - ("X̅", 10000), - ("V̅", 5000), - ("I̅V̅", 4000), - ("M", 1000), - ("CM", 900), - ("D", 500), - ("CD", 400), - ("C", 100), - ("XC", 90), - ("L", 50), - ("XL", 40), - ("X", 10), - ("IX", 9), - ("V", 5), - ("IV", 4), - ("I", 1), -]; - -static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶']; diff --git a/src/library/utility/string.rs b/src/library/utility/string.rs new file mode 100644 index 00000000..92d80be2 --- /dev/null +++ b/src/library/utility/string.rs @@ -0,0 +1,148 @@ +use crate::library::prelude::*; +use crate::library::text::{Case, TextNode}; + +/// The string representation of a value. +pub fn repr(_: &mut Context, args: &mut Args) -> TypResult { + Ok(args.expect::("value")?.repr().into()) +} + +/// Cconvert a value to a string. +pub fn str(_: &mut Context, args: &mut Args) -> TypResult { + let Spanned { v, span } = args.expect("value")?; + Ok(Value::Str(match v { + Value::Int(v) => format_eco!("{}", v), + Value::Float(v) => format_eco!("{}", v), + Value::Str(v) => v, + v => bail!(span, "cannot convert {} to string", v.type_name()), + })) +} + +/// Convert a string to lowercase. +pub fn lower(_: &mut Context, args: &mut Args) -> TypResult { + case(Case::Lower, args) +} + +/// Convert a string to uppercase. +pub fn upper(_: &mut Context, args: &mut Args) -> TypResult { + case(Case::Upper, args) +} + +/// Change the case of a string or content. +fn case(case: Case, args: &mut Args) -> TypResult { + let Spanned { v, span } = args.expect("string or content")?; + Ok(match v { + Value::Str(v) => Value::Str(case.apply(&v).into()), + Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), + v => bail!(span, "expected string or content, found {}", v.type_name()), + }) +} + +/// Converts an integer into one or multiple letters. +pub fn letter(_: &mut Context, args: &mut Args) -> TypResult { + convert(Numbering::Letter, args) +} + +/// Converts an integer into a roman numeral. +pub fn roman(_: &mut Context, args: &mut Args) -> TypResult { + convert(Numbering::Roman, args) +} + +/// Convert a number into a symbol. +pub fn symbol(_: &mut Context, args: &mut Args) -> TypResult { + convert(Numbering::Symbol, args) +} + +fn convert(numbering: Numbering, args: &mut Args) -> TypResult { + let n = args.expect::("non-negative integer")?; + Ok(Value::Str(numbering.apply(n))) +} + +/// Allows to convert a number into letters, roman numerals and symbols. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Numbering { + Arabic, + Letter, + Roman, + Symbol, +} + +impl Numbering { + /// Apply the numbering to the given number. + pub fn apply(self, mut n: usize) -> EcoString { + match self { + Self::Arabic => { + format_eco!("{}", n) + } + Self::Letter => { + if n == 0 { + return '-'.into(); + } + + n -= 1; + + let mut letters = vec![]; + loop { + letters.push(b'a' + (n % 26) as u8); + n /= 26; + if n == 0 { + break; + } + } + + letters.reverse(); + String::from_utf8(letters).unwrap().into() + } + Self::Roman => { + if n == 0 { + return 'N'.into(); + } + + // Adapted from Yann Villessuzanne's roman.rs under the Unlicense, at + // https://github.com/linfir/roman.rs/ + let mut fmt = EcoString::new(); + for &(name, value) in ROMANS { + while n >= value { + n -= value; + fmt.push_str(name); + } + } + + fmt + } + Self::Symbol => { + if n == 0 { + return '-'.into(); + } + + let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; + let amount = ((n - 1) / SYMBOLS.len()) + 1; + std::iter::repeat(symbol).take(amount).collect() + } + } + } +} + +static ROMANS: &'static [(&'static str, usize)] = &[ + ("M̅", 1000000), + ("D̅", 500000), + ("C̅", 100000), + ("L̅", 50000), + ("X̅", 10000), + ("V̅", 5000), + ("I̅V̅", 4000), + ("M", 1000), + ("CM", 900), + ("D", 500), + ("CD", 400), + ("C", 100), + ("XC", 90), + ("L", 50), + ("XL", 40), + ("X", 10), + ("IX", 9), + ("V", 5), + ("IV", 4), + ("I", 1), +]; + +static SYMBOLS: &'static [char] = &['*', '†', '‡', '§', '‖', '¶']; -- cgit v1.2.3