typst: support '+' binary operator in arg values
Build and Deploy Verso / deploy (push) Successful in 9m27s
Build and Deploy Verso / deploy (push) Successful in 9m27s
stroke: 0.8pt + brand broke arg-list parsing because '+' was not a grammar
terminal. The parser exited CodeArgs via error recovery, so subsequent
named args (radius:, inset:, fill:) were never seen as CodeArgKey.
Add codeArgValue { codeValue | codeArgValue !add "+" codeValue } — a
left-recursive inline rule used only inside CodeArgs. The !add cut point
gives the shift strict dominance over the reduce (prec add > 0 vs 0), so
a '+' after a value greedily extends the expression. Because codeArgValue
only appears inside CodeArgs, the codeStatement* LALR-merging that caused
trouble for the earlier callSuffix* approach does not apply here.
Also add PLUS to codeIdentTokenizer's valid-predecessor list so identifiers
after '+' (the right-hand operand) are correctly tokenized as CodeIdent.
Add "+" to @tokens @precedence so it beats MarkupContent in merged states.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,7 @@ const COLON = 58 // :
|
||||
const SEMICOLON = 59 // ;
|
||||
const OPEN_ANGLE = 60 // <
|
||||
const CLOSE_ANGLE = 62 // >
|
||||
const PLUS = 43 // +
|
||||
|
||||
const KEYWORDS = new Set([
|
||||
'let', 'set', 'show', 'import', 'include',
|
||||
@@ -324,7 +325,7 @@ export const codeIdentTokenizer = new ExternalTokenizer(
|
||||
while (input.peek(back) === SPACE || input.peek(back) === TAB || input.peek(back) === NEWLINE) back--
|
||||
const prev = input.peek(back)
|
||||
|
||||
if (prev !== HASH && prev !== DOT && prev !== OPEN_PAREN && prev !== COMMA && prev !== EQUALS && prev !== COLON) {
|
||||
if (prev !== HASH && prev !== DOT && prev !== OPEN_PAREN && prev !== COMMA && prev !== EQUALS && prev !== COLON && prev !== PLUS) {
|
||||
if (!isIdentTail(prev)) {
|
||||
// prev is a structural delimiter (e.g. ')' after a function call, '{' at
|
||||
// block start, '}' after a nested block). These are valid statement-start
|
||||
|
||||
@@ -88,7 +88,11 @@ codeExprBody {
|
||||
// kw — prefer CodeKeyword !kw callOrValueAndBody over CodeKeyword keywordBody?
|
||||
// when an identifier follows the keyword. shift = kw << 2, reduce
|
||||
// (second alternative) = 0; kw > 0, no @right needed.
|
||||
@precedence { call @right, kw }
|
||||
// add — resolves the shift/reduce conflict when a '+' follows a codeArgValue:
|
||||
// SHIFT '+' (extend codeArgValue → codeArgValue !add "+" codeValue): prec add
|
||||
// REDUCE codeArgItem → codeArgValue (complete arg): prec 0
|
||||
// add > 0 → shift wins, so 0.8pt + brand stays as one arg value.
|
||||
@precedence { call @right, kw, add }
|
||||
|
||||
// KeywordExpr: used in markup-level code (#show, #let, #set …) AND nested
|
||||
// inside codeExprBody (e.g. the RHS after ':' in a show-rule).
|
||||
@@ -140,10 +144,19 @@ callSuffix {
|
||||
CodeArgs { "(" codeArgList? ")" }
|
||||
codeArgList { codeArgItem ("," codeArgItem)* ","? }
|
||||
codeArgItem {
|
||||
CodeArgKey ":" codeValue |
|
||||
codeValue
|
||||
CodeArgKey ":" codeArgValue |
|
||||
codeArgValue
|
||||
}
|
||||
|
||||
// codeArgValue extends codeValue with '+' chaining for expressions like
|
||||
// `stroke: 0.8pt + brand` or `fill: base + overlay`.
|
||||
// Left-recursive rule: LALR state for codeArgValue · seeing '+':
|
||||
// SHIFT '+' (extend, !add prec): prec add > 0
|
||||
// REDUCE codeArgItem → codeArgValue (complete): prec 0
|
||||
// add > 0 → shift wins cleanly. No @right needed (strict dominance).
|
||||
// Only used inside CodeArgs, so codeStatement* LALR-merging does not apply.
|
||||
codeArgValue { codeValue | codeArgValue !add "+" codeValue }
|
||||
|
||||
codeValue {
|
||||
CodeString |
|
||||
CodeNumber |
|
||||
@@ -303,7 +316,7 @@ Escape { "\\" EscapeChar }
|
||||
// CodeString > MarkupContent: '"' starts a string literal after a keyword.
|
||||
// ":" > MarkupContent: keywordBody ':' wins over markup colon in code states.
|
||||
// URL > MarkupContent: 'https://' / 'http://' wins over plain markup text.
|
||||
@precedence { CodeBool EscapeChar CodeString URL "[" ":" "(" "." "]" ClosingSquare "_" spaces MarkupContent }
|
||||
@precedence { CodeBool EscapeChar CodeString URL "[" ":" "(" "." "+" "]" ClosingSquare "_" spaces MarkupContent }
|
||||
}
|
||||
|
||||
@skip { spaces }
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user