Remove curly

To reduce the complexity of the parse tree we remove the curly
element which is used for grouping in TeX. Instead, we use
use an attribute which defines if the group is put into curly
brackets or not. The functionality of the curly element
is transferred to the TexArray which was the only possible
child of the curly element before. To ease the transition,
we add a special constructor to TexArray.

We could not measure any performance implications of this change.

Bug: T333973
Change-Id: Idcb58694022831113bdc437576bb9f48658fff2f
This commit is contained in:
Moritz Schubotz (physikerwelt) 2024-02-18 00:02:37 +01:00
parent 5f4cfc47bc
commit a102a4ed52
No known key found for this signature in database
GPG key ID: F803DB146DDF36C3
15 changed files with 100 additions and 154 deletions

View file

@ -65,7 +65,6 @@ const useStatements =
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Big;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Big;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\ChemFun2u;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\ChemFun2u;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\ChemWord;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\ChemWord;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Curly;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Declh;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Declh;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Dollar;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\Dollar;\n' +
'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\DQ;\n' + 'use MediaWiki\\Extension\\Math\\WikiTexVC\\Nodes\\DQ;\n' +

View file

@ -31,7 +31,6 @@ use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtr; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtr;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunder; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunder;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunderover; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunderover;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1;
@ -41,6 +40,7 @@ use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun2sq;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun4; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun4;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Matrix; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Matrix;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode; use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
use MediaWiki\Extension\Math\WikiTexVC\TexVC; use MediaWiki\Extension\Math\WikiTexVC\TexVC;
@ -136,7 +136,7 @@ class BaseParsing {
foreach ( $tableRow->getArgs() as $tableCell ) { foreach ( $tableRow->getArgs() as $tableCell ) {
$renderedInner .= $mtd->getStart(); $renderedInner .= $mtd->getStart();
foreach ( $tableCell->getArgs() as $cellItem ) { foreach ( $tableCell->getArgs() as $cellItem ) {
if ( !$discarded && $cellItem instanceof Curly ) { if ( !$discarded && $cellItem->isCurly() ) {
$discarded = true; $discarded = true;
// Just discard the number of rows atm, it is in the first Curly // Just discard the number of rows atm, it is in the first Curly
} else { } else {
@ -357,7 +357,7 @@ class BaseParsing {
} }
public static function hskip( $node, $passedArgs, $operatorContent, $name ) { public static function hskip( $node, $passedArgs, $operatorContent, $name ) {
if ( $node->getArg() instanceof Curly ) { if ( $node->getArg()->isCurly() ) {
$unit = MMLutil::squashLitsToUnit( $node->getArg() ); $unit = MMLutil::squashLitsToUnit( $node->getArg() );
if ( !$unit ) { if ( !$unit ) {
return null; return null;
@ -556,7 +556,7 @@ class BaseParsing {
$addHlines = true; $addHlines = true;
} }
} }
if ( count( $arg->getArgs() ) >= 1 && $arg->getArgs()[0] instanceof Curly ) { if ( count( $arg->getArgs() ) >= 1 && $arg->getArgs()[0]->isCurly() ) {
// Discarding the column information Curly at the moment // Discarding the column information Curly at the moment
// $usedArg->getArgs()[0] = null; // $usedArg->getArgs()[0] = null;
$columnInfo = $usedArg->getArgs()[0]->render(); $columnInfo = $usedArg->getArgs()[0]->render();
@ -743,7 +743,8 @@ class BaseParsing {
} }
$arg1 = $node->getArg1(); $arg1 = $node->getArg1();
if ( $arg1 instanceof Curly ) { // the second check is to avoid a false positive for PhanTypeMismatchArgumentSuperType
if ( $arg1->isCurly() && $arg1 instanceof TexArray ) {
$unit = MMLutil::squashLitsToUnit( $arg1 ); $unit = MMLutil::squashLitsToUnit( $arg1 );
if ( !$unit ) { if ( !$unit ) {
return null; return null;
@ -1043,8 +1044,8 @@ class BaseParsing {
if ( $node instanceof Fun2sq ) { if ( $node instanceof Fun2sq ) {
$arg1 = $node->getArg1(); $arg1 = $node->getArg1();
$arg1i = ""; $arg1i = "";
if ( $arg1 instanceof Curly ) { if ( $arg1->isCurly() ) {
$arg1i = $arg1->getArg()->render(); $arg1i = $arg1->render();
} }
if ( str_contains( $arg1i, "{b}" ) ) { if ( str_contains( $arg1i, "{b}" ) ) {
@ -1099,11 +1100,12 @@ class BaseParsing {
// match args in row of subargs, unless an element has explicit annotations // match args in row of subargs, unless an element has explicit annotations
// nested annotations ? // nested annotations ?
$arg1 = $node->getArg1(); $arg1 = $node->getArg1();
if ( !$node->getArg2() instanceof Curly ) { $arg2 = $node->getArg2();
if ( !$arg2->isCurly() ) {
return null; return null;
} }
// tbd refactor intent form and fiddle in mml or tree // tbd refactor intent form and fiddle in mml or tree
$intentStr = MMLutil::squashLitsToUnitIntent( $node->getArg2() ); $intentStr = MMLutil::squashLitsToUnitIntent( $arg2 );
$intentContent = MMLParsingUtil::getIntentContent( $intentStr ); $intentContent = MMLParsingUtil::getIntentContent( $intentStr );
$intentParams = MMLParsingUtil::getIntentParams( $intentContent ); $intentParams = MMLParsingUtil::getIntentParams( $intentContent );
// Sometimes the intent has additioargs = {array[3]} nal args in the same string // Sometimes the intent has additioargs = {array[3]} nal args in the same string
@ -1122,19 +1124,19 @@ class BaseParsing {
$intentParamsState = $intentParams ? [ "intent-params" => $intentParams ] : $operatorContent; $intentParamsState = $intentParams ? [ "intent-params" => $intentParams ] : $operatorContent;
// Here are some edge cases, they might go into renderMML in the related element // Here are some edge cases, they might go into renderMML in the related element
if ( str_contains( $intentContent, "matrix" ) || if ( str_contains( $intentContent, "matrix" ) ||
( $arg1 instanceof Curly && $arg1->getArg()->getArgs()[0] instanceof Matrix ) ) { ( $arg1->isCurly() && $arg1->getArgs()[0] instanceof Matrix ) ) {
$element = $arg1->getArg()->getArgs()[0]; $element = $arg1->getArgs()[0];
$rendered = $element->renderMML( [], $intentParamsState ); $rendered = $element->renderMML( [], $intentParamsState );
$hackyXML = MMLParsingUtil::forgeIntentToSpecificElement( $rendered, $hackyXML = MMLParsingUtil::forgeIntentToSpecificElement( $rendered,
$intentContentAtr, "mtable" ); $intentContentAtr, "mtable" );
return $hackyXML; return $hackyXML;
} elseif ( $arg1 instanceof Curly && count( $arg1->getArg()->getArgs() ) >= 2 ) { } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 2 ) {
// Create a surrounding element which holds the intents // Create a surrounding element which holds the intents
$mrow = new MMLmrow( "", $intentContentAtr ); $mrow = new MMLmrow( "", $intentContentAtr );
return $mrow->encapsulateRaw( $arg1->renderMML( [], $intentParamsState ) ); return $mrow->encapsulateRaw( $arg1->renderMML( [], $intentParamsState ) );
} elseif ( $arg1 instanceof Curly && count( $arg1->getArg()->getArgs() ) >= 1 ) { } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 1 ) {
// Forge the intent attribute to the top-level element after MML rendering // Forge the intent attribute to the top-level element after MML rendering
$element = $arg1->getArg()->getArgs()[0]; $element = $arg1->getArgs()[0];
$rendered = $element->renderMML( [], $intentParamsState ); $rendered = $element->renderMML( [], $intentParamsState );
$hackyXML = MMLParsingUtil::forgeIntentToTopElement( $rendered, $intentContentAtr ); $hackyXML = MMLParsingUtil::forgeIntentToTopElement( $rendered, $intentContentAtr );
return $hackyXML; return $hackyXML;
@ -1185,7 +1187,7 @@ class BaseParsing {
$mmlMrow = new MMLmrow(); $mmlMrow = new MMLmrow();
$mtext = new MMLmtext( "", MMLParsingUtil::getFontArgs( $name, null, null ) ); $mtext = new MMLmtext( "", MMLParsingUtil::getFontArgs( $name, null, null ) );
$inner = $node->getArg() instanceof Curly ? $node->getArg()->getArg()->renderMML( $inner = $node->getArg()->isCurly() ? $node->getArg()->renderMML(
[], [ "inHBox" => true ] ) [], [ "inHBox" => true ] )
: $node->getArg()->renderMML( [ "fromHBox" => true ] ); : $node->getArg()->renderMML( [ "fromHBox" => true ] );
return $mmlMrow->encapsulateRaw( $mtext->encapsulateRaw( $inner ) ); return $mmlMrow->encapsulateRaw( $mtext->encapsulateRaw( $inner ) );

View file

@ -2,9 +2,9 @@
namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util; namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util;
use IntlChar; use IntlChar;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode; use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
/** /**
@ -78,12 +78,12 @@ class MMLutil {
/** /**
* Assumes the input curly contains an TexArray of literals, squashes the TexArray characters to a string. * Assumes the input curly contains an TexArray of literals, squashes the TexArray characters to a string.
* @param Curly $node curly containing a TexArray of literals * @param TexArray $node TexArray of literals
* @return ?string squashed string in example "2mu", "-3mu" etc. Null if no TexArray inside curly. * @return ?string squashed string in example "2mu", "-3mu" etc. Null if no TexArray inside curly.
*/ */
public static function squashLitsToUnit( Curly $node ): ?string { public static function squashLitsToUnit( TexArray $node ): ?string {
$unit = ""; $unit = "";
foreach ( $node->getArg()->getArgs() as $literal ) { foreach ( $node->getArgs() as $literal ) {
if ( !$literal instanceof Literal ) { if ( !$literal instanceof Literal ) {
continue; continue;
} }
@ -220,11 +220,11 @@ class MMLutil {
* @return ?string squashed string in example "2mu", "-3mu" etc. Null if no TexArray inside curly. * @return ?string squashed string in example "2mu", "-3mu" etc. Null if no TexArray inside curly.
*/ */
public static function squashLitsToUnitIntent( TexNode $node ): ?string { public static function squashLitsToUnitIntent( TexNode $node ): ?string {
if ( !$node instanceof Curly ) { if ( !$node->isCurly() ) {
return null; return null;
} }
$unit = ""; $unit = "";
foreach ( $node->getArg()->getArgs() as $literal ) { foreach ( $node->getArgs() as $literal ) {
if ( $literal instanceof DQ ) { if ( $literal instanceof DQ ) {
$args = $literal->getArgs(); $args = $literal->getArgs();
if ( !$args[0] instanceof Literal || !$args[1] instanceof Literal ) { if ( !$args[0] instanceof Literal || !$args[1] instanceof Literal ) {

View file

@ -1,51 +0,0 @@
<?php
declare( strict_types = 1 );
namespace MediaWiki\Extension\Math\WikiTexVC\Nodes;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
class Curly extends TexNode {
/** @var TexArray */
private $arg;
public function __construct( TexArray $arg ) {
parent::__construct( $arg );
$this->arg = $arg;
}
/**
* @return TexArray
*/
public function getArg(): TexArray {
return $this->arg;
}
public function render() {
return $this->arg->inCurlies();
}
public function renderMML( $arguments = [], $state = [] ) {
if ( count( $this->arg->getArgs() ) > 1 ) {
$mmlRow = new MMLmrow();
return $mmlRow->encapsulateRaw( parent::renderMML( $arguments, $state ) );
}
return parent::renderMML( $arguments, $state );
}
public function inCurlies() {
return $this->render();
}
public function extractSubscripts() {
return $this->arg->extractSubscripts();
}
public function getModIdent() {
return $this->arg->getModIdent();
}
}

View file

@ -47,7 +47,7 @@ class DQ extends TexNode {
$emptyMrow = ""; $emptyMrow = "";
// In cases with empty curly preceding like: "{}_pF_q" // In cases with empty curly preceding like: "{}_pF_q"
if ( $this->getBase() instanceof Curly && $this->getBase()->isEmpty() ) { if ( $this->getBase()->isCurly() && $this->getBase()->isEmpty() ) {
$mrow = new MMLmrow(); $mrow = new MMLmrow();
$emptyMrow = $mrow->getEmpty(); $emptyMrow = $mrow->getEmpty();
} }

View file

@ -57,24 +57,19 @@ class FQ extends TexNode {
// A specific FQ case with preceding limits, just invoke the limits parsing manually. // A specific FQ case with preceding limits, just invoke the limits parsing manually.
return BaseParsing::limits( $this, $arguments, $state, "" ); return BaseParsing::limits( $this, $arguments, $state, "" );
} }
$base = $this->getBase();
if ( $this->getArgs()[0]->getLength() == 0 ) { if ( $base->getLength() == 0 && !$base->isCurly() ) {
// this happens when FQ is located in Sideset (is this a common parsing way?) // this happens when FQ is located in Sideset (is this a common parsing way?)
$mrow = new MMLmrow(); $mrow = new MMLmrow();
return $mrow->encapsulateRaw( $this->getDown()->renderMML( [], $state ) ) . return $mrow->encapsulateRaw( $this->getDown()->renderMML( [], $state ) ) .
$mrow->encapsulateRaw( $this->getUp()->renderMML( [], $state ) ); $mrow->encapsulateRaw( $this->getUp()->renderMML( [], $state ) );
} }
// Not sure if this case is necessary ..
if ( is_string( $this->getArgs()[0] ) ) {
return $this->parseToMML( $this->getArgs()[0], $arguments, null );
}
$melement = new MMLmsubsup(); $melement = new MMLmsubsup();
// tbd check for more such cases like TexUtilTest 317 // tbd check for more such cases like TexUtilTest 317
$base = $this->getBase();
if ( $base instanceof Literal ) { if ( $base instanceof Literal ) {
$litArg = trim( $this->getBase()->getArgs()[0] ); $litArg = trim( $base->getArgs()[0] );
$tu = TexUtil::getInstance(); $tu = TexUtil::getInstance();
// "sum", "bigcap", "bigcup", "prod" ... all are nullary macros. // "sum", "bigcap", "bigcup", "prod" ... all are nullary macros.
if ( $tu->nullary_macro( $litArg ) && !$tu->is_literal( $litArg ) ) { if ( $tu->nullary_macro( $litArg ) && !$tu->is_literal( $litArg ) ) {
@ -85,13 +80,13 @@ class FQ extends TexNode {
$mrow = new MMLmrow(); $mrow = new MMLmrow();
$emptyMrow = ""; $emptyMrow = "";
// In cases with empty curly preceding like: "{}_1^2\!\Omega_3^4" // In cases with empty curly preceding like: "{}_1^2\!\Omega_3^4"
if ( $this->getBase() instanceof Curly && $this->getBase()->isEmpty() ) { if ( $base->isCurly() && $base->isEmpty() ) {
$emptyMrow = $mrow->getEmpty(); $emptyMrow = $mrow->getEmpty();
} }
// This seems to be the common case // This seems to be the common case
$inner = $melement->encapsulateRaw( $inner = $melement->encapsulateRaw(
$emptyMrow . $emptyMrow .
$this->getBase()->renderMML( [], $state ) . $base->renderMML( [], $state ) .
$mrow->encapsulateRaw( $this->getDown()->renderMML( $arguments, $state ) ) . $mrow->encapsulateRaw( $this->getDown()->renderMML( $arguments, $state ) ) .
$mrow->encapsulateRaw( $this->getUp()->renderMML( $arguments, $state ) ) ); $mrow->encapsulateRaw( $this->getUp()->renderMML( $arguments, $state ) ) );

View file

@ -9,11 +9,19 @@ use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\BaseMappings;
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil; use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil;
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil; use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup;
use MediaWiki\Extension\Math\WikiTexVC\TexUtil; use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
class TexArray extends TexNode { class TexArray extends TexNode {
protected bool $curly = false;
public static function newCurly( ...$args ) {
$node = new self( ...$args );
$node->curly = true;
return $node;
}
public function __construct( ...$args ) { public function __construct( ...$args ) {
$nargs = []; $nargs = [];
@ -246,10 +254,10 @@ class TexArray extends TexNode {
if ( $styleArguments ) { if ( $styleArguments ) {
$state["styleargs"] = $styleArguments; $state["styleargs"] = $styleArguments;
if ( $next instanceof Curly ) { $mmlStyle = new MMLmstyle( "", $styleArguments );
$fullRenderedArray .= $mmlStyle->getStart();
if ( $next->isCurly() ) {
// Wrap with style-tags when the next element is a Curly which determines start and end tag. // Wrap with style-tags when the next element is a Curly which determines start and end tag.
$mmlStyle = new MMLmstyle( "", $styleArguments );
$fullRenderedArray .= $mmlStyle->getStart();
$fullRenderedArray .= $this->createMMLwithContext( $currentColor, $next, $state, $arguments ); $fullRenderedArray .= $this->createMMLwithContext( $currentColor, $next, $state, $arguments );
$fullRenderedArray .= $mmlStyle->getEnd(); $fullRenderedArray .= $mmlStyle->getEnd();
$mmlStyle = null; $mmlStyle = null;
@ -257,8 +265,6 @@ class TexArray extends TexNode {
$i++; $i++;
} else { } else {
// Start the style indicator in cases like \textstyle abc // Start the style indicator in cases like \textstyle abc
$mmlStyle = new MMLmstyle( "", $styleArguments );
$fullRenderedArray .= $mmlStyle->getStart();
$mmlStyles[] = $mmlStyle->getEnd(); $mmlStyles[] = $mmlStyle->getEnd();
} }
@ -280,6 +286,10 @@ class TexArray extends TexNode {
foreach ( array_reverse( $mmlStyles ) as $mmlStyleEnd ) { foreach ( array_reverse( $mmlStyles ) as $mmlStyleEnd ) {
$fullRenderedArray .= $mmlStyleEnd; $fullRenderedArray .= $mmlStyleEnd;
} }
if ( $this->curly && count( $this->getArgs() ) > 1 ) {
$mmlRow = new MMLmrow();
return $mmlRow->encapsulateRaw( $fullRenderedArray );
}
return $fullRenderedArray; return $fullRenderedArray;
} }
@ -340,7 +350,7 @@ class TexArray extends TexNode {
if ( isset( $this->args[0] ) && count( $this->args ) == 1 ) { if ( isset( $this->args[0] ) && count( $this->args ) == 1 ) {
return $this->args[0]->inCurlies(); return $this->args[0]->inCurlies();
} else { } else {
return '{' . $this->render() . '}'; return '{' . parent::render() . '}';
} }
} }
@ -455,4 +465,15 @@ class TexArray extends TexNode {
} }
} }
public function render() {
if ( $this->curly ) {
return $this->inCurlies();
}
return parent::render();
}
public function isCurly(): bool {
return $this->curly;
}
} }

View file

@ -204,4 +204,9 @@ class TexNode {
return false; return false;
} }
public function isCurly(): bool {
return false;
}
} }

View file

@ -11,7 +11,6 @@ use MediaWiki\Extension\Math\WikiTexVC\Nodes\Box;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Big; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Big;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\ChemFun2u; use MediaWiki\Extension\Math\WikiTexVC\Nodes\ChemFun2u;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\ChemWord; use MediaWiki\Extension\Math\WikiTexVC\Nodes\ChemWord;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Declh; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Declh;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Dollar; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Dollar;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
@ -462,14 +461,14 @@ class Parser {
private function peg_f26($b, $r) { return new Big($b, $r); } private function peg_f26($b, $r) { return new Big($b, $r); }
private function peg_f27($b) { return new Big($b, "]"); } private function peg_f27($b) { return new Big($b, "]"); }
private function peg_f28($l, $e, $r) {return new Lr($l, $r, ParserUtil::lst2arr($e)); } private function peg_f28($l, $e, $r) {return new Lr($l, $r, ParserUtil::lst2arr($e)); }
private function peg_f29($name, $e, $l) { return new Fun2sq($name, new Curly(ParserUtil::lst2arr($e)), $l); } private function peg_f29($name, $e, $l) { return new Fun2sq($name, ParserUtil::lst2arr($e, true), $l); }
private function peg_f30($name, $l) { return new Fun1($name, $l); } private function peg_f30($name, $l) { return new Fun1($name, $l); }
private function peg_f31($name, $l) {return new Fun1nb($name, $l); } private function peg_f31($name, $l) {return new Fun1nb($name, $l); }
private function peg_f32($name, $l) { return new Mhchem($name, $l); } private function peg_f32($name, $l) { return new Mhchem($name, $l); }
private function peg_f33($name, $l1, $l2) { return new Fun2($name, $l1, $l2); } private function peg_f33($name, $l1, $l2) { return new Fun2($name, $l1, $l2); }
private function peg_f34($name, $l1, $l2, $l3, $l4) { return new Fun4($name, $l1, $l2, $l3, $l4); } private function peg_f34($name, $l1, $l2, $l3, $l4) { return new Fun4($name, $l1, $l2, $l3, $l4); }
private function peg_f35($name, $l1, $l2) { return new Fun2nb($name, $l1, $l2); } private function peg_f35($name, $l1, $l2) { return new Fun2nb($name, $l1, $l2); }
private function peg_f36($e) { return new Curly(ParserUtil::lst2arr($e)); } private function peg_f36($e) { return ParserUtil::lst2arr($e, true); }
private function peg_f37($e1, $name, $e2) { return new Infix($name, ParserUtil::lst2arr($e1), ParserUtil::lst2arr($e2)); } private function peg_f37($e1, $name, $e2) { return new Infix($name, ParserUtil::lst2arr($e1), ParserUtil::lst2arr($e2)); }
private function peg_f38($m) { return new Matrix("matrix", ParserUtil::lst2arr($m)); } private function peg_f38($m) { return new Matrix("matrix", ParserUtil::lst2arr($m)); }
private function peg_f39($m) { return new Matrix("pmatrix", ParserUtil::lst2arr($m)); } private function peg_f39($m) { return new Matrix("pmatrix", ParserUtil::lst2arr($m)); }
@ -504,8 +503,8 @@ class Parser {
private function peg_f57($e, $l) { return $l; } private function peg_f57($e, $l) { return $l; }
private function peg_f58($e, $tail) { return new TexArray($e, $tail); } private function peg_f58($e, $tail) { return new TexArray($e, $tail); }
private function peg_f59() { return $this->text(); } private function peg_f59() { return $this->text(); }
private function peg_f60($cs) { return new Curly(new TexArray(new Literal($cs))); } private function peg_f60($cs) { return TexArray::newCurly(new Literal($cs)); }
private function peg_f61($num) { return new Curly(new TexArray(new Literal($num))); } private function peg_f61($num) { return TexArray::newCurly(new Literal($num)); }
private function peg_f62($p, $s) { return new TexArray($p,new TexArray(new Literal(" "),$s)); } private function peg_f62($p, $s) { return new TexArray($p,new TexArray(new Literal(" "),$s)); }
private function peg_f63($p) { return new TexArray($p,new TexArray()); } private function peg_f63($p) { return new TexArray($p,new TexArray()); }
private function peg_f64($m) { return new Literal($m); } private function peg_f64($m) { return new Literal($m); }
@ -516,9 +515,9 @@ class Parser {
private function peg_f69() { return new Literal(""); } private function peg_f69() { return new Literal(""); }
private function peg_f70($m) { return $m;} private function peg_f70($m) { return $m;}
private function peg_f71($c) { return new Literal($c); } private function peg_f71($c) { return new Literal($c); }
private function peg_f72($c) { return new Curly(new TexArray($c)); } private function peg_f72($c) { return TexArray::newCurly($c); }
private function peg_f73($c) { return new Dollar(ParserUtil::lst2arr($c)); } private function peg_f73($c) { return new Dollar(ParserUtil::lst2arr($c)); }
private function peg_f74($e) { return new Curly(new TexArray(new Literal($e))); } private function peg_f74($e) { return TexArray::newCurly(new Literal($e)); }
private function peg_f75($a, $b) { return new ChemWord(new Literal($a), new Literal($b)); } private function peg_f75($a, $b) { return new ChemWord(new Literal($a), new Literal($b)); }
private function peg_f76($a, $b) { return new ChemWord(new Literal($a), $b); } private function peg_f76($a, $b) { return new ChemWord(new Literal($a), $b); }
private function peg_f77($a, $b) { return new ChemWord(new Literal($a), new Dollar(ParserUtil::lst2arr($b))); } private function peg_f77($a, $b) { return new ChemWord(new Literal($a), new Dollar(ParserUtil::lst2arr($b))); }

View file

@ -10,10 +10,11 @@ class ParserUtil {
/** /**
* @param TexArray|null $l * @param TexArray|null $l
* @param bool $curly
* @return TexArray * @return TexArray
*/ */
public static function lst2arr( $l ) { public static function lst2arr( $l, $curly = false ) {
$arr = new TexArray(); $arr = $curly ? TexArray::newCurly() : new TexArray();
while ( $l !== null ) { while ( $l !== null ) {
$first = $l->first(); $first = $l->first();

View file

@ -143,7 +143,7 @@ lit
/ b:BIG SQ_CLOSE { return new Big($b, "]"); } / b:BIG SQ_CLOSE { return new Big($b, "]"); }
/ l:left e:expr r:right {return new Lr($l, $r, ParserUtil::lst2arr($e)); } / l:left e:expr r:right {return new Lr($l, $r, ParserUtil::lst2arr($e)); }
/ name:FUN_AR1opt e:expr_nosqc SQ_CLOSE l:lit /* must be before FUN_AR1 */ / name:FUN_AR1opt e:expr_nosqc SQ_CLOSE l:lit /* must be before FUN_AR1 */
{ return new Fun2sq($name, new Curly(ParserUtil::lst2arr($e)), $l); } { return new Fun2sq($name, ParserUtil::lst2arr($e, true), $l); }
/ name:FUN_AR1 l:lit { return new Fun1($name, $l); } / name:FUN_AR1 l:lit { return new Fun1($name, $l); }
/ name:FUN_AR1nb l:lit {return new Fun1nb($name, $l); } / name:FUN_AR1nb l:lit {return new Fun1nb($name, $l); }
/ name:FUN_MHCHEM l:chem_lit { return new Mhchem($name, $l); } / name:FUN_MHCHEM l:chem_lit { return new Mhchem($name, $l); }
@ -152,7 +152,7 @@ lit
/ name:FUN_AR2nb l1:lit l2:lit { return new Fun2nb($name, $l1, $l2); } / name:FUN_AR2nb l1:lit l2:lit { return new Fun2nb($name, $l1, $l2); }
/ BOX / BOX
/ CURLY_OPEN e:expr CURLY_CLOSE / CURLY_OPEN e:expr CURLY_CLOSE
{ return new Curly(ParserUtil::lst2arr($e)); } { return ParserUtil::lst2arr($e, true); }
/ CURLY_OPEN e1:ne_expr name:FUN_INFIX e2:ne_expr CURLY_CLOSE / CURLY_OPEN e1:ne_expr name:FUN_INFIX e2:ne_expr CURLY_CLOSE
{ return new Infix($name, ParserUtil::lst2arr($e1), ParserUtil::lst2arr($e2)); } { return new Infix($name, ParserUtil::lst2arr($e1), ParserUtil::lst2arr($e2)); }
/ BEGIN_MATRIX m:(array/matrix) END_MATRIX / BEGIN_MATRIX m:(array/matrix) END_MATRIX
@ -222,7 +222,7 @@ line
column_spec column_spec
= CURLY_OPEN cs:(one_col+ { return $this->text(); }) CURLY_CLOSE = CURLY_OPEN cs:(one_col+ { return $this->text(); }) CURLY_CLOSE
{ return new Curly(new TexArray(new Literal($cs))); } { return TexArray::newCurly(new Literal($cs)); }
one_col one_col
= [lrc] _ = [lrc] _
@ -237,7 +237,7 @@ one_col
alignat_spec alignat_spec
= CURLY_OPEN num:([0-9]+ { return $this->text(); }) _ CURLY_CLOSE = CURLY_OPEN num:([0-9]+ { return $this->text(); }) _ CURLY_CLOSE
{ return new Curly(new TexArray(new Literal($num))); } { return TexArray::newCurly(new Literal($num)); }
opt_pos opt_pos
= "[" _ [tcb] _ "]" _ = "[" _ [tcb] _ "]" _
@ -249,7 +249,7 @@ opt_pos
chem_lit chem_lit
= CURLY_OPEN e:chem_sentence CURLY_CLOSE { return new Curly(ParserUtil::lst2arr($e)); } = CURLY_OPEN e:chem_sentence CURLY_CLOSE { return ParserUtil::lst2arr($e, true); }
chem_sentence = chem_sentence =
_ p:chem_phrase " " s:chem_sentence { return new TexArray($p,new TexArray(new Literal(" "),$s)); } / _ p:chem_phrase " " s:chem_sentence { return new TexArray($p,new TexArray(new Literal(" "),$s)); } /
@ -275,14 +275,14 @@ chem_char =
chem_char_nl = chem_char_nl =
m:chem_script { return $m;} / m:chem_script { return $m;} /
CURLY_OPEN c:chem_text CURLY_CLOSE { return new Curly(new TexArray($c)); } / CURLY_OPEN c:chem_text CURLY_CLOSE { return TexArray::newCurly($c); } /
BEGIN_MATH c:expr END_MATH { return new Dollar(ParserUtil::lst2arr($c)); }/ BEGIN_MATH c:expr END_MATH { return new Dollar(ParserUtil::lst2arr($c)); }/
name:CHEM_BONDI l:chem_bond { return new Fun1($name, $l); } / name:CHEM_BONDI l:chem_bond { return new Fun1($name, $l); } /
m:chem_macro { return $m; } / m:chem_macro { return $m; } /
c:CHEM_NONLETTER { return new Literal($c); } c:CHEM_NONLETTER { return new Literal($c); }
chem_bond chem_bond
= CURLY_OPEN e:CHEM_BOND_TYPE CURLY_CLOSE { return new Curly(new TexArray(new Literal($e))); } = CURLY_OPEN e:CHEM_BOND_TYPE CURLY_CLOSE { return TexArray::newCurly(new Literal($e)); }
chem_script = chem_script =
a:CHEM_SUPERSUB b:CHEM_SCRIPT_FOLLOW { return new ChemWord(new Literal($a), new Literal($b)); } / a:CHEM_SUPERSUB b:CHEM_SCRIPT_FOLLOW { return new ChemWord(new Literal($a), new Literal($b)); } /

View file

@ -2,77 +2,53 @@
namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes; namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes;
use ArgumentCountError;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray; use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode;
use MediaWikiUnitTestCase; use MediaWikiUnitTestCase;
use RuntimeException;
use TypeError;
/** /**
* @covers \MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly * @covers \MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray
*/ */
class CurlyTest extends MediaWikiUnitTestCase { class CurlyTest extends MediaWikiUnitTestCase {
public function testEmptyDollar() {
$this->expectException( ArgumentCountError::class );
new Curly();
throw new ArgumentCountError( 'Should not create an empty curly' );
}
public function testOneArgumentCurly() {
$this->expectException( ArgumentCountError::class );
new Curly( new TexArray( new TexNode( 'a' ) ), new TexArray( new TexNode( 'b' ) ) );
throw new ArgumentCountError( 'Should not create a curly with more than one argument' );
}
public function testIncorrectTypeCurly() {
$this->expectException( TypeError::class );
new Curly( new TexNode() );
throw new RuntimeException( 'Should not create a curly with incorrect type' );
}
public function testRenderTexCurly() { public function testRenderTexCurly() {
$curly = new Curly( new TexArray() ); $curly = TexArray::newCurly();
$this->assertEquals( '{}', $curly->render(), 'Should render a curly with empty tex array' ); $this->assertEquals( '{}', $curly->render(), 'Should render a curly with empty tex array' );
} }
public function testRenderListCurly() { public function testRenderListCurly() {
$curly = new Curly( new TexArray( $curly = TexArray::newCurly(
new Literal( 'hello' ), new Literal( 'hello' ),
new Literal( ' ' ), new Literal( ' ' ),
new Literal( 'world' ) new Literal( 'world' )
) ); );
$this->assertEquals( '{hello world}', $curly->render(), 'Should render a list' ); $this->assertEquals( '{hello world}', $curly->render(), 'Should render a list' );
} }
public function testGetters() { public function testGetters() {
$curly = new Curly( new TexArray( new Literal( 'b' ) ) ); $curly = TexArray::newCurly( new Literal( 'b' ) );
$this->assertNotEmpty( $curly->getArgs() ); $this->assertNotEmpty( $curly->getArgs() );
$this->assertNotEmpty( $curly->getArg() );
} }
public function testNoExtraCurliesDQ() { public function testNoExtraCurliesDQ() {
$dq = new DQ( new Literal( 'a' ), $dq = new DQ( new Literal( 'a' ),
new Curly( new TexArray( new Literal( 'b' ) ) ) ); TexArray::newCurly( new Literal( 'b' ) ) );
$this->assertEquals( 'a_{b}', $dq->render(), 'Should not create extra curlies from dq' ); $this->assertEquals( 'a_{b}', $dq->render(), 'Should not create extra curlies from dq' );
} }
public function testNoExtraCurliesCurly() { public function testNoExtraCurliesCurly() {
$curly = new Curly( new TexArray( new Literal( 'a' ) ) ); $curly = TexArray::newCurly( new Literal( 'a' ) );
$this->assertEquals( '{a}', $curly->inCurlies(), 'Should not create extra curlies from curly' ); $this->assertEquals( '{a}', $curly->inCurlies(), 'Should not create extra curlies from curly' );
} }
public function testExtractIdentifierModsCurly() { public function testExtractIdentifierModsCurly() {
$curly = new Curly( new TexArray( new Literal( 'b' ) ) ); $curly = TexArray::newCurly( new Literal( 'b' ) );
$this->assertEquals( 'b', $curly->getModIdent(), 'Should extract identifier modifications' ); $this->assertEquals( 'b', $curly->getModIdent(), 'Should extract identifier modifications' );
} }
public function testExtractSubscirpts() { public function testExtractSubscirpts() {
$curly = new Curly( new TexArray( new Literal( 'b' ) ) ); $curly = TexArray::newCurly( new Literal( 'b' ) );
$this->assertEquals( 'b', $curly->extractSubscripts(), 'Should extract subscripts' ); $this->assertEquals( 'b', $curly->extractSubscripts(), 'Should extract subscripts' );
} }
} }

View file

@ -4,7 +4,6 @@ namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes;
use ArgumentCountError; use ArgumentCountError;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray; use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
@ -53,7 +52,7 @@ class DQTest extends MediaWikiUnitTestCase {
} }
public function testRenderEmptyDq() { public function testRenderEmptyDq() {
$dq = new DQ( new Curly( new TexArray() ), new Literal( 'b' ) ); $dq = new DQ( TexArray::newCurly(), new Literal( 'b' ) );
$this->assertStringContainsString( ( new MMLmrow() )->getEmpty(), $dq->renderMML() ); $this->assertStringContainsString( ( new MMLmrow() )->getEmpty(), $dq->renderMML() );
} }

View file

@ -4,7 +4,6 @@ namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes;
use ArgumentCountError; use ArgumentCountError;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray; use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray;
@ -47,8 +46,10 @@ class FQTest extends MediaWikiUnitTestCase {
} }
public function testRenderEmptyFq() { public function testRenderEmptyFq() {
$fq = new FQ( new Curly( new TexArray() ), new Literal( 'b' ), new Literal( 'c' ) ); $fq = new FQ( TexArray::newCurly(), new Literal( 'b' ), new Literal( 'c' ) );
$this->assertStringContainsString( ( new MMLmrow() )->getEmpty(), $fq->renderMML() ); $result = $fq->renderMML();
$this->assertStringContainsString( 'msubsup', $result );
$this->assertStringContainsString( ( new MMLmrow() )->getEmpty(), $result );
} }
public function testLatin() { public function testLatin() {

View file

@ -3,7 +3,6 @@
namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes; namespace MediaWiki\Extension\Math\Tests\WikiTexVC\Nodes;
use ArgumentCountError; use ArgumentCountError;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Curly;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ; use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ;
use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1; use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1;
@ -108,28 +107,28 @@ class Fun1Test extends MediaWikiUnitTestCase {
} }
public function testMathRmCurly() { public function testMathRmCurly() {
$f = new Fun1( '\\mathrm', new Curly( new TexArray( $f = new Fun1( '\\mathrm', TexArray::newCurly(
new Literal( 'a' ), new Literal( 'a' ),
new Literal( 'b' ), new Literal( 'b' ),
) ) ); ) );
$rendering = $f->renderMML(); $rendering = $f->renderMML();
preg_match_all( '/mathvariant="normal"/', $rendering, $matches ); preg_match_all( '/mathvariant="normal"/', $rendering, $matches );
$this->assertCount( 2, $matches[0] ); $this->assertCount( 2, $matches[0] );
} }
public function testMathRmDq() { public function testMathRmDq() {
$f = new Fun1( '\\mathrm', new Curly( new TexArray( $f = new Fun1( '\\mathrm', TexArray::newCurly(
new DQ( new Literal( 'a' ), new Literal( 'b' ) ) new DQ( new Literal( 'a' ), new Literal( 'b' ) )
) ) ); ) );
$rendering = $f->renderMML(); $rendering = $f->renderMML();
preg_match_all( '/mathvariant="normal"/', $rendering, $matches ); preg_match_all( '/mathvariant="normal"/', $rendering, $matches );
$this->assertCount( 2, $matches[0] ); $this->assertCount( 2, $matches[0] );
} }
public function testMathRmFq() { public function testMathRmFq() {
$f = new Fun1( '\\mathrm', new Curly( new TexArray( $f = new Fun1( '\\mathrm', TexArray::newCurly(
new FQ( new Literal( 'a' ), new Literal( 'b' ), new Literal( 'c' ) ) new FQ( new Literal( 'a' ), new Literal( 'b' ), new Literal( 'c' ) )
) ) ); ) );
$rendering = $f->renderMML(); $rendering = $f->renderMML();
preg_match_all( '/mathvariant="normal"/', $rendering, $matches ); preg_match_all( '/mathvariant="normal"/', $rendering, $matches );
$this->assertCount( 2, $matches[0] ); $this->assertCount( 2, $matches[0] );