Math module
This commit is contained in:
parent
84c6c8b0e6
commit
4653ffebb4
@ -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::<basics::HeadingNode>("heading");
|
||||
std.def_func::<basics::ListNode>("list");
|
||||
std.def_func::<basics::EnumNode>("enum");
|
||||
std.def_func::<basics::TermsNode>("terms");
|
||||
std.def_func::<basics::TableNode>("table");
|
||||
global.def_func::<basics::HeadingNode>("heading");
|
||||
global.def_func::<basics::ListNode>("list");
|
||||
global.def_func::<basics::EnumNode>("enum");
|
||||
global.def_func::<basics::TermsNode>("terms");
|
||||
global.def_func::<basics::TableNode>("table");
|
||||
|
||||
// Text.
|
||||
std.def_func::<text::TextNode>("text");
|
||||
std.def_func::<text::LinebreakNode>("linebreak");
|
||||
std.def_func::<text::SymbolNode>("symbol");
|
||||
std.def_func::<text::SmartQuoteNode>("smartquote");
|
||||
std.def_func::<text::StrongNode>("strong");
|
||||
std.def_func::<text::EmphNode>("emph");
|
||||
std.def_func::<text::LowerFunc>("lower");
|
||||
std.def_func::<text::UpperFunc>("upper");
|
||||
std.def_func::<text::SmallcapsFunc>("smallcaps");
|
||||
std.def_func::<text::SubNode>("sub");
|
||||
std.def_func::<text::SuperNode>("super");
|
||||
std.def_func::<text::UnderlineNode>("underline");
|
||||
std.def_func::<text::StrikeNode>("strike");
|
||||
std.def_func::<text::OverlineNode>("overline");
|
||||
std.def_func::<text::RawNode>("raw");
|
||||
global.def_func::<text::TextNode>("text");
|
||||
global.def_func::<text::LinebreakNode>("linebreak");
|
||||
global.def_func::<text::SymbolNode>("symbol");
|
||||
global.def_func::<text::SmartQuoteNode>("smartquote");
|
||||
global.def_func::<text::StrongNode>("strong");
|
||||
global.def_func::<text::EmphNode>("emph");
|
||||
global.def_func::<text::LowerFunc>("lower");
|
||||
global.def_func::<text::UpperFunc>("upper");
|
||||
global.def_func::<text::SmallcapsFunc>("smallcaps");
|
||||
global.def_func::<text::SubNode>("sub");
|
||||
global.def_func::<text::SuperNode>("super");
|
||||
global.def_func::<text::UnderlineNode>("underline");
|
||||
global.def_func::<text::StrikeNode>("strike");
|
||||
global.def_func::<text::OverlineNode>("overline");
|
||||
global.def_func::<text::RawNode>("raw");
|
||||
|
||||
// Math.
|
||||
math::define(&mut std);
|
||||
global.define("math", math);
|
||||
|
||||
// Layout.
|
||||
std.def_func::<layout::PageNode>("page");
|
||||
std.def_func::<layout::PagebreakNode>("pagebreak");
|
||||
std.def_func::<layout::VNode>("v");
|
||||
std.def_func::<layout::ParNode>("par");
|
||||
std.def_func::<layout::ParbreakNode>("parbreak");
|
||||
std.def_func::<layout::HNode>("h");
|
||||
std.def_func::<layout::BoxNode>("box");
|
||||
std.def_func::<layout::BlockNode>("block");
|
||||
std.def_func::<layout::StackNode>("stack");
|
||||
std.def_func::<layout::GridNode>("grid");
|
||||
std.def_func::<layout::ColumnsNode>("columns");
|
||||
std.def_func::<layout::ColbreakNode>("colbreak");
|
||||
std.def_func::<layout::PlaceNode>("place");
|
||||
std.def_func::<layout::AlignNode>("align");
|
||||
std.def_func::<layout::PadNode>("pad");
|
||||
std.def_func::<layout::RepeatNode>("repeat");
|
||||
std.def_func::<layout::MoveNode>("move");
|
||||
std.def_func::<layout::ScaleNode>("scale");
|
||||
std.def_func::<layout::RotateNode>("rotate");
|
||||
std.def_func::<layout::HideNode>("hide");
|
||||
global.def_func::<layout::PageNode>("page");
|
||||
global.def_func::<layout::PagebreakNode>("pagebreak");
|
||||
global.def_func::<layout::VNode>("v");
|
||||
global.def_func::<layout::ParNode>("par");
|
||||
global.def_func::<layout::ParbreakNode>("parbreak");
|
||||
global.def_func::<layout::HNode>("h");
|
||||
global.def_func::<layout::BoxNode>("box");
|
||||
global.def_func::<layout::BlockNode>("block");
|
||||
global.def_func::<layout::StackNode>("stack");
|
||||
global.def_func::<layout::GridNode>("grid");
|
||||
global.def_func::<layout::ColumnsNode>("columns");
|
||||
global.def_func::<layout::ColbreakNode>("colbreak");
|
||||
global.def_func::<layout::PlaceNode>("place");
|
||||
global.def_func::<layout::AlignNode>("align");
|
||||
global.def_func::<layout::PadNode>("pad");
|
||||
global.def_func::<layout::RepeatNode>("repeat");
|
||||
global.def_func::<layout::MoveNode>("move");
|
||||
global.def_func::<layout::ScaleNode>("scale");
|
||||
global.def_func::<layout::RotateNode>("rotate");
|
||||
global.def_func::<layout::HideNode>("hide");
|
||||
|
||||
// Visualize.
|
||||
std.def_func::<visualize::ImageNode>("image");
|
||||
std.def_func::<visualize::LineNode>("line");
|
||||
std.def_func::<visualize::RectNode>("rect");
|
||||
std.def_func::<visualize::SquareNode>("square");
|
||||
std.def_func::<visualize::EllipseNode>("ellipse");
|
||||
std.def_func::<visualize::CircleNode>("circle");
|
||||
global.def_func::<visualize::ImageNode>("image");
|
||||
global.def_func::<visualize::LineNode>("line");
|
||||
global.def_func::<visualize::RectNode>("rect");
|
||||
global.def_func::<visualize::SquareNode>("square");
|
||||
global.def_func::<visualize::EllipseNode>("ellipse");
|
||||
global.def_func::<visualize::CircleNode>("circle");
|
||||
|
||||
// Meta.
|
||||
std.def_func::<meta::DocumentNode>("document");
|
||||
std.def_func::<meta::RefNode>("ref");
|
||||
std.def_func::<meta::LinkNode>("link");
|
||||
std.def_func::<meta::OutlineNode>("outline");
|
||||
global.def_func::<meta::DocumentNode>("document");
|
||||
global.def_func::<meta::RefNode>("ref");
|
||||
global.def_func::<meta::LinkNode>("link");
|
||||
global.def_func::<meta::OutlineNode>("outline");
|
||||
|
||||
// Compute.
|
||||
std.def_func::<compute::TypeFunc>("type");
|
||||
std.def_func::<compute::ReprFunc>("repr");
|
||||
std.def_func::<compute::AssertFunc>("assert");
|
||||
std.def_func::<compute::EvalFunc>("eval");
|
||||
std.def_func::<compute::IntFunc>("int");
|
||||
std.def_func::<compute::FloatFunc>("float");
|
||||
std.def_func::<compute::LumaFunc>("luma");
|
||||
std.def_func::<compute::RgbFunc>("rgb");
|
||||
std.def_func::<compute::CmykFunc>("cmyk");
|
||||
std.def_func::<compute::StrFunc>("str");
|
||||
std.def_func::<compute::LabelFunc>("label");
|
||||
std.def_func::<compute::RegexFunc>("regex");
|
||||
std.def_func::<compute::RangeFunc>("range");
|
||||
std.def_func::<compute::AbsFunc>("abs");
|
||||
std.def_func::<compute::MinFunc>("min");
|
||||
std.def_func::<compute::MaxFunc>("max");
|
||||
std.def_func::<compute::EvenFunc>("even");
|
||||
std.def_func::<compute::OddFunc>("odd");
|
||||
std.def_func::<compute::ModFunc>("mod");
|
||||
std.def_func::<compute::ReadFunc>("read");
|
||||
std.def_func::<compute::CsvFunc>("csv");
|
||||
std.def_func::<compute::JsonFunc>("json");
|
||||
std.def_func::<compute::XmlFunc>("xml");
|
||||
std.def_func::<compute::LoremFunc>("lorem");
|
||||
std.def_func::<compute::NumberingFunc>("numbering");
|
||||
global.def_func::<compute::TypeFunc>("type");
|
||||
global.def_func::<compute::ReprFunc>("repr");
|
||||
global.def_func::<compute::AssertFunc>("assert");
|
||||
global.def_func::<compute::EvalFunc>("eval");
|
||||
global.def_func::<compute::IntFunc>("int");
|
||||
global.def_func::<compute::FloatFunc>("float");
|
||||
global.def_func::<compute::LumaFunc>("luma");
|
||||
global.def_func::<compute::RgbFunc>("rgb");
|
||||
global.def_func::<compute::CmykFunc>("cmyk");
|
||||
global.def_func::<compute::StrFunc>("str");
|
||||
global.def_func::<compute::LabelFunc>("label");
|
||||
global.def_func::<compute::RegexFunc>("regex");
|
||||
global.def_func::<compute::RangeFunc>("range");
|
||||
global.def_func::<compute::AbsFunc>("abs");
|
||||
global.def_func::<compute::MinFunc>("min");
|
||||
global.def_func::<compute::MaxFunc>("max");
|
||||
global.def_func::<compute::EvenFunc>("even");
|
||||
global.def_func::<compute::OddFunc>("odd");
|
||||
global.def_func::<compute::ModFunc>("mod");
|
||||
global.def_func::<compute::ReadFunc>("read");
|
||||
global.def_func::<compute::CsvFunc>("csv");
|
||||
global.def_func::<compute::JsonFunc>("json");
|
||||
global.def_func::<compute::XmlFunc>("xml");
|
||||
global.def_func::<compute::LoremFunc>("lorem");
|
||||
global.def_func::<compute::NumberingFunc>("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(),
|
||||
|
@ -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<char> {
|
||||
let atom = accent.to::<MathNode>()?.body.to::<AtomNode>()?;
|
||||
let atom = accent.to::<FormulaNode>()?.body.to::<AtomNode>()?;
|
||||
let mut chars = atom.0.chars();
|
||||
let c = chars.next().filter(|_| chars.next().is_none())?;
|
||||
Some(combining(c))
|
||||
|
@ -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,
|
||||
|
@ -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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
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<Value> {
|
||||
delimited(args, '‖', '‖')
|
||||
}
|
||||
|
||||
fn delimited(args: &mut Args, left: char, right: char) -> SourceResult<Value> {
|
||||
Ok(Value::Content(
|
||||
LrNode(Content::sequence(vec![
|
||||
AtomNode(left.into()).pack(),
|
||||
args.expect::<Content>("body")?,
|
||||
AtomNode(right.into()).pack(),
|
||||
]))
|
||||
.pack(),
|
||||
))
|
||||
}
|
||||
|
@ -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(())
|
||||
|
@ -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::<MathNode>("math");
|
||||
scope.def_func::<LrNode>("lr");
|
||||
scope.def_func::<AccentNode>("accent");
|
||||
scope.def_func::<FracNode>("frac");
|
||||
scope.def_func::<BinomNode>("binom");
|
||||
scope.def_func::<ScriptNode>("script");
|
||||
scope.def_func::<SqrtNode>("sqrt");
|
||||
scope.def_func::<RootNode>("root");
|
||||
scope.def_func::<FloorNode>("floor");
|
||||
scope.def_func::<CeilNode>("ceil");
|
||||
scope.def_func::<VecNode>("vec");
|
||||
scope.def_func::<CasesNode>("cases");
|
||||
scope.def_func::<UnderbraceNode>("underbrace");
|
||||
scope.def_func::<OverbraceNode>("overbrace");
|
||||
scope.def_func::<BoldNode>("bold");
|
||||
scope.def_func::<ItalicNode>("italic");
|
||||
scope.def_func::<SerifNode>("serif");
|
||||
scope.def_func::<SansNode>("sans");
|
||||
scope.def_func::<CalNode>("cal");
|
||||
scope.def_func::<FrakNode>("frak");
|
||||
scope.def_func::<MonoNode>("mono");
|
||||
scope.def_func::<BbNode>("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::<FormulaNode>("formula");
|
||||
math.def_func::<LrNode>("lr");
|
||||
math.def_func::<FloorFunc>("floor");
|
||||
math.def_func::<CeilFunc>("ceil");
|
||||
math.def_func::<AbsFunc>("abs");
|
||||
math.def_func::<AccentNode>("accent");
|
||||
math.def_func::<FracNode>("frac");
|
||||
math.def_func::<BinomNode>("binom");
|
||||
math.def_func::<ScriptNode>("script");
|
||||
math.def_func::<SqrtNode>("sqrt");
|
||||
math.def_func::<RootNode>("root");
|
||||
math.def_func::<VecNode>("vec");
|
||||
math.def_func::<CasesNode>("cases");
|
||||
math.def_func::<UnderbraceNode>("underbrace");
|
||||
math.def_func::<OverbraceNode>("overbrace");
|
||||
math.def_func::<BoldNode>("bold");
|
||||
math.def_func::<ItalicNode>("italic");
|
||||
math.def_func::<SerifNode>("serif");
|
||||
math.def_func::<SansNode>("sans");
|
||||
math.def_func::<CalNode>("cal");
|
||||
math.def_func::<FrakNode>("frak");
|
||||
math.def_func::<MonoNode>("mono");
|
||||
math.def_func::<BbNode>("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<Content> {
|
||||
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<Value> {
|
||||
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<Content> {
|
||||
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
||||
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)
|
||||
}
|
||||
|
@ -25,12 +25,6 @@ pub struct OpNode {
|
||||
pub limits: bool,
|
||||
}
|
||||
|
||||
impl OpNode {
|
||||
fn new(text: impl Into<EcoString>, limits: bool) -> Self {
|
||||
Self { text: text.into(), limits }
|
||||
}
|
||||
}
|
||||
|
||||
#[node]
|
||||
impl OpNode {
|
||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
@ -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<Content> {
|
||||
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<Content> {
|
||||
Ok(Self(args.expect("body")?).pack())
|
||||
}
|
||||
fn op(name: &str, limits: bool) -> Content {
|
||||
OpNode { text: name.into(), limits }.pack()
|
||||
}
|
||||
|
@ -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<Frame> {
|
||||
let node = index?.to::<MathNode>()?.body.to::<AtomNode>()?;
|
||||
let node = index?.to::<FormulaNode>()?.body.to::<AtomNode>()?;
|
||||
let c = match node.0.as_str() {
|
||||
"3" => '∛',
|
||||
"4" => '∜',
|
||||
|
@ -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 {
|
||||
|
@ -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::<ast::Expr>();
|
||||
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,
|
||||
|
@ -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<String> {
|
||||
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<String> {
|
||||
if matches!(grand.kind(), SyntaxKind::Args);
|
||||
if let Some(grand_grand) = grand.parent();
|
||||
if let Some(expr) = grand_grand.cast::<ast::Expr>();
|
||||
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<String> {
|
||||
if matches!(parent.kind(), SyntaxKind::Args);
|
||||
if let Some(grand) = parent.parent();
|
||||
if let Some(expr) = grand.cast::<ast::Expr>();
|
||||
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,
|
||||
};
|
||||
|
@ -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<Module> {
|
||||
// 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::<SourceResult<_>>()?;
|
||||
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<Content> {
|
||||
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<Content> {
|
||||
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())
|
||||
|
@ -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);
|
||||
|
@ -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<EcoString>) -> 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(|| {
|
||||
|
@ -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<Scope>,
|
||||
/// 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<EcoString, Slot>);
|
||||
pub struct Scope(BTreeMap<EcoString, Slot>, 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<EcoString>, value: impl Into<Value>) {
|
||||
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.
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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::<TestFunc>("test");
|
||||
lib.scope.def_func::<PrintFunc>("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::<TestFunc>("test");
|
||||
lib.global.scope_mut().def_func::<PrintFunc>("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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user