Spaces:
Runtime error
Runtime error
File size: 7,631 Bytes
e4806d5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from ast_parser.lexer import Lexer
from ast_parser.linter import Linter
from ast_parser.token import Token, TokenKind
class EquationKind(str, Enum):
"""Kind of relationship between the left and right parts of the
Equation."""
EQ = "="
"""Equality operator."""
LEQ = "<="
"""Less than or equal to operator."""
GEQ = ">="
"""Greater than or equal to operator."""
@dataclass
class Equation:
"""Equation definition."""
kind: EquationKind
"""Kind of relationship between the left and right parts of the
Equation.
"""
variables: dict[str, float]
"""Equation variables. The names are the keys, the coefficients are
the values.
"""
bound: float
"""Bound of the Equation. Must be non-negative."""
@dataclass
class EquationAccumulator:
"""The EquationAccumulator collects equation Tokens: variables,
coefficients, and bounds.
"""
kind: EquationKind | None = field(default=None)
"""Kind of relationship between the left and right parts of the
Equation.
"""
variables: dict[str, float] = field(default_factory=dict)
"""Equation variables. The names are the keys, the coefficients are
the values.
"""
bound: float = field(default=0.0)
"""Bound of the Equation. Must be non-negative."""
coefficient: float | None = field(default=None)
"""Coefficient of the current variable."""
variable: str | None = field(default=None)
"""Name of the current variable."""
class Parser:
"""Source Parser. It parses the source and returns a list of
Equation objects found in the source.
Args:
source (str): The source to parse.
"""
_lexer: Lexer
"""Lexer of the source."""
_linter: Linter
"""Linter of the source."""
_accumulator: EquationAccumulator
"""Equation accumulator."""
def __init__(self, source: str) -> None:
self._lexer = Lexer(source)
self._linter = Linter(source)
self._accumulator = EquationAccumulator()
def __iter__(self) -> Parser:
"""Gets an iterator over the Equations in the source.
Returns:
Parser: _description_
"""
return self
def __next__(self) -> Equation:
"""Gets the next Equation in the source.
Accumulation, processing and validation of Equations in
real-time. The source is parsed sequentially, token by token.
Raises:
StopIteration: The end of the source has been reached.
LexerException: Unexpected character, less than operator is
not allowed.
LexerException: Unexpected character, greater than operator
is not allowed.
LexerException: Invalid character: <code>.
LexerException: Invalid coefficient, unexpected digit after
0: <code>.
LexerException: Invalid coefficient, expected digit but
got: <code>.
LinterException: Unexpected binary operator, term missed.
LinterException: Equation must contain only one relational
operator.
LinterException: Term must contain no more than one
variable.
LinterException: Unexpected comma at the beginning of the
equation.
LinterException: Unexpected comma, equation missed.
LinterException: Unexpected comma at the end of the
equation.
LinterException: Equation must contain a relational
operator.
Returns:
Equation: The next Equation from the source.
"""
while True:
token = next(self._lexer)
self._linter.lint(token)
match token.kind:
case TokenKind.SOF | TokenKind.MUL:
continue
case TokenKind.EOF:
if token.prev_token.kind == TokenKind.SOF:
raise StopIteration
return self._derive_equation()
case TokenKind.ADD:
self._parse_addition_operator()
continue
case TokenKind.SUB:
self._parse_subtraction_operator()
continue
case TokenKind.EQ | TokenKind.LEQ | TokenKind.GEQ:
self._parse_relational_operator(token)
continue
case TokenKind.COEFFICIENT:
self._parse_coefficient(token)
continue
case TokenKind.VARIABLE:
self._parse_variable(token)
continue
case TokenKind.COMMA:
return self._derive_equation()
def _extend_variables(self) -> None:
"""Extend the variables with the current variable and
coefficient.
"""
if self._accumulator.coefficient:
if self._accumulator.kind:
self._accumulator.coefficient *= -1.0
if self._accumulator.variable:
if self._accumulator.variable in self._accumulator.variables:
self._accumulator.variables[
self._accumulator.variable
] += self._accumulator.coefficient
else:
self._accumulator.variables[
self._accumulator.variable
] = self._accumulator.coefficient
else:
self._accumulator.bound -= self._accumulator.coefficient
def _derive_equation(self) -> Equation:
"""Derive the Equation from the EquationAccumulator.
Returns:
Equation: The derived Equation.
"""
self._extend_variables()
equation = Equation(
self._accumulator.kind, self._accumulator.variables, self._accumulator.bound
)
self._accumulator = EquationAccumulator()
return equation
def _parse_addition_operator(self) -> None:
"""Parse the addition operator Token."""
self._extend_variables()
self._accumulator.variable = None
self._accumulator.coefficient = 1.0
def _parse_subtraction_operator(self) -> None:
"""Parse the subtraction operator Token."""
self._extend_variables()
self._accumulator.variable = None
self._accumulator.coefficient = -1.0
def _parse_relational_operator(self, token: Token) -> None:
"""Parse the relational operator Token.
Args:
token (Token): The relational operator Token.
"""
self._extend_variables()
self._accumulator.kind = EquationKind(token.kind.value)
self._accumulator.variable = None
self._accumulator.coefficient = None
def _parse_coefficient(self, token: Token) -> None:
"""Parse the coefficient Token.
Args:
token (Token): The coefficient Token.
"""
if not self._accumulator.coefficient:
self._accumulator.coefficient = 1.0
self._accumulator.coefficient *= float(token.value)
def _parse_variable(self, token: Token) -> None:
"""Parse the variable Token.
Args:
token (Token): The variable Token.
"""
if not self._accumulator.coefficient:
self._accumulator.coefficient = 1.0
self._accumulator.variable = token.value
__all__ = ("EquationKind", "Equation", "Parser")
|