diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-29 13:37:25 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-29 14:18:13 +0100 |
| commit | 0efe669278a5e1c3f2985eba2f3360e91159c54a (patch) | |
| tree | 502712857c48f0decb5e698257c0a96d358a436e /library/src/base | |
| parent | 836692e73cff0356e409a9ba5b4887b86809d4ca (diff) | |
Reorganize library and tests
Diffstat (limited to 'library/src/base')
| -rw-r--r-- | library/src/base/calc.rs | 88 | ||||
| -rw-r--r-- | library/src/base/create.rs | 149 | ||||
| -rw-r--r-- | library/src/base/data.rs | 129 | ||||
| -rw-r--r-- | library/src/base/mod.rs | 45 | ||||
| -rw-r--r-- | library/src/base/numbering.rs | 146 |
5 files changed, 0 insertions, 557 deletions
diff --git a/library/src/base/calc.rs b/library/src/base/calc.rs deleted file mode 100644 index 3541e08c..00000000 --- a/library/src/base/calc.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::cmp::Ordering; - -use crate::prelude::*; - -/// The absolute value of a numeric value. -pub fn abs(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v, span } = args.expect("numeric value")?; - Ok(match v { - Value::Int(v) => Value::Int(v.abs()), - Value::Float(v) => Value::Float(v.abs()), - Value::Angle(v) => Value::Angle(v.abs()), - Value::Ratio(v) => Value::Ratio(v.abs()), - Value::Fraction(v) => Value::Fraction(v.abs()), - Value::Length(_) | Value::Relative(_) => { - bail!(span, "cannot take absolute value of a length") - } - v => bail!(span, "expected numeric value, found {}", v.type_name()), - }) -} - -/// The minimum of a sequence of values. -pub fn min(_: &Vm, args: &mut Args) -> SourceResult<Value> { - minmax(args, Ordering::Less) -} - -/// The maximum of a sequence of values. -pub fn max(_: &Vm, args: &mut Args) -> SourceResult<Value> { - minmax(args, Ordering::Greater) -} - -/// Find the minimum or maximum of a sequence of values. -fn minmax(args: &mut Args, goal: Ordering) -> SourceResult<Value> { - let mut extremum = args.expect::<Value>("value")?; - for Spanned { v, span } in args.all::<Spanned<Value>>()? { - match v.partial_cmp(&extremum) { - Some(ordering) => { - if ordering == goal { - extremum = v; - } - } - None => bail!( - span, - "cannot compare {} and {}", - extremum.type_name(), - v.type_name(), - ), - } - } - Ok(extremum) -} - -/// Whether an integer is even. -pub fn even(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(Value::Bool(args.expect::<i64>("integer")? % 2 == 0)) -} - -/// Whether an integer is odd. -pub fn odd(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(Value::Bool(args.expect::<i64>("integer")? % 2 != 0)) -} - -/// The modulo of two numbers. -pub fn mod_(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v: v1, span: span1 } = args.expect("integer or float")?; - let Spanned { v: v2, span: span2 } = args.expect("integer or float")?; - - let (a, b) = match (v1, v2) { - (Value::Int(a), Value::Int(b)) => match a.checked_rem(b) { - Some(res) => return Ok(Value::Int(res)), - None => bail!(span2, "divisor must not be zero"), - }, - (Value::Int(a), Value::Float(b)) => (a as f64, b), - (Value::Float(a), Value::Int(b)) => (a, b as f64), - (Value::Float(a), Value::Float(b)) => (a, b), - (Value::Int(_), b) | (Value::Float(_), b) => { - bail!(span2, format!("expected integer or float, found {}", b.type_name())) - } - (a, _) => { - bail!(span1, format!("expected integer or float, found {}", a.type_name())) - } - }; - - if b == 0.0 { - bail!(span2, "divisor must not be zero"); - } - - Ok(Value::Float(a % b)) -} diff --git a/library/src/base/create.rs b/library/src/base/create.rs deleted file mode 100644 index be8e822f..00000000 --- a/library/src/base/create.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::str::FromStr; - -use typst::model::Regex; - -use crate::prelude::*; - -/// Convert a value to an integer. -pub fn int(_: &Vm, args: &mut Args) -> SourceResult<Value> { - 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(_: &Vm, args: &mut Args) -> SourceResult<Value> { - 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()), - })) -} - -/// Create a grayscale color. -pub fn luma(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let Component(luma) = args.expect("gray component")?; - Ok(Value::Color(LumaColor::new(luma).into())) -} - -/// Create an RGB(A) color. -pub fn rgb(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(Value::Color(if let Some(string) = args.find::<Spanned<EcoString>>()? { - match RgbaColor::from_str(&string.v) { - Ok(color) => color.into(), - Err(msg) => bail!(string.span, msg), - } - } else { - 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).into() - })) -} - -/// Create a CMYK color. -pub fn cmyk(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let RatioComponent(c) = args.expect("cyan component")?; - let RatioComponent(m) = args.expect("magenta component")?; - let RatioComponent(y) = args.expect("yellow component")?; - let RatioComponent(k) = args.expect("key component")?; - Ok(Value::Color(CmykColor::new(c, m, y, k).into())) -} - -/// An integer or ratio component. -struct Component(u8); - -castable! { - Component, - Expected: "integer or ratio", - Value::Int(v) => match v { - 0 ..= 255 => Self(v as u8), - _ => Err("must be between 0 and 255")?, - }, - Value::Ratio(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%")? - }, -} - -/// A component that must be a ratio. -struct RatioComponent(u8); - -castable! { - RatioComponent, - Expected: "ratio", - Value::Ratio(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%")? - }, -} - -/// Convert a value to a string. -pub fn str(_: &Vm, args: &mut Args) -> SourceResult<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::Label(label) => label.0.into(), - Value::Str(v) => v, - v => bail!(span, "cannot convert {} to string", v.type_name()), - })) -} - -/// Create a blind text string. -pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let words: usize = args.expect("number of words")?; - Ok(Value::Str(lipsum::lipsum(words).into())) -} - -/// Create a label from a string. -pub fn label(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(Value::Label(Label(args.expect("string")?))) -} - -/// Create a regular expression from a string. -pub fn regex(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v, span } = args.expect::<Spanned<EcoString>>("regular expression")?; - Ok(Regex::new(&v).at(span)?.into()) -} - -/// Create an array consisting of a sequence of numbers. -pub fn range(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let first = args.expect::<i64>("end")?; - let (start, end) = match args.eat::<i64>()? { - Some(second) => (first, second), - None => (0, first), - }; - - let step: i64 = match args.named("step")? { - Some(Spanned { v: 0, span }) => bail!(span, "step must not be zero"), - Some(Spanned { v, .. }) => v, - None => 1, - }; - - let mut x = start; - let mut seq = vec![]; - - while x.cmp(&end) == 0.cmp(&step) { - seq.push(Value::Int(x)); - x += step; - } - - Ok(Value::Array(Array::from_vec(seq))) -} diff --git a/library/src/base/data.rs b/library/src/base/data.rs deleted file mode 100644 index 4f6e3b67..00000000 --- a/library/src/base/data.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::fmt::Write; - -use typst::diag::{format_xml_like_error, FileError}; - -use crate::prelude::*; - -/// Read structured data from a CSV file. -pub fn csv(vm: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to csv file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world().file(&path).at(span)?; - - let mut builder = csv::ReaderBuilder::new(); - builder.has_headers(false); - - let mut reader = builder.from_reader(data.as_slice()); - let mut vec = vec![]; - - for result in reader.records() { - let row = result.map_err(format_csv_error).at(span)?; - let array = row.iter().map(|field| Value::Str(field.into())).collect(); - vec.push(Value::Array(array)) - } - - Ok(Value::Array(Array::from_vec(vec))) -} - -/// Format the user-facing CSV error message. -fn format_csv_error(error: csv::Error) -> String { - match error.kind() { - csv::ErrorKind::Utf8 { .. } => "file is not valid utf-8".into(), - csv::ErrorKind::UnequalLengths { pos, expected_len, len } => { - let mut msg = format!( - "failed to parse csv file: found {len} instead of {expected_len} fields" - ); - if let Some(pos) = pos { - write!(msg, " in line {}", pos.line()).unwrap(); - } - msg - } - _ => "failed to parse csv file".into(), - } -} - -/// Read structured data from a JSON file. -pub fn json(vm: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to json file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world().file(&path).at(span)?; - let value: serde_json::Value = - serde_json::from_slice(&data).map_err(format_json_error).at(span)?; - - Ok(convert_json(value)) -} - -/// Convert a JSON value to a Typst value. -fn convert_json(value: serde_json::Value) -> Value { - match value { - serde_json::Value::Null => Value::None, - serde_json::Value::Bool(v) => Value::Bool(v), - serde_json::Value::Number(v) => match v.as_i64() { - Some(int) => Value::Int(int), - None => Value::Float(v.as_f64().unwrap_or(f64::NAN)), - }, - serde_json::Value::String(v) => Value::Str(v.into()), - serde_json::Value::Array(v) => { - Value::Array(v.into_iter().map(convert_json).collect()) - } - serde_json::Value::Object(v) => Value::Dict( - v.into_iter() - .map(|(key, value)| (key.into(), convert_json(value))) - .collect(), - ), - } -} - -/// Format the user-facing JSON error message. -fn format_json_error(error: serde_json::Error) -> String { - assert!(error.is_syntax() || error.is_eof()); - format!("failed to parse json file: syntax error in line {}", error.line()) -} - -/// Read structured data from an XML file. -pub fn xml(vm: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v: path, span } = - args.expect::<Spanned<EcoString>>("path to xml file")?; - - let path = vm.locate(&path).at(span)?; - let data = vm.world().file(&path).at(span)?; - let text = std::str::from_utf8(&data).map_err(FileError::from).at(span)?; - - let document = roxmltree::Document::parse(text).map_err(format_xml_error).at(span)?; - - Ok(convert_xml(document.root())) -} - -/// Convert an XML node to a Typst value. -fn convert_xml(node: roxmltree::Node) -> Value { - if node.is_text() { - return Value::Str(node.text().unwrap_or_default().into()); - } - - let children: Array = node.children().map(convert_xml).collect(); - if node.is_root() { - return Value::Array(children); - } - - let tag: Str = node.tag_name().name().into(); - let attrs: Dict = node - .attributes() - .iter() - .map(|attr| (attr.name().into(), attr.value().into())) - .collect(); - - Value::Dict(dict! { - "tag" => tag, - "attrs" => attrs, - "children" => children, - }) -} - -/// Format the user-facing XML error message. -fn format_xml_error(error: roxmltree::Error) -> String { - format_xml_like_error("xml file", error) -} diff --git a/library/src/base/mod.rs b/library/src/base/mod.rs deleted file mode 100644 index 501edd71..00000000 --- a/library/src/base/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Foundational functions. - -mod calc; -mod create; -mod data; -mod numbering; - -pub use self::calc::*; -pub use self::create::*; -pub use self::data::*; -pub use self::numbering::*; - -use comemo::Track; -use typst::model::{self, Route, Vm}; -use typst::syntax::Source; - -use crate::prelude::*; - -/// The name of a value's type. -pub fn type_(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(args.expect::<Value>("value")?.type_name().into()) -} - -/// The string representation of a value. -pub fn repr(_: &Vm, args: &mut Args) -> SourceResult<Value> { - Ok(args.expect::<Value>("value")?.repr().into()) -} - -/// Ensure that a condition is fulfilled. -pub fn assert(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v, span } = args.expect::<Spanned<bool>>("condition")?; - if !v { - bail!(span, "assertion failed"); - } - Ok(Value::None) -} - -/// Evaluate a string as Typst markup. -pub fn eval(vm: &Vm, args: &mut Args) -> SourceResult<Value> { - let Spanned { v: text, span } = args.expect::<Spanned<String>>("source")?; - let source = Source::synthesized(text, span); - let route = Route::default(); - let module = model::eval(vm.world(), route.track(), &source)?; - Ok(Value::Content(module.content)) -} diff --git a/library/src/base/numbering.rs b/library/src/base/numbering.rs deleted file mode 100644 index ea45fbc6..00000000 --- a/library/src/base/numbering.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::str::FromStr; - -use unscanny::Scanner; - -use crate::prelude::*; - -/// Apply a numbering pattern to a number. -pub fn numbering(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let number = args.expect::<usize>("number")?; - let pattern = args.expect::<NumberingPattern>("pattern")?; - Ok(Value::Str(pattern.apply(number).into())) -} - -/// A numbering pattern for lists or headings. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct NumberingPattern { - prefix: EcoString, - numbering: NumberingKind, - upper: bool, - suffix: EcoString, -} - -impl NumberingPattern { - /// Apply the pattern to the given number. - pub fn apply(&self, n: usize) -> EcoString { - let fmt = self.numbering.apply(n); - let mid = if self.upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - format_eco!("{}{}{}", self.prefix, mid, self.suffix) - } -} - -impl FromStr for NumberingPattern { - type Err = &'static str; - - fn from_str(pattern: &str) -> Result<Self, Self::Err> { - let mut s = Scanner::new(pattern); - let mut prefix; - let numbering = loop { - prefix = s.before(); - match s.eat().map(|c| c.to_ascii_lowercase()) { - Some('1') => break NumberingKind::Arabic, - Some('a') => break NumberingKind::Letter, - Some('i') => break NumberingKind::Roman, - Some('*') => break NumberingKind::Symbol, - Some(_) => {} - None => Err("invalid numbering pattern")?, - } - }; - let upper = s.scout(-1).map_or(false, char::is_uppercase); - let suffix = s.after().into(); - Ok(Self { prefix: prefix.into(), numbering, upper, suffix }) - } -} - -castable! { - NumberingPattern, - Expected: "numbering pattern", - Value::Str(s) => s.parse()?, -} - -/// Different kinds of numberings. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum NumberingKind { - Arabic, - Letter, - Roman, - Symbol, -} - -impl NumberingKind { - /// 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 &[ - ("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), - ] { - while n >= value { - n -= value; - fmt.push_str(name); - } - } - - fmt - } - Self::Symbol => { - if n == 0 { - return '-'.into(); - } - - const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; - let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; - let amount = ((n - 1) / SYMBOLS.len()) + 1; - std::iter::repeat(symbol).take(amount).collect() - } - } - } -} |
