// Copyright 2017-2022 The MathJax Consortium // SPDX-License-Identifier: Apache-2.0 // Based on PhysicsMethods.Derivative/OperatorApplication, PhysicsItems.AutoOpen // and ParseUtil.fenced.
package tex import ( ) // commandResult carries one invocation's stack item and after-node action. // Neither lives in shared parseState or escapes a genuine child parser. // Recipients deliver nodes before activating afterNode. type commandResult struct { nodes []*mml.Node namedFunction bool notItem bool nonscriptItem bool dotsItem *pendingDots positionItem *positionItem cellItem *cellItem environmentEnd *environmentEndItem casesLeft *environmentEndItem afterNode *derivativeAutoOpen } func ( *parser) ( string) (commandResult, error) { return .commandEventWithCasesBegin(, nil) } // A Cases Begin is pushed after its left argument is read, before its body. // This invocation-local action lets the current recipient observe that push. func ( *parser) ( string, func() error) ( commandResult, error) { := .commandCasesBegin .commandCasesBegin = , := .commandNamedFunction, .commandNot := .commandNonscript , := .commandDots, .commandPosition := .commandCell := .commandEnvironmentEnd := .commandCasesLeft .commandNamedFunction, .commandNot = false, false .commandNonscript = false .commandDots, .commandPosition = nil, nil .commandCell = nil .commandEnvironmentEnd = nil .commandCasesLeft = nil defer func() { .commandCasesBegin = .commandNamedFunction, .commandNot = , .commandNonscript = .commandDots, .commandPosition = , .commandCell = .commandEnvironmentEnd = .commandCasesLeft = }() .nodes, = .commandNodes(, &.afterNode) .namedFunction, .notItem = .commandNamedFunction, .commandNot .nonscriptItem = .commandNonscript .dotsItem, .positionItem = .commandDots, .commandPosition .cellItem = .commandCell .environmentEnd = .commandEnvironmentEnd .casesLeft = .commandCasesLeft return , } // Direct command callers have no pending recipient. The row and script // consumers use commandEvent so they can deliver the initial nodes first. func ( *parser) ( string) ([]*mml.Node, error) { , := .commandEvent() if != nil { return nil, } if .casesLeft != nil { return nil, fmt.Errorf("Cases left block requires a pending node") } if .environmentEnd != nil { return nil, .environmentEnd.extra() } if .cellItem != nil { if !.cellItem.linebreak { return nil, .cellItem.misplaced() } .nodes = append(.nodes, .cellItem.spaces()...) } if .notItem { .nodes = append(.nodes, notFallback()) } if .dotsItem != nil { .nodes = append(.nodes, .dotsItem.finish()...) } , := .afterNode.complete() return append(.nodes, ...), } type derivativeAutoOpen struct { open byte closer byte application *physicsApplicationArgument quantity *physicsQuantityArgument big string bigOpen, bigClose *mml.Node fencesParsed bool ignore bool openCount int closed bool openingConsumed bool smash bool right string rightNode *mml.Node rightParsed bool trailing []finalItem } func ( *derivativeAutoOpen) () byte { if .open == 0 { return '(' } return .open } func ( *derivativeAutoOpen) () byte { if .closer == 0 { return ')' } return .closer } func ( *derivativeAutoOpen) ( *parser) bool { if == nil { return false } if .openingConsumed { return true } // TexParser.GetNext uses ECMAScript whitespace; this is not a change to // ordinary character scanning or argument readers. for .pos < len(.source) && isPrimeSpace(.peekRune()) { .consumeRune() } if .pos == len(.source) || .source[.pos] != .openingFence() { return false } .pos++ return true } // The normal matching AutoClose calls toMml before its lexical environment // is popped. Parse the right child at that boundary, before SetFont row // continuations restore their caller. Their complete content is assembled // while unwinding; no parser tokens are executed during that assembly. // A direct SpreadLines Pop calls this only after the outer env is restored. func ( *derivativeAutoOpen) ( *parser) error { if == nil { return nil } if .right != "" && !.rightParsed { , := .parseChild(.right) if != nil { return } .rightNode, .rightParsed = , true } if .big != "" && !.fencesParsed { , := .parseChild("\\" + .big + "l" + string(.openingFence())) if != nil { return } , := .parseChild("\\" + .big + "r" + string(.closingFence())) if != nil { return } .bigOpen, .bigClose, .fencesParsed = , , true } return nil } func ( *derivativeAutoOpen) ( *parser) ([]*mml.Node, error) { , := .completeAfter(, nil) if != nil { return nil, } for , := range .takeTrailing() { = append(, .node) } return , nil } func ( *derivativeAutoOpen) () []finalItem { if == nil { return nil } := .trailing .trailing = nil return } // A real AutoOpen is a non-MML successor. Notify its recipient only if it // starts, after the command's initial nodes but before parsing its body. func ( *derivativeAutoOpen) ( *parser, func()) ([]*mml.Node, error) { if != nil && .quantity != nil { return .quantity.parse() } if != nil && .application != nil { , := .application.prepare(, ) if != nil || ! { return nil, } } if !.start() { return nil, nil } if != nil { () } , , := .parseRowWithAutoOpen(0, false, false, ) if != nil { return nil, } := .environmentPopped .environmentPopped = false // Only an autoclose matching checkItem discards an ignored AutoOpen. // SpreadLines directly calls the popped item's fenced toMml(). if .ignore && ! { return nil, nil } if := .parseRight(); != nil { return nil, } if .smash { := node("mpadded", row(, true)) .Attributes.Set("height", 0) .Attributes.Set("depth", 0) = []*mml.Node{texAtom(, mml.TeXClassOrd)} } if .rightNode != nil { // BaseItem.Push receives the child's one MML result, not PushAll. = append(, .rightNode) } // AutoOpen.toMml delegates to fenced, then removes the row's open/close/ // texClass properties. Fence nodes use the node factory, not token factory. , := .bigOpen, .bigClose if .big == "" { = .autoOpenFence(string(.openingFence()), mml.TeXClassOpen) = .autoOpenFence(string(.closingFence()), mml.TeXClassClose) } := append([]*mml.Node{}, ...) = append(, ) // Removing the texClass property retains the class assigned by fenced. := forcedRow(, false) .TeXClass = mml.TeXClassInner if { if := mathtoolsSpreadPop(, nil); != nil { return nil, } } return []*mml.Node{}, nil } func autoOpenFence( string, mml.TeXClass) *mml.Node { := node("mo", mml.NewText()) .Attributes.Set("fence", true) .Attributes.Set("stretchy", true) .Attributes.Set("symmetric", true) .TeXClass = .SetProperty("texClass", ) return } func ( *derivativeAutoOpen) ( finalItem) bool { // AutoOpen.checkItem first observes the delivered MML, then tests the // same item's marker. A raw bar therefore increments before decrementing. if .node.Kind == "mo" && textContent(.node) == string(.openingFence()) { .openCount++ } return .autoclose != 0 && .autoclose == .closingFence() && .close() } func ( *derivativeAutoOpen) () bool { := .openCount .openCount-- // Preserve the source's zero-to-minus-one closing transition. if == 0 { .closed = true } return .closed } func ( *derivativeAutoOpen) () error { return texError("ExtraOrMissingDelims", "Extra open or missing close delimiter") }