Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 56 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# math-parser

[![Latest Stable Version](https://poser.pugx.org/mossadal/math-parser/v/stable)](https://packagist.org/packages/mossadal/math-parser) [![Total Downloads](https://poser.pugx.org/mossadal/math-parser/downloads)](https://packagist.org/packages/mossadal/math-parser) [![License](https://poser.pugx.org/mossadal/math-parser/license)](https://packagist.org/packages/mossadal/math-parser)
[![Latest Stable Version](https://poser.pugx.org/mossadal/math-parser/v/stable)](https://packagist.org/packages/mossadal/math-parser) [![Total Downloads](https://poser.pugx.org/mossadal/math-parser/downloads)](https://packagist.org/packages/mossadal/math-parser) [![License](https://poser.pugx.org/mossadal/math-parser/license)](https://packagist.org/packages/mossadal/math-parser)
[![Code Climate](https://codeclimate.com/github/mossadal/math-parser/badges/gpa.svg)](https://codeclimate.com/github/mossadal/math-parser)

## DESCRIPTION
Expand All @@ -11,68 +11,103 @@ Intended use: safe and reasonably efficient evaluation of user submitted formula

The lexer and parser produces an abstract syntax tree (AST) that can be traversed using a tree interpreter. The math-parser library ships with three interpreters:

* an evaluator computing the value of the given expression.
* a differentiator transforming the AST into a (somewhat) simplied AST representing the derivative of the supplied expression.
* a rudimentary LaTeX output generator, useful for pretty printing expressions using MathJax

- an evaluator computing the value of the given expression.
- a differentiator transforming the AST into a (somewhat) simplied AST representing the derivative of the supplied expression.
- a rudimentary LaTeX output generator, useful for pretty printing expressions using MathJax

## EXAMPLES

It is possible to fine-tune the lexer and parser, but the library ships with a StdMathParser class, capable of tokenizing and parsing standard mathematical expressions, including arithmetical operations as well as elementary functions.

~~~{.php}
By default all single letters are interpreted as variables. Sequences of letters are interpreted as products of variables. Default constants are `e, pi, NAN, INF`. This default can be used as follows.

```{.php}
use MathParser\StdMathParser;
use MathParser\Interpreting\Evaluator;

$parser = new StdMathParser();
```

This default can be changed by calling a parser for a new language with newly defined variables and constants:

```{.php}
use MathParser\Lexing\Language;
use MathParser\StdMathParser;

$lang = new Language;
$lang->setVariables(['phi','x','t']);
$lang->setConstants(['h']);

$parser = new StdMathParser($lang);

```


```{.php}
// Generate an abstract syntax tree
$AST = $parser->parse('1+2');

// Do something with the AST, e.g. evaluate the expression:
use MathParser\Interpreting\Evaluator;

$evaluator = new Evaluator();

$value = $AST->accept($evaluator);
echo $value;
~~~
```

More interesting example, containing variables:

~~~{.php}
$AST = $parser->parse('x+sqrt(y)');
```{.php}
$AST = $parser->parse('t+sqrt(phi)');

$evaluator->setVariables([ 'x' => 2, 'y' => 3 ]);
$evaluator->setVariables([ 't' => 2, 'phi' => 3 ]);
$value = $AST->accept($evaluator);
~~~
```

We can do other things with the AST. The library ships with a differentiator, computing the (symbolic) derivative with respect to a given variable.

~~~{.php}
```{.php}
use MathParser\Interpreting\Differentiator;

$differentiator = new Differentiator('x');
$f = $parser->parse('exp(2*x)-x*y');
$df = $f->accept($differentiator);

// $df now contains the AST of '2*exp(x)-y' and can be evaluated further
$evaluator->setVariables([ 'x' => 1, 'y' => 2 ]);
$evaluator->setVariables([ 't' => 1, 'phi' => 2 ]);
$df->accept($evaluator);
~~~
```

We can test whether a term, given as AST, is an instance of another term.

```{.php}
$lang->addVariables(['c','d','u','v']);
$parser = new StdMathParser($lang);
$AST = $parser->parse('x*phi*x');
$AST1 = $parser->parse('(c+d)*(u+v)*(c+d)');
$AST->hasInstance($AST1)['result']; // true
$AST->hasInstance($AST1)['instantiation']['phi']; // AST of u+v

$AST = $parser->parse('x*h*x');
$AST->hasInstance($AST1)['result']; // false as 'h' is a constant and cannot be instantiated
$AST2 = $parser->parse('(c+d)*h*(c+d)');
$AST->hasInstance($AST2)['result']; // true
$AST3 = $parser->parse('h*h*h');
$AST->hasInstance($AST3)['result']; // true
$AST4 = $parser->parse('h*(h*h)');
$AST->hasInstance($AST4)['result']; // false as h*(h*h) is not an instance of x*phi*x=(x*phi)*x
```

### Implicit multiplication

Another helpful feature is that the parser understands implicit multiplication. An expression as `2x` is parsed the same as `2*x` and `xsin(x)cos(x)^2` is parsed as `x*sin(x)*cos(x)^2`.

Note that implicit multiplication has the same precedence as explicit multiplication. In particular, `xy^2z` is parsed as `x*y^2*z` and **not** as `x*y^(2*z)`.

To make full use of implicit multiplication, the standard lexer only allows one-letter variables. (Otherwise, we wouldn't know if `xy` should be parsed as `x*y` or as the single variable `xy`).

## DOCUMENTATION

For complete documentation, see the [github.io project page](http://mossadal.github.io/math-parser/index.html)

## THANKS

This software is an adaptation of the [math-parser by Frank Wikström](https://github.com/mossadal/math-parser).

The Lexer is based on the lexer described by Marc-Oliver Fiset in his [blog](http://marcofiset.com/programming-language-implementation-part-1-lexer/).

The parser is a version of the "Shunting yard" algorithm, described for example by [Theodore Norvell](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm#shunting_yard).
13 changes: 9 additions & 4 deletions src/MathParser/Interpreting/ASCIIPrinter.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<?php
/*
* @author Frank Wikström <frank@mossadal.se>
* @author Frank Wikström <frank@mossadal.se>, modified by Ingo Dahn <dahn@dahn-research.eu>
* @copyright 2016 Frank Wikström
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
*/

namespace MathParser\Interpreting;

use MathParser\Lexing\Language;
use MathParser\Exceptions\UnknownConstantException;
use MathParser\Interpreting\Visitors\Visitor;
use MathParser\Lexing\StdMathLexer;
Expand Down Expand Up @@ -51,9 +52,12 @@ class ASCIIPrinter implements Visitor
/**
* Constructor. Create an ASCIIPrinter.
*/
public function __construct()
public function __construct($lang = null)
{
$this->lexer = new StdMathLexer();
if ($lang === null) {
$lang = new Language;
}
$this->lexer = new StdMathLexer($lang);
}

/**
Expand Down Expand Up @@ -189,8 +193,9 @@ public function visitConstantNode(ConstantNode $node)
return 'NAN';
case 'INF':
return 'INF';
default: return $node->getName();

default:throw new UnknownConstantException($node->getName());
//default:throw new UnknownConstantException($node->getName());
}
}

Expand Down
1 change: 0 additions & 1 deletion src/MathParser/Interpreting/Evaluator.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ public function visitFunctionNode(FunctionNode $node)
// Exponentials and logarithms
case 'exp':
return exp($inner);

case 'log':
case 'ln':
return log($inner);
Expand Down
97 changes: 97 additions & 0 deletions src/MathParser/Lexing/Language.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/*
* @package Lexical analysis
* @author Ingo Dahn <dahn@dahn-research.eu>
* @copyright 2019 Ingo Dahn
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
*
*/

/**
* @namespace MathParser::Lexing
* Lexer and Token related classes.
*
* [Lexical analysis](https://en.wikipedia.org/wiki/Lexical_analysis)
* or *lexing* is the process of converting an input string into a sequence
* of tokens, representing discrete parts of the input each carrying certain meaning.
*
*/
namespace MathParser\Lexing;
/**
* Class Language keeps definitions of variables and constants
* Maybe extended later by function and relation symbols
*/
class Language
{
private $constants=[];
private $variables=['[a-zA-Z]'];
/**
* setting the list of constnts
* @retval void
* @param Array $carray Array of constants
*/
public function setConstants(Array $carray) {
$this->constants=$carray;
}
/**
* Adding a list of constants
* @retval void
* @param Array $carray Array of constants
*/
public function addConstants(Array $carray) {
$oldconsts=$this->constants;
$this->constants=array_merge($oldconsts,$carray);
}
/**
* Removing a list of constants
* @retval void
* @param Array $carray Array of constnts
*/
public function removeConstants(Array $carray) {
$oldconsts=$this->constants;
$this->constants=array_values(array_diff($oldconsts,$carray));
}
/**
* getting the list of constnts
* @retval Array of constants
*/
public function getConstants() {
return $this->constants;
}

/**
* setting the list of variables
* As default, all letters are variables
* @retval void
* @param Array $carray Array of variables
*/
public function setVariables(Array $carray) {
$this->variables=$carray;
}
/**
* Adding a list of variables
* @retval void
* @param Array $carray Array of variables
*/
public function addVariables(Array $carray) {
$oldvars=$this->variables;
$this->variables=array_merge($oldvars,$carray);
}
/**
* Removing a list of variables
* Use removeVariables(['[a-zA-Z]']) to remove the default single letter variable declaration
* @retval void
* @param Array $carray Array of variables
*/
public function removeVariables(Array $carray) {
$oldvars=$this->variables;
$this->variables=array_values(array_diff($oldvars,$carray));
}
/**
* getting the list of variables
* @retval Array of variables
*/
public function getVariables() {
return $this->variables;
}
}
19 changes: 15 additions & 4 deletions src/MathParser/Lexing/StdMathLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
* Long description
*
* @package Lexical analysis
* @author Frank Wikström <frank@mossadal.se>
* @author Frank Wikström <frank@mossadal.se>, modified by Ingo Dahn <dahn@dahn-research.eu>
* @copyright 2015 Frank Wikström
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
*
*/

namespace MathParser\Lexing;
use MathParser\Lexing\Language;

/**
* Lexer capable of recognizing all standard mathematical expressions.
Expand Down Expand Up @@ -67,8 +68,11 @@
*/
class StdMathLexer extends Lexer
{
public function __construct()
public function __construct(Language $lang=null)
{
if ($lang === null ) {
$lang=new Language;
}
$this->add(new TokenDefinition('/\d+[,\.]\d+(e[+-]?\d+)?/', TokenType::RealNumber));

$this->add(new TokenDefinition('/\d+/', TokenType::PosInt));
Expand Down Expand Up @@ -129,8 +133,15 @@ public function __construct()
$this->add(new TokenDefinition('/e/', TokenType::Constant));
$this->add(new TokenDefinition('/NAN/', TokenType::Constant));
$this->add(new TokenDefinition('/INF/', TokenType::Constant));

$this->add(new TokenDefinition('/[a-zA-Z]/', TokenType::Identifier));
$consts=$lang->getConstants();
foreach ($consts as $c) {
$this->add(new TokenDefinition('/'.$c.'/', TokenType::Constant));
}
$vars=$lang->getVariables();
foreach ($vars as $v) {
$this->add(new TokenDefinition('/'.$v.'/', TokenType::Identifier));
}
//$this->add(new TokenDefinition('/[a-zA-Z]/', TokenType::Identifier));

$this->add(new TokenDefinition('/\n/', TokenType::Terminator));
$this->add(new TokenDefinition('/\s+/', TokenType::Whitespace));
Expand Down
26 changes: 24 additions & 2 deletions src/MathParser/Parsing/Nodes/ConstantNode.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<?php
/*
* @package Parsing
* @author Frank Wikström <frank@mossadal.se>
* @author Frank Wikström <frank@mossadal.se>, modified by Ingo Dahn <dahn@dahn-research.eu>
* @copyright 2015 Frank Wikström
* @license http://www.opensource.org/licenses/lgpl-license.php LGPL
*
*/

namespace MathParser\Parsing\Nodes;

use MathParser\Lexing\Language;
use MathParser\Interpreting\Visitors\Visitor;

/**
Expand All @@ -33,8 +34,12 @@ class ConstantNode extends Node
* ~~~
*
*/
function __construct($value)
function __construct($value, $lang = null)
{
if ($lang === null) {
$lang = new Language;
}
$this->language = $lang;
$this->value = $value;
}

Expand Down Expand Up @@ -70,4 +75,21 @@ public function compareTo($other)
return $this->getName() == $other->getName();
}

/** Implementing the hasInstance abstract method. */
public function hasInstance($other,$inst=[])
{
if ($other === null) {
return ['result' => false];
}
if (!($other instanceof ConstantNode)) {
return ['result' => false];
}
$result=($this->getName() == $other->getName());
if ($result) {
return ['result' => true, 'instantiation'=> $inst];
}

return ['result' => false];
}

}
Loading