diff --git a/library/src/lib.rs b/library/src/lib.rs index c2f51ee40..c4b6710f4 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -11,144 +11,146 @@ pub mod text; pub mod visualize; use typst::geom::{Align, Color, Dir, GenAlign}; -use typst::model::{LangItems, Library, Node, NodeId, Scope, StyleMap}; +use typst::model::{LangItems, Library, Module, Node, NodeId, Scope, StyleMap}; use self::layout::LayoutRoot; /// Construct the standard library. pub fn build() -> Library { - Library { scope: scope(), styles: styles(), items: items() } + let math = math::module(); + let global = global(math.clone()); + Library { global, math, styles: styles(), items: items() } } -/// Construct the standard scope. -fn scope() -> Scope { - let mut std = Scope::new(); +/// Construct the module with global definitions. +fn global(math: Module) -> Module { + let mut global = Scope::deduplicating(); // Basics. - std.def_func::("heading"); - std.def_func::("list"); - std.def_func::("enum"); - std.def_func::("terms"); - std.def_func::("table"); + global.def_func::("heading"); + global.def_func::("list"); + global.def_func::("enum"); + global.def_func::("terms"); + global.def_func::("table"); // Text. - std.def_func::("text"); - std.def_func::("linebreak"); - std.def_func::("symbol"); - std.def_func::("smartquote"); - std.def_func::("strong"); - std.def_func::("emph"); - std.def_func::("lower"); - std.def_func::("upper"); - std.def_func::("smallcaps"); - std.def_func::("sub"); - std.def_func::("super"); - std.def_func::("underline"); - std.def_func::("strike"); - std.def_func::("overline"); - std.def_func::("raw"); + global.def_func::("text"); + global.def_func::("linebreak"); + global.def_func::("symbol"); + global.def_func::("smartquote"); + global.def_func::("strong"); + global.def_func::("emph"); + global.def_func::("lower"); + global.def_func::("upper"); + global.def_func::("smallcaps"); + global.def_func::("sub"); + global.def_func::("super"); + global.def_func::("underline"); + global.def_func::("strike"); + global.def_func::("overline"); + global.def_func::("raw"); // Math. - math::define(&mut std); + global.define("math", math); // Layout. - std.def_func::("page"); - std.def_func::("pagebreak"); - std.def_func::("v"); - std.def_func::("par"); - std.def_func::("parbreak"); - std.def_func::("h"); - std.def_func::("box"); - std.def_func::("block"); - std.def_func::("stack"); - std.def_func::("grid"); - std.def_func::("columns"); - std.def_func::("colbreak"); - std.def_func::("place"); - std.def_func::("align"); - std.def_func::("pad"); - std.def_func::("repeat"); - std.def_func::("move"); - std.def_func::("scale"); - std.def_func::("rotate"); - std.def_func::("hide"); + global.def_func::("page"); + global.def_func::("pagebreak"); + global.def_func::("v"); + global.def_func::("par"); + global.def_func::("parbreak"); + global.def_func::("h"); + global.def_func::("box"); + global.def_func::("block"); + global.def_func::("stack"); + global.def_func::("grid"); + global.def_func::("columns"); + global.def_func::("colbreak"); + global.def_func::("place"); + global.def_func::("align"); + global.def_func::("pad"); + global.def_func::("repeat"); + global.def_func::("move"); + global.def_func::("scale"); + global.def_func::("rotate"); + global.def_func::("hide"); // Visualize. - std.def_func::("image"); - std.def_func::("line"); - std.def_func::("rect"); - std.def_func::("square"); - std.def_func::("ellipse"); - std.def_func::("circle"); + global.def_func::("image"); + global.def_func::("line"); + global.def_func::("rect"); + global.def_func::("square"); + global.def_func::("ellipse"); + global.def_func::("circle"); // Meta. - std.def_func::("document"); - std.def_func::("ref"); - std.def_func::("link"); - std.def_func::("outline"); + global.def_func::("document"); + global.def_func::("ref"); + global.def_func::("link"); + global.def_func::("outline"); // Compute. - std.def_func::("type"); - std.def_func::("repr"); - std.def_func::("assert"); - std.def_func::("eval"); - std.def_func::("int"); - std.def_func::("float"); - std.def_func::("luma"); - std.def_func::("rgb"); - std.def_func::("cmyk"); - std.def_func::("str"); - std.def_func::("label"); - std.def_func::("regex"); - std.def_func::("range"); - std.def_func::("abs"); - std.def_func::("min"); - std.def_func::("max"); - std.def_func::("even"); - std.def_func::("odd"); - std.def_func::("mod"); - std.def_func::("read"); - std.def_func::("csv"); - std.def_func::("json"); - std.def_func::("xml"); - std.def_func::("lorem"); - std.def_func::("numbering"); + global.def_func::("type"); + global.def_func::("repr"); + global.def_func::("assert"); + global.def_func::("eval"); + global.def_func::("int"); + global.def_func::("float"); + global.def_func::("luma"); + global.def_func::("rgb"); + global.def_func::("cmyk"); + global.def_func::("str"); + global.def_func::("label"); + global.def_func::("regex"); + global.def_func::("range"); + global.def_func::("abs"); + global.def_func::("min"); + global.def_func::("max"); + global.def_func::("even"); + global.def_func::("odd"); + global.def_func::("mod"); + global.def_func::("read"); + global.def_func::("csv"); + global.def_func::("json"); + global.def_func::("xml"); + global.def_func::("lorem"); + global.def_func::("numbering"); // Colors. - 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); + global.define("black", Color::BLACK); + global.define("gray", Color::GRAY); + global.define("silver", Color::SILVER); + global.define("white", Color::WHITE); + global.define("navy", Color::NAVY); + global.define("blue", Color::BLUE); + global.define("aqua", Color::AQUA); + global.define("teal", Color::TEAL); + global.define("eastern", Color::EASTERN); + global.define("purple", Color::PURPLE); + global.define("fuchsia", Color::FUCHSIA); + global.define("maroon", Color::MAROON); + global.define("red", Color::RED); + global.define("orange", Color::ORANGE); + global.define("yellow", Color::YELLOW); + global.define("olive", Color::OLIVE); + global.define("green", Color::GREEN); + global.define("lime", Color::LIME); // Other constants. - std.define("ltr", Dir::LTR); - std.define("rtl", Dir::RTL); - std.define("ttb", Dir::TTB); - std.define("btt", Dir::BTT); - std.define("start", GenAlign::Start); - std.define("end", GenAlign::End); - std.define("left", GenAlign::Specific(Align::Left)); - std.define("center", GenAlign::Specific(Align::Center)); - std.define("right", GenAlign::Specific(Align::Right)); - std.define("top", GenAlign::Specific(Align::Top)); - std.define("horizon", GenAlign::Specific(Align::Horizon)); - std.define("bottom", GenAlign::Specific(Align::Bottom)); + global.define("ltr", Dir::LTR); + global.define("rtl", Dir::RTL); + global.define("ttb", Dir::TTB); + global.define("btt", Dir::BTT); + global.define("start", GenAlign::Start); + global.define("end", GenAlign::End); + global.define("left", GenAlign::Specific(Align::Left)); + global.define("center", GenAlign::Specific(Align::Center)); + global.define("right", GenAlign::Specific(Align::Right)); + global.define("top", GenAlign::Specific(Align::Top)); + global.define("horizon", GenAlign::Specific(Align::Horizon)); + global.define("bottom", GenAlign::Specific(Align::Bottom)); - std + Module::new("global").with_scope(global) } /// Construct the standard style map. @@ -187,7 +189,7 @@ fn items() -> LangItems { term_item: |term, description| { layout::ListItem::Term(basics::TermItem { term, description }).pack() }, - math: |body, block| math::MathNode { body, block }.pack(), + math_formula: |body, block| math::FormulaNode { body, block }.pack(), math_atom: |atom| math::AtomNode(atom).pack(), math_delimited: |body| math::LrNode(body).pack(), math_script: |base, sub, sup| math::ScriptNode { base, sub, sup }.pack(), diff --git a/library/src/math/accent.rs b/library/src/math/accent.rs index b8c31c19b..a15a60204 100644 --- a/library/src/math/accent.rs +++ b/library/src/math/accent.rs @@ -133,7 +133,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs { /// Extract a single character from content. fn extract(accent: &Content) -> Option { - let atom = accent.to::()?.body.to::()?; + let atom = accent.to::()?.body.to::()?; let mut chars = atom.0.chars(); let c = chars.next().filter(|_| chars.next().is_none())?; Some(combining(c)) diff --git a/library/src/math/frac.rs b/library/src/math/frac.rs index 50a68fea1..e945473ca 100644 --- a/library/src/math/frac.rs +++ b/library/src/math/frac.rs @@ -98,6 +98,7 @@ fn layout( denom: &Content, binom: bool, ) -> SourceResult<()> { + let short_fall = DELIM_SHORT_FALL.scaled(ctx); let axis = scaled!(ctx, axis_height); let thickness = scaled!(ctx, fraction_rule_thickness); let shift_up = scaled!( @@ -149,9 +150,9 @@ fn layout( frame.push_frame(denom_pos, denom); if binom { - ctx.push(GlyphFragment::new(ctx, '(')); + ctx.push(GlyphFragment::new(ctx, '(').stretch_vertical(ctx, height, short_fall)); ctx.push(frame); - ctx.push(GlyphFragment::new(ctx, ')')); + ctx.push(GlyphFragment::new(ctx, ')').stretch_vertical(ctx, height, short_fall)); } else { frame.push( line_pos, diff --git a/library/src/math/lr.rs b/library/src/math/lr.rs index 9cfc6e5fb..89d123808 100644 --- a/library/src/math/lr.rs +++ b/library/src/math/lr.rs @@ -1,7 +1,7 @@ use super::*; /// How much less high scaled delimiters can be than what they wrap. -const DELIM_SHORT_FALL: Em = Em::new(0.1); +pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1); /// # Left-Right /// Scales delimiters. @@ -62,7 +62,7 @@ impl LayoutMath for LrNode { } let MathFragment::Glyph(glyph) = *fragment else { continue }; - let short_fall = DELIM_SHORT_FALL.at(glyph.font_size); + let short_fall = DELIM_SHORT_FALL.scaled(ctx); *fragment = MathFragment::Variant( glyph.stretch_vertical(ctx, height, short_fall), ); @@ -76,3 +76,90 @@ impl LayoutMath for LrNode { Ok(()) } } + +/// # Floor +/// Floor an expression. +/// +/// ## Example +/// ``` +/// $ floor(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to floor. +/// +/// ## Category +/// math +#[func] +pub fn floor(args: &mut Args) -> SourceResult { + delimited(args, '⌊', '⌋') +} + +/// # Ceil +/// Ceil an expression. +/// +/// ## Example +/// ``` +/// $ ceil(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to ceil. +/// +/// ## Category +/// math +#[func] +pub fn ceil(args: &mut Args) -> SourceResult { + delimited(args, '⌈', '⌉') +} + +/// # Abs +/// Take the absolute value of an expression. +/// +/// ## Example +/// ``` +/// $ abs(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to take the absolute value of. +/// +/// ## Category +/// math +#[func] +pub fn abs(args: &mut Args) -> SourceResult { + delimited(args, '|', '|') +} + +/// # Norm +/// Take the norm of an expression. +/// +/// ## Example +/// ``` +/// $ norm(x/2) $ +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The expression to take the norm of. +/// +/// ## Category +/// math +#[func] +pub fn norm(args: &mut Args) -> SourceResult { + delimited(args, '‖', '‖') +} + +fn delimited(args: &mut Args, left: char, right: char) -> SourceResult { + Ok(Value::Content( + LrNode(Content::sequence(vec![ + AtomNode(left.into()).pack(), + args.expect::("body")?, + AtomNode(right.into()).pack(), + ])) + .pack(), + )) +} diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index aa21e9cf7..9472e9895 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -161,6 +161,7 @@ fn layout( ) -> SourceResult<()> { let axis = scaled!(ctx, axis_height); let gap = ROW_GAP.scaled(ctx); + let short_fall = DELIM_SHORT_FALL.scaled(ctx); ctx.style(ctx.style.for_denominator()); let mut rows = vec![]; @@ -169,17 +170,20 @@ fn layout( } ctx.unstyle(); - if let Some(left) = left { - ctx.push(GlyphFragment::new(ctx, left)); - } - let mut frame = stack(ctx, rows, align, gap, 0); + let height = frame.height(); frame.set_baseline(frame.height() / 2.0 + axis); + if let Some(left) = left { + ctx.push(GlyphFragment::new(ctx, left).stretch_vertical(ctx, height, short_fall)); + } + ctx.push(frame); if let Some(right) = right { - ctx.push(GlyphFragment::new(ctx, right)); + ctx.push( + GlyphFragment::new(ctx, right).stretch_vertical(ctx, height, short_fall), + ); } Ok(()) diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 44b52e961..0de440be9 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -33,7 +33,7 @@ pub use self::style::*; use ttf_parser::GlyphId; use ttf_parser::Rect; use typst::font::Font; -use typst::model::{Guard, Scope, SequenceNode}; +use typst::model::{Guard, Module, Scope, SequenceNode}; use unicode_math_class::MathClass; use self::ctx::*; @@ -48,38 +48,38 @@ use crate::text::TextNode; use crate::text::TextSize; use crate::text::{families, variant, FallbackList, FontFamily, SpaceNode, SymbolNode}; -/// Hook up all math definitions. -pub fn define(scope: &mut Scope) { - scope.def_func::("math"); - scope.def_func::("lr"); - scope.def_func::("accent"); - scope.def_func::("frac"); - scope.def_func::("binom"); - scope.def_func::("script"); - scope.def_func::("sqrt"); - scope.def_func::("root"); - scope.def_func::("floor"); - scope.def_func::("ceil"); - scope.def_func::("vec"); - scope.def_func::("cases"); - scope.def_func::("underbrace"); - scope.def_func::("overbrace"); - scope.def_func::("bold"); - scope.def_func::("italic"); - scope.def_func::("serif"); - scope.def_func::("sans"); - scope.def_func::("cal"); - scope.def_func::("frak"); - scope.def_func::("mono"); - scope.def_func::("bb"); - scope.define("thin", HNode::strong(THIN).pack()); - scope.define("med", HNode::strong(MEDIUM).pack()); - scope.define("thick", HNode::strong(THICK).pack()); - scope.define("quad", HNode::strong(QUAD).pack()); - define_operators(scope); +/// Create a module with all math definitions. +pub fn module() -> Module { + let mut math = Scope::deduplicating(); + math.def_func::("formula"); + math.def_func::("lr"); + math.def_func::("floor"); + math.def_func::("ceil"); + math.def_func::("abs"); + math.def_func::("accent"); + math.def_func::("frac"); + math.def_func::("binom"); + math.def_func::("script"); + math.def_func::("sqrt"); + math.def_func::("root"); + math.def_func::("vec"); + math.def_func::("cases"); + math.def_func::("underbrace"); + math.def_func::("overbrace"); + math.def_func::("bold"); + math.def_func::("italic"); + math.def_func::("serif"); + math.def_func::("sans"); + math.def_func::("cal"); + math.def_func::("frak"); + math.def_func::("mono"); + math.def_func::("bb"); + define_spacings(&mut math); + define_operators(&mut math); + Module::new("math").with_scope(math) } -/// # Math +/// # Formula /// A mathematical formula. /// /// ## Syntax @@ -131,7 +131,7 @@ pub fn define(scope: &mut Scope) { #[func] #[capable(Show, Finalize, Layout, Inline, LayoutMath)] #[derive(Debug, Clone, Hash)] -pub struct MathNode { +pub struct FormulaNode { /// Whether the formula is displayed as a separate block. pub block: bool, /// The content of the formula. @@ -139,7 +139,7 @@ pub struct MathNode { } #[node] -impl MathNode { +impl FormulaNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let body = args.expect("body")?; let block = args.named("block")?.unwrap_or(false); @@ -148,13 +148,14 @@ impl MathNode { fn field(&self, name: &str) -> Option { match name { + "body" => Some(Value::Content(self.body.clone())), "block" => Some(Value::Bool(self.block)), _ => None, } } } -impl Show for MathNode { +impl Show for FormulaNode { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult { let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::())); if self.block { @@ -164,7 +165,7 @@ impl Show for MathNode { } } -impl Finalize for MathNode { +impl Finalize for FormulaNode { fn finalize(&self, realized: Content) -> Content { realized.styled( TextNode::FAMILY, @@ -173,7 +174,7 @@ impl Finalize for MathNode { } } -impl Layout for MathNode { +impl Layout for FormulaNode { fn layout( &self, vt: &mut Vt, @@ -200,14 +201,14 @@ impl Layout for MathNode { } } -impl Inline for MathNode {} +impl Inline for FormulaNode {} #[capability] trait LayoutMath { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()>; } -impl LayoutMath for MathNode { +impl LayoutMath for FormulaNode { fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> { self.body.layout_math(ctx) } diff --git a/library/src/math/op.rs b/library/src/math/op.rs index aef0d41b2..a7d291660 100644 --- a/library/src/math/op.rs +++ b/library/src/math/op.rs @@ -25,12 +25,6 @@ pub struct OpNode { pub limits: bool, } -impl OpNode { - fn new(text: impl Into, limits: bool) -> Self { - Self { text: text.into(), limits } - } -} - #[node] impl OpNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { @@ -55,96 +49,41 @@ impl LayoutMath for OpNode { } /// Hook up all operators. -pub fn define_operators(scope: &mut Scope) { - let mut define = |name: &str, limits| { - scope.define(name, OpNode { text: name.into(), limits }.pack()); - }; - - // These have the same name in code and display. - define("arccos", false); - define("arcsin", false); - define("arctan", false); - define("arg", false); - define("cos", false); - define("cosh", false); - define("cot", false); - define("coth", false); - define("csc", false); - define("deg", false); - define("det", true); - define("dim", false); - define("exp", false); - define("gcd", true); - define("hom", false); - define("inf", true); - define("ker", false); - define("lg", false); - define("lim", true); - define("ln", false); - define("log", false); - define("max", true); - define("min", true); - define("Pr", true); - define("sec", false); - define("sin", false); - define("sinh", false); - define("sup", true); - define("tan", false); - define("tanh", false); - - // These have an extra thin space. - scope.define("liminf", OpNode::new("lim inf", true).pack()); - scope.define("limsup", OpNode::new("lim sup", true).pack()); +pub(super) fn define_operators(math: &mut Scope) { + math.define("arccos", op("arccos", false)); + math.define("arcsin", op("arcsin", false)); + math.define("arctan", op("arctan", false)); + math.define("arg", op("arg", false)); + math.define("cos", op("cos", false)); + math.define("cosh", op("cosh", false)); + math.define("cot", op("cot", false)); + math.define("coth", op("coth", false)); + math.define("csc", op("csc", false)); + math.define("deg", op("deg", false)); + math.define("det", op("det", true)); + math.define("dim", op("dim", false)); + math.define("exp", op("exp", false)); + math.define("gcd", op("gcd", true)); + math.define("hom", op("hom", false)); + math.define("inf", op("inf", true)); + math.define("ker", op("ker", false)); + math.define("lg", op("lg", false)); + math.define("lim", op("lim", true)); + math.define("ln", op("ln", false)); + math.define("log", op("log", false)); + math.define("max", op("max", true)); + math.define("min", op("min", true)); + math.define("Pr", op("Pr", true)); + math.define("sec", op("sec", false)); + math.define("sin", op("sin", false)); + math.define("sinh", op("sinh", false)); + math.define("sup", op("sup", true)); + math.define("tan", op("tan", false)); + math.define("tanh", op("tanh", false)); + math.define("liminf", op("lim inf", true)); + math.define("limsup", op("lim sup", true)); } -/// # Floor -/// A floored expression. -/// -/// ## Example -/// ``` -/// $ floor(x/2) $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The expression to floor. -/// -/// ## Category -/// math -#[func] -#[capable] -#[derive(Debug, Hash)] -pub struct FloorNode(pub Content); - -#[node] -impl FloorNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Self(args.expect("body")?).pack()) - } -} - -/// # Ceil -/// A ceiled expression. -/// -/// ## Example -/// ``` -/// $ ceil(x/2) $ -/// ``` -/// -/// ## Parameters -/// - body: Content (positional, required) -/// The expression to ceil. -/// -/// ## Category -/// math -#[func] -#[capable] -#[derive(Debug, Hash)] -pub struct CeilNode(pub Content); - -#[node] -impl CeilNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult { - Ok(Self(args.expect("body")?).pack()) - } +fn op(name: &str, limits: bool) -> Content { + OpNode { text: name.into(), limits }.pack() } diff --git a/library/src/math/root.rs b/library/src/math/root.rs index f5c5b2b87..2efe4e078 100644 --- a/library/src/math/root.rs +++ b/library/src/math/root.rs @@ -155,7 +155,7 @@ fn layout( /// Select a precomposed radical, if the font has it. fn precomposed(ctx: &MathContext, index: Option<&Content>, target: Abs) -> Option { - let node = index?.to::()?.body.to::()?; + let node = index?.to::()?.body.to::()?; let c = match node.0.as_str() { "3" => '∛', "4" => '∜', diff --git a/library/src/math/spacing.rs b/library/src/math/spacing.rs index 0f613309a..d5a7603d5 100644 --- a/library/src/math/spacing.rs +++ b/library/src/math/spacing.rs @@ -1,10 +1,18 @@ use super::*; -pub(super) const ZERO: Em = Em::zero(); -pub(super) const THIN: Em = Em::new(1.0 / 6.0); -pub(super) const MEDIUM: Em = Em::new(2.0 / 9.0); -pub(super) const THICK: Em = Em::new(5.0 / 18.0); -pub(super) const QUAD: Em = Em::new(1.0); +const ZERO: Em = Em::zero(); +const THIN: Em = Em::new(1.0 / 6.0); +const MEDIUM: Em = Em::new(2.0 / 9.0); +const THICK: Em = Em::new(5.0 / 18.0); +const QUAD: Em = Em::new(1.0); + +/// Hook up all spacings. +pub(super) fn define_spacings(math: &mut Scope) { + math.define("thin", HNode::strong(THIN).pack()); + math.define("med", HNode::strong(MEDIUM).pack()); + math.define("thick", HNode::strong(THICK).pack()); + math.define("quad", HNode::strong(QUAD).pack()); +} /// Determine the spacing between two fragments in a given style. pub(super) fn spacing(left: MathClass, right: MathClass, style: MathStyle) -> Em { diff --git a/src/ide/complete.rs b/src/ide/complete.rs index f0808b21f..5b044746f 100644 --- a/src/ide/complete.rs +++ b/src/ide/complete.rs @@ -4,7 +4,6 @@ use if_chain::if_chain; use super::{plain_docs_sentence, summarize_font_family}; use crate::model::{CastInfo, Scope, Value}; -use crate::syntax::ast::AstNode; use crate::syntax::{ast, LinkedNode, Source, SyntaxKind}; use crate::util::{format_eco, EcoString}; use crate::World; @@ -118,8 +117,8 @@ fn complete_params(ctx: &mut CompletionContext) -> bool { if let Some(grand) = parent.parent(); if let Some(expr) = grand.cast::(); let set = matches!(expr, ast::Expr::Set(_)); - if let Some(callee) = match expr { - ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), + if let Some(ast::Expr::Ident(callee)) = match expr { + ast::Expr::FuncCall(call) => Some(call.callee()), ast::Expr::Set(set) => Some(set.target()), _ => None, }; @@ -377,7 +376,7 @@ impl<'a> CompletionContext<'a> { let leaf = LinkedNode::new(source.root()).leaf_at(cursor)?; Some(Self { world, - scope: &world.library().scope, + scope: &world.library().global.scope(), before: &text[..cursor], after: &text[cursor..], leaf, diff --git a/src/ide/tooltip.rs b/src/ide/tooltip.rs index 0d46695b7..076e2b456 100644 --- a/src/ide/tooltip.rs +++ b/src/ide/tooltip.rs @@ -2,7 +2,7 @@ use if_chain::if_chain; use super::{plain_docs_sentence, summarize_font_family}; use crate::model::{CastInfo, Value}; -use crate::syntax::ast::{self, AstNode}; +use crate::syntax::ast; use crate::syntax::{LinkedNode, Source, SyntaxKind}; use crate::World; @@ -23,7 +23,7 @@ fn function_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { leaf.parent_kind(), Some(SyntaxKind::FuncCall | SyntaxKind::SetRule), ); - if let Some(Value::Func(func)) = world.library().scope.get(&ident); + if let Some(Value::Func(func)) = world.library().global.scope().get(&ident); if let Some(info) = func.info(); then { return Some(plain_docs_sentence(info.docs)); @@ -44,14 +44,14 @@ fn named_param_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { if matches!(grand.kind(), SyntaxKind::Args); if let Some(grand_grand) = grand.parent(); if let Some(expr) = grand_grand.cast::(); - if let Some(callee) = match expr { - ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), + if let Some(ast::Expr::Ident(callee)) = match expr { + ast::Expr::FuncCall(call) => Some(call.callee()), ast::Expr::Set(set) => Some(set.target()), _ => None, }; // Find metadata about the function. - if let Some(Value::Func(func)) = world.library().scope.get(&callee); + if let Some(Value::Func(func)) = world.library().global.scope().get(&callee); if let Some(info) = func.info(); then { (info, named) } else { return None; } @@ -103,8 +103,8 @@ fn font_family_tooltip(world: &dyn World, leaf: &LinkedNode) -> Option { if matches!(parent.kind(), SyntaxKind::Args); if let Some(grand) = parent.parent(); if let Some(expr) = grand.cast::(); - if let Some(callee) = match expr { - ast::Expr::FuncCall(call) => call.callee().as_untyped().cast(), + if let Some(ast::Expr::Ident(callee)) = match expr { + ast::Expr::FuncCall(call) => Some(call.callee()), ast::Expr::Set(set) => Some(set.target()), _ => None, }; diff --git a/src/model/eval.rs b/src/model/eval.rs index 67c733cef..6de328bc2 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::mem; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use comemo::{Track, Tracked}; use unicode_segmentation::UnicodeSegmentation; @@ -32,9 +32,9 @@ pub fn eval( ) -> SourceResult { // Prevent cyclic evaluation. let id = source.id(); + let path = if id.is_detached() { Path::new("") } else { world.source(id).path() }; if route.contains(id) { - let path = world.source(id).path().display(); - panic!("Tried to cyclicly evaluate {}", path); + panic!("Tried to cyclicly evaluate {}", path.display()); } // Hook up the lang items. @@ -43,7 +43,7 @@ pub fn eval( // Evaluate the module. let route = unsafe { Route::insert(route, id) }; - let scopes = Scopes::new(Some(&library.scope)); + let scopes = Scopes::new(Some(library)); let mut vm = Vm::new(world, route.track(), id, scopes, 0); let result = source.ast()?.eval(&mut vm); @@ -53,7 +53,8 @@ pub fn eval( } // Assemble the module. - Ok(Module::evaluated(source.path(), vm.scopes.top, result?)) + let name = path.file_stem().unwrap_or_default().to_string_lossy(); + Ok(Module::new(name).with_scope(vm.scopes.top).with_content(result?)) } /// A virtual machine. @@ -521,7 +522,7 @@ impl Eval for ast::Math { .map(|expr| expr.eval_in_math(vm)) .collect::>()?; let block = self.block(); - Ok((vm.items.math)(Content::sequence(seq), block)) + Ok((vm.items.math_formula)(Content::sequence(seq), block)) } } @@ -608,11 +609,11 @@ impl Eval for ast::Ident { impl ast::Ident { fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { if self.as_untyped().len() == self.len() - && matches!(vm.scopes.get(self), Ok(Value::Func(_)) | Err(_)) + && matches!(vm.scopes.get_in_math(self), Ok(Value::Func(_)) | Err(_)) { Ok((vm.items.symbol)(EcoString::from(self.get()) + ":op".into())) } else { - Ok(self.eval(vm)?.display_in_math()) + Ok(vm.scopes.get_in_math(self).at(self.span())?.clone().display_in_math()) } } } @@ -933,7 +934,13 @@ impl Eval for ast::FuncCall { impl ast::FuncCall { fn eval_in_math(&self, vm: &mut Vm) -> SourceResult { - let callee = self.callee().eval(vm)?; + let callee = match self.callee() { + ast::Expr::Ident(ident) => { + vm.scopes.get_in_math(&ident).at(ident.span())?.clone() + } + expr => expr.eval(vm)?, + }; + if let Value::Func(callee) = callee { let args = self.args().eval(vm)?; Ok(Self::eval_call(vm, &callee, args, self.span())?.display_in_math()) diff --git a/src/model/library.rs b/src/model/library.rs index 54eeeb5b8..a64b02636 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -4,7 +4,7 @@ use std::num::NonZeroUsize; use once_cell::sync::OnceCell; -use super::{Content, NodeId, Scope, StyleChain, StyleMap, Vt}; +use super::{Content, Module, NodeId, StyleChain, StyleMap, Vt}; use crate::diag::SourceResult; use crate::doc::Document; use crate::geom::{Abs, Dir}; @@ -14,7 +14,9 @@ use crate::util::{hash128, EcoString}; #[derive(Debug, Clone, Hash)] pub struct Library { /// The scope containing definitions that are available everywhere. - pub scope: Scope, + pub global: Module, + /// The scope containing definitions available in math mode. + pub math: Module, /// The default properties for page size, font selection and so on. pub styles: StyleMap, /// Defines which standard library items fulfill which syntactical roles. @@ -66,7 +68,7 @@ pub struct LangItems { /// An item in a term list: `/ Term: Details`. pub term_item: fn(term: Content, description: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. - pub math: fn(body: Content, block: bool) -> Content, + pub math_formula: fn(body: Content, block: bool) -> Content, /// A subsection in a math formula that is surrounded by matched delimiters: /// `[x + y]`. pub math_delimited: fn(body: Content) -> Content, @@ -106,7 +108,7 @@ impl Hash for LangItems { self.list_item.hash(state); self.enum_item.hash(state); self.term_item.hash(state); - self.math.hash(state); + self.math_formula.hash(state); self.math_atom.hash(state); self.math_script.hash(state); self.math_frac.hash(state); diff --git a/src/model/module.rs b/src/model/module.rs index ba6c76fb0..6a1c60a5d 100644 --- a/src/model/module.rs +++ b/src/model/module.rs @@ -1,5 +1,4 @@ use std::fmt::{self, Debug, Formatter}; -use std::path::Path; use std::sync::Arc; use super::{Content, Scope, Value}; @@ -22,7 +21,7 @@ struct Repr { } impl Module { - /// Create a new, empty module with the given `name`. + /// Create a new module. pub fn new(name: impl Into) -> Self { Self(Arc::new(Repr { name: name.into(), @@ -31,10 +30,16 @@ impl Module { })) } - /// Create a new module from an evalauted file. - pub fn evaluated(path: &Path, scope: Scope, content: Content) -> Self { - let name = path.file_stem().unwrap_or_default().to_string_lossy().into(); - Self(Arc::new(Repr { name, scope, content })) + /// Update the module's scope. + pub fn with_scope(mut self, scope: Scope) -> Self { + Arc::make_mut(&mut self.0).scope = scope; + self + } + + /// Update the module's content. + pub fn with_content(mut self, content: Content) -> Self { + Arc::make_mut(&mut self.0).content = content; + self } /// Get the module's name. @@ -47,6 +52,11 @@ impl Module { &self.0.scope } + /// Access the module's scope, mutably. + pub fn scope_mut(&mut self) -> &mut Scope { + &mut Arc::make_mut(&mut self.0).scope + } + /// Try to access a definition in the module. pub fn get(&self, name: &str) -> StrResult<&Value> { self.scope().get(&name).ok_or_else(|| { diff --git a/src/model/scope.rs b/src/model/scope.rs index c54cf1b34..bb0d46849 100644 --- a/src/model/scope.rs +++ b/src/model/scope.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; -use super::{Func, FuncType, Value}; +use super::{Func, FuncType, Library, Value}; use crate::diag::StrResult; use crate::util::EcoString; @@ -13,13 +13,13 @@ pub struct Scopes<'a> { pub top: Scope, /// The stack of lower scopes. pub scopes: Vec, - /// The base scope. - pub base: Option<&'a Scope>, + /// The standard library. + pub base: Option<&'a Library>, } impl<'a> Scopes<'a> { /// Create a new, empty hierarchy of scopes. - pub fn new(base: Option<&'a Scope>) -> Self { + pub fn new(base: Option<&'a Library>) -> Self { Self { top: Scope::new(), scopes: vec![], base } } @@ -39,7 +39,16 @@ impl<'a> Scopes<'a> { pub fn get(&self, var: &str) -> StrResult<&Value> { Ok(std::iter::once(&self.top) .chain(self.scopes.iter().rev()) - .chain(self.base.into_iter()) + .chain(self.base.map(|base| base.global.scope())) + .find_map(|scope| scope.get(var)) + .ok_or("unknown variable")?) + } + + /// Try to access a variable immutably from within a math formula. + pub fn get_in_math(&self, var: &str) -> StrResult<&Value> { + Ok(std::iter::once(&self.top) + .chain(self.scopes.iter().rev()) + .chain(self.base.map(|base| base.math.scope())) .find_map(|scope| scope.get(var)) .ok_or("unknown variable")?) } @@ -50,10 +59,9 @@ impl<'a> Scopes<'a> { .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" + match self.base.and_then(|base| base.global.scope().get(var)) { + Some(_) => "cannot mutate a constant", + _ => "unknown variable", } })? } @@ -61,17 +69,29 @@ impl<'a> Scopes<'a> { /// A map from binding names to values. #[derive(Default, Clone, Hash)] -pub struct Scope(BTreeMap); +pub struct Scope(BTreeMap, bool); impl Scope { /// Create a new empty scope. pub fn new() -> Self { - Self::default() + Self(BTreeMap::new(), false) + } + + /// Create a new scope with duplication prevention. + pub fn deduplicating() -> Self { + Self(BTreeMap::new(), true) } /// 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)); + let name = name.into(); + + #[cfg(debug_assertions)] + if self.1 && self.0.contains_key(&name) { + panic!("duplicate definition: {name}"); + } + + self.0.insert(name, Slot::new(value.into(), Kind::Normal)); } /// Define a function through a native rust function. diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index ceda2d57c..4bab0c429 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1505,7 +1505,7 @@ node! { impl SetRule { /// The function to set style properties for. - pub fn target(&self) -> Ident { + pub fn target(&self) -> Expr { self.0.cast_first_match().expect("set rule is missing target") } diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 15839e18b..07f533728 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -712,7 +712,14 @@ fn let_binding(p: &mut Parser) { fn set_rule(p: &mut Parser) { let m = p.marker(); p.assert(SyntaxKind::Set); + + let m2 = p.marker(); p.expect(SyntaxKind::Ident); + while p.eat_if(SyntaxKind::Dot) { + p.expect(SyntaxKind::Ident); + p.wrap(m2, SyntaxKind::FieldAccess); + } + args(p); if p.eat_if(SyntaxKind::If) { code_expr(p); diff --git a/tests/src/tests.rs b/tests/src/tests.rs index 16dea380f..ac3e4d85d 100644 --- a/tests/src/tests.rs +++ b/tests/src/tests.rs @@ -186,10 +186,14 @@ fn library() -> Library { lib.styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into())); // Hook up helpers into the global scope. - lib.scope.def_func::("test"); - lib.scope.def_func::("print"); - lib.scope.define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); - lib.scope.define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); + lib.global.scope_mut().def_func::("test"); + lib.global.scope_mut().def_func::("print"); + lib.global + .scope_mut() + .define("conifer", RgbaColor::new(0x9f, 0xEB, 0x52, 0xFF)); + lib.global + .scope_mut() + .define("forest", RgbaColor::new(0x43, 0xA1, 0x27, 0xFF)); lib } diff --git a/tests/typ/layout/par.typ b/tests/typ/layout/par.typ index aabe63ef8..264209b87 100644 --- a/tests/typ/layout/par.typ +++ b/tests/typ/layout/par.typ @@ -25,7 +25,6 @@ Hello // While we're at it, test the larger block spacing wins. #set block(spacing: 0pt) #show raw: set block(spacing: 15pt) -#show math: set block(spacing: 7.5pt) #show list: set block(spacing: 2.5pt) ```rust diff --git a/tests/typ/math/matrix.typ b/tests/typ/math/matrix.typ index 3d67800dd..aa99bb1b5 100644 --- a/tests/typ/math/matrix.typ +++ b/tests/typ/math/matrix.typ @@ -7,7 +7,7 @@ $ v = vec(1, 2+3, 4) $ $ binom(n, 1) = 1/2 n (n-1) $ --- -#set vec(delim: "|") +#set math.vec(delim: "|") $ vec(1, 2) $ --- @@ -19,8 +19,8 @@ $ f(x, y) := cases( ) $ --- -// Error: 17-20 expected "(", "[", "{", or "|" -#set vec(delim: "%") +// Error: 22-25 expected "(", "[", "{", "|", or "||" +#set math.vec(delim: "%") --- // Error: 9-12 missing argument: lower index diff --git a/tests/typ/math/style.typ b/tests/typ/math/style.typ index e249a45a7..a8b9c7a50 100644 --- a/tests/typ/math/style.typ +++ b/tests/typ/math/style.typ @@ -1,6 +1,6 @@ #let part = $ a b A B $ -#let kinds = (serif, sans, cal, frak, mono, bb) -#let modifiers = (v => v, italic, bold, v => italic(bold(v))) +#let kinds = (math.serif, math.sans, math.cal, math.frak, math.mono, math.bb) +#let modifiers = (v => v, math.italic, math.bold, v => math.italic(math.bold(v))) #let cells = ([:triangle:nested:], [--], [`italic`], [`bold`], [both]) #for k in kinds {