From be6629c7cbd00b06beab2b1477c4270859906cb2 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 9 Jan 2025 10:49:06 +0000 Subject: [PATCH] Better math argument parsing (#5008) --- crates/typst-eval/src/call.rs | 3 +- crates/typst-library/src/math/mod.rs | 6 +- crates/typst-syntax/src/lexer.rs | 44 +++++ crates/typst-syntax/src/parser.rs | 171 ++++++++++-------- crates/typst-syntax/src/set.rs | 4 + tests/ref/math-call-named-args.png | Bin 0 -> 526 bytes .../ref/math-call-spread-shorthand-clash.png | Bin 0 -> 119 bytes tests/ref/math-mat-gaps.png | Bin 489 -> 1309 bytes tests/ref/math-mat-spread-1d.png | Bin 0 -> 1017 bytes tests/ref/math-mat-spread-2d.png | Bin 0 -> 3391 bytes tests/ref/math-mat-spread.png | Bin 0 -> 1814 bytes tests/suite/math/call.typ | 134 ++++++++++++++ tests/suite/math/mat.typ | 26 +++ 13 files changed, 308 insertions(+), 80 deletions(-) create mode 100644 tests/ref/math-call-named-args.png create mode 100644 tests/ref/math-call-spread-shorthand-clash.png create mode 100644 tests/ref/math-mat-spread-1d.png create mode 100644 tests/ref/math-mat-spread-2d.png create mode 100644 tests/ref/math-mat-spread.png diff --git a/crates/typst-eval/src/call.rs b/crates/typst-eval/src/call.rs index fc934cef5..0a9e1c486 100644 --- a/crates/typst-eval/src/call.rs +++ b/crates/typst-eval/src/call.rs @@ -685,8 +685,7 @@ mod tests { // Named-params. test(s, "$ foo(bar: y) $", &["foo"]); - // This should be updated when we improve named-param parsing: - test(s, "$ foo(x-y: 1, bar-z: 2) $", &["bar", "foo"]); + test(s, "$ foo(x-y: 1, bar-z: 2) $", &["foo"]); // Field access in math. test(s, "$ foo.bar $", &["foo"]); diff --git a/crates/typst-library/src/math/mod.rs b/crates/typst-library/src/math/mod.rs index 5a83c854f..3b4b133d9 100644 --- a/crates/typst-library/src/math/mod.rs +++ b/crates/typst-library/src/math/mod.rs @@ -82,8 +82,9 @@ use crate::text::TextElem; /// - Within them, Typst is still in "math mode". Thus, you can write math /// directly into them, but need to use hash syntax to pass code expressions /// (except for strings, which are available in the math syntax). -/// - They support positional and named arguments, but don't support trailing -/// content blocks and argument spreading. +/// - They support positional and named arguments, as well as argument +/// spreading. +/// - They don't support trailing content blocks. /// - They provide additional syntax for 2-dimensional argument lists. The /// semicolon (`;`) merges preceding arguments separated by commas into an /// array argument. @@ -92,6 +93,7 @@ use crate::text::TextElem; /// $ frac(a^2, 2) $ /// $ vec(1, 2, delim: "[") $ /// $ mat(1, 2; 3, 4) $ +/// $ mat(..#range(1, 5).chunks(2)) $ /// $ lim_x = /// op("lim", limits: #true)_x $ /// ``` diff --git a/crates/typst-syntax/src/lexer.rs b/crates/typst-syntax/src/lexer.rs index b0cb5c464..6b5d28162 100644 --- a/crates/typst-syntax/src/lexer.rs +++ b/crates/typst-syntax/src/lexer.rs @@ -616,6 +616,11 @@ impl Lexer<'_> { '~' if self.s.eat_if('>') => SyntaxKind::MathShorthand, '*' | '-' | '~' => SyntaxKind::MathShorthand, + '.' => SyntaxKind::Dot, + ',' => SyntaxKind::Comma, + ';' => SyntaxKind::Semicolon, + ')' => SyntaxKind::RightParen, + '#' => SyntaxKind::Hash, '_' => SyntaxKind::Underscore, '$' => SyntaxKind::Dollar, @@ -685,6 +690,45 @@ impl Lexer<'_> { } SyntaxKind::Text } + + /// Handle named arguments in math function call. + pub fn maybe_math_named_arg(&mut self, start: usize) -> Option { + let cursor = self.s.cursor(); + self.s.jump(start); + if self.s.eat_if(is_id_start) { + self.s.eat_while(is_id_continue); + // Check that a colon directly follows the identifier, and not the + // `:=` or `::=` math shorthands. + if self.s.at(':') && !self.s.at(":=") && !self.s.at("::=") { + // Check that the identifier is not just `_`. + let node = if self.s.from(start) != "_" { + SyntaxNode::leaf(SyntaxKind::Ident, self.s.from(start)) + } else { + let msg = SyntaxError::new("expected identifier, found underscore"); + SyntaxNode::error(msg, self.s.from(start)) + }; + return Some(node); + } + } + self.s.jump(cursor); + None + } + + /// Handle spread arguments in math function call. + pub fn maybe_math_spread_arg(&mut self, start: usize) -> Option { + let cursor = self.s.cursor(); + self.s.jump(start); + if self.s.eat_if("..") { + // Check that neither a space nor a dot follows the spread syntax. + // A dot would clash with the `...` math shorthand. + if !self.space_or_end() && !self.s.at('.') { + let node = SyntaxNode::leaf(SyntaxKind::Dots, self.s.from(start)); + return Some(node); + } + } + self.s.jump(cursor); + None + } } /// Code. diff --git a/crates/typst-syntax/src/parser.rs b/crates/typst-syntax/src/parser.rs index 6c1778c4a..335b8f1a2 100644 --- a/crates/typst-syntax/src/parser.rs +++ b/crates/typst-syntax/src/parser.rs @@ -217,16 +217,20 @@ fn math(p: &mut Parser, stop_set: SyntaxSet) { p.wrap(m, SyntaxKind::Math); } -/// Parses a sequence of math expressions. -fn math_exprs(p: &mut Parser, stop_set: SyntaxSet) { +/// Parses a sequence of math expressions. Returns the number of expressions +/// parsed. +fn math_exprs(p: &mut Parser, stop_set: SyntaxSet) -> usize { debug_assert!(stop_set.contains(SyntaxKind::End)); + let mut count = 0; while !p.at_set(stop_set) { if p.at_set(set::MATH_EXPR) { math_expr(p); + count += 1; } else { p.unexpected(); } } + count } /// Parses a single math expression: This includes math elements like @@ -254,6 +258,13 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) { } } + SyntaxKind::Dot + | SyntaxKind::Comma + | SyntaxKind::Semicolon + | SyntaxKind::RightParen => { + p.convert_and_eat(SyntaxKind::Text); + } + SyntaxKind::Text | SyntaxKind::MathShorthand => { continuable = matches!( math_class(p.current_text()), @@ -398,7 +409,13 @@ fn math_delimited(p: &mut Parser) { while !p.at_set(syntax_set!(Dollar, End)) { if math_class(p.current_text()) == Some(MathClass::Closing) { p.wrap(m2, SyntaxKind::Math); - p.eat(); + // We could be at the shorthand `|]`, which shouldn't be converted + // to a `Text` kind. + if p.at(SyntaxKind::RightParen) { + p.convert_and_eat(SyntaxKind::Text); + } else { + p.eat(); + } p.wrap(m, SyntaxKind::MathDelimited); return; } @@ -455,94 +472,90 @@ fn math_args(p: &mut Parser) { let m = p.marker(); p.convert_and_eat(SyntaxKind::LeftParen); - let mut namable = true; - let mut named = None; + let mut positional = true; let mut has_arrays = false; - let mut array = p.marker(); - let mut arg = p.marker(); - // The number of math expressions per argument. - let mut count = 0; - while !p.at_set(syntax_set!(Dollar, End)) { - if namable - && (p.at(SyntaxKind::MathIdent) || p.at(SyntaxKind::Text)) - && p.text[p.current_end()..].starts_with(':') - { - p.convert_and_eat(SyntaxKind::Ident); - p.convert_and_eat(SyntaxKind::Colon); - named = Some(arg); - arg = p.marker(); - array = p.marker(); - } + let mut maybe_array_start = p.marker(); + let mut seen = HashSet::new(); + while !p.at_set(syntax_set!(End, Dollar, RightParen)) { + positional = math_arg(p, &mut seen); - match p.current_text() { - ")" => break, - ";" => { - maybe_wrap_in_math(p, arg, count, named); - p.wrap(array, SyntaxKind::Array); - p.convert_and_eat(SyntaxKind::Semicolon); - array = p.marker(); - arg = p.marker(); - count = 0; - namable = true; - named = None; - has_arrays = true; - continue; - } - "," => { - maybe_wrap_in_math(p, arg, count, named); - p.convert_and_eat(SyntaxKind::Comma); - arg = p.marker(); - count = 0; - namable = true; - if named.is_some() { - array = p.marker(); - named = None; + match p.current() { + SyntaxKind::Comma => { + p.eat(); + if !positional { + maybe_array_start = p.marker(); } - continue; } - _ => {} - } + SyntaxKind::Semicolon => { + if !positional { + maybe_array_start = p.marker(); + } - if p.at_set(set::MATH_EXPR) { - math_expr(p); - count += 1; - } else { - p.unexpected(); - } - - namable = false; - } - - if arg != p.marker() { - maybe_wrap_in_math(p, arg, count, named); - if named.is_some() { - array = p.marker(); + // Parses an array: `a, b, c;`. + // The semicolon merges preceding arguments separated by commas + // into an array argument. + p.wrap(maybe_array_start, SyntaxKind::Array); + p.eat(); + maybe_array_start = p.marker(); + has_arrays = true; + } + SyntaxKind::End | SyntaxKind::Dollar | SyntaxKind::RightParen => {} + _ => p.expected("comma or semicolon"), } } - if has_arrays && array != p.marker() { - p.wrap(array, SyntaxKind::Array); - } - - if p.at(SyntaxKind::Text) && p.current_text() == ")" { - p.convert_and_eat(SyntaxKind::RightParen); - } else { - p.expected("closing paren"); - p.balanced = false; + // Check if we need to wrap the preceding arguments in an array. + if maybe_array_start != p.marker() && has_arrays && positional { + p.wrap(maybe_array_start, SyntaxKind::Array); } + p.expect_closing_delimiter(m, SyntaxKind::RightParen); p.wrap(m, SyntaxKind::Args); } -/// Wrap math function arguments to join adjacent math content or create an -/// empty 'Math' node for when we have 0 args. +/// Parses a single argument in a math argument list. /// -/// We don't wrap when `count == 1`, since wrapping would change the type of the -/// expression from potentially non-content to content. Ex: `$ func(#12pt) $` -/// would change the type from size to content if wrapped. -fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, count: usize, named: Option) { +/// Returns whether the parsed argument was positional or not. +fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool { + let m = p.marker(); + let start = p.current_start(); + + if p.at(SyntaxKind::Dot) { + // Parses a spread argument: `..args`. + if let Some(spread) = p.lexer.maybe_math_spread_arg(start) { + p.token.node = spread; + p.eat(); + math_expr(p); + p.wrap(m, SyntaxKind::Spread); + return true; + } + } + + let mut positional = true; + if p.at_set(syntax_set!(Text, MathIdent, Underscore)) { + // Parses a named argument: `thickness: #12pt`. + if let Some(named) = p.lexer.maybe_math_named_arg(start) { + p.token.node = named; + let text = p.current_text(); + p.eat(); + p.convert_and_eat(SyntaxKind::Colon); + if !seen.insert(text) { + p[m].convert_to_error(eco_format!("duplicate argument: {text}")); + } + positional = false; + } + } + + // Parses a normal positional argument. + let arg = p.marker(); + let count = math_exprs(p, syntax_set!(End, Dollar, Comma, Semicolon, RightParen)); if count == 0 { + // Named argument requires a value. + if !positional { + p.expected("expression"); + } + // Flush trivia so that the new empty Math node will be wrapped _inside_ // any `SyntaxKind::Array` elements created in `math_args`. // (And if we don't follow by wrapping in an array, it has no effect.) @@ -553,13 +566,19 @@ fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, count: usize, named: OptionlYDU@OZcq8+xeR|OVW@dc;k#Z6&Sg_!P z!wbQ-37jv^by&cSeN6USz>8@v&%QN)TNnMK1Cap?JN!_`f&~i}oNRcbe|W?MPSuw? zE#TEdjEq^p>zOz;Q#F8@^rni+Nw8qSf_DpCsoB8Kvt2gu`6_PUPq}ou3{)ooSlDyV z4O~C@rh6J-Z4LlZxPfzJ27LbQ^;t*orH`#+I{+U-03HrHf@@=U-vRKf1VB&4q~pf@ zZcN~BSLXHAf&~i}oIto11fPK@0{9*T!KxFumFeAk^Cd7k3M^;OT+RAU;Gq(Ivj6~W zk}s~~1U}K=_!s^z@OoF=zz_Uk4}d}e;PlquxEq+L$$x9|J66Yn1q*h$KT)ZH31x;{ Q3jhEB07*qoM6N<$f;J`WvH$=8 literal 0 HcmV?d00001 diff --git a/tests/ref/math-call-spread-shorthand-clash.png b/tests/ref/math-call-spread-shorthand-clash.png new file mode 100644 index 0000000000000000000000000000000000000000..4129ef5d2200ad7666e413e0845a80cd8798cc16 GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_0VEhE<%|3RQg)s$jv*Ddl7HAcG$dYm6xi*q zE4Q@*#9-fgl>h(#s(=6Qitmd4WBcO&%U}PE&3lf0Tyb=FVdQ&MBb@06@$x0ssI2 literal 0 HcmV?d00001 diff --git a/tests/ref/math-mat-gaps.png b/tests/ref/math-mat-gaps.png index 5c954766c7bd01ca2b3a9d3a5959873a35dcbbae..405358776c3c42f9e20a7ac48ae152506cf3dfa1 100644 GIT binary patch delta 1303 zcmV+y1?c+e1Dy(x7k_OC00000PZatl000E!NklcPa0;jeqW?GhtmxUHynDfG;a*KJ19c7$ zXhcfm0G-2@a-@`7`fSH$*gRuD!vXh@QXimmctjaeE(Pfvo^S?5{?UVoxxPn{IZ@~E z{1kxSqM1;FP^uM0!a|+HJ68g@nq6Z%5V=*LhFPAVbAPzr}&7Z|EFm z#A8(9c?cY-xihgHaEwIn@Y)O@ZWGb#5h%?*5ONX7UZs1uc?NLRmSt^3#JMrl4$N-W zJE8K6);;0#OeE)z$><@TtPi`+(~u7k~2M%1?pglS_TO@ukxX;cgV5vgV_> z9p*&j+}v$3e*#P@K|UN&3$UaVd?$Ny4zTDts0OPq^(krTh@7?A4xspaw|ux}9{&i_ z$&YcsrA4TEp7{$Y->#MquS^kyX`0;w@MbHD+UJpzxIjJ}@<0%FZ0xQ9h{-@v5{#U~ zQ-7tyjiXVx7v_VP`{ONJ;{j5mQ526tPP$b(d~6DeQ)#J{cs;b)Y-Ipduc9anL5^*T zbT~c|4IKf7?Re=rv;?#m6%J2AOJJE%VQV^CMim69&OzH609cUSx z+h?PG>cYViXKY9~bSGMdzLYgz;iF?{3sCqSD)+P907uiWh9dUECKNGo z@?m02fQqyf4?fNuKLNbo3aY?50Q>nzLp_MxUZaM2?-26gwgn*eOB|q@a}vePFY*vd zJ*9>jQ;B?-tTcok%yt2){a>QGG=CM5>~=LwS1|sB*=8rnZV_b}lO zdbRG10paW4(3`u_7!YPpKH54yYBMCvzBV)+Y&3rxn_)9-hM%7B-&bRxJ`iZnT1o%_ N002ovPDHLkV1nF(Wyt^l delta 476 zcmV<20VDpM3h4uo7k@Pf00000ugP${00057NklR@q(inw=kf(WD*#3Iv zQQx8Td;bHkzI}He8()-7!y4AGhCc)DoyWAL!O+02DuUGK41WcDnGGD(p>Cws3FKZO z59jT{)9N3Txu&r%z)6lge3l77EXq8?6mIDGOCQ&qce3EnrJjNgmSBwKyHRM;KXw}{QcU28=mx;svRA8&iW%r@#wK~=V zr2EBT3Jw5#0ZiH`L>|7&CRhQ-E%Gp)c7jwkM<`$>mN9L~t)~bLYgohI8h!&^m0`w3 S3eG%zR|d%uk&H|0z=f;($0H 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 V44U_U8)N_g002ovPDHLkV1kQrigN$} literal 0 HcmV?d00001 diff --git a/tests/ref/math-mat-spread.png b/tests/ref/math-mat-spread.png new file mode 100644 index 0000000000000000000000000000000000000000..dc8b2bf7e6b1ead308d6f3d127edb61bc7cb0c16 GIT binary patch literal 1814 zcmV+x2kH2UP)(>&(DjCi~IZg#>U2deSN;ZzOS#Z{QUgj;NY5?nlm#qDk>_vy1Hg&X4~7_ z$jHcqgoJu}diM7A@$vCPLqla{Wwf-k!otFFadFht)LmU&#l^*wlauc5?&am>Yinyp zMn->sf6dL!pP!$Sl9K1==Z%exVq#+6i?Z;{-TCS8%~6c_=kGEyGW_-UZEbCkkdQ(` zLQqgp;g`CcoSdPdp;lH_-QC^T*x1+C*TXJ#KtMo;hllOI)y-0k>b1^nY;4Fuf%4Mf zg@uKtrlv(jMJy~VT3TATxVWdMr+j>TfPjGT$=l(WyZP(#%Sne*Q&UJtNYGuB+=Z_8 z;Og3cs`S|9>$=hV@b&fE=ilGoxw*OIqQS{Ugzm-J^3&q?$uS4pTGCw?8{4t;+wtSj)sc~r>bB3Rsi~KjmmeP=qN1Xno}OuG zY0%KnS65f_)#Kse;p5}uwY9aCm6hh^=IZL|etv#TOiWQxQJ9#RR8&-CWMm;BA#`+f zv$M0nz`%lnf?;7{$H&Lk*4AojY91aQHa0ddFE3tRUZ|+3s;a8SIebk`O{1fu+S=MW zIy%hE%y4jUq@<)dIXRDykA{YZot>S%y}ilF$s;2pK|w)FOH0|=+3V};DJdy+b#3vQn9hIva+&ZU|?@=Z?3Mc;^N{oG&IZ0%jxOq z?d|RG@bL2T^6&5OI5;@-^YcDFKK1qWSXfwS@00Y`dL_t(|+U?kPP?Ki>$MG+a zkU;{13MxaHDj@DHSUc@*)!M!H-h1!8_uhNjJ+)Qax++efs8E$k!2yzltp3q^?s@J= za^!MHeZqP7e*Svz_r90!-J82N?*)UwVA$)WOd^~qlXw_=s2P$_%(*sr=UvdR)CJZ9 zD?rG~%pPxt#3RtIUm`%CG!TkLQe6P1QnV|`4Gr7+GPM}^%!_93;i$0E*qQ{as6n&z zZ(TrybX|)K+nbafMv}9=`~+9Abee0TU!M1mKVrN z2I}CoXzsC)mkun-p?m9J0n8&RzHdhIFGcun4P-fiiATfv z7t8_AzDP4FD=VRN3`*G{Fz>NPq2U~)ODZd8!buijAW^*eDqz{g<}FA~K7^%yz%dj^ zM=C5QggF~f+K@Wzgb1DtEdG=zY8?Q!{Ga?Fq^w$va3VlXHvdmj8PT*&Kv=b?US4TM zgmIsMX2J15djgI(5UfAc&2IuGO-8r<8=!dv6JAG|5AdTO-73BX%Bv4ZMtc8)CirFn z`mG%Xk;dhsUtI zYs4@7>u;?cAk-~>`6WPY+QUJww-c$o5I}93!_gVxJt`3Ev@RQf&K+$*5Nt)Gkkqz8 z>#g49cW(xvzBmy;ZQ8%%0Bj9=Y^Y6l*dw0M{;;sxG>2RMf!)brVYO)vvmOG3h1I4z zyzZBuw}4>pv$7dboAxlSzDUWIWDB4+t>M37oz}${B~ zpP7g>Zm4H1Ah&qF>;)ju8}HR#A1vwYg>Xu-WZt#{XglgCQ>d1AEkC6|_)yo&W#<07*qoM6N<$ Ef(eM%`v3p{ literal 0 HcmV?d00001 diff --git a/tests/suite/math/call.typ b/tests/suite/math/call.typ index 136be8a74..5caacfac6 100644 --- a/tests/suite/math/call.typ +++ b/tests/suite/math/call.typ @@ -8,6 +8,112 @@ $ pi(a,) $ $ pi(a,b) $ $ pi(a,b,) $ +--- math-call-unclosed-func --- +#let func(x) = x +// Error: 6-7 unclosed delimiter +$func(a$ + +--- math-call-unclosed-non-func --- +// Error: 5-6 unclosed delimiter +$sin(x$ + +--- math-call-named-args --- +#let func1(my: none) = my +#let func2(_my: none) = _my +#let func3(my-body: none) = my-body +#let func4(_my-body: none) = _my-body +#let func5(m: none) = m +$ func1(my: a) $ +$ func2(_my: a) $ +$ func3(my-body: a) $ +$ func4(_my-body: a) $ +$ func5(m: a) $ +$ func5(m: sigma : f) $ +$ func5(m: sigma:pi) $ + +--- math-call-named-args-no-expr --- +#let func(m: none) = m +// Error: 10 expected expression +$ func(m: ) $ + +--- math-call-named-args-duplicate --- +#let func(my: none) = my +// Error: 15-17 duplicate argument: my +$ func(my: a, my: b) $ + +--- math-call-named-args-shorthand-clash-1 --- +#let func(m: none) = m +// Error: 18-21 unexpected argument +$func(m: =) func(m:=)$ + +--- math-call-named-args-shorthand-clash-2 --- +#let func(m: none) = m +// Error: 41-45 unexpected argument +$func(m::) func(m: :=) func(m:: =) func(m::=)$ + +--- math-call-named-single-underscore --- +#let func(x) = x +// Error: 8-9 expected identifier, found underscore +$ func(_: a) $ + +--- math-call-named-single-char-error --- +#let func(m: none) = m +// Error: 8-13 unexpected argument +$ func(m : a) $ + +--- math-call-named-args-repr --- +#let args(..body) = body +#let check(it, r) = test-repr(it.body.text, r) +#check($args(_a: a)$, "arguments(_a: [a])") +#check($args(_a-b: a)$, "arguments(_a-b: [a])") +#check($args(a-b: a)$, "arguments(a-b: [a])") +#check($args(a-b-c: a)$, "arguments(a-b-c: [a])") +#check($args(a--c: a)$, "arguments(a--c: [a])") +#check($args(a: a-b)$, "arguments(a: sequence([a], [−], [b]))") +#check($args(a-b: a-b)$, "arguments(a-b: sequence([a], [−], [b]))") +#check($args(a-b)$, "arguments(sequence([a], [−], [b]))") + +--- math-call-spread-content-error --- +#let args(..body) = body +// Error: 7-16 cannot spread content +$args(..(a + b))$ + +--- math-call-spread-multiple-exprs --- +#let args(..body) = body +// Error: 10 expected comma or semicolon +$args(..a + b)$ + +--- math-call-spread-unexpected-dots --- +#let args(..body) = body +// Error: 8-10 unexpected dots +$args(#..range(1, 5).chunks(2))$ + +--- math-call-spread-shorthand-clash --- +#let func(body) = body +$func(...)$ + +--- math-call-spread-repr --- +#let args(..body) = body +#let check(it, r) = test-repr(it.body.text, r) +#check($args(..#range(0, 4).chunks(2))$, "arguments((0, 1), (2, 3))") +#check($#args(range(1, 5).chunks(2))$, "arguments(((1, 2), (3, 4)))") +#check($#args(..range(1, 5).chunks(2))$, "arguments((1, 2), (3, 4))") +#check($args(#(..range(2, 6).chunks(2)))$, "arguments(((2, 3), (4, 5)))") +#let nums = range(0, 4).chunks(2) +#check($args(..nums)$, "arguments((0, 1), (2, 3))") +#check($args(..nums;)$, "arguments(((0, 1), (2, 3)))") +#check($args(..nums, ..nums)$, "arguments((0, 1), (2, 3), (0, 1), (2, 3))") +#check($args(..nums, 4, 5)$, "arguments((0, 1), (2, 3), [4], [5])") +#check($args(..nums, ..#range(4, 6))$, "arguments((0, 1), (2, 3), 4, 5)") +#check($args(..nums, #range(4, 6))$, "arguments((0, 1), (2, 3), (4, 5))") +#check($args(..nums, 1, 2; 3, 4)$, "arguments(((0, 1), (2, 3), [1], [2]), ([3], [4]))") +#check($args(1, 2; ..nums)$, "arguments(([1], [2]), ((0, 1), (2, 3)))") +#check($args(1, 2; 3, 4)$, "arguments(([1], [2]), ([3], [4]))") +#check($args(1, 2; 3, 4; ..#range(5, 7))$, "arguments(([1], [2]), ([3], [4]), (5, 6))") +#check($args(1, 2; 3, 4, ..#range(5, 7))$, "arguments(([1], [2]), ([3], [4], 5, 6))") +#check($args(1, 2; 3, 4, ..#range(5, 7);)$, "arguments(([1], [2]), ([3], [4], 5, 6))") +#check($args(1, 2; 3, 4, ..#range(5, 7),)$, "arguments(([1], [2]), ([3], [4], 5, 6))") + --- math-call-repr --- #let args(..body) = body #let check(it, r) = test-repr(it.body.text, r) @@ -35,6 +141,34 @@ $ mat(#"code"; "wins") $ #check($args(a,b;c)$, "arguments(([a], [b]), ([c],))") #check($args(a,b;c,d;e,f)$, "arguments(([a], [b]), ([c], [d]), ([e], [f]))") +--- math-call-2d-named-repr --- +#let args(..body) = (body.pos(), body.named()) +#let check(it, r) = test-repr(it.body.text, r) +#check($args(a: b)$, "((), (a: [b]))") +#check($args(1, 2; 3, 4)$, "((([1], [2]), ([3], [4])), (:))") +#check($args(a: b, 1, 2; 3, 4)$, "((([1], [2]), ([3], [4])), (a: [b]))") +#check($args(1, a: b, 2; 3, 4)$, "(([1], ([2],), ([3], [4])), (a: [b]))") +#check($args(1, 2, a: b; 3, 4)$, "(([1], [2], (), ([3], [4])), (a: [b]))") +#check($args(1, 2; a: b, 3, 4)$, "((([1], [2]), ([3], [4])), (a: [b]))") +#check($args(1, 2; 3, a: b, 4)$, "((([1], [2]), [3], ([4],)), (a: [b]))") +#check($args(1, 2; 3, 4, a: b)$, "((([1], [2]), [3], [4]), (a: [b]))") +#check($args(a: b, 1, 2, 3, c: d)$, "(([1], [2], [3]), (a: [b], c: [d]))") +#check($args(1, 2, 3; a: b)$, "((([1], [2], [3]),), (a: [b]))") +#check($args(a-b: a,, e:f;; d)$, "(([], (), ([],), ([d],)), (a-b: [a], e: [f]))") +#check($args(a: b, ..#range(0, 4))$, "((0, 1, 2, 3), (a: [b]))") + +--- math-call-2d-escape-repr --- +#let args(..body) = body +#let check(it, r) = test-repr(it.body.text, r) +#check($args(a\;b)$, "arguments(sequence([a], [;], [b]))") +#check($args(a\,b;c)$, "arguments((sequence([a], [,], [b]),), ([c],))") +#check($args(b\;c\,d;e)$, "arguments((sequence([b], [;], [c], [,], [d]),), ([e],))") +#check($args(a\: b)$, "arguments(sequence([a], [:], [ ], [b]))") +#check($args(a : b)$, "arguments(sequence([a], [ ], [:], [ ], [b]))") +#check($args(\..a)$, "arguments(sequence([.], [.], [a]))") +#check($args(.. a)$, "arguments(sequence([.], [.], [ ], [a]))") +#check($args(a..b)$, "arguments(sequence([a], [.], [.], [b]))") + --- math-call-2d-repr-structure --- #let args(..body) = body #let check(it, r) = test-repr(it.body.text, r) diff --git a/tests/suite/math/mat.typ b/tests/suite/math/mat.typ index 391ff1677..b7d6a6871 100644 --- a/tests/suite/math/mat.typ +++ b/tests/suite/math/mat.typ @@ -54,6 +54,30 @@ $ a + mat(delim: #none, 1, 2; 3, 4) + b $ $ mat(1, 2; 3, 4; delim: "[") $, ) +--- math-mat-spread --- +// Test argument spreading in matrix. +$ mat(..#range(1, 5).chunks(2)) + mat(#(..range(2).map(_ => range(2)))) $ + +#let nums = ((1,) * 5).intersperse(0).chunks(3) +$ mat(..nums, delim: "[") $ + +--- math-mat-spread-1d --- +$ mat(..#range(1, 5) ; 1, ..#range(2, 5)) + mat(..#range(1, 3), ..#range(3, 5) ; ..#range(1, 4), 4) $ + +--- math-mat-spread-2d --- +#let nums = range(0, 2).map(i => (i, i+1)) +$ mat(..nums, delim: "|",) + mat(..nums; delim: "|",) $ +$ mat(..nums) mat(..nums;) \ + mat(..nums;,) mat(..nums,) $ + +--- math-mat-spread-expected-array-error --- +#let nums = range(0, 2).map(i => (i, i+1)) +// Error: 15-16 expected array, found content +$ mat(..nums, 0, 1) $ + --- math-mat-gap --- #set math.mat(gap: 1em) $ mat(1, 2; 3, 4) $ @@ -61,6 +85,8 @@ $ mat(1, 2; 3, 4) $ --- math-mat-gaps --- #set math.mat(row-gap: 1em, column-gap: 2em) $ mat(1, 2; 3, 4) $ +$ mat(column-gap: #1em, 1, 2; 3, 4) + mat(row-gap: #2em, 1, 2; 3, 4) $ --- math-mat-augment --- // Test matrix line drawing (augmentation).