semantics mostly done

This commit is contained in:
Kyler Olsen 2025-02-12 23:07:05 -07:00
parent 72461740eb
commit 0836610b3d
1 changed files with 296 additions and 169 deletions

View File

@ -1974,6 +1974,11 @@ class ContextNamespace:
return self._parent.lookup_func(function)
class ContextNamespaceNull(ContextNamespace):
def __init__(self): pass
class ContextExpression:
_file_info: FileInfo
@ -2265,20 +2270,6 @@ class ContextFunctionCall(ContextExpression):
return s
class ContextFunction:
_context: "Context"
_values: "dict[str, ContextAnimation | ContextExpression]"
def value(self, context: ContextNamespace) -> int | float:
pass
class ContextAnimation:
_file_info: FileInfo
@ -2315,6 +2306,10 @@ class ContextAnimation:
@property
def file_info(self) -> FileInfo: return self._file_info
@property
def as_context_literal(self) -> ContextLiteral:
return ContextLiteral(self.file_info, str(self._current))
def step(self, context: ContextNamespace) -> int | float:
if self._direction == AnimationDirection.Increase or (
self._direction == AnimationDirection.Bounce and not self._reversed
@ -2373,36 +2368,39 @@ class ColorSpace(Enum):
class ContextGraph:
_x: None | ContextFunction | ContextAnimation
_y: None | ContextFunction | ContextAnimation
_file_info: FileInfo
_x: None | ContextExpression | ContextAnimation
_y: None | ContextExpression | ContextAnimation
_t: None | ContextAnimation
_r: None | ContextFunction
_r: None | ContextExpression
_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
_color_alpha: None | ContextExpression
_color_grey: None | ContextExpression
_color_red: None | ContextExpression
_color_green: None | ContextExpression
_color_blue: None | ContextExpression
_color_hue: None | ContextExpression
_color_saturation: None | ContextExpression
_color_luminosity: None | ContextExpression
def __init__(
self,
x: None | ContextFunction | ContextAnimation,
y: None | ContextFunction | ContextAnimation,
file_info: FileInfo,
x: None | ContextExpression | ContextAnimation,
y: None | ContextExpression | ContextAnimation,
t: None | ContextAnimation,
r: None | ContextFunction,
r: None | ContextExpression,
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,
color_alpha: None | ContextExpression,
color_grey: None | ContextExpression,
color_red: None | ContextExpression,
color_green: None | ContextExpression,
color_blue: None | ContextExpression,
color_hue: None | ContextExpression,
color_saturation: None | ContextExpression,
color_luminosity: None | ContextExpression,
):
self._file_info = file_info
self._x = x
self._y = y
self._t = t
@ -2417,6 +2415,45 @@ class ContextGraph:
self._color_saturation = color_saturation
self._color_luminosity = color_luminosity
@property
def file_info(self) -> FileInfo: return self._file_info
def _local_ns(
self,
context: ContextNamespace,
skip: list[str] | None = None,
) -> ContextNamespace:
skip = skip or []
namespace: dict[\
str, int | float | ContextExpression | ContextAnimation] = {}
if self._x is not None and 'x' not in skip:
namespace['x'] = self._x
if self._y is not None and 'y' not in skip:
namespace['y'] = self._y
if self._t is not None and 't' not in skip:
namespace['t'] = self._t
if self._r is not None and 'r' not in skip:
namespace['r'] = self._r
if self._theta is not None and 'θ' not in skip:
namespace['θ'] = self._theta
if self._color_alpha is not None and 'c_a' not in skip:
namespace['c_a'] = self._color_alpha
if self._color_grey is not None and 'c_w' not in skip:
namespace['c_w'] = self._color_grey
if self._color_red is not None and 'c_r' not in skip:
namespace['c_r'] = self._color_red
if self._color_green is not None and 'c_g' not in skip:
namespace['c_g'] = self._color_green
if self._color_blue is not None and 'c_b' not in skip:
namespace['c_b'] = self._color_blue
if self._color_hue is not None and 'c_h' not in skip:
namespace['c_h'] = self._color_hue
if self._color_saturation is not None and 'c_s' not in skip:
namespace['c_s'] = self._color_saturation
if self._color_luminosity is not None and 'c_l' not in skip:
namespace['c_l'] = self._color_luminosity
return ContextNamespace(context, namespace)
def step(self, context: ContextNamespace) -> tuple[
int | float,
int | float,
@ -2426,26 +2463,26 @@ class ContextGraph:
]:
match self.graph_type:
case GraphType.X_Independent:
x_last = self._x.value(context) # type: ignore
y_last = self._y.value(context) # type: ignore
x = self._x.step(context) # type: ignore
y = self._y.value(context) # type: ignore
x_last = self._x.value(self._local_ns(context, ['x'])) # type: ignore
y_last = self._y.value(self._local_ns(context, ['y'])) # type: ignore
x = self._x.step(self._local_ns(context, ['x'])) # type: ignore
y = self._y.value(self._local_ns(context, ['y'])) # type: ignore
case GraphType.Y_Independent:
y_last = self._y.value(context) # type: ignore
x_last = self._x.value(context) # type: ignore
y = self._y.step(context) # type: ignore
x = self._x.value(context) # type: ignore
y_last = self._y.value(self._local_ns(context, ['y'])) # type: ignore
x_last = self._x.value(self._local_ns(context, ['x'])) # type: ignore
y = self._y.step(self._local_ns(context, ['y'])) # type: ignore
x = self._x.value(self._local_ns(context, ['x'])) # type: ignore
case GraphType.Parametric:
x_last = self._x.value(context) # type: ignore
y_last = self._y.value(context) # type: ignore
self._t.step(context) # type: ignore
x = self._x.value(context) # type: ignore
y = self._y.value(context) # type: ignore
x_last = self._x.value(self._local_ns(context, ['x'])) # type: ignore
y_last = self._y.value(self._local_ns(context, ['y'])) # type: ignore
self._t.step(self._local_ns(context, ['t'])) # type: ignore
x = self._x.value(self._local_ns(context, ['x'])) # type: ignore
y = self._y.value(self._local_ns(context, ['y'])) # type: ignore
case GraphType.Polar:
theta_last = self._theta.value(context) # type: ignore
r_last = self._r.value(context) # type: ignore
theta = self._theta.step(context) # type: ignore
r = self._r.value(context) # type: ignore
theta_last = self._theta.value(self._local_ns(context, ['θ'])) # type: ignore
r_last = self._r.value(self._local_ns(context, ['r'])) # type: ignore
theta = self._theta.step(self._local_ns(context, ['θ'])) # type: ignore
r = self._r.value(self._local_ns(context, ['r'])) # type: ignore
x_last = r_last * math.cos(theta_last)
y_last = r_last * math.sin(theta_last)
x = r * math.cos(theta)
@ -2463,21 +2500,21 @@ class ContextGraph:
elif isinstance(self._theta, ContextAnimation):
return GraphType.Polar
else:
raise SemanticsError("Invalid Function Type.")
raise GraphRuntimeError("Invalid Graph.", self.file_info)
@property
def color_space(self) -> ColorSpace:
if isinstance(self._color_red, ContextFunction):
if isinstance(self._color_red, ContextExpression):
return ColorSpace.RGB
elif isinstance(self._color_green, ContextFunctionCallable):
elif isinstance(self._color_green, ContextExpression):
return ColorSpace.RGB
elif isinstance(self._color_blue, ContextFunctionCallable):
elif isinstance(self._color_blue, ContextExpression):
return ColorSpace.RGB
elif isinstance(self._color_hue, ContextFunctionCallable):
elif isinstance(self._color_hue, ContextExpression):
return ColorSpace.HSL
elif isinstance(self._color_saturation, ContextFunctionCallable):
elif isinstance(self._color_saturation, ContextExpression):
return ColorSpace.HSL
elif isinstance(self._color_luminosity, ContextFunctionCallable):
elif isinstance(self._color_luminosity, ContextExpression):
return ColorSpace.HSL
else:
return ColorSpace.Grey
@ -2485,23 +2522,23 @@ class ContextGraph:
def color(
self, context: ContextNamespace) -> tuple[float,float,float,float,]:
if self._color_alpha is not None:
a = self._color_alpha.value(context)
a = self._color_alpha.value(self._local_ns(context, ['c_a']))
else: a = 1
match self.color_space:
case ColorSpace.Grey:
if self._color_grey is not None:
c = self._color_grey.value(context)
c = self._color_grey.value(self._local_ns(context, ['c_w']))
else: c = 1
r, g, b = c, c, c
case ColorSpace.RGB:
if self._color_red is not None:
r = self._color_red.value(context)
r = self._color_red.value(self._local_ns(context, ['c_r']))
else: r = 1
if self._color_green is not None:
g = self._color_green.value(context)
g = self._color_green.value(self._local_ns(context, ['c_g']))
else: g = 1
if self._color_blue is not None:
b = self._color_blue.value(context)
b = self._color_blue.value(self._local_ns(context, ['c_b']))
else: b = 1
case ColorSpace.HSL:
r,g,b = 1,1,1
@ -2510,22 +2547,43 @@ class ContextGraph:
def value(self, context: ContextNamespace) -> int | float:
match self.graph_type:
case GraphType.X_Independent:
return self._x.value(context) # type: ignore
return self._x.value(self._local_ns(context, ['x'])) # type: ignore
case GraphType.Y_Independent:
return self._y.value(context) # type: ignore
return self._y.value(self._local_ns(context, ['y'])) # type: ignore
case GraphType.Parametric:
return self._t.value(context) # type: ignore
return self._t.value(self._local_ns(context, ['t'])) # type: ignore
case GraphType.Polar:
return self._theta.value(context) # type: ignore
return self._theta.value(self._local_ns(context, ['θ'])) # type: ignore
class Context:
_file_info: FileInfo
_screen: Screen
_constants: dict[str, int | float]
_functions: dict[str, ContextFunctionCallable]
_animations: dict[str, ContextAnimation]
_graphs: list[ContextGraph]
def __init__(
self,
file_info: FileInfo,
screen: Screen,
constants: dict[str, int | float],
functions: dict[str, ContextFunctionCallable],
animations: dict[str, ContextAnimation],
graphs: list[ContextGraph],
):
self._file_info = file_info
self._screen = screen
self._constants = constants
self._functions = functions
self._animations = animations
self._graphs = graphs
@property
def file_info(self) -> FileInfo: return self._file_info
def step(self):
for anim in self._animations.values():
anim.step(ContextNamespace(self))
@ -2546,6 +2604,16 @@ class Context:
return self._functions[function.identifier]
def _constants_and_animations(
constants: dict[str, ContextLiteral],
animations: dict[str, ContextAnimation],
) -> dict[str, ContextLiteral]:
combined: dict[str, ContextLiteral] = {}
for key, value in constants.items(): combined[key] = value
for key, value in animations.items():
combined[key] = value.as_context_literal
return combined
def _simplify_expression(
expression: Expression,
constants: dict[str, ContextLiteral],
@ -2604,8 +2672,8 @@ def _simplify_expression(
isinstance(value1, ContextLiteral) and
isinstance(value2, ContextLiteral)
):
value1 = float(value1._value)
value2 = float(value2._value)
value1 = math.pi if value1._value == 'π' else float(value1._value)
value2 = math.pi if value2._value == 'π' else float(value2._value)
match expression.operator:
case BinaryOperator.Exponential:
return ContextLiteral(
@ -2681,7 +2749,7 @@ def _simplify_expression(
)
else:
if all(isinstance(a, ContextLiteral) for a in args):
args = [a._value for a in args] # type: ignore
args = [math.pi if a._value == 'π' else float(a._value) for a in args] # type: ignore
bad_arg, domain = func.check_domain(args)
if bad_arg > 0:
raise FunctionDomainError(
@ -2708,7 +2776,6 @@ def _simplify_expression(
)
else: raise SemanticError("Expression Error", expression.file_info)
def _simplify_animation(
animation: Animation | InlineAnimation,
constants: dict[str, ContextLiteral],
@ -2729,6 +2796,7 @@ def _simplify_animation(
if not isinstance(step, ContextLiteral):
raise UnderDefinedConstantDefinition(animation.step.file_info, None)
return ContextAnimation(
animation.file_info,
start_value,
range_start,
animation.range_start_inclusive,
@ -2738,6 +2806,140 @@ def _simplify_animation(
animation.direction,
)
def _check_graph(graph, name, anim, yes_none, not_none):
for i in anim:
if isinstance(i, ContextAnimation):
raise MultipleAnimationsInGraph(
i.file_info,
graph.file_info
)
for i, j in yes_none:
if i is not None:
raise InvalidGraphDefinition(
f"{j} cannot be defined in a {name} Graph",
i.file_info,
graph.file_info
)
for i, j in not_none:
if i is None:
raise InvalidGraphDefinition(
f"{j} must be defined in a {name} Graph",
graph.file_info
)
def _simplify_graph(
graph: Graph,
constants: dict[str, ContextLiteral],
functions: dict[str, ContextFunctionCallable],
) -> ContextGraph:
x = None
y = None
t = None
r = None
theta = None
color_alpha = None
color_grey = None
color_red = None
color_green = None
color_blue = None
color_hue = None
color_saturation = None
color_luminosity = None
if graph.x is not None:
if isinstance(graph.x, InlineAnimation):
x = _simplify_animation(
graph.x, constants, functions)
else:
x = _simplify_expression(
graph.x, constants, functions)
if graph.y is not None:
if isinstance(graph.y, InlineAnimation):
y = _simplify_animation(
graph.y, constants, functions)
else:
y = _simplify_expression(
graph.y, constants, functions)
if graph.t is not None:
t = _simplify_animation(
graph.t, constants, functions)
if graph.r is not None:
r = _simplify_expression(
graph.r, constants, functions)
if graph.theta is not None:
theta = _simplify_animation(
graph.theta, constants, functions)
if graph.color_alpha is not None:
color_alpha = _simplify_expression(
graph.color_alpha, constants, functions)
if graph.color_grey is not None:
color_grey = _simplify_expression(
graph.color_grey, constants, functions)
if graph.color_red is not None:
color_red = _simplify_expression(
graph.color_red, constants, functions)
if graph.color_green is not None:
color_green = _simplify_expression(
graph.color_green, constants, functions)
if graph.color_blue is not None:
color_blue = _simplify_expression(
graph.color_blue, constants, functions)
if graph.color_hue is not None:
color_hue = _simplify_expression(
graph.color_hue, constants, functions)
if graph.color_saturation is not None:
color_saturation = _simplify_expression(
graph.color_saturation, constants, functions)
if graph.color_luminosity is not None:
color_luminosity = _simplify_expression(
graph.color_luminosity, constants, functions)
if isinstance(x, ContextAnimation):
_check_graph(
graph,
"X-Independent",
[y, t, theta,],
[(t, 'T'),(r, 'R'),(theta, 'θ'),],
[(y, 'Y'),]
)
elif isinstance(y, ContextAnimation):
_check_graph(
graph,
"Y-Independent",
[x, t, theta,],
[(t, 'T'),(r, 'R'),(theta, 'θ'),],
[(x, 'X'),]
)
elif isinstance(t, ContextAnimation):
_check_graph(
graph,
"Parametric",
[x, y, theta,],
[(r, 'R'),(theta, 'θ'),],
[(x, 'X'),(y, 'Y'),]
)
elif isinstance(theta, ContextAnimation):
_check_graph(
graph,
"Polar",
[x, y, t,],
[(x, 'X'),(y, 'Y'),(t, 'T'),],
[(r, 'R'),]
)
else:
raise InvalidGraphDefinition(
"A graph must have at least one animation.",
graph.file_info
)
return ContextGraph(
graph.file_info,
x, y, t,
r, theta,
color_alpha, color_grey,
color_red, color_green, color_blue,
color_hue, color_saturation, color_luminosity,
)
def semantics_analyzer(file: File) -> Context:
screen: Screen | None = None
constants: dict[str, ContextLiteral] = {}
@ -2761,7 +2963,7 @@ def semantics_analyzer(file: File) -> Context:
elif isinstance(child, Constant):
value = _simplify_expression(
child.expression,
constants,
_constants_and_animations(constants,animations),
functions,
True,
)
@ -2770,105 +2972,29 @@ def semantics_analyzer(file: File) -> Context:
constants[child.identifier.value] = value
elif isinstance(child, Animation):
animations[child.identifier.value] = _simplify_animation(
child, constants, functions)
child,
_constants_and_animations(constants,animations),
functions,
)
elif isinstance(child, Graph):
x = None
y = None
t = None
r = None
theta = None
color_alpha = None
color_grey = None
color_red = None
color_green = None
color_blue = None
color_hue = None
color_saturation = None
color_luminosity = None
if child.x is not None:
if isinstance(child.x, InlineAnimation):
x = _simplify_animation(
child.x, constants, functions)
else:
x = _simplify_expression(
child.x, constants, functions, True)
if child.y is not None:
if isinstance(child.y, InlineAnimation):
y = _simplify_animation(
child.y, constants, functions)
else:
y = _simplify_expression(
child.y, constants, functions, True)
if child.t is not None:
t = _simplify_animation(
child.t, constants, functions)
if child.r is not None:
r = _simplify_expression(
child.r, constants, functions, True)
if child.theta is not None:
theta = _simplify_animation(
child.theta, constants, functions)
if child.color_alpha is not None:
color_alpha = _simplify_expression(
child.color_alpha, constants, functions, True)
if child.color_grey is not None:
color_grey = _simplify_expression(
child.color_grey, constants, functions, True)
if child.color_red is not None:
color_red = _simplify_expression(
child.color_red, constants, functions, True)
if child.color_green is not None:
color_green = _simplify_expression(
child.color_green, constants, functions, True)
if child.color_blue is not None:
color_blue = _simplify_expression(
child.color_blue, constants, functions, True)
if child.color_hue is not None:
color_hue = _simplify_expression(
child.color_hue, constants, functions, True)
if child.color_saturation is not None:
color_saturation = _simplify_expression(
child.color_saturation, constants, functions, True)
if child.color_luminosity is not None:
color_luminosity = _simplify_expression(
child.color_luminosity, constants, functions, True)
if isinstance(x, ContextAnimation):
for i in [y, t, theta]:
if isinstance(i, ContextAnimation):
raise MultipleAnimationsInGraph(
i.file_info,
child.file_info
)
for i, j in [(t, 'T'),(r, 'R'),(theta, 'θ')]:
if i is not None:
raise InvalidGraphDefinition(
f"{j} cannot be defined in an X-Independent Graph",
i.file_info,
child.file_info
)
if isinstance(y, ContextAnimation):
for i in [t, theta]:
if isinstance(i, ContextAnimation):
raise MultipleAnimationsInGraph(
i.file_info,
child.file_info
)
for i, j in [(t, 'T'),(r, 'R'),(theta, 'θ')]:
if i is not None:
raise InvalidGraphDefinition(
f"{j} cannot be defined in an Y-Independent Graph",
i.file_info,
child.file_info
)
graphs.append(ContextGraph(
x, y, t, r, theta, color_alpha, color_grey, color_red,
color_green, color_blue, color_hue, color_saturation,
color_luminosity,
graphs.append(_simplify_graph(
child,
_constants_and_animations(constants,animations),
functions,
))
if screen is None:
screen = Screen(file.file_info)
context_constants: dict[str, int | float] = {}
for key, value in constants.items():
context_constants[key] = value.value(ContextNamespaceNull())
return Context(
file.file_info,
screen,
context_constants,
functions,
animations,
graphs,
)
if __name__ == '__main__':
@ -2881,6 +3007,7 @@ if __name__ == '__main__':
syntax_tree = syntactical_analyzer(tokens)
with open("syntax.txt", 'w', encoding='utf-8') as file:
file.write(syntax_tree.tree_str())
context = semantics_analyzer(syntax_tree)
except CompilerError as err:
print(err.compiler_error())
# raise