diff --git a/src/eval/mod.rs b/src/eval/mod.rs index d1307b6db..5bc2f1015 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -18,7 +18,7 @@ use std::rc::Rc; use crate::cache::Cache; use crate::color::Color; use crate::diag::{Diag, DiagSet, Pass}; -use crate::geom::{Angle, Length, Relative}; +use crate::geom::{Angle, Fractional, Length, Relative}; use crate::loading::{FileHash, Loader}; use crate::parse::parse; use crate::syntax::visit::Visit; @@ -250,6 +250,7 @@ impl Eval for Expr { Self::Length(_, v, unit) => Value::Length(Length::with_unit(v, unit)), Self::Angle(_, v, unit) => Value::Angle(Angle::with_unit(v, unit)), Self::Percent(_, v) => Value::Relative(Relative::new(v / 100.0)), + Self::Fractional(_, v) => Value::Fractional(Fractional::new(v)), Self::Color(_, v) => Value::Color(Color::Rgba(v)), Self::Str(_, ref v) => Value::Str(v.clone()), Self::Ident(ref v) => match ctx.scopes.get(&v) { diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 69a0b02b2..15c09e03a 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -11,6 +11,7 @@ pub fn pos(value: Value) -> Value { Length(v) => Length(v), Angle(v) => Angle(v), Relative(v) => Relative(v), + Fractional(v) => Fractional(v), Linear(v) => Linear(v), _ => Error, } @@ -24,6 +25,7 @@ pub fn neg(value: Value) -> Value { Length(v) => Length(-v), Angle(v) => Angle(-v), Relative(v) => Relative(-v), + Fractional(v) => Fractional(-v), Linear(v) => Linear(-v), _ => Error, } @@ -44,6 +46,7 @@ pub fn add(lhs: Value, rhs: Value) -> Value { (Relative(a), Length(b)) => Linear(a + b), (Relative(a), Relative(b)) => Relative(a + b), (Relative(a), Linear(b)) => Linear(a + b), + (Fractional(a), Fractional(b)) => Fractional(a + b), (Linear(a), Length(b)) => Linear(a + b), (Linear(a), Relative(b)) => Linear(a + b), (Linear(a), Linear(b)) => Linear(a + b), @@ -84,6 +87,7 @@ pub fn sub(lhs: Value, rhs: Value) -> Value { (Relative(a), Length(b)) => Linear(a - b), (Relative(a), Relative(b)) => Relative(a - b), (Relative(a), Linear(b)) => Linear(a - b), + (Fractional(a), Fractional(b)) => Fractional(a - b), (Linear(a), Length(b)) => Linear(a - b), (Linear(a), Relative(b)) => Linear(a - b), (Linear(a), Linear(b)) => Linear(a - b), @@ -108,8 +112,13 @@ pub fn mul(lhs: Value, rhs: Value) -> Value { (Float(a), Angle(b)) => Angle(a * b), (Relative(a), Int(b)) => Relative(a * b as f64), (Relative(a), Float(b)) => Relative(a * b), + (Fractional(a), Fractional(b)) => Fractional(a * b.get()), + (Fractional(a), Int(b)) => Fractional(a * b as f64), + (Fractional(a), Float(b)) => Fractional(a * b), (Int(a), Relative(b)) => Relative(a as f64 * b), + (Int(a), Fractional(b)) => Fractional(a as f64 * b), (Float(a), Relative(b)) => Relative(a * b), + (Float(a), Fractional(b)) => Fractional(a * b), (Linear(a), Int(b)) => Linear(a * b as f64), (Linear(a), Float(b)) => Linear(a * b), (Int(a), Linear(b)) => Linear(a as f64 * b), @@ -134,6 +143,9 @@ pub fn div(lhs: Value, rhs: Value) -> Value { (Relative(a), Int(b)) => Relative(a / b as f64), (Relative(a), Float(b)) => Relative(a / b), (Relative(a), Relative(b)) => Float(a / b), + (Fractional(a), Fractional(b)) => Float(a.get() / b.get()), + (Fractional(a), Int(b)) => Fractional(a / b as f64), + (Fractional(a), Float(b)) => Fractional(a / b), (Linear(a), Int(b)) => Linear(a / b as f64), (Linear(a), Float(b)) => Linear(a / b), _ => Error, diff --git a/src/eval/value.rs b/src/eval/value.rs index 94f7f5698..498403e69 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -8,7 +8,7 @@ use std::rc::Rc; use super::EvalContext; use crate::color::{Color, RgbaColor}; use crate::exec::ExecContext; -use crate::geom::{Angle, Length, Linear, Relative}; +use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::syntax::{Expr, Span, Spanned, Tree}; /// A computational value. @@ -28,6 +28,8 @@ pub enum Value { Angle(Angle), /// A relative value: `50%`. Relative(Relative), + /// A fractional value: `1fr`. + Fractional(Fractional), /// A combination of an absolute length and a relative value: `20% + 5cm`. Linear(Linear), /// A color value: `#f79143ff`. @@ -75,6 +77,7 @@ impl Value { Self::Length(_) => Length::TYPE_NAME, Self::Angle(_) => Angle::TYPE_NAME, Self::Relative(_) => Relative::TYPE_NAME, + Self::Fractional(_) => Fractional::TYPE_NAME, Self::Linear(_) => Linear::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME, Self::Str(_) => String::TYPE_NAME, @@ -601,6 +604,7 @@ primitive! { primitive! { Length: "length", Value::Length } primitive! { Angle: "angle", Value::Angle } primitive! { Relative: "relative", Value::Relative } +primitive! { Fractional: "fractional", Value::Fractional } primitive! { Linear: "linear", Value::Linear, diff --git a/src/geom/fr.rs b/src/geom/fr.rs new file mode 100644 index 000000000..974d675ee --- /dev/null +++ b/src/geom/fr.rs @@ -0,0 +1,101 @@ +use decorum::N64; + +use super::*; + +/// A fractional length. +#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Hash)] +pub struct Fractional(N64); + +impl Fractional { + /// Takes up zero space: `0fr`. + pub fn zero() -> Self { + Self(N64::from(0.0)) + } + + /// Takes up as much space as all other items with this fractional size: `1fr`. + pub fn one() -> Self { + Self(N64::from(1.0)) + } + + /// Create a new fractional value. + pub fn new(ratio: f64) -> Self { + Self(N64::from(ratio)) + } + + /// Get the underlying ratio. + pub fn get(self) -> f64 { + self.0.into() + } + + /// Whether the ratio is zero. + pub fn is_zero(self) -> bool { + self.0 == 0.0 + } +} + +impl Display for Fractional { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}fr", self.get()) + } +} + +impl Debug for Fractional { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Fractional { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Fractional { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +sub_impl!(Fractional - Fractional -> Fractional); + +impl Mul for Fractional { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul for f64 { + type Output = Fractional; + + fn mul(self, other: Fractional) -> Fractional { + other * self + } +} + +impl Div for Fractional { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +impl Div for Fractional { + type Output = f64; + + fn div(self, other: Self) -> f64 { + self.get() / other.get() + } +} + +assign_impl!(Fractional += Fractional); +assign_impl!(Fractional -= Fractional); +assign_impl!(Fractional *= f64); +assign_impl!(Fractional /= f64); diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 0031c6df1..ce8a7276e 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -5,6 +5,7 @@ mod macros; mod align; mod angle; mod dir; +mod fr; mod gen; mod length; mod linear; @@ -18,6 +19,7 @@ mod spec; pub use align::*; pub use angle::*; pub use dir::*; +pub use fr::*; pub use gen::*; pub use length::*; pub use linear::*; diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 048bcb1c5..3d4cc2ac1 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -335,6 +335,7 @@ fn literal(p: &mut Parser) -> Option { Token::Length(val, unit) => Expr::Length(span, val, unit), Token::Angle(val, unit) => Expr::Angle(span, val, unit), Token::Percent(p) => Expr::Percent(span, p), + Token::Fraction(p) => Expr::Fractional(span, p), Token::Color(color) => Expr::Color(span, color), Token::Str(token) => Expr::Str(span, { if !token.terminated { diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 74051801d..9d3cbc9a2 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -393,6 +393,7 @@ impl<'s> Tokens<'s> { // Otherwise parse into the fitting numeric type. let build = match suffix { "%" => Token::Percent, + "fr" => Token::Fraction, "pt" => |x| Token::Length(x, LengthUnit::Pt), "mm" => |x| Token::Length(x, LengthUnit::Mm), "cm" => |x| Token::Length(x, LengthUnit::Cm), @@ -880,6 +881,7 @@ mod tests { let suffixes = [ ("%", Percent as fn(f64) -> Token<'static>), + ("fr", Fraction as fn(f64) -> Token<'static>), ("mm", |x| Length(x, LengthUnit::Mm)), ("pt", |x| Length(x, LengthUnit::Pt)), ("cm", |x| Length(x, LengthUnit::Cm)), diff --git a/src/pretty.rs b/src/pretty.rs index dc1e284ca..df1d844c1 100644 --- a/src/pretty.rs +++ b/src/pretty.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Arguments, Write}; use crate::color::{Color, RgbaColor}; use crate::eval::*; -use crate::geom::{Angle, Length, Linear, Relative}; +use crate::geom::{Angle, Fractional, Length, Linear, Relative}; use crate::syntax::*; /// Pretty print an item and return the resulting string. @@ -192,6 +192,7 @@ impl Pretty for Expr { Self::Length(_, v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Angle(_, v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Percent(_, v) => write!(p, "{}%", v).unwrap(), + Self::Fractional(_, v) => write!(p, "{}fr", v).unwrap(), Self::Color(_, v) => v.pretty(p), Self::Str(_, v) => v.pretty(p), Self::Ident(v) => v.pretty(p), @@ -456,6 +457,7 @@ impl Pretty for Value { Value::Length(v) => v.pretty(p), Value::Angle(v) => v.pretty(p), Value::Relative(v) => v.pretty(p), + Value::Fractional(v) => v.pretty(p), Value::Linear(v) => v.pretty(p), Value::Color(v) => v.pretty(p), Value::Str(v) => v.pretty(p), @@ -575,6 +577,7 @@ pretty_display! { Length, Angle, Relative, + Fractional, Linear, RgbaColor, Color, @@ -659,6 +662,7 @@ mod tests { roundtrip("{10pt}"); roundtrip("{14.1deg}"); roundtrip("{20%}"); + roundtrip("{0.5fr}"); roundtrip("{#abcdef}"); roundtrip(r#"{"hi"}"#); test_parse(r#"{"let's \" go"}"#, r#"{"let's \" go"}"#); @@ -725,6 +729,7 @@ mod tests { test_value(Angle::deg(90.0), "90deg"); test_value(Relative::one() / 2.0, "50%"); test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm"); + test_value(Fractional::one() * 7.55, "7.55fr"); test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_value("hello", r#""hello""#); test_value("\n", r#""\n""#); diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index e0135d1c3..4dac9c596 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -24,6 +24,8 @@ pub enum Expr { /// _Note_: `50%` is stored as `50.0` here, but as `0.5` in the /// corresponding [value](crate::geom::Relative). Percent(Span, f64), + /// A fraction unit literal: `1fr`. + Fractional(Span, f64), /// A color literal: `#ffccee`. Color(Span, RgbaColor), /// A string literal: `"hello!"`. @@ -73,6 +75,7 @@ impl Expr { Self::Length(span, _, _) => span, Self::Angle(span, _, _) => span, Self::Percent(span, _) => span, + Self::Fractional(span, _) => span, Self::Color(span, _) => span, Self::Str(span, _) => span, Self::Ident(ref v) => v.span, diff --git a/src/syntax/token.rs b/src/syntax/token.rs index 9098f1768..56ab3dd6d 100644 --- a/src/syntax/token.rs +++ b/src/syntax/token.rs @@ -133,6 +133,8 @@ pub enum Token<'s> { /// _Note_: `50%` is stored as `50.0` here, as in the corresponding /// [literal](super::Expr::Percent). Percent(f64), + /// A fraction unit: `3fr`. + Fraction(f64), /// A color value: `#20d82a`. Color(RgbaColor), /// A quoted string: `"..."`. @@ -258,6 +260,7 @@ impl<'s> Token<'s> { Self::Length(_, _) => "length", Self::Angle(_, _) => "angle", Self::Percent(_) => "percentage", + Self::Fraction(_) => "`fr` value", Self::Color(_) => "color", Self::Str(_) => "string", Self::LineComment(_) => "line comment", diff --git a/src/syntax/visit.rs b/src/syntax/visit.rs index 86481d4e1..ba7555f20 100644 --- a/src/syntax/visit.rs +++ b/src/syntax/visit.rs @@ -80,6 +80,7 @@ visit! { Expr::Length(_, _, _) => {} Expr::Angle(_, _, _) => {} Expr::Percent(_, _) => {} + Expr::Fractional(_, _) => {} Expr::Color(_, _) => {} Expr::Str(_, _) => {} Expr::Ident(_) => {} diff --git a/tests/typ/code/ops.typ b/tests/typ/code/ops.typ index ef249c433..e2ecd4ff0 100644 --- a/tests/typ/code/ops.typ +++ b/tests/typ/code/ops.typ @@ -10,7 +10,7 @@ // Test math operators. // Test plus and minus. -#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt) { +#for v in (1, 3.14, 12pt, 45deg, 90%, 13% + 10pt, 6.3fr) { // Test plus. test(+v, v) diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index c7fdcb52d..0b0298fef 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -275,6 +275,10 @@ "name": "constant.numeric.percentage.typst", "match": "\\b(\\d*)?\\.?\\d+([eE][+-]?\\d+)?%" }, + { + "name": "constant.numeric.fr.typst", + "match": "\\b(\\d*)?\\.?\\d+([eE][+-]?\\d+)?fr" + }, { "name": "constant.numeric.integer.typst", "match": "\\b\\d+\\b"