// Copyright (c) 2009-2022 The MathJax Consortium
// SPDX-License-Identifier: Apache-2.0
//
// This file is a Go translation and modification of MathJax 3.2.2.
// Sources: ts/input/tex/{TexParser,Stack,StackItem,StackItemFactory,
// ParseMethods,ParseUtil,NodeFactory,NodeUtil}.ts and
// base/{BaseItems,BaseMappings,BaseMethods}.ts.

package tex

import (
	
	
	
	
	
	

	
	texcolor 
)

type macroDefinition struct {
	body            string
	arguments       int
	optionalDefault *string
	prefix          string
	delimiters      []string
	builtinNot      bool
}

type pairedDelimiter struct {
	open, close string
	body        string
	arguments   int
	// GetArgCount returns a nonempty digit string even for zero. That string
	// still enables substitution with an empty argument list.
	substituteZeroArgs bool
}

type environmentDefinition struct {
	begin, end      string
	arguments       int
	optionalDefault *string
}

type parseState struct {
	operators         []*mml.Node
	nonscriptSpaces   []*mml.Node
	poppedScripts     []*mml.Node
	macros            map[string]macroDefinition
	pairedDelimiters  map[string]pairedDelimiter
	environments      map[string]environmentDefinition
	amsTags           *amsTagState
	augmentedPackages bool
	macroCount        int
	colorModel        *texcolor.Model
}

// Stack.global belongs to one logical TexParser, not its shared configuration.
// Synthetic body and cell parsers share it; genuine child parsers start fresh.
type parserStackGlobal struct {
	eqnEnv           bool
	leftRoot, upRoot string
}

func newParseState() *parseState {
	 := &parseState{
		macros:           make(map[string]macroDefinition),
		pairedDelimiters: make(map[string]pairedDelimiter),
		environments:     make(map[string]environmentDefinition),
		colorModel:       texcolor.NewModel(),
	}
	for ,  := range simpleMacros {
		.macros[] = macroDefinition{body: , arguments: macroArguments()}
	}
	// Mathtools registers MoveEqLeft as a Macro with one optional argument.
	// Keep the inserted alignment token and hspace commands in the caller.
	 := "2"
	.macros["MoveEqLeft"] = macroDefinition{
		body: "\\hspace{#1em}&\\hspace{-#1em}", arguments: 1,
		optionalDefault: &,
	}
	return 
}

type identifierPattern uint8

const (
	identifierPatternNone identifierPattern = iota
	identifierPatternLetters
	identifierPatternOperator
)

func ( identifierPattern) ( rune) bool {
	return isASCIILetter() ||  == identifierPatternOperator && ( == '-' ||  == '*')
}

type parser struct {
	source                string
	pos                   int
	state                 *parseState
	stackGlobal           *parserStackGlobal
	display               bool
	commandNamedFunction  bool
	commandNot            bool
	commandNonscript      bool
	commandDots           *pendingDots
	commandPosition       *positionItem
	commandCell           *cellItem
	commandEnvironmentEnd *environmentEndItem
	commandCasesLeft      *environmentEndItem
	commandCasesBegin     func() error
	pendingEnvironmentEnd *environmentEndItem
	environmentOwner      *environmentFrame
	environmentRow        *environmentFrame
	environmentPopped     bool
	ordinaryArray         *ordinaryArrayItem
	pendingCell           *cellItem
	stoppedCell           *cellItem
	multiLetterFont       string
	activeFont            string
	activeColor           string
	rowDelimiter          *rowDelimiterItem
	cdCellEnd             *cdCellEnd
	cdArrayEntry          bool
	cdEntryStopped        bool
	arrayCell             *arrayCellState
	matrixClose           bool
	liveMatrix            bool
	identifierPattern     identifierPattern
	operatorLetters       bool
	noAutoOP              bool
	fontExplicitEmpty     bool
	vectorFactory         bool
	vectorFont            string
	vectorStar            bool
	vectorAlias           bool
	genfracPalette        bool
	starMacroChildren     bool
	derivativeChildren    bool
	braketOwner           *braketItem
	inRoot                bool
}

func ( *parser) () *parserStackGlobal {
	if .stackGlobal == nil {
		.stackGlobal = &parserStackGlobal{}
	}
	return .stackGlobal
}

func ( *parser) () error {
	 := .ensureStackGlobal()
	if .eqnEnv {
		return texError("ErroneousNestingEq", "Erroneous nesting of equation structures")
	}
	.eqnEnv = true
	return nil
}

// parseRow corresponds to TexParser.Parse plus the base Stack reduction.  A
// non-zero terminator is consumed.  stopRight lets a \left subparse return the
// delimiter consumed by its matching \right.
func ( *parser) ( byte,  bool) ([]*mml.Node, string, error) {
	return .parseRowWithPrefix(, , nil)
}

// A LeftItem retains its nodes when Middle closes the current segment. A later
// OverItem must see that entire prefix, not just the nodes following Middle.
func ( *parser) ( byte,  bool,  []*mml.Node) ([]*mml.Node, string, error) {
	// Ordinary groups and LeftItems mask an enclosing BraketItem. Its own
	// body enters through parseRowWithInfix, retaining the active owner.
	 := .braketOwner
	.braketOwner = nil
	, ,  := .matrixClose, .cdArrayEntry, .arrayCell
	,  := .environmentRow, .ordinaryArray
	.environmentRow, .ordinaryArray = nil, nil
	.matrixClose, .cdArrayEntry, .arrayCell = false, false, nil
	defer func() {
		.braketOwner, .matrixClose, .cdArrayEntry, .arrayCell = , , , 
		.environmentRow = 
		.ordinaryArray = 
	}()
	return .parseRowContinuation(, , false, nil, "", )
}

// parseRowWithInfix retains an OverItem across denominator and declaration
// continuations of the same logical row. Real groups and subparsers enter via
// parseRow, so they cannot inherit an outer row's pending fraction.
func ( *parser) ( byte, ,  bool) ([]*mml.Node, string, error) {
	return .parseRowWithAutoOpen(, , , nil)
}

// Only a direct AutoOpen recipient may consume its matching AutoClose item.
// Local style frames and the denominator continuation retain those barriers;
// SetFont changes the environment without adding a stack item.
func ( *parser) ( byte, ,  bool,  *derivativeAutoOpen) ([]*mml.Node, string, error) {
	if  != nil {
		, ,  := .braketOwner, .environmentRow, .ordinaryArray
		.braketOwner, .environmentRow, .ordinaryArray = nil, nil, nil
		defer func() { .braketOwner, .environmentRow, .ordinaryArray = , ,  }()
	}
	return .parseRowContinuation(, , , , "", nil)
}

type rowStyleFrame struct {
	prefix     []*mml.Node
	attributes mjSourceObject
}

// pendingFont continues a SetFont in this logical row across OverItem. Real
// groups and child parsers still enter through the ordinary row entry points.
func ( *parser) ( byte, ,  bool,  *derivativeAutoOpen,  string,  []*mml.Node) ([]*mml.Node, string, error) {
	 := .braketOwner
	defer func() { .braketOwner =  }()
	, ,  := .vectorFont, .vectorStar, .activeFont
	 := .fontExplicitEmpty
	 := .activeColor
	defer func() {
		.vectorFont, .vectorStar, .activeFont = , , 
		.fontExplicitEmpty = 
		.activeColor = 
	}()
	 := 
	var  *pendingPrime
	var  pendingNot
	var  pendingDots
	 := false
	var  []rowStyleFrame
	var  []rowPositionFrame
	// PositionItem waits for the next final MML item on this same parser.
	// A style opened after it must finish before it can supply that item.
	 := func() {
		for len() != 0 && len() != 0 {
			 := [len()-1]
			if .styleDepth != len() {
				break
			}
			 = [:len()-1]
			 = append(.item.apply([0]), [1:]...)
			 = append(.prefix, ...)
		}
	}
	// FnItem keeps its payload in nodes so Stack.Prev can remove it for a
	// script or Prime. functionOffset distinguishes that empty FnItem from
	// the already-delivered row prefix while its script is being completed.
	 := false
	 := 0
	var  error
	var  func([]finalItem)
	var  func(bool)
	 = func( []finalItem) {
		for len() != 0 {
			if  != nil {
				return
			}
			if  != nil && .closed {
				.trailing = append(.trailing, ...)
				return
			}
			 := [0]
			 = [1:]
			if  {
				 = false
				.node = .nonscriptSpace(.node)
			}
			if  != nil {
				 := .finish()
				 = nil
				 = append([]finalItem{{node: }, }, ...)
				continue
			}
			if bool() {
				 = false
				if !applyNotToken(.node) {
					 = append([]finalItem{{node: notFallback()}, }, ...)
					continue
				}
				// Not mutates and replays this same MmlItem, including autoclose.
			}
			if .active() {
				 := .apply([]*mml.Node{.node})[0]
				 = append([]finalItem{{node: }, }, ...)
				continue
			}
			if  {
				if len() ==  {
					// BaseItem stores the first final item of an empty FnItem;
					// its item properties do not survive that storage.
					 = append(, .node)
					continue
				}
				 := [:]
				 = [:]
				 = false
				 := finalItems()
				if !suppressesFunctionApplication(.node) {
					 = append(, finalItem{node: .operator("\u2061", mml.TeXClassNone, nil)})
				}
				 = append(, )
				 = append(, ...)
				continue
			}
			if len() != 0 && [len()-1].styleDepth == len() {
				 := [len()-1]
				 = [:len()-1]
				 = append(.prefix, ...)
				 = append(.item.applyFinal(), ...)
				continue
			}
			if  != nil && len() == 0 && .accept() {
				// Complete the close-time child while this owner's lexical
				// environment is still current. Remaining replacement items
				// are delivered outside, after the completed fenced result.
				 = .parseRight()
				.trailing = append(.trailing, ...)
				return
			}
			 = append(, .node)
		}
	}
	 = func( bool) {
		if ! {
			return
		}
		 := [:]
		 = [:]
		 = false
		 := finalItems()
		if  && len() != 0 {
			 = append(, finalItem{node: .operator("\u2061", mml.TeXClassNone, nil)})
		}
		()
	}
	 := func() { (finalItems(.finish())) }
	 := func() { (finalItems(.finish())) }
	 := func() {
		if  != nil {
			 := .finish()
			 = nil
			([]finalItem{{node: }})
		}
	}
	 := func() {
		 = false
		()
		()
		()
		(false)
		()
	}
	 := func( mjSourceObject) {
		// StyleItem is a non-MML successor, so an earlier FnItem finishes
		// without ApplyFunction. Prime/Not/Dots also reduce before it.
		()
		 = append(, rowStyleFrame{, })
		.braketOwner = nil // StyleItem is now the actual stack top.
		 = nil
	}
	 := func() error {
		()
		// Closing items reduce styles from the top. Each resulting final
		// mstyle is then delivered to a waiting position at that stack depth.
		for  := len() - 1;  >= 0; -- {
			if len() != 0 && [len()-1].styleDepth == len() {
				return [len()-1].item.missingBox()
			}
			 := []
			 := node("mstyle", row(, true))
			for ,  := range .attributes {
				.Attributes.Set(.Name, .Value)
			}
			 = append(.prefix, )
			 = [:]
			()
		}
		if len() != 0 {
			return [len()-1].item.missingBox()
		}
		return nil
	}
	 := func( []finalItem) {
		for ,  := range  {
			if  != "" {
				applyScopedMathVariant(.node, )
			}
			.applyVectorFactory(.node)
		}
	}
	 := func( []*mml.Node,  bool) {
		if len() == 0 {
			return
		}
		 := finalItems()
		()
		if  {
			// An FnItem is not final MML. Earlier pending items reduce
			// before it, but a lower Position waits for its final payload.
			 = false
			()
			()
			()
			(true)
			 = len()
			 = append(, ...)
			 = true
			return
		}
		()
	}
	for .pos < len(.source) || .pendingCell != nil || .pendingEnvironmentEnd != nil ||  != nil ||  != nil && .closed {
		if  != nil {
			return nil, "", 
		}
		if  != nil && .closed {
			return , "", nil
		}
		if .cdEntryStopped {
			break // A completed single Braket returns the same closing Entry.
		}
		if  := .pendingEnvironmentEnd;  != nil {
			if .spread {
				// SpreadLines calls Pop().toMml() on the actual top. It does
				// not push an EndItem through Style/Over/pending recipients.
				.pendingEnvironmentEnd = nil
				switch {
				case  != nil:
					 := []*mml.Node{.base, .prime}
					if  := mathtoolsSpreadPending();  != nil {
						return nil, "", 
					}
					 = nil
					(, false)
				case bool():
					 = false
				case .active():
					 = pendingDots{}
				case :
					 = false
				case :
					if  := mathtoolsSpreadPending([len()-1:]);  != nil {
						return nil, "", 
					}
					 = false
					()
				case len() != 0 && [len()-1].styleDepth == len():
					if  := mathtoolsSpreadPending();  != nil {
						return nil, "", 
					}
					 := [len()-1]
					 = [:len()-1]
					 = append(.prefix, ...)
					()
				case len() != 0:
					if  := mathtoolsSpreadPending();  != nil {
						return nil, "", 
					}
					 := [len()-1]
					 = [:len()-1]
					 = append(.prefix, ...)
					()
				case  ||  != 0 && !(.matrixClose && .ordinaryArray != nil && .ordinaryArray.begin == nil) ||  ||  != nil ||  != nil:
					// Braket and AutoOpen override toMml with an explicit
					// fenced row. Their caller checks that completed result;
					// an Over above either still owns these raw nodes.
					if  ||  == nil &&  == nil {
						if  := mathtoolsSpreadPending();  != nil {
							return nil, "", 
						}
					}
					.environmentPopped = true
					return , "", nil
				case .ordinaryArray != nil:
					if  := mathtoolsSpreadPending();  != nil {
						return nil, "", 
					}
					.ordinaryArray.popped = true
					return , "", nil
				case .environmentRow != nil:
					if  := .environmentRow.casesBegin;  != nil {
						 := row(, true)
						if  := mathtoolsSpreadPop(, nil);  != nil {
							return nil, "", 
						}
						.entries--
						if .entries == 0 {
							.environmentRow.closed = true
							return []*mml.Node{}, "", nil
						}
						// Pop does not clear the shared CasesBegin nodes.
						// Push(toMml()) appends into its remaining alias.
						([]*mml.Node{}, false)
						continue
					}
					if .environmentRow.ordinaryArray {
						if  := mathtoolsSpreadPending();  != nil {
							return nil, "", 
						}
					} else if .environmentRow.name != .name {
						// A legacy captured nested Begin still owns its stop.
						return nil, "", .environmentRow.missing()
					}
					.environmentRow.closed = true
					return , "", nil
				default:
					return nil, "", .extra()
				}
				continue
			}
			if  := ();  != nil {
				return nil, "", 
			}
			if  ||  != nil && .single && len() != 0 {
				// Reduce the final MML, then replay this same EndItem to the
				// enclosing stack recipient without charging it a second time.
				return , "", nil
			}
			if .matrixClose &&  != 0 {
				return nil, "", texError("MissingCloseBrace", "Missing close brace")
			}
			if  != 0 ||  ||  != nil ||  != nil {
				return nil, "", .extra()
			}
			if .ordinaryArray != nil {
				// Array finishes its table before this same EndItem reaches
				// Begin. Over continuations returned above without consuming it.
				.ordinaryArray.end = 
				.pendingEnvironmentEnd = nil
				return , "", nil
			}
			if  := .closeEnvironment(.environmentRow, );  != nil {
				return nil, "", 
			}
			return , "", nil
		}
		if  := .pendingCell;  != nil {
			.saveEnvironment()
			if  := ();  != nil {
				return nil, "", 
			}
			if  ||  != nil && .single && len() != 0 {
				// The final fraction/Braket must be pushed before replaying
				// this same closing item to its enclosing recipient.
				return , "", nil
			}
			.pendingCell = nil
			if .cdArrayEntry && ! &&  == nil &&  == nil {
				.stoppedCell, .cdEntryStopped = , true
				return , "", nil
			}
			if !.linebreak {
				return nil, "", .misplaced()
			}
			// The open owner swallowed the CellItem. Its lexical env is
			// retained, including declarations in a denominator continuation.
			.activeFont, .fontExplicitEmpty, .activeColor = .font, .emptyFont, .color
			 = .font
			(.spaces(), false)
			continue
		}
		// A single BraketItem closes on its first MML delivery. Any remaining
		// nodes in that delivery belong to the caller, not to its fenced body.
		if  != nil && .single && len() > 0 && len() == 0 && len() == 0 && ! && ! {
			if  := ();  != nil {
				return nil, "", 
			}
			return , "", nil
		}
		.braketOwner = 
		if len() != 0 ||  != nil || bool() || .active() ||  ||  ||  != nil || len() != 0 {
			.braketOwner = nil // An intervening stack item owns this token.
		}
		 := .source[.pos]
		if  != 0 &&  ==  {
			if  := ();  != nil {
				return nil, "", 
			}
			.pos++
			return , "", nil
		}
		if  == '}' {
			if len() != 0 {
				if  := ();  != nil {
					return nil, "", 
				}
			}
			return nil, "", texError("ExtraCloseMissingOpen", "Extra close brace or missing open brace")
		}
		if  == '%' {
			.skipComment()
			continue
		}
		if isMathTokenSpace(.peekRune()) {
			.consumeRune()
			continue
		}

		if  == '\\' {
			.pos++
			 := .readControlSequence()
			// Dynamic command maps precede the builtin handlers represented
			// by these row shortcuts. Paired delimiters have their own map,
			// so a registration need not also appear in the macro map.
			,  := .state.macros[]
			,  := .state.pairedDelimiters[]
			 :=  || 
			if ( == "shoveleft" ||  == "shoveright") && ! {
				if .arrayCell == nil || !.arrayCell.multline ||  != nil ||  != nil ||  ||
					len() != 0 || len() != 0 ||  != nil || bool() || .active() ||  ||  {
					return nil, "", texError("CommandOnlyAllowedInEnv", "\\%s only allowed in %s environment", , "multline")
				}
				if .arrayCell.offset+len() != 0 {
					return nil, "", texError("CommandAtTheBeginingOfLine", "\\%s must come at the beginning of the line", )
				}
				// D2 loads AMS after Mathtools, so its HandleShove wins.
				// It changes the row property without consuming any argument;
				// a following group or bracket remains on this same parser.
				.arrayCell.shove = strings.TrimPrefix(, "shove")
				continue
			}
			if isMathtoolsArrayCommand() && ! {
				 := .arrayCell != nil &&  == nil &&  == nil && ! &&
					len() == 0 && len() == 0 &&  == nil &&
					!bool() && !.active() && ! && !
				,  := false, 
				if  == "shortvdotswithin" {
					 = .readMathtoolsStar()
					 = "MTFlushSpaceAbove"
				}
				if ! || ( != "vdotswithin" && .arrayCell.equation == nil) {
					return nil, "", notInAlignment()
				}
				 := .arrayCell.equation
				switch  {
				case "ArrowBetweenLines":
					if .arrayCell.offset+len() != 0 || len(.entries) != 0 {
						return nil, "", texError("BetweenLines", "%s must be on a row by itself", "\\"+)
					}
					if  := .arrowBetweenLines();  != nil {
						return nil, "", 
					}
				case "Aboxed":
					if  := .equationAboxed();  != nil {
						return nil, "", 
					}
				case "MTFlushSpaceAbove":
					.table.flushAbove = .table.rows
					.table.addSpacing("-" + .mathtoolsOption("shortvdotsadjustabove"))
				case "MTFlushSpaceBelow":
					if len() != 0 {
						.endEntry()
						 = nil
					}
					if  := .endRow();  != nil {
						return nil, "", 
					}
					.table.addSpacing("-" + .mathtoolsOption("shortvdotsadjustbelow"))
				case "vdotswithin", "shortvdotswithin":
					if  == "shortvdotswithin" {
						.table.flushAbove = .table.rows
						.table.addSpacing("-" + .mathtoolsOption("shortvdotsadjustabove"))
						if ! {
							.endEntry()
							 = nil
						}
					}
					 :=  != nil && .table.flushAbove == .table.rows
					,  := .equationVDots("vdotswithin", )
					if  != nil {
						return nil, "", 
					}
					([]*mml.Node{}, false)
					if  == "shortvdotswithin" {
						if len() != 0 {
							.endEntry()
							 = nil
						}
						if  := .endRow();  != nil {
							return nil, "", 
						}
						.table.addSpacing("-" + .mathtoolsOption("shortvdotsadjustbelow"))
					}
				}
				continue
			}
			if isHLine() && ! {
				if .arrayCell == nil || .arrayCell.rules == nil ||  != nil ||  != nil ||  ||
					len() != 0 || len() != 0 ||  != nil ||
					bool() || .active() ||  ||  ||
					.arrayCell.offset+len() != 0 {
					return nil, "", misplacedHLine()
				}
				.arrayCell.rules.add()
				continue
			}
			if isHFill() && ! {
				// HFill requires the actual top item to be ArrayItem. Pending
				// items and real child scopes reject it before reduction.
				if .arrayCell == nil ||  != nil ||  != nil ||  ||
					len() != 0 || len() != 0 ||  != nil ||
					bool() || .active() ||  ||  {
					return nil, "", unsupportedHFill()
				}
				.arrayCell.fills = append(.arrayCell.fills, .arrayCell.offset+len())
				continue
			}
			if ! && ( == "right" ||  == "middle") {
				,  := .readDelimiter(, false)
				if  != nil {
					return nil, "", 
				}
				// LeftRight records the lexical color before the closing item
				// reduces Style/Over/Position and restores the LeftItem env.
				.rowDelimiter = &rowDelimiterItem{middle:  == "middle", delimiter: , color: .activeColor}
				if  := ();  != nil {
					return nil, "", 
				}
				if  != nil || ! {
					if  == nil && .matrixClose {
						return nil, "", texError("MissingCloseBrace", "Missing close brace")
					}
					if  == "middle" {
						return nil, "", texError("ExtraMiddle", "Extra \\middle")
					}
					return nil, "", texError("MissingLeftExtraRight", "Missing \\left or extra \\right")
				}
				return , , nil
			}
			if ! && ( == "over" ||  == "atop" ||  == "above" ||  == "choose" ||  == "brace" ||  == "brack" ||  == "overwithdelims" ||  == "atopwithdelims" ||  == "abovewithdelims") {
				// BaseMethods.Over reads arguments before pushing the closing
				// item that reduces styles and checks an earlier OverItem.
				,  := .readInfixSpec()
				if  != nil {
					return nil, "", 
				}
				if  := ();  != nil {
					return nil, "", 
				}
				.braketOwner = nil // OverItem owns the denominator.
				, ,  := .finishInfixFraction(, , , , , , )
				if  != nil {
					return nil, "", 
				}
				if  != nil {
					if .pendingCell == nil && .pendingEnvironmentEnd == nil {
						return nil, "", .stopError()
					}
				}
				if .environmentPopped {
					.environmentPopped = false
					 = unwrapInferred()
					continue
				}
				if .pendingCell != nil || .pendingEnvironmentEnd != nil {
					 = []*mml.Node{}
					continue
				}
				return []*mml.Node{}, , nil
			}
			if ! &&  == "color" {
				,  := .colorDeclaration()
				if  != nil {
					return nil, "", 
				}
				()
				continue
			}
			if ,  := styleDeclarations[];  && ! {
				// BaseMethods.SetStyle writes displaystyle before scriptlevel.
				(mjSourceObject{
					{Name: "displaystyle", Value: ["displaystyle"]},
					{Name: "scriptlevel", Value: ["scriptlevel"]},
				})
				continue
			}
			if ,  := sizeDeclarations[];  && ! {
				(mjSourceObject{{Name: "mathsize", Value: emLength()}})
				continue
			}
			if ,  := fontDeclarations[];  && ! {
				// SetFont changes the current environment without pushing a
				// stack item, so pending Prime/Not/Dots items stay in this row.
				if (.arrayCell != nil && .arrayCell.equation != nil) || len() != 0 ||  != nil || bool() || .active() ||  ||  || len() != 0 {
					.activeFont,  = , 
					.fontExplicitEmpty =  == ""
					continue
				}
				 := .activeFont
				 := .fontExplicitEmpty
				.activeFont = 
				.fontExplicitEmpty =  == ""
				if .arrayCell != nil {
					.arrayCell.offset += len()
				}
				, ,  := .parseRowWithAutoOpen(, , , )
				if .arrayCell != nil {
					.arrayCell.offset -= len()
				}
				.activeFont = 
				.fontExplicitEmpty = 
				if  != nil {
					return nil, "", 
				}
				applyScopedMathVariant(row(, true), )
				// This continuation used the same AutoOpen recipient. Its
				// final items have already been observed; only assemble them.
				 = append(, ...)
				return , , nil
			}
			if ! && ( == "limits" ||  == "nolimits") {
				if  != nil || bool() || .active() ||  {
					return nil, "", texError("MisplacedLimits", "%s is allowed only on operators", "\\"+)
				}
				var  error
				,  = .parseLimits(, )
				if  != nil {
					return nil, "", 
				}
				continue
			}
			,  := .commandEventWithCasesBegin(, func() error {
				// Begin is open, so Fn and Position wait. Prime always reduces;
				// Not/Dots retain only Open/Left, and Nonscript is removed.
				 = false
				()
				()
				()
				return 
			})
			if  != nil {
				return nil, "", 
			}
			if .casesLeft != nil {
				// Cases discarded its last alias without delivering MML.
				// Last belongs to the newly exposed top, not its lost nodes.
				var  *mml.Node
				 := "undefined"
				switch {
				case  != nil:
					 = .base
				case bool(), .active(), :
					// These pending recipients have no raw stored MML.
				case :
					if len() >  {
						 = []
					}
				default:
					if len() != 0 {
						 = [0]
					}
				}
				if  := .finishCasesLeft(, );  != nil {
					return nil, "", 
				}
				.pendingEnvironmentEnd = .casesLeft
				continue
			}
			if .environmentEnd != nil {
				.pendingEnvironmentEnd = .environmentEnd
				continue
			}
			if .cellItem != nil {
				.pendingCell = .cellItem
				continue
			}
			if .notItem {
				 = false
				()
				()
				// An incoming NotItem finalizes a prior FnItem without U2061.
				(false)
				()
				 = true
			}
			if .dotsItem != nil {
				 = false
				()
				()
				()
				// DotsItem is not MML: it settles a prior FnItem without
				// ApplyFunction and cannot be negated as an eventual token.
				(false)
				 = *.dotsItem
			}
			if .positionItem != nil {
				()
				 = append(, rowPositionFrame{item: .positionItem, prefix: , styleDepth: len()})
				 = nil
			}
			if .nonscriptItem {
				()
				 = true
			}
			(.nodes, .namedFunction)
			,  := .afterNode.completeAfter(, func() {
				// An actual AutoOpen is a non-MML open successor. It settles
				// Prime/Not/Dots and consumes Nonscript, but a waiting Fn or
				// Position remains until a final MML item is delivered.
				 :=  != nil || bool() || .active()
				 = false
				()
				()
				()
				if  {
					()
				}
			})
			if  != nil {
				return nil, "", 
			}
			(, false)
			(.afterNode.takeTrailing())
			continue
		}

		switch  {
		case '{':
			 = false // OpenItem consumes the pending NonscriptItem.
			()
			.pos++
			, ,  := .parseRow('}', false)
			if  != nil {
				return nil, "", 
			}
			if .environmentPopped {
				.environmentPopped = false
				(, false)
				continue
			}
			([]*mml.Node{texAtom(row(, true), mml.TeXClassOrd)}, false)
		case '^', '_':
			.pos++
			var  error
			var  *derivativeAutoOpen
			var  []*mml.Node
			if bool() || .active() ||  {
				// Stack.Prev sees an empty NotItem/DotsItem, not the preceding
				// row node or the fallback emitted when this item is pushed.
				()
				()
				 = false
				var  []*mml.Node
				, , , ,  = .attachScriptWithFont(nil, , )
				 = append(, ...)
			} else if  == nil {
				, , , ,  = .attachScriptWithFont(, , )
			} else {
				var  *mml.Node
				.scriptInitialLookahead()
				,  := .base.Property("movesupsub")
				var  *scriptAttachment
				,  = prepareScriptAttachment(.base, , limitsTruthy())
				if  == nil {
					, , , ,  = .parseScriptArgument(, )
				}
				if  == nil {
					var  *mml.Node
					if .environmentPopped {
						.environmentPopped = false
						 = 
					} else {
						,  = .attach(, )
					}
					if  == nil {
						 = append(, )
						 = nil
					}
				}
			}
			if  != nil {
				return nil, "", 
			}
			if ! {
				()
			}
			// Complete the script (and its pending position) before delivering
			// subsequent final items, then start any following AutoOpen item.
			(, false)
			,  := .completeAfter(, nil)
			if  != nil {
				return nil, "", 
			}
			(, false)
			(.takeTrailing())
		case '\'', 0xE2: // Only ASCII apostrophe and U+2019 enter Prime.
			if isPrimeRune(.peekRune()) {
				.consumeRune()
				var  error
				()
				var  *mml.Node
				if bool() || .active() ||  {
					()
					()
					 = false
					 = node("mi")
				} else if len() == 0 {
					 = node("mi")
				} else {
					 = [len()-1]
					 = [:len()-1]
				}
				,  = .startPrime()
				if  != nil {
					return nil, "", 
				}
			} else {
				,  := .parseCharacterChecked()
				if  != nil {
					return nil, "", 
				}
				([]*mml.Node{}, false)
			}
		case '&':
			.pos++
			// Cases.Entry tests its actual ArrayItem before reducing pending
			// items. Base cases performs a different, post-Push test.
			 := .arrayCell != nil && .arrayCell.numCases &&
				 == nil &&  == nil && ! &&
				len() == 0 && len() == 0 &&  == nil &&
				!bool() && !.active() && ! && !
			.pendingCell = &cellItem{name: "&", numCasesText: }
		case '#':
			return nil, "", texError("CantUseHash1", "You can't use 'macro parameter character #' in math mode")
		default:
			,  := .parseCharacterChecked()
			if  != nil {
				return nil, "", 
			}
			 := finalItem{node: }
			// Only Physics' registered raw-character handlers create a
			// marked MmlItem. Command-produced identical mo stays unmarked.
			if  == ')' ||  == ']' ||  == '|' {
				.autoclose = 
			}
			([]finalItem{})
			([]finalItem{})
		}
	}
	// AmsCdMethods.cell reads the actual stack top. ArrayItem supplies
	// the grid position; pending items have no table or row properties and
	// therefore receive a strut even in later cells and arrow rows.
	if  := .cdCellEnd;  != nil && !.cdEntryStopped &&  == 0 && ! {
		.cdCellEnd = nil
		 := len() != 0 || len() != 0 ||  ||
			 != nil || bool() || .active() ||  ||  ||
			( != nil && .single && len() == 0)
		if .firstObjectCell ||  {
			([]*mml.Node{cdStrut()}, false)
		}
	}

	if  := ();  != nil {
		return nil, "", 
	}
	if .environmentRow != nil && .environmentRow.stream && !.environmentRow.closed {
		return nil, "", .environmentRow.missing()
	}
	if  != nil {
		return nil, "", .stopError()
	}
	if .matrixClose && .ordinaryArray != nil && .ordinaryArray.begin == nil &&  != 0 {
		return nil, "", texError("MissingCloseBrace", "Missing close brace")
	}
	if  != 0 {
		return nil, "", texError("ExtraOpenMissingClose", "Extra open brace or missing close brace")
	}
	if  {
		return nil, "", texError("ExtraLeftMissingRight", "Extra \\left or missing \\right")
	}
	return , "", nil
}

// The pinned TeX digits option matches the original source, including braces
// in thousands separators; only the token text removes those braces.
var ordinaryNumberPattern = regexp.MustCompile(`^(?:[0-9]+(?:\{,\}[0-9]{3})*(?:\.[0-9]*)?|\.[0-9]+)`)

// ParseMethods.variable indexes the selected regular expression's match.
// A literal operator letter cannot match MathFont's letters-only pattern;
// that source failure is an ordinary error, not a rendered TeX error.
func ( *parser) () (*mml.Node, error) {
	 := .peekRune()
	if .operatorLetters && ( == '-' ||  == '*') &&
		.identifierPattern == identifierPatternLetters && !.fontExplicitEmpty {
		.consumeRune()
		return nil, fmt.Errorf("identifier pattern does not match %q", string())
	}
	 := .parseCharacter()
	if  == nil {
		// Other dereferences its Unicode range in the original. Preserve
		// that conversion failure without a panic or forbidden XML text.
		return nil, fmt.Errorf("no Unicode range for character U+%04X", )
	}
	return , nil
}

func ( *parser) () *mml.Node {
	 := .consumeRune()
	// BaseMethods.Tilde uses the token factory without getFontDef or Other.
	if  == '~' {
		return token("mtext", "\u00a0")
	}
	if isASCIILetter() || .operatorLetters && ( == '-' ||  == '*') {
		 := string()
		 := .identifierPattern != identifierPatternNone && !.fontExplicitEmpty && .identifierPattern.matches()
		if  {
			 := .pos - utf8.RuneLen()
			for .pos < len(.source) && .identifierPattern.matches(.peekRune()) {
				.consumeRune()
			}
			 = .source[:.pos]
		}
		 := token("mi", )
		if  && .noAutoOP && .activeFont == "normal" && len() > 1 {
			// noAutoOP is copied independently of the selected pattern and font.
			// OperatorName changes both without installing MathFont's flag.
			.SetProperty("autoOP", false)
		}
		return ambientLiteralToken(, )
	}
	if  >= '0' &&  <= '9' ||  == '.' ||  == ',' {
		 := .pos - 1
		if  := ordinaryNumberPattern.FindString(.source[:]);  != "" {
			.pos =  + len()
			 := strings.NewReplacer("{", "", "}", "").Replace()
			return ambientFontToken(token("mn", ))
		}
		// ParseMethods.digit uses getFontDef even when matching fails. Unlike
		// BaseConfiguration.Other, it does not register fixStretchy.
		return ambientFontToken(.token("mo", string()))
	}
	if unicode.IsDigit() {
		// Non-ASCII Nd characters enter Other individually. Reuse its pinned
		// range Kind and the existing range-variant/font policy, not a run of
		// Unicode digits. Other token classes remain outside this branch.
		for ,  := range mjOperatorRanges {
			if int() < .First {
				break
			}
			if int() <= .Last {
				 := .token(.Kind, string())
				if .activeFont != "" {
					.Attributes.Set("mathvariant", .activeFont)
				}
				// Other's range override follows the creating token factory.
				// Its existing done marker prevents a second vector application.
				.applyVectorFactory()
				return ambientLiteralToken(, )
			}
		}
		// A missing pinned range throws in primary Other, without an SVG.
		// Preserve the accepted Go fallback instead of inventing that error.
		 := .pos - utf8.RuneLen()
		for .pos < len(.source) {
			 := .peekRune()
			if !unicode.IsDigit() &&  != '.' &&  != ',' {
				break
			}
			.consumeRune()
		}
		return ambientLiteralToken(token("mn", .source[:.pos]), )
	}
	// The selected package closure installs two character-map overrides ahead
	// of BaseConfiguration.Other. Mathtools consults the current centered-colon
	// option at character dispatch, and Physics' AutoClose
	// creates a non-stretchy closer.  Neither node is placed on Base's
	// fixStretchy list, so retaining the marker is observable in copied trees.
	if  == ':' {
		return .mathtoolsCenterColon(true, false, false)
	}
	if  == ')' ||  == ']' ||  == '|' {
		return setAttributes(.token("mo", string()), map[string]any{"stretchy": false})
	}
	 := string()
	if  == '-' {
		 = "−"
	} else if  == '*' {
		 = "∗"
	} else if  == '`' {
		 = "‘"
	}
	// BaseConfiguration.Other selects the token kind from the same pinned
	// range that supplies its variant. Earlier explicit character maps and
	// letter/digit scanners keep their distinct construction paths.
	 := ""
	for ,  := range mjOperatorRanges {
		if int() < .First {
			break
		}
		if int() <= .Last {
			 = .Kind
			break
		}
	}
	if  == "" {
		return nil // The checked token boundary reports the source failure.
	}
	 := .token(, )
	if .activeFont != "" {
		.Attributes.Set("mathvariant", .activeFont)
	}
	// Other's creation font applies to every kind, including mtext; its
	// range override follows the creating token factory.
	.applyVectorFactory()
	 = ambientLiteralToken(, )
	if  == "mo" {
		// Other records only operators for the fixStretchy postfilter.
		// addNode() leaves in-lists after the temporary property is removed.
		.SetProperty("fixStretchy", true)
		.SetProperty("in-lists", "fixStretchy")
	}
	return 
}

func ( *parser) ( []*mml.Node,  byte) ([]*mml.Node, error) {
	, , , ,  := .attachScriptWithFont(, , "")
	if  != nil {
		return nil, 
	}
	 = append(, ...)
	,  := .complete()
	return append(, ...), 
}

func ( *parser) ( []*mml.Node,  byte,  string) ([]*mml.Node, string, []*mml.Node, *derivativeAutoOpen, error) {
	var  *mml.Node
	if len() == 0 {
		 = token("mi", "")
	} else {
		 = [len()-1]
		 = [:len()-1]
	}
	,  := .Property("movesupsub")
	,  := .(bool)
	if ,  := .Attributes.Get("movesupsub");  {
		, _ = .(bool)
	}
	.scriptInitialLookahead()
	,  := prepareScriptAttachment(, , )
	if  != nil {
		return nil, , nil, nil, 
	}
	, , , ,  := .parseScriptArgument(, )
	if  != nil {
		return nil, , nil, nil, 
	}
	if .environmentPopped {
		.environmentPopped = false
		return append(, ), , , , nil
	}
	 := .fill()
	.Flags.Embellished = .Flags.Embellished
	.Flags.CoreIndex = 0
	.SetProperty(limitsScriptOrigin, true)
	if  {
		.SetProperty("movesupsub", )
	}
	return append(, ), , , , nil
}

func ( *parser) () ([]*mml.Node, error) {
	,  := .parseOneTokenEvent()
	if  != nil {
		return nil, 
	}
	if .environmentEnd != nil {
		return nil, .environmentEnd.extra()
	}
	if .notItem {
		.nodes = append(.nodes, notFallback())
	}
	if .dotsItem != nil {
		.nodes = append(.nodes, .dotsItem.finish()...)
	}
	,  := .afterNode.complete()
	return append(.nodes, ...), 
}

func ( *parser) () (commandResult, error) {
	return .parseOneTokenEventWithCasesBegin(nil)
}

func ( *parser) ( func() error) ( commandResult,  error) {
	defer func() {
		if  == nil {
			for ,  := range .nodes {
				.applyVectorFactory()
			}
		}
	}()
	.skipMathTokenSpaces()
	if .pos >= len(.source) {
		return , texError("MissingArgFor", "Missing argument")
	}
	if .source[.pos] == '\\' {
		.pos++
		,  = .commandEventWithCasesBegin(.readControlSequence(), )
		return , 
	}
	if .source[.pos] == '{' {
		.pos++
		.nodes, _,  = .parseRow('}', false)
		return , 
	}
	,  := .parseCharacterChecked()
	if  != nil {
		return , 
	}
	.nodes = []*mml.Node{}
	return , nil
}

func ( *parser) ( string) (*mml.Node, error) {
	, ,  := .readArgument(, false)
	if  != nil {
		return nil, 
	}
	return .parseArgumentString()
}

func ( *parser) ( string) (*mml.Node, error) {
	// TexParser.ParseArg always creates a genuine child parser, including
	// ordinary arguments reached outside a generated Physics expression.
	 := .state.macroCount
	.state.macroCount = 0
	defer func() { .state.macroCount =  }()
	return .parseString()
}

func ( *parser) ( string) (*mml.Node, error) {
	return .parseStringWithStack(, nil)
}

func ( *parser) ( string) (*mml.Node, error) {
	return .parseStringWithEnvironment(, .ensureStackGlobal(), nil, nil, .environmentOwner)
}

func ( *parser) ( string,  *parserStackGlobal) (*mml.Node, error) {
	return .parseStringWithStackCDCell(, , nil)
}

func ( *parser) ( string,  *parserStackGlobal,  *cdCellEnd) (*mml.Node, error) {
	return .parseStringWithStackArray(, , , nil)
}

func ( *parser) ( string,  *parserStackGlobal,  *cdCellEnd,  *arrayCellState) (*mml.Node, error) {
	return .parseStringWithEnvironment(, , , , nil)
}

// The caller chooses physical-input ownership explicitly. Shared configuration
// or a nonnil Stack.global does not make a genuine child a continuation.
func ( *parser) ( string,  *parserStackGlobal,  *cdCellEnd,  *arrayCellState,  *environmentFrame) (*mml.Node, error) {
	 := &parser{environmentOwner: , source: , state: .state, stackGlobal: , display: .display, inRoot: .inRoot, cdCellEnd: , arrayCell: ,
		activeFont: .activeFont, activeColor: .activeColor, vectorFactory: .vectorFactory,
		operatorLetters: .operatorLetters, noAutoOP: .noAutoOP, fontExplicitEmpty: .fontExplicitEmpty,
		vectorFont: .vectorFont, vectorStar: .vectorStar, vectorAlias: .vectorAlias,
		genfracPalette: .genfracPalette, starMacroChildren: .starMacroChildren,
		derivativeChildren: .derivativeChildren}
	if .vectorFactory || .vectorAlias || .derivativeChildren {
		.multiLetterFont = .multiLetterFont
		.identifierPattern = .identifierPattern
	}
	var  []*mml.Node
	var  error
	if  == nil {
		, _,  = .parseRow(0, false)
	} else {
		, _,  = .parseRowWithInfix(0, false, false)
	}
	if  != nil {
		return nil, 
	}
	return .finish(row(, true), len()), nil
}

// parseMathFontString ports BaseMethods.MathFont's nested TexParser
// environment.  Its multiLetterIdentifiers expression consumes an ASCII
// letter run into one mi and noAutoOP prevents a multi-letter roman identifier
// from being reclassified as a named operator.
func ( *parser) (,  string,  bool) (*mml.Node, error) {
	// MathFont always creates a genuine child TexParser with its own count.
	 := .state.macroCount
	.state.macroCount = 0
	defer func() { .state.macroCount =  }()
	 := &parser{
		source:             ,
		state:              .state,
		display:            .display,
		inRoot:             .inRoot,
		multiLetterFont:    ,
		activeFont:         ,
		activeColor:        .activeColor,
		identifierPattern:  identifierPatternLetters,
		operatorLetters:    .operatorLetters,
		noAutoOP:           true,
		fontExplicitEmpty:   == "",
		vectorFactory:      .vectorFactory,
		vectorFont:         .vectorFont,
		vectorStar:         .vectorStar,
		vectorAlias:        .vectorAlias,
		genfracPalette:     .genfracPalette,
		derivativeChildren: .derivativeChildren,
	}
	, ,  := .parseRow(0, false)
	if  != nil {
		return nil, 
	}
	 := row(, true)
	// ParseUtil.getFontDef applies the selected font to variables, digits,
	// mapped math characters, and operators created by the nested parser.
	applyFontScope(, , )
	return , nil
}

// suppressesFunctionApplication is BaseItems.FnItem's look-ahead decision.
// Explicit TeX spacers and BIN/REL/CLOSE/PUNCT operators terminate the FnItem
// without U+2061; every other following MML item receives ApplyFunction.
func suppressesFunctionApplication( *mml.Node) bool {
	if  == nil {
		return false
	}
	if .Kind == "mspace" {
		return true
	}
	if .Kind == "mstyle" && len(.Children) != 0 {
		 := .Children[0]
		if  != nil && len(.Children) != 0 && .Children[0] != nil && .Children[0].Kind == "mspace" {
			return true
		}
	}
	 := 
	for  != nil && .Kind != "mo" && .Flags.Embellished {
		 := .Flags.CoreIndex
		if  < 0 ||  >= len(.Children) {
			break
		}
		 := .Children[]
		if  ==  {
			break
		}
		 = 
	}
	if  == nil || .Kind != "mo" {
		return false
	}
	 := .TeXClass
	return  == mml.TeXClassBin ||  == mml.TeXClassRel ||  == mml.TeXClassClose ||  == mml.TeXClassPunct
}

func ( *parser) ( string,  bool) (string, bool, error) {
	// GetArgument calls GetNext, whose JavaScript whitespace includes BOM
	// and excludes NEL. Keep this boundary distinct from math tokenization.
	for .pos < len(.source) && internalTextSpace(.peekRune()) {
		.consumeRune()
	}
	return .readArgumentAtCursor(, )
}

func ( *parser) ( string,  bool) (string, bool, error) {
	if .pos >= len(.source) {
		if  {
			return "", false, nil
		}
		return "", false, texError("MissingArgFor", "Missing argument for %s", "\\"+)
	}
	if .source[.pos] == '}' {
		if  {
			return "", false, nil
		}
		return "", false, texError("ExtraCloseMissingOpen", "Extra close brace or missing open brace")
	}
	if .source[.pos] == '{' {
		 := .pos + 1
		,  := .scanBalanced(.pos, '{', '}')
		if  != nil {
			return "", false, 
		}
		.pos =  + 1
		return .source[:], true, nil
	}
	if .source[.pos] == '\\' {
		.pos++
		// GetArgument returns the control-sequence value, not the consumed
		// source bytes. GetCS discards its delimiter space and returns a
		// control space for a backslash at EOF.
		return "\\" + .readControlSequence(), false, nil
	}
	 := .pos
	.consumeRune()
	return .source[:.pos], false, nil
}

func ( *parser) ( *string) (string, bool, error) {
	// GetBrackets calls GetNext, whose nextIsSpace uses JavaScript's \s.
	// It includes BOM and excludes NEL, unlike Go's unicode.IsSpace. Keep
	// this source-helper boundary separate from ordinary math tokenization.
	for .pos < len(.source) && internalTextSpace(.peekRune()) {
		.consumeRune()
	}
	if .pos >= len(.source) || .source[.pos] != '[' {
		if  == nil {
			return "", false, nil
		}
		return *, false, nil
	}
	 := .pos + 1
	 := 0
	for  := ;  < len(.source); {
		switch .source[] {
		case '\\':
			++
			if  < len(.source) {
				,  := utf8.DecodeRuneInString(.source[:])
				 += 
			}
		case '{':
			++
			++
		case '}':
			if  == 0 {
				return "", false, texError("ExtraCloseLooking", "Extra close brace while looking for ']'")
			}
			--
			++
		case ']':
			if  == 0 {
				.pos =  + 1
				return .source[:], true, nil
			}
			++
		default:
			,  := utf8.DecodeRuneInString(.source[:])
			 += 
		}
	}
	return "", false, texError("MissingCloseBracket", "Could not find closing ']' for argument")
}

func ( *parser) ( int, ,  byte) (int, error) {
	 := 0
	for  := ;  < len(.source); ++ {
		switch .source[] {
		case '\\':
			++
		case :
			++
		case :
			--
			if  == 0 {
				return , nil
			}
		}
	}
	return 0, texError("MissingCloseBrace", "Missing close brace")
}

func ( *parser) () string {
	if .pos >= len(.source) {
		return " "
	}
	,  := utf8.DecodeRuneInString(.source[.pos:])
	if !isASCIILetter() {
		.pos += 
		// GetCS uses a non-dotall regexp; these line endings take its
		// one-character fallback and become control-space.
		switch  {
		case '\n', '\r', '\u2028', '\u2029':
			return " "
		}
		return string()
	}
	 := .pos
	for .pos < len(.source) {
		,  = utf8.DecodeRuneInString(.source[.pos:])
		if !isASCIILetter() {
			break
		}
		.pos += 
	}
	 := .source[:.pos]
	if .pos < len(.source) && .source[.pos] == ' ' {
		.pos++
	}
	return 
}

func ( *parser) ( string,  bool) (string, error) {
	// GetDelimiter calls GetNext, whose nextIsSpace uses JavaScript's \s.
	for .pos < len(.source) && internalTextSpace(.peekRune()) {
		.consumeRune()
	}
	if .pos < len(.source) {
		var  string
		if .source[.pos] == '\\' {
			.pos++
			 = "\\" + .readControlSequence()
		} else if .source[.pos] == '{' &&  {
			, ,  := .readArgument(, false)
			if  != nil {
				return "", 
			}
			 = strings.TrimFunc(, internalTextSpace)
		} else {
			 = string(.consumeRune())
		}
		if ,  := lookupDelimiter();  {
			return , nil
		}
	}
	return "", texError("MissingOrUnrecognizedDelim", "Missing or unrecognized delimiter for \\%s", )
}

func ( *parser) () {
	for .pos < len(.source) && unicode.IsSpace(.peekRune()) {
		.consumeRune()
	}
}

func ( *parser) () {
	// BaseMethods.Comment consumes bare CR and leaves LF for the Space handler.
	for .pos < len(.source) && .peekRune() != '\n' {
		.consumeRune()
	}
}

func ( *parser) () rune {
	,  := utf8.DecodeRuneInString(.source[.pos:])
	return 
}

func ( *parser) () rune {
	,  := utf8.DecodeRuneInString(.source[.pos:])
	.pos += 
	return 
}

func isASCIILetter( rune) bool {
	return  >= 'a' &&  <= 'z' ||  >= 'A' &&  <= 'Z'
}

func macroArguments( string) int {
	 := 0
	for  := 0; +1 < len(); ++ {
		if [] != '#' || [+1] < '1' || [+1] > '9' {
			continue
		}
		 := int([+1] - '0')
		if  >  {
			 = 
		}
	}
	return 
}

func parseInteger( string,  string) (int, error) {
	,  := strconv.Atoi(strings.TrimSpace())
	if  != nil {
		return 0, texError(, "Invalid number %q", )
	}
	return , nil
}

func applyMathVariant( *mml.Node,  string) {
	.Walk(func( *mml.Node) bool {
		if .Kind == "mi" || .Kind == "mn" || .Kind == "mo" {
			// MmlToken's kept explicit attributes override the surrounding TeX
			// font environment, as in the pinned NodeFactory/MmlToken path.
			if ,  := .Attributes.GetExplicit("mjx-keep-attrs");  {
				if ,  := .(string);  && strings.Contains(" "++" ", " mathvariant ") {
					return true
				}
			}
			.Attributes.Set("mathvariant", )
		}
		return true
	})
}

// MathFont and SetFont select the current lexical environment in MathJax.
// This parser lowers those environments after parsing their contents, so an
// inner environment has already resolved its tokens when an outer one runs.
// Keep that choice until compilation finishes instead of overwriting it.
// The transient property survives the parser's existing node clones and is
// removed before any MathML is returned to the renderer or caller.
const resolvedFontScope = "go-resolved-font-scope"

func applyScopedMathVariant( *mml.Node,  string) {
	applyFontScope(, , true)
}

func applyFontScope( *mml.Node,  string,  bool) {
	.Walk(func( *mml.Node) bool {
		if .Kind != "mi" && .Kind != "mn" && .Kind != "mo" {
			return true
		}
		if ,  := .Property(ambientFontSource);  &&  != true {
			return true
		}
		if ,  := .Property(resolvedFontScope);  {
			return true
		}
		if ,  := .Attributes.GetExplicit("mjx-keep-attrs");  {
			if ,  := .(string);  && strings.Contains(" "++" ", " mathvariant ") {
				return true
			}
		}
		.Attributes.Set("mathvariant", )
		.SetProperty(resolvedFontScope, true)
		return true
	})
}

// Font environments apply at selected token-creation sites in ParseMethods,
// BaseConfiguration.Other and BaseMethods.Accent, not at the generic factory.
// Keep that provenance while the parser lowers MathFont/SetFont scopes.
const ambientFontSource = "go-ambient-font-source"

func ambientFontToken( *mml.Node) *mml.Node {
	.SetProperty(ambientFontSource, true)
	return 
}

func ambientLiteralToken( *mml.Node,  rune) *mml.Node {
	// Other's Unicode-range variant overrides the current font. Reuse the
	// pinned table without changing token-kind or character classification.
	for ,  := range mjOperatorRanges {
		if int() < .First {
			break
		}
		if int() <= .Last {
			if .HasVariant {
				.Attributes.Set("mathvariant", .Variant)
				return 
			}
			break
		}
	}
	return ambientFontToken()
}

var styleDeclarations = map[string]map[string]any{
	"displaystyle":      {"displaystyle": true, "scriptlevel": 0},
	"textstyle":         {"displaystyle": false, "scriptlevel": 0},
	"scriptstyle":       {"displaystyle": false, "scriptlevel": 1},
	"scriptscriptstyle": {"displaystyle": false, "scriptlevel": 2},
}

var sizeDeclarations = map[string]float64{
	"tiny": 0.5, "Tiny": 0.6, "scriptsize": 0.7, "small": 0.85,
	"normalsize": 1, "large": 1.2, "Large": 1.44, "LARGE": 1.73,
	"huge": 2.07, "Huge": 2.49,
}

func emLength( float64) string {
	if  > -0.001 &&  < 0.001 {
		return "0"
	}
	 := strconv.FormatFloat(, 'f', 3, 64)
	 = strings.TrimRight(strings.TrimRight(, "0"), ".")
	return  + "em"
}

var fontDeclarations = baseFontMappings("SetFont")

type infixFractionSpec struct {
	attributes  map[string]any
	open, close string
}

func ( *parser) ( string,  []*mml.Node,  byte, ,  bool) (*mml.Node, string, error) {
	,  := .readInfixSpec()
	if  != nil {
		return nil, "", 
	}
	return .finishInfixFraction(, , , , , , "")
}

func ( *parser) ( string) (infixFractionSpec, error) {
	 := infixFractionSpec{attributes: map[string]any{}}
	if strings.HasSuffix(, "withdelims") {
		// BaseMethods.Over reads both converted delimiters before any
		// thickness argument and before pushing its closing stack item.
		var  error
		.open,  = .readDelimiter(, false)
		if  != nil {
			return , 
		}
		.close,  = .readDelimiter(, false)
		if  != nil {
			return , 
		}
	}
	if  == "atop" ||  == "choose" ||  == "brace" ||  == "brack" {
		.attributes["linethickness"] = "0"
	} else if  == "atopwithdelims" {
		.attributes["linethickness"] = 0
	}
	if  == "above" ||  == "abovewithdelims" {
		,  := .readDimension()
		if  != nil {
			return , 
		}
		.attributes["linethickness"] = 
	}
	return , nil
}

func ( *parser) ( string,  []*mml.Node,  infixFractionSpec,  byte, ,  bool,  string) (*mml.Node, string, error) {
	// BaseMethods.Over reads its arguments before OverItem checks ambiguity.
	// In particular, malformed incoming above dimensions keep their own error.
	if  {
		return nil, "", texError("AmbiguousUseOf", "Ambiguous use of \\%s", )
	}
	, ,  := .parseRowContinuation(, , true, nil, , nil)
	if  != nil {
		return nil, "", 
	}
	if .environmentPopped {
		return row(, true), , nil
	}
	 := setAttributes(node("mfrac", row(, true), row(, true)), .attributes)
	if  == "choose" ||  == "brace" ||  == "brack" {
		,  := "(", ")"
		if  == "brace" {
			,  = "{", "}"
		} else if  == "brack" {
			,  = "[", "]"
		}
		// OverItem uses numeric zero and ParseUtil.fixedFence: the fraction
		// has no null-delimiter padding, and its fences select the existing
		// bigg/big palette after inherited display/script style is known.
		.Attributes.Set("linethickness", 0)
		.SetProperty("withDelims", true)
		 = forcedRow([]*mml.Node{
			.amsFixedFencePalette(, mml.TeXClassOpen),
			,
			.amsFixedFencePalette(, mml.TeXClassClose),
		}, false)
		.SetProperty("open", )
		.SetProperty("close", )
		.SetProperty("texClass", mml.TeXClassOrd)
		.TeXClass = mml.TeXClassOrd
	} else if .open != "" || .close != "" {
		// GetDelimiter converts '.' to empty. OverItem calls fixedFence
		// only when at least one converted side is nonempty, independently
		// of its default, zero, or explicitly supplied line thickness.
		.SetProperty("withDelims", true)
		 := forcedRow(nil, false)
		.SetProperty("open", .open)
		.SetProperty("close", .close)
		.SetProperty("texClass", mml.TeXClassOrd)
		.TeXClass = mml.TeXClassOrd
		if .open != "" {
			// Reuse the source-shaped mathPalette child parser: converted
			// glyphs must still pass its generated bigg/big delimiter read.
			,  := .amsGenfracPalette(.open, "l")
			if  != nil {
				return nil, "", 
			}
			.AppendChild()
		}
		.AppendChild()
		if .close != "" {
			,  := .amsGenfracPalette(.close, "r")
			if  != nil {
				return nil, "", 
			}
			.AppendChild()
		}
		 = 
	}
	return , , nil
}

func fenced( string,  *mml.Node,  string,  bool) *mml.Node {
	return fencedWithOperator(, , , , operator)
}

func fencedWithOperator( string,  *mml.Node,  string,  bool,  func(string, mml.TeXClass, map[string]any) *mml.Node) *mml.Node {
	 := make([]*mml.Node, 0, 3)
	 := (, mml.TeXClassOpen, nil)
	.Attributes.Set("fence", true)
	.Attributes.Set("stretchy", )
	.Attributes.Set("symmetric", true)
	 = append(, )
	if  != nil && .Kind == "mrow" && .Flags.Inferred {
		 = append(, .Children...)
	} else {
		 = append(, )
	}
	 := (, mml.TeXClassClose, nil)
	.Attributes.Set("fence", true)
	.Attributes.Set("stretchy", )
	.Attributes.Set("symmetric", true)
	 = append(, )
	return forcedRow(, false)
}

// leftRightFenced records the LeftItem properties that distinguish a
// \left...\right (and Matrix ArrayItem fence) row from macros that merely
// emit delimiter tokens, such as physics \qty.
func leftRightFenced( string,  *mml.Node,  string,  bool) *mml.Node {
	 := fenced(, , , )
	.SetProperty("open", )
	.SetProperty("close", )
	.SetProperty("texClass", mml.TeXClassInner)
	.TeXClass = mml.TeXClassInner
	return 
}

func debugNode( *mml.Node) string {
	return fmt.Sprintf("%s(%q)", .Kind, textContent())
}