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
|
||||
|
||||
from enum import Enum
|
||||
import math
|
||||
from typing import ClassVar, Sequence
|
||||
from textwrap import indent
|
||||
|
||||
|
@ -1668,6 +1669,310 @@ def _expression_sa(tokens: list[Token]) -> Expression:
|
|||
def syntactical_analyzer(tokens: Sequence[Token]) -> File:
|
||||
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__':
|
||||
try:
|
||||
with open("example.graph", encoding='utf-8') as file:
|
||||
|
|
Loading…
Reference in New Issue