// 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/base/{BaseItems,BaseMappings,BaseMethods}.ts,
// ams/{AmsItems,AmsMappings,AmsMethods}.ts,
// amscd/{AmsCdConfiguration,AmsCdMappings,AmsCdMethods}.ts,
// cases/CasesConfiguration.ts,
// empheq/{EmpheqConfiguration,EmpheqUtil}.ts,
// mathtools/{MathtoolsConfiguration,MathtoolsItems,MathtoolsMappings,
// MathtoolsMethods,MathtoolsUtil}.ts, and
// newcommand/{NewcommandItems,NewcommandMethods}.ts.

package tex

import (
	
	

	
)

func ( *parser) ( string) ([]*mml.Node, error) {
	,  := .readEnvironmentName()
	if  != nil {
		return nil, 
	}
	if  := .countEnvironment();  != nil {
		return nil, 
	}
	// BeginEnd dispatches the literal name after charging the begin.
	// A missing registration must fail before a body owner or reader runs.
	,  := .sourceEndDefinition()
	if ! {
		return nil, texError("UnknownEnv", "Unknown environment '%s'", )
	}
	 := .environmentOwner
	 := &environmentFrame{name: , parent: , stream:  == "spreadlines" ||  == "numcases" ||  == "subnumcases"}
	.environmentOwner = 
	defer func() { .environmentOwner =  }()
	if ,  := .state.environments[]; ! && isOrdinaryArrayEnvironment() {
		return .ordinaryArrayEnvironment(, )
	}
	if , ,  := .casesEnvironment();  {
		return , 
	}
	if rootIndexArrayEnvironment() {
		,  := .inRoot, .activeColor
		.inRoot, .activeColor = false, ""
		defer func() { .inRoot, .activeColor = ,  }()
	}
	if , ,  := .mathtoolsEnvironment();  {
		return , 
	}
	if .state.augmentedPackages {
		if , ,  := .empheqEnvironment();  {
			return , 
		}
	}
	if , ,  := .physicsEnvironment();  {
		return , 
	}
	if , ,  := .amscdEnvironment();  {
		return , 
	}
	if , ,  := .baseAMSEnvironment();  {
		return , 
	}
	if , ,  := .amsTagEnvironment();  {
		return , 
	}

	if ,  := .state.environments[];  {
		 := make([]string, 0, .arguments)
		if .optionalDefault != nil {
			, ,  := .readBrackets(.optionalDefault)
			if  != nil {
				return nil, 
			}
			if ! {
				 = *.optionalDefault
			}
			 = append(, )
		}
		for len() < .arguments {
			, ,  := .readArgument("begin{"++"}", false)
			if  != nil {
				return nil, 
			}
			 = append(, )
		}
		,  := substituteArguments(.begin, )
		if  != nil {
			return nil, 
		}
		,  := substituteArguments(.end, nil)
		if  != nil {
			return nil, 
		}
		,  := .captureEnvironment()
		if  != nil {
			return nil, 
		}
		,  := .parseContinuationString( +  + )
		if  != nil {
			return nil, 
		}
		return []*mml.Node{}, nil
	}

	 := "c"
	if  == "array" ||  == "subarray" ||  == "crampedsubarray" {
		, _,  = .readArgument("begin{"++"}", false)
		if  != nil {
			return nil, 
		}
	}
	if strings.HasSuffix(, "*") && strings.Contains(, "matrix") {
		 := "c"
		, ,  := .readBrackets(&)
		if  != nil {
			return nil, 
		}
		,  = .completeArrayAlignment()
		if  != nil {
			return nil, 
		}
	}
	if strings.Contains(, "alignat") {
		// AMS consumes the maximum pair count before the environment body; it
		// does not appear in the resulting MathML table.
		if ,  := .readEquationPairCount();  != nil {
			return nil, 
		}
	}
	 := ""
	if  == "gathered" ||  == "lgathered" ||  == "rgathered" {
		// AmsEqnArray consumes its optional position before parsing the body.
		,  = .readArrayAlignment()
		if  != nil {
			return nil, 
		}
	}
	if isGuardedEquationEnvironment() {
		if  := .checkEquationEnvironment();  != nil {
			return nil, 
		}
	}
	,  := .captureEnvironment()
	if  != nil {
		return nil, 
	}

	switch  {
	case "equation", "equation*", "displaymath", "math", "split":
		,  := .parseContinuationString()
		if  != nil {
			return nil, 
		}
		return []*mml.Node{}, nil
	case "matrix", "matrix*", "smallmatrix", "smallmatrix*", "array", "subarray", "crampedsubarray",
		"pmatrix", "pmatrix*", "bmatrix", "bmatrix*", "Bmatrix", "Bmatrix*", "vmatrix", "vmatrix*", "Vmatrix", "Vmatrix*",
		"psmallmatrix", "psmallmatrix*", "bsmallmatrix", "bsmallmatrix*", "Bsmallmatrix", "Bsmallmatrix*", "vsmallmatrix", "vsmallmatrix*", "Vsmallmatrix", "Vsmallmatrix*":
		 := "T"
		if strings.Contains(, "small") || strings.Contains(, "subarray") {
			 = "S"
		}
		 := "4pt"
		if strings.Contains(, "subarray") {
			 = "0.1em"
		}
		,  := .parseTable(, , )
		if  != nil {
			return nil, 
		}
		if strings.Contains(, "matrix") {
			// Array's literal "c" alignment must override an enclosing table.
			.Attributes.Set("columnalign", "center")
		}
		if  == "subarray" ||  == "crampedsubarray" {
			resetTableAttributes(,
				"columnspacing", "0em",
				"rowspacing", tableRowSpacing(),
			)
			.SetProperty("useHeight", false)
			.SetProperty("scriptlevel", 1)
			// Array's S/S' style is explicit while inheritance runs; the
			// cleanAttributes postfilter then removes it because it equals the
			// mtable default, leaving no materialized displaystyle layer.
			.Attributes.Set("displaystyle", false)
		}
		if  == "crampedsubarray" {
			// BaseMethods.Array marks the primed S' style before inheritance.
			.Attributes.Set("data-cramped", true)
		}
		 = applyColumnSpec(, )
		,  := matrixDelimiters()
		if  != "" ||  != "" {
			 = .leftRightFenced(, , , true)
		}
		return []*mml.Node{}, nil
	case "cases", "dcases", "rcases", "drcases", "cases*", "dcases*", "rcases*", "drcases*":
		 := "T"
		if strings.HasPrefix(, "d") {
			 = "D"
		}
		,  := .parseTable(, , ".2em")
		if  != nil {
			return nil, 
		}
		resetTableAttributes(,
			"columnalign", "left left",
			"columnspacing", "1em",
			"rowspacing", tableRowSpacing(),
		)
		// AMS Array's text style is explicit during inheritance.  The
		// clean-attributes postfilter removes it because it matches mtable's
		// default, leaving only scriptlevel in the inherited layer.
		.Attributes.Set("displaystyle", false)
		,  := "{", ""
		if strings.Contains(, "rcases") {
			,  = "", "}"
		}
		return []*mml.Node{.leftRightFenced(, finishArrayRules(), , true)}, nil
	case "numcases", "subnumcases":
		, ,  := .readArgument("begin{"++"}", true)
		if  != nil {
			return nil, 
		}
		,  := .parseTable(, "T")
		if  != nil {
			return nil, 
		}
		if  != "" {
			,  := .parseString()
			if  != nil {
				return nil, 
			}
			return []*mml.Node{, .leftRightFenced("{", finishArrayRules(), "", true)}, nil
		}
		return []*mml.Node{.leftRightFenced("{", finishArrayRules(), "", true)}, nil
	case "align", "align*", "alignat", "alignat*", "xalignat", "xalignat*", "xxalignat", "aligned", "alignedat", "gather", "gather*", "gathered", "multline", "multline*", "multlined", "lgathered", "rgathered", "spreadlines":
		var  *mml.Node
		var  *equationTableState
		if isEquationArray() {
			, ,  = .parseEquationTable(, )
		} else {
			,  = .parseTableWithAlignment(, "D", false)
		}
		if  != nil {
			return nil, 
		}
		if strings.Contains(, "gather") {
			// Mathtools supplies l/r to AmsEqnArray for these two owners.
			 := "center"
			switch  {
			case "lgathered":
				 = "left"
			case "rgathered":
				 = "right"
			}
			resetTableAttributes(,
				"displaystyle", true,
				"columnalign", ,
				"columnspacing", "1em",
				"rowspacing", "3pt",
				"side", "right",
				"minlabelspacing", "0.8em",
			)
		} else if strings.Contains(, "multline") {
			finishAMSMultlineTable()
		} else {
			if !isEquationArray() {
				fixInitialArrayOperators(, 0, 1)
			}
			resetTableAttributes(,
				"displaystyle", true,
				"columnalign", "right left",
				"columnspacing", "0em",
				"rowspacing", "3pt",
			)
		}
		setArrayAlign(, )
		.applySpacing()
		return []*mml.Node{finishArrayRules()}, nil
	case "CD":
		return .parseCD()
	default:
		return nil, texError("UnknownEnv", "Unknown environment '%s'", )
	}
}

// These are the supported AMS routes that call ParseUtil.checkEqnEnv.
// Untaggable arrays such as split, aligned and gathered do not set the flag.
func isGuardedEquationEnvironment( string) bool {
	switch  {
	case "equation", "equation*", "align", "align*", "gather", "gather*", "eqnarray", "eqnarray*",
		"multline", "multline*", "alignat", "alignat*", "xalignat", "xalignat*",
		"xxalignat", "flalign", "flalign*":
		return true
	}
	return false
}

func ( *parser) ( string) (string, error) {
	// AlignAt's GetArgument calls GetNext (JavaScript whitespace), and its
	// diagnostics name the active control sequence, \\begin.
	for .pos < len(.source) && internalTextSpace(.peekRune()) {
		.consumeRune()
	}
	, ,  := .readArgumentAtCursor("begin", false)
	if  != nil {
		return "", 
	}
	if  == "alignat" ||  == "alignat*" ||  == "alignedat" || isAMSXAlignAt() {
		// AlignAt/XalignAt reject /[^0-9]/ before checking equation nesting.
		// In particular, their source regexp accepts empty and zero counts.
		for ,  := range  {
			if  < '0' ||  > '9' {
				return "", texError("PositiveIntegerArg", "Argument to \\begin{%s} must me a positive integer", )
			}
		}
	}
	return , nil
}

func isAMSXAlignAt( string) bool {
	return  == "xalignat" ||  == "xalignat*" ||  == "xxalignat"
}

// finishAMSMultlineTable ports AmsMethods.Multline's arraydef and
// MultlineItem.EndTable's default first/last row alignment.
func finishAMSMultlineTable( *mml.Node) {
	fixInitialArrayOperators(, 1, 0)
	alignAMSMultlineCells()
	resetTableAttributes(,
		"displaystyle", true,
		"rowspacing", ".5em",
		"columnspacing", "100%",
		"width", "100%",
		"side", "right",
		"minlabelspacing", "0.8em",
		"framespacing", "1em 0",
		"frame", "",
		"data-width-includes-label", true,
	)
}

func alignAMSMultlineCells( *mml.Node) {
	if len(.Children) != 0 {
		 := .Children[0].Children[0]
		if ,  := .Attributes.GetExplicit("columnalign"); ! {
			.Attributes.Set("columnalign", "left")
		}
		 := .Children[len(.Children)-1]
		 := .Children[len(.Children)-1]
		if ,  := .Attributes.GetExplicit("columnalign"); ! {
			.Attributes.Set("columnalign", "right")
		}
	}
}

func ( *parser) ( string) (string, error) {
	 := .pos
	 := 1
	for  := .pos;  < len(.source); {
		if .source[] != '\\' {
			,  := utf8.DecodeRuneInString(.source[:])
			 += 
			continue
		}
		 := 
		 := &parser{source: .source, pos:  + 1}
		 := .readControlSequence()
		if  != "begin" &&  != "end" {
			 = .pos
			continue
		}
		, ,  := .readArgument(, false)
		if  != nil {
			return "", 
		}
		if strings.TrimSpace() !=  {
			 = .pos
			continue
		}
		if  == "begin" {
			++
		} else {
			--
			if  == 0 {
				 := .source[:]
				.pos = .pos
				return , nil
			}
		}
		 = .pos
	}
	return "", texError("MissingEnd", "Missing \\end{%s}", )
}

func ( *parser) (,  string,  ...string) (*mml.Node, error) {
	return .parseTableWithAlignment(, , true, ...)
}

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

func ( *parser) (,  string,  bool,  *ordinaryArrayItem,  ...string) (*mml.Node, error) {
	 := node("mtable")
	var  []*mml.Node
	 := &arrayRowSpacing{}
	var  *environmentFrame
	if  != nil {
		 = .begin
	}
	 := .parseArrayBody(, arrayBodyOwner{
		environment: ,
		ordinary:    ,
		rules:       newArrayRules(),
		hasEntries:  func() bool { return len() != 0 },
		endEntry: func( []*mml.Node,  *arrayCellState) error {
			 := .finish(matrixCellContent(), len())
			 := node("mtd", )
			if  {
				 = arrayCellNode()
			}
			 = append(, )
			return nil
		},
		endRow: func() error {
			.AppendChild(node("mtr", ...))
			 = nil
			.rows++
			return nil
		},
		addSpacing: .add,
	})
	if  != nil {
		return nil, 
	}
	if  != nil && .popped {
		return nil, nil
	}
	.Attributes.Set("columnspacing", "1em")
	 := "4pt"
	if len() != 0 {
		 = [0]
	}
	.apply(, )
	return , nil
}

func resetTableAttributes( *mml.Node,  ...any) {
	for ,  := range .Attributes.ExplicitNames() {
		.Attributes.Explicit().Delete()
	}
	for  := 0;  < len();  += 2 {
		.Attributes.Set([].(string), [+1])
	}
}

// repeatAMSEqnArrayDefinition ports EqnArrayItem.extendArray.  Multi-value
// column definitions repeat to the longest row and are then truncated to the
// exact number of columns (or inter-column gaps).  A single value remains a
// single value because MathML itself repeats it.
func repeatAMSEqnArrayDefinition( string,  int) string {
	 := strings.Fields()
	if len() <= 1 {
		return 
	}
	for len() <  {
		 = append(, ...)
	}
	// JavaScript slice(0, -1) drops the last spacing when an entirely empty
	// EqnArray has maxrow=0. Preserve that source behavior, not a Go panic.
	if  < 0 {
		 += len()
		if  < 0 {
			 = 0
		}
	}
	if  < len() {
		 = [:]
	}
	return strings.Join(, " ")
}

// finishAMSEqnArrayTable applies the source map's initial EqnArray column
// definitions followed by EqnArrayItem.EndTable's repetition pass.
func finishAMSEqnArrayTable( *mml.Node,  string,  int) {
	if  == "eqnarray" ||  == "eqnarray*" {
		// BaseMethods.EqnArray uses 3pt row spacing even though the source
		// registrations contain an unused extra .5em argument.
		resetTableAttributes(,
			"displaystyle", true,
			"columnalign", repeatAMSEqnArrayDefinition("right center left", ),
			"columnspacing", repeatAMSEqnArrayDefinition("0em 0.278em", -1),
			"rowspacing", "3pt",
			"side", "right",
			"minlabelspacing", "0.8em",
		)
		return
	}

	if  == "gather" ||  == "gather*" {
		resetTableAttributes(,
			"displaystyle", true,
			"columnalign", "center",
			"columnspacing", "1em",
			"rowspacing", "3pt",
			"side", "right",
			"minlabelspacing", "0.8em",
		)
		return
	}
	 := "0em"
	switch  {
	case "align", "align*", "aligned":
		 = "0em 2em"
	}
	resetTableAttributes(,
		"displaystyle", true,
		"columnalign", repeatAMSEqnArrayDefinition("right left", ),
		"columnspacing", repeatAMSEqnArrayDefinition(, -1),
		"rowspacing", "3pt",
	)
}

func splitTable( string) [][]string {
	 := [][]string{{}}
	 := 0
	 := 0
	for  := 0;  < len(); {
		switch [] {
		case '{':
			++
			++
		case '}':
			if  > 0 {
				--
			}
			++
		case '\\':
			if +1 < len() && [+1] == '\\' &&  == 0 {
				[len()-1] = append([len()-1], [:])
				 += 2
				if  < len() && [] == '[' {
					if  := strings.IndexByte([:], ']');  >= 0 {
						 +=  + 1
					}
				}
				 = append(, []string{})
				 = 
				continue
			}
			++
			if  < len() {
				,  := utf8.DecodeRuneInString([:])
				 += 
			}
		case '&':
			if  == 0 {
				[len()-1] = append([len()-1], [:])
				 =  + 1
			}
			++
		default:
			,  := utf8.DecodeRuneInString([:])
			 += 
		}
	}
	[len()-1] = append([len()-1], [:])
	return 
}

func splitTopLevel( string,  rune) []string {
	 := []string{}
	 := 0
	 := 0
	for  := 0;  < len(); {
		,  := utf8.DecodeRuneInString([:])
		if  == '\\' {
			 += 
			if  < len() {
				,  := utf8.DecodeRuneInString([:])
				 += 
			}
			continue
		}
		if  == '{' {
			++
		} else if  == '}' &&  > 0 {
			--
		} else if  ==  &&  == 0 {
			 = append(, [:])
			 =  + 
		}
		 += 
	}
	return append(, [:])
}

func matrixDelimiters( string) (string, string) {
	 = strings.TrimSuffix(, "*")
	switch  {
	case "pmatrix", "psmallmatrix":
		return "(", ")"
	case "bmatrix", "bsmallmatrix":
		return "[", "]"
	case "Bmatrix", "Bsmallmatrix":
		return "{", "}"
	case "vmatrix", "vsmallmatrix":
		return "|", "|"
	case "Vmatrix", "Vsmallmatrix":
		return "‖", "‖"
	}
	return "", ""
}

// MtMatrix and MtSmallMatrix pass their optional alignment to Array. An
// exactly empty value makes Array consume one GetArgument; nonempty strings
// stay arguments even when filtering later removes every alignment letter.
func ( *parser) ( string) (string, error) {
	if  != "" {
		return , nil
	}
	for .pos < len(.source) && internalTextSpace(.peekRune()) {
		.consumeRune()
	}
	// The frozen runtime names the active control sequence in diagnostics.
	, ,  := .readArgumentAtCursor("begin", false)
	return , 
}

func applyColumnSpec( *mml.Node,  string) *mml.Node {
	var  []string
	// BaseMethods.Array first filters the specification, with a leading c
	// representing the left edge. Each letter followed by a run of rules is
	// replaced by the last rule in that run; remaining letters are no-rule
	// positions. Keeping these positions prevents MathML's final line style
	// from repeating over later columns that have no authored separator.
	 := []byte{'c'}
	for ,  := range  {
		switch  {
		case 'l':
			 = append(, "left")
			 = append(, 'l')
		case 'c':
			 = append(, "center")
			 = append(, 'c')
		case 'r':
			 = append(, "right")
			 = append(, 'r')
		case '|', ':':
			 = append(, byte())
		}
	}
	// BaseMethods.Array always materializes its alignment, including the
	// empty string. Omitting it would inherit an enclosing table's alignment.
	.Attributes.Set("columnalign", strings.Join(, " "))
	var  []byte
	 := func( byte) bool { return  == '|' ||  == ':' }
	for  := 0;  < len(); ++ {
		 := []
		if !() {
			for +1 < len() && ([+1]) {
				++
				 = []
			}
		}
		 = append(, )
	}
	if !strings.ContainsAny(string(), "|:") {
		return finishArrayRules()
	}
	var ,  []string
	if ([0]) {
		 = append(, "left")
	}
	if ([len()-1]) {
		 = append(, "right")
	}
	for  := 1;  < len()-1; ++ {
		 := "none"
		if [] == '|' {
			 = "solid"
		} else if [] == ':' {
			 = "dashed"
		}
		 = append(, )
	}
	 := strings.Join(, " ")
	.Attributes.Set("columnlines", )
	 := tableArrayRules()
	.frame = append(, .frame...)
	.dashed = [0] == ':'
	return finishArrayRules()
}

func ( *parser) ( string) ([]*mml.Node, error) {
	 := .matrixCellParser("")
	.matrixClose = false
	 := node("mtable")
	.arrayCell.rules = newArrayRules()
	 := &arrayRowSpacing{}
	 := 
	for {
		, , ,  := .parseCDRow(, len(.Children))
		if  != nil {
			return nil, 
		}
		if len() != 0 {
			.AppendChild(node("mtr", ...))
			.rows++
		}
		if  == nil {
			break
		}
		if .spacing != "" {
			.add(.spacing)
		}
		 = 
	}
	.Attributes.Set("columnspacing", "5pt")
	.apply(, "5pt")
	.Attributes.Set("displaystyle", true)
	return []*mml.Node{matrixCellContent([]*mml.Node{})}, nil
}

type cdCellEnd struct {
	firstObjectCell bool
}

// parseCDSegment delivers explicit Entry items through the ordinary parser.
// Argument readers may consume an ampersand without emitting an Entry; each
// actual Entry closes the current cell and clears its lexical environment.
type cdSegmentResult struct {
	cells     []*mml.Node
	contents  *mml.Node
	fill      *arrayCellState
	remaining string
	stop      *cellItem
}

func ( *parser) ( string,  []*mml.Node, ,  bool) (cdSegmentResult, error) {
	for {
		 := .matrixCellParser()
		.matrixClose = false
		.cdArrayEntry = true
		.arrayCell.rules = .arrayCell.rules
		if  {
			.cdCellEnd = &cdCellEnd{firstObjectCell:  && len() == 0}
		}
		, ,  := .parseRowWithInfix(0, false, false)
		if  != nil {
			return cdSegmentResult{}, 
		}
		 := .arrayCell.finish(matrixCellContent(), len())
		if .stoppedCell == nil {
			return cdSegmentResult{cells: , contents: , fill: .arrayCell}, nil
		}
		// Explicit Entry/CR receives no AmsCdMethods.cell strut. A CR before
		// the raw @ boundary returns its expanded suffix without consuming @.
		 = append(, arrayCellNode())
		 = .source[.pos:]
		if .stoppedCell.cr {
			return cdSegmentResult{cells: , remaining: , stop: .stoppedCell}, nil
		}
	}
}

func ( *parser) ( string,  int) ([]*mml.Node, string, *cellItem, error) {
	var  []*mml.Node
	 := 0
	 := %2 == 0
	for  < len() {
		 := 
		for {
			 := strings.IndexByte([:], '@')
			if  < 0 {
				 = -1
				break
			}
			 += 
			if +1 < len() && strings.ContainsRune("><VA.|=", rune([+1])) {
				break
			}
			// AmsCdMethods.arrow delegates an ordinary @ to Other;
			// it does not close a cell or add alignment padding.
			++
		}
		if  < 0 {
			break
		}
		,  := .parseCDSegment([:], , , true)
		if  != nil {
			return nil, "", nil, 
		}
		if .stop != nil {
			return .cells, .remaining + [:], .stop, nil
		}
		 = .cells
		,  := .contents, .fill
		if !emptyCDContents() {
			 = append(, arrayCellNode())
			 = nil
		}
		// AmsCdMethods.arrow closes enough empty cells to place arrows in
		// odd columns on object rows and even columns on arrow rows.
		if (len()%2 == 0) ==  {
			// With no emitted prefix, HFill still belongs to the pending
			// array cell. Padding closes it before the arrow is inserted.
			if  != nil && len(.fills) != 0 {
				 = append(, arrayCellNode(.finish(forcedRow(nil, true), 0)))
			} else {
				 = append(, node("mtd"))
			}
			 = nil
		}
		, ,  := .parseCDArrow(, )
		if  != nil {
			return nil, "", nil, 
		}
		if  != nil {
			 := 1
			if emptyCDContents() {
				 = 0
			}
			.finish(, )
		}
		 = append(, arrayCellNode())
		 = 
	}
	,  := .parseCDSegment([:], , , false)
	if  != nil {
		return nil, "", nil, 
	}
	if .stop != nil {
		return .cells, .remaining, .stop, nil
	}
	 = .cells
	if len() != 0 || !emptyCDContents(.contents) {
		 = append(, arrayCellNode(.contents))
	}
	return , "", nil, nil
}

func emptyCDContents( *mml.Node) bool {
	return .Kind == "mrow" && .Flags.Inferred && len(.Children) == 0
}

func cdStrut() *mml.Node {
	 := node("mpadded")
	.Attributes.Set("height", "8.5pt")
	.Attributes.Set("depth", "2pt")
	return 
}

func ( *parser) ( string,  int) (*mml.Node, int, error) {
	if +1 >= len() {
		return nil, , texError("CDMissingArrow", "Missing CD arrow after @")
	}
	 := [+1]
	if !strings.ContainsRune("><VA.|=", rune()) {
		,  := .parseString("@")
		return ,  + 1, 
	}
	 :=  + 2
	if  == '.' {
		return forcedRow(nil, true), , nil
	}
	if  == '|' {
		return .cdVerticalArrow("‖"), , nil
	}
	if  == '=' {
		return .cdHorizontalArrow("="), , nil
	}
	, ,  := cdLabel(, , )
	if  != nil {
		return nil, , 
	}
	, ,  := cdLabel(, , )
	if  != nil {
		return nil, , 
	}
	 := map[byte]string{'>': "→", '<': "←", 'V': "↓", 'A': "↑"}
	 := .cdHorizontalArrow([])
	if  == 'V' ||  == 'A' {
		 = .cdVerticalArrow([])
	}
	if  == '>' ||  == '<' {
		if  == "" {
			 = "\\kern " + .amscdState(amscdMinWidthState, "2.75em")
		}
		,  := .parseArgumentString()
		if  != nil {
			return nil, , 
		}
		 = cdArrowLabel(, true)
		if  == "" {
			return node("mover", , ), , nil
		}
		,  := .parseArgumentString()
		if  != nil {
			return nil, , 
		}
		 = cdArrowLabel(, false)
		return node("munderover", , , ), , nil
	}
	.TeXClass = mml.TeXClassOrd
	if  == "" &&  == "" {
		return , , nil
	}
	var  []*mml.Node
	if  != "" {
		,  := .parseArgumentString("\\scriptstyle\\llap{" +  + "}")
		if  != nil {
			return nil, , 
		}
		 = append(, )
	}
	 = append(, )
	if  != "" {
		,  := .parseArgumentString("\\scriptstyle\\rlap{" +  + "}")
		if  != nil {
			return nil, , 
		}
		 = append(, )
	}
	return forcedRow(, false), , nil
}

func cdArrowLabel( *mml.Node,  bool) *mml.Node {
	 := node("mpadded", )
	.Attributes.Set("width", "+.67em")
	.Attributes.Set("lspace", ".33em")
	if  {
		.Attributes.Set("voffset", ".1em")
	}
	return 
}

func cdHorizontalArrow( string) *mml.Node {
	 := token("mo", )
	.Attributes.Set("minsize", "2.75em")
	.Attributes.Set("stretchy", true)
	return 
}

func cdVerticalArrow( string) *mml.Node {
	 := token("mo", )
	.Attributes.Set("minsize", "1.75em")
	.Attributes.Set("stretchy", true)
	.Attributes.Set("symmetric", true)
	.Attributes.Set("lspace", 0)
	.Attributes.Set("rspace", 0)
	return 
}

func cdLabel( string,  int,  byte) (string, int, error) {
	 := 0
	for  := ;  < len(); ++ {
		switch [] {
		case '\\':
			++
		case '{':
			++
		case '}':
			if  > 0 {
				--
			}
		default:
			if [] ==  &&  == 0 {
				return [:],  + 1, nil
			}
		}
	}
	return "", , texError("CDMissingArrow", "Missing closing %c for CD arrow", )
}

func ( *parser) ( string) ([]*mml.Node, error) {
	, ,  := .readBrackets(nil)
	if  != nil {
		return nil, 
	}
	, ,  := .readArgument("empheq", false)
	if  != nil {
		return nil, 
	}
	 := strings.TrimSpace()
	 := &parser{source: "\\begin{" +  + "}" +  + "\\end{" +  + "}", state: .state, display: .display}
	, ,  := .parseRow(0, false)
	if  != nil {
		return nil, 
	}
	 := row(, true)
	 := keyvalString()
	if  := ["left"];  != "" {
		,  := .convertDelimiterArgument()
		 = .fenced(, , "", true)
	}
	if  := ["right"];  != "" {
		,  := .convertDelimiterArgument()
		 = .fenced("", , , true)
	}
	return []*mml.Node{}, nil
}