From d940161a3e36a03739d14dc6e4561c90e9b123ae Mon Sep 17 00:00:00 2001 From: Stegmujo Date: Mon, 29 Aug 2022 12:15:58 +0200 Subject: [PATCH] Add further nodes Related code: https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/services/texvcjs/+/fb56991251b8889b554fc42ef9fe4825bc35d0ed/lib/nodes/ Bug: T312528 Change-Id: I8bd30bc45d2c23214b317ca0c04aa8e7d6a2da33 --- src/TexVC/Nodes/Big.php | 35 +++++ src/TexVC/Nodes/Box.php | 35 +++++ src/TexVC/Nodes/ChemFun2u.php | 38 +++++ src/TexVC/Nodes/ChemWord.php | 35 +++++ src/TexVC/Nodes/Curly.php | 36 +++++ src/TexVC/Nodes/Declh.php | 70 +++++++++ src/TexVC/Nodes/Dollar.php | 24 +++ src/TexVC/Nodes/Fun1.php | 74 ++++++++++ src/TexVC/Nodes/Fun1nb.php | 20 +++ src/TexVC/Nodes/Fun2.php | 82 +++++++++++ src/TexVC/Nodes/Fun2nb.php | 20 +++ src/TexVC/Nodes/Literal.php | 3 +- src/TexVC/Nodes/TexArray.php | 139 ++++++++++++++++++ src/TexVC/Nodes/TexNode.php | 10 +- src/TexVC/Nodes/UQ.php | 27 ++++ tests/phpunit/unit/TexVC/Nodes/BigTest.php | 49 ++++++ tests/phpunit/unit/TexVC/Nodes/BoxTest.php | 49 ++++++ .../unit/TexVC/Nodes/ChemFun2uTest.php | 43 ++++++ .../phpunit/unit/TexVC/Nodes/ChemWordTest.php | 44 ++++++ tests/phpunit/unit/TexVC/Nodes/CurlyTest.php | 72 +++++++++ tests/phpunit/unit/TexVC/Nodes/DeclhTest.php | 78 ++++++++++ tests/phpunit/unit/TexVC/Nodes/DollarTest.php | 59 ++++++++ tests/phpunit/unit/TexVC/Nodes/Fun1Test.php | 95 ++++++++++++ tests/phpunit/unit/TexVC/Nodes/Fun1nbTest.php | 44 ++++++ tests/phpunit/unit/TexVC/Nodes/Fun2Test.php | 52 +++++++ tests/phpunit/unit/TexVC/Nodes/Fun2nbTest.php | 44 ++++++ .../phpunit/unit/TexVC/Nodes/TexArrayTest.php | 77 ++++++++++ tests/phpunit/unit/TexVC/Nodes/UQTest.php | 45 ++++++ 28 files changed, 1396 insertions(+), 3 deletions(-) create mode 100644 src/TexVC/Nodes/Big.php create mode 100644 src/TexVC/Nodes/Box.php create mode 100644 src/TexVC/Nodes/ChemFun2u.php create mode 100644 src/TexVC/Nodes/ChemWord.php create mode 100644 src/TexVC/Nodes/Curly.php create mode 100644 src/TexVC/Nodes/Declh.php create mode 100644 src/TexVC/Nodes/Dollar.php create mode 100644 src/TexVC/Nodes/Fun1.php create mode 100644 src/TexVC/Nodes/Fun1nb.php create mode 100644 src/TexVC/Nodes/Fun2.php create mode 100644 src/TexVC/Nodes/Fun2nb.php create mode 100644 src/TexVC/Nodes/TexArray.php create mode 100644 src/TexVC/Nodes/UQ.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/BigTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/BoxTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/ChemFun2uTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/ChemWordTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/CurlyTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/DeclhTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/DollarTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/Fun1Test.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/Fun1nbTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/Fun2Test.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/Fun2nbTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/TexArrayTest.php create mode 100644 tests/phpunit/unit/TexVC/Nodes/UQTest.php diff --git a/src/TexVC/Nodes/Big.php b/src/TexVC/Nodes/Big.php new file mode 100644 index 000000000..ac6fc63fc --- /dev/null +++ b/src/TexVC/Nodes/Big.php @@ -0,0 +1,35 @@ +fname = $fname; + $this->arg = $arg; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return '{' . $this->fname . ' ' . $this->arg . '}'; + } + + public function extractIdentifiers( $args = null ) { + return []; + } + + public function name() { + return 'BIG'; + } +} diff --git a/src/TexVC/Nodes/Box.php b/src/TexVC/Nodes/Box.php new file mode 100644 index 000000000..93ee56ac9 --- /dev/null +++ b/src/TexVC/Nodes/Box.php @@ -0,0 +1,35 @@ +fname = $fname; + $this->arg = $arg; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return '{' . $this->fname . '{' . $this->arg . '}}'; + } + + public function extractIdentifiers( $args = null ) { + return []; + } + + public function name() { + return 'BOX'; + } +} diff --git a/src/TexVC/Nodes/ChemFun2u.php b/src/TexVC/Nodes/ChemFun2u.php new file mode 100644 index 000000000..f6c61748a --- /dev/null +++ b/src/TexVC/Nodes/ChemFun2u.php @@ -0,0 +1,38 @@ +fname = $fname; + $this->left = $left; + $this->right = $right; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return $this->fname . $this->left->inCurlies() . '_' . $this->right->inCurlies(); + } + + public function extractIdentifiers( $args = null ) { + return []; + } + + public function name() { + return 'CHEM_FUN2u'; + } +} diff --git a/src/TexVC/Nodes/ChemWord.php b/src/TexVC/Nodes/ChemWord.php new file mode 100644 index 000000000..5caed52f2 --- /dev/null +++ b/src/TexVC/Nodes/ChemWord.php @@ -0,0 +1,35 @@ +left = $left; + $this->right = $right; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return $this->left->render() . $this->right->render(); + } + + public function extractIdentifiers( $args = null ) { + return []; + } + + public function name() { + return 'CHEM_WORD'; + } +} diff --git a/src/TexVC/Nodes/Curly.php b/src/TexVC/Nodes/Curly.php new file mode 100644 index 000000000..6e1d7d2a6 --- /dev/null +++ b/src/TexVC/Nodes/Curly.php @@ -0,0 +1,36 @@ +arg = $arg; + } + + public function render() { + return $this->arg->inCurlies(); + } + + public function inCurlies() { + return $this->render(); + } + + public function extractSubscripts() { + return $this->arg->extractSubscripts(); + } + + public function getModIdent() { + return $this->arg->getModIdent(); + } + + public function name() { + return 'CURLY'; + } +} diff --git a/src/TexVC/Nodes/Declh.php b/src/TexVC/Nodes/Declh.php new file mode 100644 index 000000000..0b6773c8a --- /dev/null +++ b/src/TexVC/Nodes/Declh.php @@ -0,0 +1,70 @@ +fname = $fname; + $this->arg = $arg; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return '{' . $this->fname . ' ' . $this->arg->inCurlies() . '}'; + } + + public function extractIdentifiers( $args = null ) { + if ( $args == null ) { + $args = [ $this->arg ]; + } + + $identifier = parent::extractIdentifiers( $args ); + if ( isset( $identifier[0] ) ) { + return [ implode( '', $identifier ) ]; + } + return $identifier; + } + + public function extractSubscripts() { + $f = $this->fname; + // @see + // http://tex.stackexchange.com/questions/98406/which-command-should-i-use-for-textual-subscripts-in-math-mode + // cf https://phabricator.wikimedia.org/T56818 a is always RM + // for f there are only four cases + switch ( $f ) { + case '\\rm': + $f = '\\mathrm'; + break; + case '\\it': + $f = '\\mathit'; + break; + case '\\cal': + $f = '\\mathcal'; + break; + case '\\bf': + $f = '\\mathbf'; + } + + $x = $this->arg->extractSubscripts(); + if ( isset( $x[0] ) ) { + return [ $f . '{' . $x . '}' ]; + } + return parent::extractSubscripts(); + } + + public function name() { + return 'DECLh'; + } +} diff --git a/src/TexVC/Nodes/Dollar.php b/src/TexVC/Nodes/Dollar.php new file mode 100644 index 000000000..bfd20eb7f --- /dev/null +++ b/src/TexVC/Nodes/Dollar.php @@ -0,0 +1,24 @@ +fname = $fname; + $this->arg = $arg; + $this->tu = new TexUtil(); + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return '{' . $this->fname . ' ' . $this->arg->inCurlies() . '}'; + } + + public function extractIdentifiers( $args = null ) { + if ( $args == null ) { + $args = [ $this->arg ]; + } + $letterMods = array_keys( $this->tu->getBaseElements()['is_letter_mod'] ); + if ( in_array( $this->fname, $letterMods ) ) { + $ident = $this->arg->getModIdent(); + if ( !isset( $ident[0] ) ) { + return parent::extractIdentifiers( $args ); + } + // in difference to javascript code: taking first element of array here. + return [ $this->fname . '{' . $ident[0] . '}' ]; + + } elseif ( array_key_exists( $this->fname, $this->tu->getBaseElements()['ignore_identifier'] ) ) { + return []; + } + + return parent::extractIdentifiers( $args ); + } + + public function extractSubscripts() { + return $this->getSubs( $this->arg->extractSubscripts() ); + } + + public function getModIdent() { + return $this->getSubs( $this->arg->getModIdent() ); + } + + private function getSubs( $subs ) { + $letterMods = array_keys( $this->tu->getBaseElements()['is_letter_mod'] ); + + if ( isset( $subs[0] ) && in_array( $this->fname, $letterMods ) ) { + // in difference to javascript code: taking first element of array here. + return [ $this->fname . '{' . $subs[0] . '}' ]; + } + return []; + } + + public function name() { + return 'FUN1'; + } +} diff --git a/src/TexVC/Nodes/Fun1nb.php b/src/TexVC/Nodes/Fun1nb.php new file mode 100644 index 000000000..e6976b112 --- /dev/null +++ b/src/TexVC/Nodes/Fun1nb.php @@ -0,0 +1,20 @@ +render() . '}'; + } + + public function render() { + return $this->fname . ' ' . $this->arg->inCurlies() . ' '; + } +} diff --git a/src/TexVC/Nodes/Fun2.php b/src/TexVC/Nodes/Fun2.php new file mode 100644 index 000000000..f9febc9af --- /dev/null +++ b/src/TexVC/Nodes/Fun2.php @@ -0,0 +1,82 @@ +fname = $fname; + $this->arg1 = $arg1; + $this->arg2 = $arg2; + } + + public function inCurlies() { + return $this->render(); + } + + public function render() { + return '{' . $this->fname . ' ' . $this->arg1->inCurlies() . $this->arg2->inCurlies() . '}'; + } + + public function extractIdentifiers( $args = null ) { + if ( $args == null ) { + $args = [ $this->arg1, $this->arg2 ]; + } + return parent::extractIdentifiers( $args ); + } + + public function name() { + return 'FUN2'; + } +} + +/** + * 'use strict'; + * const TexNode = require('./texnode'); + * const assert = require('assert'); + * + * class Fun2 extends TexNode { + * constructor(fname, arg1, arg2) { + * assert.strictEqual( + * arguments.length, + * 3, + * 'Incorrect number or arguments'); + * assert.ok( + * (fname instanceof String || typeof fname === 'string') && + * arg1 instanceof TexNode, + * arg2 instanceof TexNode, + * 'Incorrect argument type'); + * super(fname, arg1, arg2); + * this.fname = fname; + * this.arg1 = arg1; + * this.arg2 = arg2; + * } + * + * inCurlies() { + * return this.render(); + * } + * + * render() { + * return '{' + this.fname + + * ' ' + this.arg1.inCurlies() + + * this.arg2.inCurlies() + '}'; + * } + * + * extractIdentifiers(args = [this.arg1, this.arg2]) { + * return super.extractIdentifiers(args); + * } + * get name() { + * return 'FUN2'; + * } + * } + */ diff --git a/src/TexVC/Nodes/Fun2nb.php b/src/TexVC/Nodes/Fun2nb.php new file mode 100644 index 000000000..d9dbbc18c --- /dev/null +++ b/src/TexVC/Nodes/Fun2nb.php @@ -0,0 +1,20 @@ +render() . '}'; + } + + public function render() { + return $this->fname . ' ' . $this->arg1->inCurlies() . $this->arg2->inCurlies(); + } +} diff --git a/src/TexVC/Nodes/Literal.php b/src/TexVC/Nodes/Literal.php index a504c7cd1..f5930d41d 100644 --- a/src/TexVC/Nodes/Literal.php +++ b/src/TexVC/Nodes/Literal.php @@ -19,7 +19,7 @@ class Literal extends TexNode { $tu = new TexUtil(); $this->literals = array_keys( $tu->getBaseElements()['is_literal'] ); $this->extendedLiterals = $this->literals; - array_push( $this->extendedLiterals, [ '\\infty', '\\emptyset' ] ); + array_push( $this->extendedLiterals, '\\infty', '\\emptyset' ); } public function extractIdentifiers( $args = null ) { @@ -39,7 +39,6 @@ class Literal extends TexNode { private function getLiteral( $lit, $regexp ) { $s = trim( $this->arg ); - if ( preg_match( $regexp, $s ) == 1 ) { return [ $s ]; } elseif ( in_array( $s, $lit ) ) { diff --git a/src/TexVC/Nodes/TexArray.php b/src/TexVC/Nodes/TexArray.php new file mode 100644 index 000000000..21da07d45 --- /dev/null +++ b/src/TexVC/Nodes/TexArray.php @@ -0,0 +1,139 @@ +args[0] ) && count( $this->args ) == 1 ) { + return $this->args[0]->inCurlies(); + } else { + return '{' . $this->render() . '}'; + } + } + + public function extractSubscripts() { + $y = []; + + foreach ( $this->args as $x ) { + $y = array_merge( $y, $x->extractSubscripts() ); + } + if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) { + return implode( '', $y ); + } + return []; + } + + public function extractIdentifiers( $args = null ) { + if ( $args == null ) { + $args = $this->args; + } + $list = parent::extractIdentifiers( $args ); + $outpos = 0; + $offset = 0; + $int = 0; + + for ( $inpos = 0; $inpos < count( $list ); $inpos++ ) { + $outpos = $inpos - $offset; + switch ( $list[$inpos] ) { + case '\'': + $list[$outpos - 1] .= '\''; + $offset++; + break; + case '\\int': + $int++; + $offset++; + break; + case '\\mathrm{d}': + case 'd': + if ( $int ) { + $int--; + $offset++; + break; + } + // no break + default: + if ( isset( $list[0] ) ) { + $list[$outpos] = $list[$inpos]; + } + } + } + return array_slice( $list, 0, count( $list ) - $offset ); + } + + public function getModIdent() { + $y = []; + + foreach ( $this->args as $x ) { + $y = array_merge( $y, $x->getModIdent() ); + } + + if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) { + return implode( "", $y ); + } + return []; + } + + public function push( ...$elements ) { + self::checkInput( $elements ); + + array_push( $this->args, ...$elements ); + } + + /** + * @throws InvalidArgumentException if first value not defined + * @return TexNode|string first value + */ + public function first() { + if ( isset( $this->args[0] ) ) { + return $this->args[0]; + } else { + throw new InvalidArgumentException( 'Input arguments not have been filled.' ); + } + } + + /** + * @throws InvalidArgumentException if second value not defined + * @return TexNode|string second value + */ + public function second() { + if ( isset( $this->args[1] ) ) { + return $this->args[1]; + } else { + throw new InvalidArgumentException( 'Input arguments not have been filled.' ); + } + } + + public function name() { + return 'ARRAY'; + } + + /** + * @throws InvalidArgumentException if args not of correct type + * @param TexNode[] $args input args + * @return void + */ + private static function checkInput( $args ): void { + foreach ( $args as $arg ) { + if ( !( $arg instanceof TexNode ) ) { + throw new InvalidArgumentException( 'Wrong input type specified in input elements.' ); + } + } + } +} diff --git a/src/TexVC/Nodes/TexNode.php b/src/TexVC/Nodes/TexNode.php index ab2322951..6cde08837 100644 --- a/src/TexVC/Nodes/TexNode.php +++ b/src/TexVC/Nodes/TexNode.php @@ -9,7 +9,7 @@ use InvalidArgumentException; class TexNode { /** @var list */ - private $args; + protected $args; /** * Creates a TexNode @@ -38,6 +38,14 @@ class TexNode { return $child; } + public function getLength(): ?int { + if ( isset( $this->args[0] ) ) { + return count( $this->args ); + } else { + return 0; + } + } + /** * Wraps the rendered result in curly brackets. * @return string rendered result in curlies. diff --git a/src/TexVC/Nodes/UQ.php b/src/TexVC/Nodes/UQ.php new file mode 100644 index 000000000..c7bf78e85 --- /dev/null +++ b/src/TexVC/Nodes/UQ.php @@ -0,0 +1,27 @@ +base = $base; + $this->up = $down; + } + + public function render() { + return $this->base->render() . '^' . $this->up->inCurlies(); + } + + public function name() { + return 'UQ'; + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/BigTest.php b/tests/phpunit/unit/TexVC/Nodes/BigTest.php new file mode 100644 index 000000000..e0fbb00eb --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/BigTest.php @@ -0,0 +1,49 @@ +expectException( ArgumentCountError::class ); + new Big(); + throw new ArgumentCountError( 'Should not create an empty big' ); + } + + public function testOneArgumentBig() { + $this->expectException( ArgumentCountError::class ); + new Big( '\\big' ); + throw new ArgumentCountError( 'Should not create a big with one argument' ); + } + + public function testIncorrectTypeBig() { + $this->expectException( TypeError::class ); + new Big( '\\big', new Literal( 'a' ) ); + throw new RuntimeException( 'Should not create a big with incorrect type' ); + } + + public function testBasicFunctionBig() { + $big = new Big( '\\big', 'a' ); + $this->assertEquals( '{\\big a}', $big->render(), 'Should create a basic function' ); + } + + public function testExtractIdentifiersBig() { + $big = new Big( '\\big', 'a' ); + $this->assertEquals( [], $big->extractIdentifiers(), 'Should extract identifiers' ); + } + + public function testCurliesBig() { + $big = new Big( '\\big', 'a' ); + $this->assertEquals( '{\\big a}', $big->inCurlies(), 'Should create exactly one set of curlies' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/BoxTest.php b/tests/phpunit/unit/TexVC/Nodes/BoxTest.php new file mode 100644 index 000000000..2866b6f60 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/BoxTest.php @@ -0,0 +1,49 @@ +expectException( ArgumentCountError::class ); + new Box(); + throw new ArgumentCountError( 'Should not create an empty box' ); + } + + public function testOneArgumentBox() { + $this->expectException( ArgumentCountError::class ); + new Box( '\\hbox' ); + throw new ArgumentCountError( 'Should not create a box with one argument' ); + } + + public function testIncorrectTypeBox() { + $this->expectException( TypeError::class ); + new Box( '\\hbox', new Literal( 'a' ) ); + throw new RuntimeException( 'Should not create a box with incorrect type' ); + } + + public function testBasicFunctionBox() { + $box = new Box( '\\hbox', 'a' ); + $this->assertEquals( '{\\hbox{a}}', $box->render(), 'Should create a basic function' ); + } + + public function testExtractIdentifiersBox() { + $box = new Box( '\\hbox', 'a' ); + $this->assertEquals( [], $box->extractIdentifiers(), 'Should extract identifiers' ); + } + + public function testCurliesBox() { + $box = new Box( '\\hbox', 'a' ); + $this->assertEquals( '{\\hbox{a}}', $box->inCurlies(), 'Should create exactly one set of curlies' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/ChemFun2uTest.php b/tests/phpunit/unit/TexVC/Nodes/ChemFun2uTest.php new file mode 100644 index 000000000..d2a2828bd --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/ChemFun2uTest.php @@ -0,0 +1,43 @@ +expectException( ArgumentCountError::class ); + new ChemFun2u(); + throw new ArgumentCountError( 'Should not create an empty ChemFun2u' ); + } + + public function testOneArgumentChemFun2u() { + $this->expectException( ArgumentCountError::class ); + new ChemFun2u( 'a' ); + throw new ArgumentCountError( 'Should not create a ChemFun2u with one argument' ); + } + + public function testIncorrectTypeChemFun2u() { + $this->expectException( TypeError::class ); + new ChemFun2u( 'a', 'b', 'c' ); + throw new TypeError( 'Should not create a ChemFun2u with incorrect type' ); + } + + public function testBasicChemFun2u() { + $fun2u = new ChemFun2u( 'a', new Literal( 'b' ), new Literal( 'c' ) ); + $this->assertEquals( 'a{b}_{c}', $fun2u->render(), 'Should create a basic ChemFun2u' ); + } + + public function testExtractIdentifiers() { + $fun2u = new ChemFun2u( 'a', new Literal( 'b' ), new Literal( 'c' ) ); + $this->assertEquals( [], $fun2u->extractIdentifiers(), 'Should extract identifiers' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/ChemWordTest.php b/tests/phpunit/unit/TexVC/Nodes/ChemWordTest.php new file mode 100644 index 000000000..4fcfad034 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/ChemWordTest.php @@ -0,0 +1,44 @@ +expectException( ArgumentCountError::class ); + new ChemWord(); + throw new ArgumentCountError( 'Should not create an empty ChemWord' ); + } + + public function testOneArgumentChemWord() { + $this->expectException( ArgumentCountError::class ); + new ChemWord( new Literal( 'a' ) ); + throw new ArgumentCountError( 'Should not create a ChemWord with one argument' ); + } + + public function testIncorrectTypeChemWord() { + $this->expectException( TypeError::class ); + new ChemWord( 'a', 'b' ); + throw new RuntimeException( 'Should not create a ChemWord with incorrect type' ); + } + + public function testBasicFunctionChemWord() { + $chemWord = new ChemWord( new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( 'ab', $chemWord->render(), 'Should create a basic function' ); + } + + public function testExtractIdentifiersBox() { + $chemWord = new ChemWord( new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( [], $chemWord->extractIdentifiers(), 'Should extract identifiers' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/CurlyTest.php b/tests/phpunit/unit/TexVC/Nodes/CurlyTest.php new file mode 100644 index 000000000..d8b7e37d4 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/CurlyTest.php @@ -0,0 +1,72 @@ +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() { + $curly = new Curly( new TexArray() ); + $this->assertEquals( '{}', $curly->render(), 'Should render a curly with empty tex array' ); + } + + public function testRenderListCurly() { + $curly = new Curly( new TexArray( + new Literal( 'hello' ), + new Literal( ' ' ), + new Literal( 'world' ) + ) ); + $this->assertEquals( '{hello world}', $curly->render(), 'Should render a list' ); + } + + public function testNoExtraCurliesDQ() { + $dq = new DQ( new Literal( 'a' ), + new Curly( new TexArray( new Literal( 'b' ) ) ) ); + $this->assertEquals( 'a_{b}', $dq->render(), 'Should not create extra curlies from dq' ); + } + + public function testNoExtraCurliesCurly() { + $curly = new Curly( new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( '{a}', $curly->inCurlies(), 'Should not create extra curlies from curly' ); + } + + public function testExtractIdentifierModsCurly() { + $curly = new Curly( new TexArray( new Literal( 'b' ) ) ); + $this->assertEquals( 'b', $curly->getModIdent(), 'Should extract identifier modifications' ); + } + + public function testExtractSubscirpts() { + $curly = new Curly( new TexArray( new Literal( 'b' ) ) ); + $this->assertEquals( 'b', $curly->extractSubscripts(), 'Should extract subscripts' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/DeclhTest.php b/tests/phpunit/unit/TexVC/Nodes/DeclhTest.php new file mode 100644 index 000000000..d9882802c --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/DeclhTest.php @@ -0,0 +1,78 @@ +expectException( ArgumentCountError::class ); + new Declh(); + throw new ArgumentCountError( 'Should not create an empty Declh' ); + } + + public function testOneArgumentDeclh() { + $this->expectException( ArgumentCountError::class ); + new Declh( '\\f' ); + throw new ArgumentCountError( 'Should not create a Declh with one argument' ); + } + + public function testIncorrectTypeDeclh() { + $this->expectException( TypeError::class ); + new Declh( '\\f', 'x' ); + throw new RuntimeException( 'Should not create a Declh with incorrect type' ); + } + + public function testBasicFunctionDeclh() { + $f = new Declh( '\\rm', new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( '{\\rm {a}}', $f->render(), 'Should create a basic function' ); + } + + public function testTwoArgsFunctionDeclh() { + $f = new Declh( '\\rm', + new TexArray( new Literal( 'a' ), new Literal( 'b' ) ) ); + $this->assertEquals( '{\\rm {ab}}', + $f->render(), 'Should create a function with two arguments' ); + } + + public function testCurliesDeclh() { + $f = new Declh( '\\f', new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( '{\\f {a}}', $f->inCurlies(), 'Should create exactly one set of curlies' ); + } + + public function testExtractIdentifiersDeclh() { + $f = new Declh( '\\rm', new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( [ 'a' ], $f->extractIdentifiers(), 'Should extract identifiers' ); + } + + public function testExtractIdentifiersMultiDeclh() { + $f = new Declh( '\\rm', new TexArray( new Literal( 'a' ), new Literal( 'b' ) ) ); + $this->assertEquals( [ 'ab' ], $f->extractIdentifiers(), 'Should extract multiple identifiers' ); + } + + public function testNotExtractSomeSubscripts() { + $f = new Declh( '\\bf', new TexArray( new Literal( '' ) ) ); + $this->assertEquals( [], $f->extractSubscripts(), + 'Should not extract empty font modifier subscripts identifiers' ); + } + + public function testSubscriptsForFontMod() { + $mods = [ 'rm','it','cal','bf' ]; + foreach ( $mods as $mod ) { + $f = new Declh( "\\${mod}", new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( [ "\\math${mod}{a}" ], $f->extractSubscripts(), + "Should extract subscripts for ${mod} font modification" ); + } + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/DollarTest.php b/tests/phpunit/unit/TexVC/Nodes/DollarTest.php new file mode 100644 index 000000000..b321c8490 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/DollarTest.php @@ -0,0 +1,59 @@ +expectException( ArgumentCountError::class ); + new Dollar(); + throw new ArgumentCountError( 'Should not create an empty dollar' ); + } + + public function testOneArgumentDollar() { + $this->expectException( ArgumentCountError::class ); + new Dollar( new TexArray(), new TexArray() ); + throw new ArgumentCountError( 'Should not create a dollar with more than one argument' ); + } + + public function testIncorrectTypeDollar() { + $this->expectException( TypeError::class ); + new Dollar( new TexNode() ); + throw new RuntimeException( 'Should not create a dollar with incorrect type' ); + } + + public function testRenderTexDollar() { + $dollar = new Dollar( new TexArray() ); + $this->assertEquals( '$$', $dollar->render(), 'Should render a dollar with empty tex array' ); + } + + public function testRenderListDollar() { + $dollar = new Dollar( new TexArray( + new Literal( 'hello' ), + new Literal( ' ' ), + new Literal( 'world' ) + ) ); + $this->assertEquals( '$hello world$', $dollar->render(), 'Should render a list' ); + } + + public function testExtractIdentifiersDollar() { + $dollar = new Dollar( new TexArray( + new Literal( 'a' ), + new Literal( 'b' ), + new Literal( 'c' ) + ) ); + $this->assertEquals( [], $dollar->extractIdentifiers(), 'Should extract identifiers' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/Fun1Test.php b/tests/phpunit/unit/TexVC/Nodes/Fun1Test.php new file mode 100644 index 000000000..0aee85668 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/Fun1Test.php @@ -0,0 +1,95 @@ +expectException( ArgumentCountError::class ); + new Fun1(); + throw new ArgumentCountError( 'Should not create an empty fun1' ); + } + + public function testOneArgumentFun1() { + $this->expectException( ArgumentCountError::class ); + new Fun1( '\\f' ); + throw new ArgumentCountError( 'Should not create a fun1 with one argument' ); + } + + public function testIncorrectTypeFun1() { + $this->expectException( TypeError::class ); + new Fun1( '\\f', 'x' ); + throw new RuntimeException( 'Should not create a fun1 with incorrect type' ); + } + + public function testBasicFunctionFun1() { + $f = new Fun1( '\\f', new Literal( 'a' ) ); + $this->assertEquals( '{\\f {a}}', $f->render(), + 'Should create a basic function' ); + } + + public function testCurliesFun1() { + $f = new Fun1( '\\f', new Literal( 'a' ) ); + $this->assertEquals( '{\\f {a}}', $f->inCurlies(), + 'Should create exactly one set of curlies' ); + } + + public function testExtractIdentifiersFun1() { + $f = new Fun1( '\\mathbf', new Literal( 'B' ) ); + $this->assertEquals( [ '\\mathbf{B}' ], $f->extractIdentifiers(), + 'Should extract identifiers' ); + } + + public function testExtractExtendedLiteralsFun1() { + $f = new Fun1( '\\mathbf', new Literal( '\\infty' ) ); + $this->assertEquals( [], $f->extractIdentifiers(), + 'Should not extract extended literals as identifiers.' ); + } + + public function testExtractPhantomIdentifiers() { + $f = new Fun1( '\\hphantom', new Literal( 'A' ) ); + $this->assertEquals( [], $f->extractIdentifiers(), + 'Should not extract phantom identifiers.' ); + } + + public function testIgnoreUnknownFunctions() { + $f = new Fun1( '\\unknown', new Literal( 'A' ) ); + $this->assertEquals( [ 'A' ], $f->extractIdentifiers(), + 'Should ignore unknown functions.' ); + } + + public function testExtractIdentifierMods() { + $f = new Fun1( '\\mathbf', new Literal( 'B' ) ); + $this->assertEquals( [ '\\mathbf{B}' ], $f->getModIdent(), + 'Should extract identifier modifications.' ); + } + + public function testExtractSubscripts() { + $f = new Fun1( '\\mathbf', new Literal( 'B' ) ); + $this->assertEquals( [ '\\mathbf{B}' ], + $f->extractSubscripts(), 'Should extract subscripts.' ); + } + + public function testExtractSubscriptsExtendedLits() { + $f = new Fun1( '\\mathbf', new Literal( '\\infty' ) ); + $this->assertEquals( [ '\\mathbf{\\infty}' ], $f->extractSubscripts(), + 'Should extract subscripts for extended literals.' ); + } + + public function testExtractSubscriptsEmptyMods() { + $f = new Fun1( '\\mathbf', new Literal( '' ) ); + $this->assertEquals( [], $f->extractSubscripts(), + 'Should not extract subscripts for empty mods.' ); + } + +} diff --git a/tests/phpunit/unit/TexVC/Nodes/Fun1nbTest.php b/tests/phpunit/unit/TexVC/Nodes/Fun1nbTest.php new file mode 100644 index 000000000..501301b04 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/Fun1nbTest.php @@ -0,0 +1,44 @@ +expectException( ArgumentCountError::class ); + new Fun1nb(); + throw new ArgumentCountError( 'Should not create an empty fun1nb' ); + } + + public function testOneArgumentFun1nb() { + $this->expectException( ArgumentCountError::class ); + new Fun1nb( '\\f' ); + throw new ArgumentCountError( 'Should not create a fun1nb with one argument' ); + } + + public function testIncorrectTypeFun1nb() { + $this->expectException( TypeError::class ); + new Fun1nb( '\\f', 'x' ); + throw new TypeError( 'Should not create a fun1nb with incorrect type' ); + } + + public function testBasicFunctionFun1nb() { + $fq = new Fun1nb( '\\f', new Literal( 'a' ) ); + $this->assertEquals( '\\f {a} ', $fq->render(), 'Should create a basic function' ); + } + + public function testCurliesFun1nb() { + $f = new Fun1nb( '\\f', new Literal( 'a' ) ); + $this->assertEquals( '{\\f {a} }', $f->inCurlies(), + 'Should create exactly one set of curlies' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/Fun2Test.php b/tests/phpunit/unit/TexVC/Nodes/Fun2Test.php new file mode 100644 index 000000000..2af753fff --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/Fun2Test.php @@ -0,0 +1,52 @@ +expectException( ArgumentCountError::class ); + new Fun2(); + throw new ArgumentCountError( 'Should not create an empty fun2' ); + } + + public function testOneArgumentFun2() { + $this->expectException( ArgumentCountError::class ); + new Fun2( '\\f' ); + throw new ArgumentCountError( 'Should not create a fun2 with one argument' ); + } + + public function testIncorrectTypeFun2() { + $this->expectException( TypeError::class ); + new Fun2( '\\f', 'x', 'y' ); + throw new RuntimeException( 'Should not create a fun2 with incorrect types' ); + } + + public function testBasicFunctionFun2() { + $f = new Fun2( '\\f', new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( '{\\f {a}{b}}', $f->render(), + 'Should create a basic function' ); + } + + public function testCurliesFun2() { + $f = new Fun2( '\\f', new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( '{\\f {a}{b}}', $f->inCurlies(), + 'Should create exactly one set of curlies' ); + } + + public function testExtractIdentifiersFun2() { + $f = new Fun2( '\\f', new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( [ 'a','b' ], $f->extractIdentifiers(), + 'Should extract identifiers' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/Fun2nbTest.php b/tests/phpunit/unit/TexVC/Nodes/Fun2nbTest.php new file mode 100644 index 000000000..03c47e736 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/Fun2nbTest.php @@ -0,0 +1,44 @@ +expectException( ArgumentCountError::class ); + new Fun2nb(); + throw new ArgumentCountError( 'Should not create an empty fun2nb' ); + } + + public function testOneArgumentFun2nb() { + $this->expectException( ArgumentCountError::class ); + new Fun2nb( '\\f' ); + throw new ArgumentCountError( 'Should not create a fun2nb with one argument' ); + } + + public function testIncorrectTypeFun2nb() { + $this->expectException( TypeError::class ); + new Fun2nb( '\\f', 'x', 'y' ); + throw new TypeError( 'Should not create a fun2nb with incorrect type' ); + } + + public function testBasicFunctionFun2nb() { + $fq = new Fun2nb( '\\f', new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( '\\f {a}{b}', $fq->render(), 'Should create a basic function' ); + } + + public function testCurliesFun2nb() { + $f = new Fun2nb( '\\f', new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( '{\\f {a}{b}}', $f->inCurlies(), + 'Should create exactly one set of curlies' ); + } +} diff --git a/tests/phpunit/unit/TexVC/Nodes/TexArrayTest.php b/tests/phpunit/unit/TexVC/Nodes/TexArrayTest.php new file mode 100644 index 000000000..3c65a8661 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/TexArrayTest.php @@ -0,0 +1,77 @@ +assertEquals( new TexArray() instanceof TexNode, true, + 'Should create an instance of TexNode' ); + } + + public function testConcatOutput() { + $ta = new TexArray( new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( 'ab', $ta->render(), 'Should concatenate its input' ); + } + + public function testExtractCurlies() { + $n = new TexNode( new TexArray( new Literal( 'a' ) ) ); + $this->assertEquals( '{a}', $n->inCurlies(), + 'Should create exactly one pair of curlies' ); + } + + public function testExtractIdentifiers() { + $n = new TexArray( new Literal( 'd' ) ); + $this->assertEquals( [ 'd' ], $n->extractIdentifiers(), + 'Should extract identifiers' ); + } + + public function testExtractIdentifiersFromArg() { + $n = new TexArray(); + $this->assertEquals( [ 'd' ], + $n->extractIdentifiers( [ new Literal( 'd' ) ] ), + 'Should extract identifiers from the argument' ); + } + + public function testExtractSplitIdentifiers() { + $n = new TexArray( new Literal( 'a' ), new Literal( '\'' ) ); + $this->assertEquals( [ 'a\'' ], $n->extractIdentifiers(), + 'Should extract split identifiers' ); + } + + public function testNotConfuseIntegralsIdentifiers() { + $n = new TexArray( new Literal( 'd' ), new Literal( '\\int' ) ); + $this->assertEquals( [ 'd' ], $n->extractIdentifiers(), + 'Should not confuse integrals and identifiers' ); + } + + public function testNotConfuseIntegralD() { + $n = new TexArray( new Literal( '\\int' ), new Literal( 'd' ) ); + $this->assertEquals( [], $n->extractIdentifiers(), 'Should not confuse integral d with d identifier' ); + } + + public function testNotConfuseUprightIntegralD() { + $n = new TexArray( new Literal( '\\int' ), new Literal( '\\mathrm{d}' ) ); + $this->assertEquals( [], $n->extractIdentifiers(), + 'Should not confuse upright integral d with d identifier' ); + } + + public function testExtractIdentifierMods() { + $n = new TexArray( new TexNode( '' ) ); + $this->assertEquals( [], $n->getModIdent(), 'Should extract identifier modifications' ); + } + + public function testExtractSubscripts() { + $n = new TexArray( new TexNode( '' ) ); + $this->assertEquals( [], $n->extractSubscripts(), 'Should extract subscripts' ); + } + +} diff --git a/tests/phpunit/unit/TexVC/Nodes/UQTest.php b/tests/phpunit/unit/TexVC/Nodes/UQTest.php new file mode 100644 index 000000000..6fe739d53 --- /dev/null +++ b/tests/phpunit/unit/TexVC/Nodes/UQTest.php @@ -0,0 +1,45 @@ +expectException( ArgumentCountError::class ); + new UQ(); + throw new ArgumentCountError( 'Should not create an empty uq' ); + } + + public function testOneArgumentUQ() { + $this->expectException( ArgumentCountError::class ); + new UQ( new Literal( 'a' ) ); + throw new ArgumentCountError( 'Should not create a uq with one argument' ); + } + + public function testIncorrectTypeUQ() { + $this->expectException( TypeError::class ); + new UQ( 'a', 'b' ); + throw new RuntimeException( 'Should not create a uq with incorrect type' ); + } + + public function testBasicUQ() { + $uq = new UQ( new Literal( 'a' ), new Literal( 'b' ) ); + $this->assertEquals( 'a^{b}', $uq->render(), 'Should create a basic uq' ); + } + + public function testEmptyBaseUQ() { + $uq = new UQ( new TexNode(), new Literal( 'b' ) ); + $this->assertEquals( '^{b}', $uq->render(), 'Should create an empty base uq' ); + } +}