Tools 是非官方社区Wiki。社区文档正在编写中,欢迎参与。 Wiki编辑答疑群:717421103
版本250722.2
全站通知:

帮助:解析函数/expr

来自WIKI实验室WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索

expr是一个解析函数。帮助:解析函数页列出了所有解析函数的说明。

expr

计算数学表达式。出自扩展 ParserFunctions

此函数在Scribunto扩展中也可用,函数名为mw.ext.ParserFunctions.expr

语法

{{#expr: 表达式 }}

支持的表达式(优先级自上而下)
类型 运算符
括号 ( )
数字 1234.5e (2.718) 、 pi (3.142)
e(科学计数法) + -(正负号)
一元
运算符
not ceil trunc floor abs exp ln sin cos tan acos asin atan
二元
运算符
^
* / div mod
+ -
round
逻辑运算符 = != <> > < >= <=
and
or

说明:

  • 详细功能见Manual:Expr parser function syntax
  • 结果精度、格式取决于服务器系统和语言的数字格式。
  • 逻辑运算中非零即真,就是说只有0为false,其他数为true。字符串比较可以用#ifeq
  • 部分魔术字可指定参数 R 避免结果包含逗号,如{{NUMBEROFUSERS:R}}9993365
  • 溢出可能导致未预期报错:{{#expr:123 mod 2^64}}零除。
  • 支持使用 #iferror 处理错误


示例

  • {{#expr: 1 + 2 * 3 }}7
  • {{#expr: 1.1 or 0}}1 逻辑运算中非零即真
  • {{#expr: 1+ }}表达式错误:缺少+的操作数。

更多示例

缺少内容。本Wiki由与你一样的用户共同编写,请帮助我们完善内容:开始编辑

底层代码

/** mediawiki-1.37.0\extensions\ParserFunctions\includes\ParserFunctions.php
 * {{#expr: expression }}
 *
 * @link https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##expr
 *
 * @param Parser $parser
 * @param string $expr
 * @return string
 */
public static function expr( Parser $parser, $expr = '' ) {
	try {
		return self::getExprParser()->doExpression( $expr );
	} catch ( ExprError $e ) {
		return '<strong class="error">' . htmlspecialchars( $e->getUserFriendlyMessage() ) . '</strong>';
	}
}

/** mediawiki-1.37.0\extensions\ParserFunctions\includes\ExprParser.php
 * Evaluate a mathematical expression
 *
 * The algorithm here is based on the infix to RPN algorithm given in
 * http://montcs.bloomu.edu/~bobmon/Information/RPN/infix2rpn.shtml
 * It's essentially the same as Dijkstra's shunting yard algorithm.
 * @param string $expr
 * @return string
 * @throws ExprError
 */
public function doExpression( $expr ) {
	$operands = [];
	$operators = [];

	# Unescape inequality operators
	$expr = strtr( $expr, [ '&lt;' => '<', '&gt;' => '>',
		'&minus;' => '-', '−' => '-' ] );

	$p = 0;
	$end = strlen( $expr );
	$expecting = 'expression';
	$name = '';

	while ( $p < $end ) {
		if ( count( $operands ) > self::MAX_STACK_SIZE || count( $operators ) > self::MAX_STACK_SIZE ) {
			throw new ExprError( 'stack_exhausted' );
		}
		$char = $expr[$p];
		$char2 = substr( $expr, $p, 2 );

		// Mega if-elseif-else construct
		// Only binary operators fall through for processing at the bottom, the rest
		// finish their processing and continue

		// First the unlimited length classes

		// @phan-suppress-next-line PhanParamSuspiciousOrder false positive
		if ( strpos( self::EXPR_WHITE_CLASS, $char ) !== false ) {
			// Whitespace
			$p += strspn( $expr, self::EXPR_WHITE_CLASS, $p );
			continue;
			// @phan-suppress-next-line PhanParamSuspiciousOrder false positive
		} elseif ( strpos( self::EXPR_NUMBER_CLASS, $char ) !== false ) {
			// Number
			if ( $expecting !== 'expression' ) {
				throw new ExprError( 'unexpected_number' );
			}

			// Find the rest of it
			$length = strspn( $expr, self::EXPR_NUMBER_CLASS, $p );
			// Convert it to float, silently removing double decimal points
			$operands[] = (float)substr( $expr, $p, $length );
			$p += $length;
			$expecting = 'operator';
			continue;
		} elseif ( ctype_alpha( $char ) ) {
			// Word
			// Find the rest of it
			$remaining = substr( $expr, $p );
			if ( !preg_match( '/^[A-Za-z]*/', $remaining, $matches ) ) {
				// This should be unreachable
				throw new ExprError( 'preg_match_failure' );
			}
			$word = strtolower( $matches[0] );
			$p += strlen( $word );

			// Interpret the word
			if ( !isset( self::WORDS[$word] ) ) {
				throw new ExprError( 'unrecognised_word', $word );
			}
			$op = self::WORDS[$word];
			switch ( $op ) {
				// constant
				case self::EXPR_EXPONENT:
					if ( $expecting !== 'expression' ) {
						break;
					}
					$operands[] = exp( 1 );
					$expecting = 'operator';
					continue 2;
				case self::EXPR_PI:
					if ( $expecting !== 'expression' ) {
						throw new ExprError( 'unexpected_number' );
					}
					$operands[] = pi();
					$expecting = 'operator';
					continue 2;
				// Unary operator
				case self::EXPR_NOT:
				case self::EXPR_SINE:
				case self::EXPR_COSINE:
				case self::EXPR_TANGENS:
				case self::EXPR_ARCSINE:
				case self::EXPR_ARCCOS:
				case self::EXPR_ARCTAN:
				case self::EXPR_EXP:
				case self::EXPR_LN:
				case self::EXPR_ABS:
				case self::EXPR_FLOOR:
				case self::EXPR_TRUNC:
				case self::EXPR_CEIL:
				case self::EXPR_SQRT:
					if ( $expecting !== 'expression' ) {
						throw new ExprError( 'unexpected_operator', $word );
					}
					$operators[] = $op;
					continue 2;
			}
			// Binary operator, fall through
			$name = $word;
		} elseif ( $char2 === '<=' ) {
			$name = $char2;
			$op = self::EXPR_LESSEQ;
			$p += 2;
		} elseif ( $char2 === '>=' ) {
			$name = $char2;
			$op = self::EXPR_GREATEREQ;
			$p += 2;
		} elseif ( $char2 === '<>' || $char2 === '!=' ) {
			$name = $char2;
			$op = self::EXPR_NOTEQ;
			$p += 2;
		} elseif ( $char === '+' ) {
			++$p;
			if ( $expecting === 'expression' ) {
				// Unary plus
				$operators[] = self::EXPR_POSITIVE;
				continue;
			} else {
				// Binary plus
				$op = self::EXPR_PLUS;
			}
		} elseif ( $char === '-' ) {
			++$p;
			if ( $expecting === 'expression' ) {
				// Unary minus
				$operators[] = self::EXPR_NEGATIVE;
				continue;
			} else {
				// Binary minus
				$op = self::EXPR_MINUS;
			}
		} elseif ( $char === '*' ) {
			$name = $char;
			$op = self::EXPR_TIMES;
			++$p;
		} elseif ( $char === '/' ) {
			$name = $char;
			$op = self::EXPR_DIVIDE;
			++$p;
		} elseif ( $char === '^' ) {
			$name = $char;
			$op = self::EXPR_POW;
			++$p;
		} elseif ( $char === '(' ) {
			if ( $expecting === 'operator' ) {
				throw new ExprError( 'unexpected_operator', '(' );
			}
			$operators[] = self::EXPR_OPEN;
			++$p;
			continue;
		} elseif ( $char === ')' ) {
			$lastOp = end( $operators );
			while ( $lastOp && $lastOp !== self::EXPR_OPEN ) {
				$this->doOperation( $lastOp, $operands );
				array_pop( $operators );
				$lastOp = end( $operators );
			}
			if ( $lastOp ) {
				array_pop( $operators );
			} else {
				throw new ExprError( 'unexpected_closing_bracket' );
			}
			$expecting = 'operator';
			++$p;
			continue;
		} elseif ( $char === '=' ) {
			$name = $char;
			$op = self::EXPR_EQUALITY;
			++$p;
		} elseif ( $char === '<' ) {
			$name = $char;
			$op = self::EXPR_LESS;
			++$p;
		} elseif ( $char === '>' ) {
			$name = $char;
			$op = self::EXPR_GREATER;
			++$p;
		} else {
			$utfExpr = Validator::cleanUp( substr( $expr, $p ) );
			throw new ExprError( 'unrecognised_punctuation', mb_substr( $utfExpr, 0, 1 ) );
		}

		// Binary operator processing
		if ( $expecting === 'expression' ) {
			throw new ExprError( 'unexpected_operator', $name );
		}

		// Shunting yard magic
		$lastOp = end( $operators );
		while ( $lastOp && self::PRECEDENCE[$op] <= self::PRECEDENCE[$lastOp] ) {
			$this->doOperation( $lastOp, $operands );
			array_pop( $operators );
			$lastOp = end( $operators );
		}
		$operators[] = $op;
		$expecting = 'expression';
	}

	// Finish off the operator array
	// phpcs:ignore MediaWiki.ControlStructures.AssignmentInControlStructures.AssignmentInControlStructures
	while ( $op = array_pop( $operators ) ) {
		if ( $op === self::EXPR_OPEN ) {
			throw new ExprError( 'unclosed_bracket' );
		}
		$this->doOperation( $op, $operands );
	}

	return implode( "<br />\n", $operands );
}

实际用例

一些Wiki使用了相关特性,如下所示这个静态列表可能在下列页面更改后过时仅供批判性参考
碧蓝航线 - blhx

原神 - ys

战双帕弥什 - zspms

明日方舟 - arknights

恋与深空 - lysk

崩坏:星穹铁道 - sr

代号鸢 - yuan

赛马娘 - umamusume

第五人格 - dwrg

坎特伯雷公主与骑士唤醒冠军之剑的奇幻冒险 - gt