Add semantic analysis classes and context handling for expressions and graphs
This commit is contained in:
parent
3d07510cf7
commit
e2f37553df
305
compiler.py
305
compiler.py
|
@ -2,6 +2,7 @@
|
||||||
# Feb 2024
|
# Feb 2024
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import math
|
||||||
from typing import ClassVar, Sequence
|
from typing import ClassVar, Sequence
|
||||||
from textwrap import indent
|
from textwrap import indent
|
||||||
|
|
||||||
|
@ -1668,6 +1669,310 @@ def _expression_sa(tokens: list[Token]) -> Expression:
|
||||||
def syntactical_analyzer(tokens: Sequence[Token]) -> File:
|
def syntactical_analyzer(tokens: Sequence[Token]) -> File:
|
||||||
return File._sa(list(tokens))
|
return File._sa(list(tokens))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Semantics --
|
||||||
|
|
||||||
|
|
||||||
|
# class SemanticsError(CompilerError):
|
||||||
|
class SemanticsError(Exception):
|
||||||
|
|
||||||
|
_compiler_error_type = "Semantics"
|
||||||
|
|
||||||
|
|
||||||
|
class ContextOperator(Enum):
|
||||||
|
NoOp = "0"
|
||||||
|
Identifier = "i"
|
||||||
|
FunctionCall = "f"
|
||||||
|
Negate = "n"
|
||||||
|
Factorial = "!"
|
||||||
|
Exponential = "^"
|
||||||
|
Division = "/"
|
||||||
|
Modulus = "%"
|
||||||
|
Multiplication = "*"
|
||||||
|
Subtraction = "-"
|
||||||
|
Addition = "+"
|
||||||
|
|
||||||
|
|
||||||
|
class ContextExpression:
|
||||||
|
|
||||||
|
_context: "Context | ContextFunction"
|
||||||
|
_value: None | int | float
|
||||||
|
_name: None | str
|
||||||
|
_operator: ContextOperator
|
||||||
|
_expressions: "list[ContextExpression]"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> int | float:
|
||||||
|
a = 0 if len(self._expressions) > 0 else self._expressions[0].value
|
||||||
|
b = 0 if len(self._expressions) > 1 else self._expressions[1].value
|
||||||
|
match self._operator:
|
||||||
|
case ContextOperator.NoOp:
|
||||||
|
return self._value or 0
|
||||||
|
case ContextOperator.Identifier:
|
||||||
|
if self._name is not None:
|
||||||
|
return self._context.lookup(self._name)
|
||||||
|
else: raise Exception
|
||||||
|
case ContextOperator.FunctionCall:
|
||||||
|
if self._name is not None:
|
||||||
|
return self._context.call(self._name, self._expressions)
|
||||||
|
else: raise Exception
|
||||||
|
case ContextOperator.Negate:
|
||||||
|
if len(self._expressions) > 0:
|
||||||
|
return (0 if self._value is None else -self._value)
|
||||||
|
else: return -self._expressions[0].value
|
||||||
|
case ContextOperator.Factorial:
|
||||||
|
if len(self._expressions) > 0:
|
||||||
|
if self._value is None: return 1
|
||||||
|
else: return math.factorial(int(self._value))
|
||||||
|
else: return math.factorial(int(self._expressions[0].value))
|
||||||
|
case ContextOperator.Exponential:
|
||||||
|
return a ** b
|
||||||
|
case ContextOperator.Division:
|
||||||
|
return a / b
|
||||||
|
case ContextOperator.Modulus:
|
||||||
|
return a % b
|
||||||
|
case ContextOperator.Multiplication:
|
||||||
|
return a * b
|
||||||
|
case ContextOperator.Subtraction:
|
||||||
|
return a - b
|
||||||
|
case ContextOperator.Addition:
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
class ContextFunction:
|
||||||
|
|
||||||
|
_context: "Context"
|
||||||
|
_values: "dict[str, ContextAnimation | ContextExpression]"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> int | float: pass
|
||||||
|
|
||||||
|
def lookup(self, value: str) -> int | float:
|
||||||
|
if value in self._values:
|
||||||
|
return self._values[value].value
|
||||||
|
else: return self._context.lookup(value)
|
||||||
|
|
||||||
|
def call(self, value: str, args: list[ContextExpression]) -> int | float:
|
||||||
|
return self._context.call(value, args)
|
||||||
|
|
||||||
|
|
||||||
|
class ContextAnimation:
|
||||||
|
|
||||||
|
_current: int | float
|
||||||
|
_range_start: ContextExpression
|
||||||
|
_range_start_inclusive: bool
|
||||||
|
_range_end: ContextExpression
|
||||||
|
_range_end_inclusive: bool
|
||||||
|
_step: ContextExpression
|
||||||
|
_direction: AnimationDirection
|
||||||
|
_reversed: bool
|
||||||
|
|
||||||
|
def __iter__(self): return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
if self._direction == AnimationDirection.Increase or (
|
||||||
|
self._direction == AnimationDirection.Bounce and not self._reversed
|
||||||
|
):
|
||||||
|
self._current += self._step.value
|
||||||
|
if self._range_end_inclusive:
|
||||||
|
if self._current > self._range_end.value:
|
||||||
|
self._current = self._range_start.value
|
||||||
|
if self._direction == AnimationDirection.Bounce:
|
||||||
|
self._reversed = True
|
||||||
|
if not self._range_start_inclusive:
|
||||||
|
self._current += self._step.value
|
||||||
|
else:
|
||||||
|
if self._current >= self._range_end.value:
|
||||||
|
self._current = self._range_start.value
|
||||||
|
if self._direction == AnimationDirection.Bounce:
|
||||||
|
self._reversed = True
|
||||||
|
if not self._range_start_inclusive:
|
||||||
|
self._current += self._step.value
|
||||||
|
if self._direction == AnimationDirection.Decrease or (
|
||||||
|
self._direction == AnimationDirection.Bounce and self._reversed
|
||||||
|
):
|
||||||
|
self._current -= self._step.value
|
||||||
|
if self._range_start_inclusive:
|
||||||
|
if self._current < self._range_start.value:
|
||||||
|
self._current = self._range_end.value
|
||||||
|
if self._direction == AnimationDirection.Bounce:
|
||||||
|
self._reversed = False
|
||||||
|
if not self._range_end_inclusive:
|
||||||
|
self._current -= self._step.value
|
||||||
|
else:
|
||||||
|
if self._current <= self._range_start.value:
|
||||||
|
self._current = self._range_end.value
|
||||||
|
if self._direction == AnimationDirection.Bounce:
|
||||||
|
self._reversed = False
|
||||||
|
if not self._range_end_inclusive:
|
||||||
|
self._current -= self._step.value
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> int | float: return self._current
|
||||||
|
|
||||||
|
|
||||||
|
class GraphType(Enum):
|
||||||
|
X_Independent = "x independent"
|
||||||
|
Y_Independent = "y independent"
|
||||||
|
Parametric = "parametric"
|
||||||
|
Polar = "polar"
|
||||||
|
|
||||||
|
|
||||||
|
class ColorSpace(Enum):
|
||||||
|
Grey = "Grey Scale"
|
||||||
|
RGB = "RGB"
|
||||||
|
HSL = "HSL"
|
||||||
|
|
||||||
|
|
||||||
|
class ContextGraph:
|
||||||
|
|
||||||
|
_x: None | ContextFunction | ContextAnimation
|
||||||
|
_y: None | ContextFunction | ContextAnimation
|
||||||
|
_t: None | ContextAnimation
|
||||||
|
_r: None | ContextFunction
|
||||||
|
_theta: None | ContextAnimation
|
||||||
|
_color_alpha: None | ContextFunction
|
||||||
|
_color_grey: None | ContextFunction
|
||||||
|
_color_red: None | ContextFunction
|
||||||
|
_color_green: None | ContextFunction
|
||||||
|
_color_blue: None | ContextFunction
|
||||||
|
_color_hue: None | ContextFunction
|
||||||
|
_color_saturation: None | ContextFunction
|
||||||
|
_color_luminosity: None | ContextFunction
|
||||||
|
|
||||||
|
def __iter__(self): return self
|
||||||
|
|
||||||
|
def __next__(self) -> tuple[
|
||||||
|
int | float,
|
||||||
|
int | float,
|
||||||
|
int | float,
|
||||||
|
int | float,
|
||||||
|
tuple[float,float,float,float,],
|
||||||
|
]:
|
||||||
|
match self.graph_type:
|
||||||
|
case GraphType.X_Independent:
|
||||||
|
x_last = self._x.value # type: ignore
|
||||||
|
y_last = self._y.value # type: ignore
|
||||||
|
x = next(self._x) # type: ignore
|
||||||
|
y = self._y.value # type: ignore
|
||||||
|
case GraphType.Y_Independent:
|
||||||
|
y_last = self._y.value # type: ignore
|
||||||
|
x_last = self._x.value # type: ignore
|
||||||
|
y = next(self._y) # type: ignore
|
||||||
|
x = self._x.value # type: ignore
|
||||||
|
case GraphType.Parametric:
|
||||||
|
x_last = self._x.value # type: ignore
|
||||||
|
y_last = self._y.value # type: ignore
|
||||||
|
next(self._t) # type: ignore
|
||||||
|
x = self._x.value # type: ignore
|
||||||
|
y = self._y.value # type: ignore
|
||||||
|
case GraphType.Polar:
|
||||||
|
theta_last = self._theta.value # type: ignore
|
||||||
|
r_last = self._r.value # type: ignore
|
||||||
|
theta = next(self._theta) # type: ignore
|
||||||
|
r = self._r.value # type: ignore
|
||||||
|
return x_last, y_last, x, y, self.color
|
||||||
|
|
||||||
|
@property
|
||||||
|
def graph_type(self) -> GraphType:
|
||||||
|
if isinstance(self._x, ContextAnimation):
|
||||||
|
return GraphType.X_Independent
|
||||||
|
elif isinstance(self._y, ContextAnimation):
|
||||||
|
return GraphType.Y_Independent
|
||||||
|
elif isinstance(self._t, ContextAnimation):
|
||||||
|
return GraphType.Parametric
|
||||||
|
elif isinstance(self._theta, ContextAnimation):
|
||||||
|
return GraphType.Polar
|
||||||
|
else:
|
||||||
|
raise SemanticsError("Invalid Function Type.")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color_space(self) -> ColorSpace:
|
||||||
|
if isinstance(self._color_red, ContextFunction):
|
||||||
|
return ColorSpace.RGB
|
||||||
|
elif isinstance(self._color_green, ContextFunction):
|
||||||
|
return ColorSpace.RGB
|
||||||
|
elif isinstance(self._color_blue, ContextFunction):
|
||||||
|
return ColorSpace.RGB
|
||||||
|
elif isinstance(self._color_hue, ContextFunction):
|
||||||
|
return ColorSpace.HSL
|
||||||
|
elif isinstance(self._color_saturation, ContextFunction):
|
||||||
|
return ColorSpace.HSL
|
||||||
|
elif isinstance(self._color_luminosity, ContextFunction):
|
||||||
|
return ColorSpace.HSL
|
||||||
|
else:
|
||||||
|
return ColorSpace.Grey
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color(self) -> tuple[float,float,float,float,]:
|
||||||
|
if self._color_alpha is not None:
|
||||||
|
a = self._color_alpha.value
|
||||||
|
else: a = 1
|
||||||
|
match self.color_space:
|
||||||
|
case ColorSpace.Grey:
|
||||||
|
if self._color_grey is not None:
|
||||||
|
c = self._color_grey.value
|
||||||
|
else: c = 1
|
||||||
|
r, g, b = c, c, c
|
||||||
|
case ColorSpace.RGB:
|
||||||
|
if self._color_red is not None:
|
||||||
|
r = self._color_red.value
|
||||||
|
else: r = 1
|
||||||
|
if self._color_green is not None:
|
||||||
|
g = self._color_green.value
|
||||||
|
else: g = 1
|
||||||
|
if self._color_blue is not None:
|
||||||
|
b = self._color_blue.value
|
||||||
|
else: b = 1
|
||||||
|
case ColorSpace.HSL:
|
||||||
|
r,g,b = 1,1,1
|
||||||
|
return r, g, b, a
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> int | float:
|
||||||
|
match self.graph_type:
|
||||||
|
case GraphType.X_Independent:
|
||||||
|
return self._x.value # type: ignore
|
||||||
|
case GraphType.Y_Independent:
|
||||||
|
return self._y.value # type: ignore
|
||||||
|
case GraphType.Parametric:
|
||||||
|
return self._t.value # type: ignore
|
||||||
|
case GraphType.Polar:
|
||||||
|
return self._theta.value # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
funcs_1 = {
|
||||||
|
"sin": math.sin,
|
||||||
|
"asin": math.asin,
|
||||||
|
"cos": math.cos,
|
||||||
|
"acos": math.acos,
|
||||||
|
"tan": math.tan,
|
||||||
|
"atan": math.atan,
|
||||||
|
"ln": math.log,
|
||||||
|
"log": math.log10,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Context:
|
||||||
|
|
||||||
|
_constants: dict[str, int | float]
|
||||||
|
_animations: dict[str, ContextAnimation]
|
||||||
|
_graphs: list[ContextGraph]
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
for anim in self._animations.values():
|
||||||
|
next(anim)
|
||||||
|
|
||||||
|
def lookup(self, value: str) -> int | float:
|
||||||
|
return self._constants[value]
|
||||||
|
|
||||||
|
def call(self, value: str, args: list[ContextExpression]) -> int | float:
|
||||||
|
if value in funcs_1 and len(args) == 1:
|
||||||
|
return funcs_1[value](args[0].value)
|
||||||
|
raise KeyError
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
try:
|
try:
|
||||||
with open("example.graph", encoding='utf-8') as file:
|
with open("example.graph", encoding='utf-8') as file:
|
||||||
|
|
Loading…
Reference in New Issue