feat: support for common math constants
This commit is contained in:
@@ -27,6 +27,7 @@ class TokenType(Enum):
|
|||||||
SEPARATOR = 2
|
SEPARATOR = 2
|
||||||
FLOAT = 3
|
FLOAT = 3
|
||||||
FUNCTION = 4
|
FUNCTION = 4
|
||||||
|
CONSTANT = 5
|
||||||
|
|
||||||
|
|
||||||
# The token can optionally capture a string value.
|
# The token can optionally capture a string value.
|
||||||
@@ -43,6 +44,7 @@ operators: set[str] = {"+", "-", "*", "/", "^"}
|
|||||||
functions: set[str] = {"abs", "cos", "sin", "tan", "atan", "exp", "ln", "log"}
|
functions: set[str] = {"abs", "cos", "sin", "tan", "atan", "exp", "ln", "log"}
|
||||||
separators: set[str] = {"(", ")"}
|
separators: set[str] = {"(", ")"}
|
||||||
variables: set[str] = {"x"}
|
variables: set[str] = {"x"}
|
||||||
|
constants: set[str] = {"e", "pi"}
|
||||||
|
|
||||||
|
|
||||||
# The lexer is a generator function that yields token as it scans the input string
|
# The lexer is a generator function that yields token as it scans the input string
|
||||||
@@ -75,13 +77,18 @@ def lex(input: str) -> Iterator[Token]:
|
|||||||
i += 1
|
i += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# functions
|
# functions and constants
|
||||||
if char.isalpha():
|
if char.isalpha():
|
||||||
j = i + 1
|
j = i + 1
|
||||||
while j < len(input) and input[j].isalpha():
|
while j < len(input) and input[j].isalpha():
|
||||||
j += 1
|
j += 1
|
||||||
|
|
||||||
name = input[i:j]
|
name = input[i:j]
|
||||||
|
if name in constants:
|
||||||
|
yield Token(type=TokenType.CONSTANT, value=name)
|
||||||
|
i = j
|
||||||
|
continue
|
||||||
|
|
||||||
if name not in functions:
|
if name not in functions:
|
||||||
yield Token(
|
yield Token(
|
||||||
type=TokenType.ERROR, value=f"unknown function name '{name}'"
|
type=TokenType.ERROR, value=f"unknown function name '{name}'"
|
||||||
@@ -134,12 +141,18 @@ class Expression(ABC):
|
|||||||
class Atom(Expression):
|
class Atom(Expression):
|
||||||
token: Token
|
token: Token
|
||||||
|
|
||||||
|
_constants = {
|
||||||
|
"pi": math.pi,
|
||||||
|
"e": math.e,
|
||||||
|
}
|
||||||
|
|
||||||
def eval(self, x: float) -> float:
|
def eval(self, x: float) -> float:
|
||||||
if self.token.type == TokenType.VARIABLE:
|
if self.token.type == TokenType.VARIABLE:
|
||||||
return x
|
return x
|
||||||
if self.token.type == TokenType.FLOAT:
|
if self.token.type == TokenType.FLOAT:
|
||||||
return float(self.token.value or 0)
|
return float(self.token.value or 0)
|
||||||
|
if self.token.type == TokenType.CONSTANT:
|
||||||
|
return self._constants[self.token.value]
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import math
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from plotter.parser import (
|
from plotter.parser import (
|
||||||
@@ -98,6 +99,21 @@ from plotter.parser import (
|
|||||||
Token(type=TokenType.FLOAT, value="0.5"),
|
Token(type=TokenType.FLOAT, value="0.5"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"pi * cos(2.4 * x) ^ e",
|
||||||
|
[
|
||||||
|
Token(type=TokenType.CONSTANT, value="pi"),
|
||||||
|
Token(type=TokenType.OPERATOR, value="*"),
|
||||||
|
Token(type=TokenType.FUNCTION, value="cos"),
|
||||||
|
Token(type=TokenType.SEPARATOR, value="("),
|
||||||
|
Token(type=TokenType.FLOAT, value="2.4"),
|
||||||
|
Token(type=TokenType.OPERATOR, value="*"),
|
||||||
|
Token(type=TokenType.VARIABLE, value="x"),
|
||||||
|
Token(type=TokenType.SEPARATOR, value=")"),
|
||||||
|
Token(type=TokenType.OPERATOR, value="^"),
|
||||||
|
Token(type=TokenType.CONSTANT, value="e"),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_lexer(expression, expected):
|
def test_lexer(expression, expected):
|
||||||
@@ -108,6 +124,13 @@ def test_lexer(expression, expected):
|
|||||||
"expression, expected",
|
"expression, expected",
|
||||||
argvalues=[
|
argvalues=[
|
||||||
("3.14", Atom(Token(type=TokenType.FLOAT, value="3.14"))),
|
("3.14", Atom(Token(type=TokenType.FLOAT, value="3.14"))),
|
||||||
|
("pi", Atom(Token(type=TokenType.CONSTANT, value="pi"))),
|
||||||
|
(
|
||||||
|
"exp(e)",
|
||||||
|
FunctionExpression(
|
||||||
|
function="exp", argument=Atom(Token(type=TokenType.CONSTANT, value="e"))
|
||||||
|
),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"2 + 2",
|
"2 + 2",
|
||||||
InfixExpression(
|
InfixExpression(
|
||||||
@@ -246,6 +269,8 @@ def test_unary_minus(expression, expected):
|
|||||||
("(-2)^3", 0, -8), # (-2)^3 = -8
|
("(-2)^3", 0, -8), # (-2)^3 = -8
|
||||||
("-2^2", 0, -4), # -(2^2) = -4
|
("-2^2", 0, -4), # -(2^2) = -4
|
||||||
("(-2)^2", 0, 4), # (-2)^2 = 4
|
("(-2)^2", 0, 4), # (-2)^2 = 4
|
||||||
|
("pi", 0, math.pi),
|
||||||
|
("e", 0, math.e),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_unary_minus_eval(expression, x, expected):
|
def test_unary_minus_eval(expression, x, expected):
|
||||||
|
|||||||
Reference in New Issue
Block a user