src/Parsing/Nodes/Node.php
<?php
declare(strict_types=1);
namespace MyEval\Parsing\Nodes;
use MyEval\Exceptions\UnknownOperatorException;
use MyEval\Lexing\Token;
use MyEval\Lexing\TokenType;
use MyEval\Parsing\Nodes\Operand\BooleanNode;
use MyEval\Parsing\Nodes\Operand\ConstantNode;
use MyEval\Parsing\Nodes\Operand\FloatNode;
use MyEval\Parsing\Nodes\Operand\IntegerNode;
use MyEval\Parsing\Nodes\Operand\RationalNode;
use MyEval\Parsing\Nodes\Operand\VariableNode;
use MyEval\Parsing\Nodes\Operator\CloseBraceNode;
use MyEval\Parsing\Nodes\Operator\CloseParenthesisNode;
use MyEval\Parsing\Nodes\Operator\FunctionNode;
use MyEval\Parsing\Nodes\Operator\InfixExpressionNode;
use MyEval\Parsing\Nodes\Operator\OpenBraceNode;
use MyEval\Parsing\Nodes\Operator\OpenParenthesisNode;
use MyEval\Parsing\Nodes\Operator\PostfixExpressionNode;
use MyEval\Parsing\Nodes\Operator\TerminatorNode;
use MyEval\Parsing\Nodes\Operator\TernaryExpressionNode;
use MyEval\Parsing\Nodes\Operator\UnmappedNode;
use MyEval\Solving\ASCIIPrinter;
use MyEval\Solving\StdMathEvaluator;
/**
* Abstract base class for nodes in the abstract syntax tree generated by the Parser (and some AST transformers).
*/
abstract class Node implements Visitable
{
public const NUMERIC_INTEGER = 1;
public const NUMERIC_RATIONAL = 2;
public const NUMERIC_FLOAT = 3;
/**
* Node factory, creating an appropriate Node from a Token.
*
* @param Token $token Provided token.
*
* @return Node
* @throws UnknownOperatorException
*/
public static function factory(Token $token): Node
{
return match ($token->type) {
// Operands
TokenType::NATURAL_NUMBER,
TokenType::INTEGER => new IntegerNode((int)$token->value),
TokenType::REAL_NUMBER => new FloatNode((float)(str_replace(',', '.', $token->value))),
TokenType::BOOLEAN => new BooleanNode($token->value),
TokenType::VARIABLE => new VariableNode($token->value),
TokenType::CONSTANT => new ConstantNode($token->value),
// Operators
TokenType::ADDITION_OPERATOR,
TokenType::SUBTRACTION_OPERATOR,
TokenType::MULTIPLICATION_OPERATOR,
TokenType::DIVISION_OPERATOR,
TokenType::EXPONENTIAL_OPERATOR,
TokenType::EQUAL_TO,
TokenType::GREATER_THAN,
TokenType::LESS_THAN,
TokenType::DIFFERENT_THAN,
TokenType::GREATER_OR_EQUAL_THAN,
TokenType::LESS_OR_EQUAL_THAN,
TokenType::AND,
TokenType::OR,
TokenType::NOT => new InfixExpressionNode($token->value),
TokenType::FACTORIAL_OPERATOR,
TokenType::SEMI_FACTORIAL_OPERATOR => new PostfixExpressionNode($token->value),
TokenType::IF => new TernaryExpressionNode(),
TokenType::FUNCTION_NAME => new FunctionNode($token->value),
TokenType::OPEN_PARENTHESIS => new OpenParenthesisNode($token->value),
TokenType::CLOSE_PARENTHESIS => new CloseParenthesisNode(),
TokenType::OPEN_BRACE => new OpenBraceNode($token->value),
TokenType::CLOSE_BRACE => new CloseBraceNode(),
TokenType::THEN,
TokenType::ELSE,
TokenType::TERMINATOR => new TerminatorNode($token->value),
default => new UnmappedNode($token->value),
};
}
/**
* Convenience function for evaluating a tree, using the StdMathEvaluator class.
*
* ## Example usage:
*
* ~~~{.php}
* use MyEval\Parsing\Parse;
*
* $parser = new Parser();
* $node = $parser->parse('sin(x)cos(y)');
* $functionValue = $node->evaluate( [ 'x' => 1.3, 'y' => 1.4 ] );
* ~~~
*
* @param array $variables key-value array of variable values.
*
* @return float
*/
public function evaluate(array $variables = []): float
{
$evaluator = new StdMathEvaluator($variables);
return $this->accept($evaluator);
}
/**
* Helper function, comparing two ASTs.
*
* Useful for testing and also for some AST transformers.
*
* @param Node $other Compare to this tree.
*
* @return bool
*/
abstract public function compareTo(Node $other): bool;
/**
* Rough estimate of the complexity of the AST.
*
* Gives a rough measure of the complexity of an AST. This can be useful to choose between different simplification
* rules or how to print a tree ("e^{...}" or ("\exp(...)") for example.
*
* More precisely, the complexity is computed as the sum of the complexity of all nodes of the AST.
* - NumberNodes, VariableNodes and ConstantNodes have complexity 1,
* - FunctionNodes have complexity 5 (plus the complexity of its operand),
* - ExpressionNodes have complexity 1 (for `+`, `-`, `*`), 2 (for `/`), or 8 (for `^`)
*/
public function complexity(): int
{
switch (\get_class($this)) {
case IntegerNode::class:
case VariableNode::class:
case ConstantNode::class:
$complexity = 1;
break;
case RationalNode::class:
case FloatNode::class:
$complexity = 2;
break;
case FunctionNode::class:
/** @var FunctionNode $this */
$complexity = 5 + $this->operand->complexity();
break;
case InfixExpressionNode::class:
/** @var InfixExpressionNode $this */
$operator = $this->operator;
$left = $this->getLeft();
$right = $this->getRight();
return match ($operator) {
'+', '-', '*' => 1 + $left?->complexity() + (($right === null) ? 0 : $right->complexity()),
'/' => 3 + $left?->complexity() + (($right === null) ? 0 : $right->complexity()),
'^' => 8 + $left?->complexity() + (($right === null) ? 0 : $right->complexity()),
};
default:
// This shouldn't happen under normal circumstances.
$complexity = 1000;
break;
}
return $complexity;
}
/**
* String representation of a Node.
*
* @return string
*/
public function __toString(): string
{
$printer = new ASCIIPrinter();
return $this->accept($printer);
}
}