Worked on semantics

This commit is contained in:
Kyler Olsen 2025-02-11 00:44:21 -07:00
parent 38ee57547d
commit 7e33f6294c
1 changed files with 672 additions and 82 deletions

View File

@ -3,7 +3,7 @@
from enum import Enum from enum import Enum
import math import math
from typing import ClassVar, Sequence from typing import Any, Callable, ClassVar, Iterable, Sequence
from textwrap import indent from textwrap import indent
@ -508,16 +508,20 @@ class Expression:
class LiteralExpression(Expression): class LiteralExpression(Expression):
_file_info: FileInfo _file_info: FileInfo
_value: NumberLiteral | Punctuation _value: NumberLiteral | Punctuation | Identifier
def __init__( def __init__(
self, self,
file_info: FileInfo, file_info: FileInfo,
value: NumberLiteral | Punctuation, value: NumberLiteral | Punctuation | Identifier,
): ):
self._file_info = file_info self._file_info = file_info
self._value = value self._value = value
@property
def value(self) -> NumberLiteral | Punctuation | Identifier:
return self._value
def has_pi(self) -> bool: return self._value.value == 'π' def has_pi(self) -> bool: return self._value.value == 'π'
def tree_str(self, pre: str = "", pre_cont: str = "") -> str: def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
@ -546,6 +550,12 @@ class UnaryExpression(Expression):
self._expression = expression self._expression = expression
self._operator = operator self._operator = operator
@property
def expression(self) -> Expression: return self._expression
@property
def operator(self) -> UnaryOperator: return self._operator
def has_pi(self) -> bool: return self._expression.has_pi() def has_pi(self) -> bool: return self._expression.has_pi()
def tree_str(self, pre: str = "", pre_cont: str = "") -> str: def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
@ -583,6 +593,15 @@ class BinaryExpression(Expression):
self._expression2 = expression2 self._expression2 = expression2
self._operator = operator self._operator = operator
@property
def expression1(self) -> Expression: return self._expression1
@property
def expression2(self) -> Expression: return self._expression2
@property
def operator(self) -> BinaryOperator: return self._operator
def has_pi(self) -> bool: def has_pi(self) -> bool:
return self._expression1.has_pi() or self._expression2.has_pi() return self._expression1.has_pi() or self._expression2.has_pi()
@ -626,6 +645,12 @@ class FunctionCall(Expression):
self._identifier = identifier self._identifier = identifier
self._arguments = arguments self._arguments = arguments
@property
def identifier(self) -> Identifier: return self._identifier
@property
def arguments(self) -> list[Expression]: return self._arguments[:]
def tree_str(self, pre: str = "", pre_cont: str = "") -> str: def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Function Call ({self._identifier.value})\n" s: str = f"{pre} Function Call ({self._identifier.value})\n"
for arg in self._arguments[:-1]: for arg in self._arguments[:-1]:
@ -653,6 +678,12 @@ class Constant:
@property @property
def file_info(self) -> FileInfo: return self._file_info def file_info(self) -> FileInfo: return self._file_info
@property
def identifier(self) -> Identifier: return self._identifier
@property
def expression(self) -> Expression: return self._expression
def tree_str(self, pre: str = "", pre_cont: str = "") -> str: def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Constant ({self._identifier.value})\n" s: str = f"{pre} Constant ({self._identifier.value})\n"
s += self._expression.tree_str(f"{pre_cont}└─", f"{pre_cont} ") s += self._expression.tree_str(f"{pre_cont}└─", f"{pre_cont} ")
@ -1687,65 +1718,440 @@ class DuplicateScreen(SemanticError):
) )
class ContextOperator(Enum): class UndefinedVariableIdentifier(SemanticError):
NoOp = "0"
Identifier = "i" def __init__(self, name: str, file_info: FileInfo):
FunctionCall = "f" super().__init__(
Negate = "n" f"Undefined Variable {name}.",
Factorial = "!" file_info,
Exponential = "^" )
Division = "/"
Modulus = "%"
Multiplication = "*" class UndefinedFunctionIdentifier(SemanticError):
Subtraction = "-"
Addition = "+" def __init__(self, name: str, file_info: FileInfo):
super().__init__(
f"Undefined Function {name}.",
file_info,
)
class DivideByZeroError(SemanticError):
def __init__(self, file_info: FileInfo, file_info_context: FileInfo):
super().__init__(
"Cannot divide by zero.",
file_info,
file_info_context,
)
class ParameterCountError(SemanticError):
def __init__(
self,
name: str,
expected: int,
found: int,
file_info: FileInfo,
file_info_context: FileInfo | None,
):
super().__init__(
f"Function {name} expects {expected} parameters but was given "
f"{found} parameters.",
file_info,
file_info_context,
)
class FunctionDomainError(SemanticError):
def __init__(
self,
arg: int | float,
domain: str,
file_info: FileInfo,
file_info_context: FileInfo,
):
super().__init__(
f"Argument ({arg}) outside of function domain: {domain}.",
file_info,
file_info_context,
)
class UnderDefinedConstantDefinition(SemanticError):
def __init__(self, file_info: FileInfo, file_info_context: FileInfo | None):
super().__init__(
"Under-Defined Constant Definition.",
file_info,
file_info_context,
)
class GraphRuntimeError(CompilerError):
_compiler_error_type = "Runtime"
class UndefinedVariableIdentifierRuntime(GraphRuntimeError):
def __init__(self, name: str, file_info: FileInfo):
super().__init__(
f"Undefined Variable {name}.",
file_info,
)
class UndefinedFunctionIdentifierRuntime(GraphRuntimeError):
def __init__(self, name: str, file_info: FileInfo):
super().__init__(
f"Undefined Function {name}.",
file_info,
)
class DivideByZeroErrorRuntime(GraphRuntimeError):
def __init__(self, file_info: FileInfo, file_info_context: FileInfo):
super().__init__(
"Cannot divide by zero.",
file_info,
file_info_context,
)
class ParameterCountErrorRuntime(GraphRuntimeError):
def __init__(
self,
name: str,
expected: int,
found: int,
file_info: FileInfo,
file_info_context: FileInfo | None,
):
super().__init__(
f"Function {name} expects {expected} parameters but was given "
f"{found} parameters.",
file_info,
file_info_context,
)
class FunctionDomainErrorRuntime(GraphRuntimeError):
def __init__(
self,
arg: int | float,
domain: str,
file_info: FileInfo,
file_info_context: FileInfo,
):
super().__init__(
f"Argument ({arg}) outside of function domain: {domain}.",
file_info,
file_info_context,
)
class ContextExpression: class ContextExpression:
_context: "Context | ContextFunction" _file_info: FileInfo
_value: None | int | float
_name: None | str
_operator: ContextOperator
_expressions: "list[ContextExpression]"
@property @property
def value(self) -> int | float: def file_info(self) -> FileInfo: return self._file_info
a = 0 if len(self._expressions) > 0 else self._expressions[0].value
b = 0 if len(self._expressions) > 1 else self._expressions[1].value def value(self, context: "Context") -> int | float:
match self._operator: raise RuntimeError
case ContextOperator.NoOp:
return self._value or 0 def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
case ContextOperator.Identifier: s: str = f"{pre} Context Expression\n"
if self._name is not None: return s
return self._context.lookup(self._name)
else: raise Exception
case ContextOperator.FunctionCall: class ContextLiteral(ContextExpression):
if self._name is not None:
return self._context.call(self._name, self._expressions) _file_info: FileInfo
else: raise Exception _value: str
case ContextOperator.Negate:
if len(self._expressions) > 0: def __init__(
return (0 if self._value is None else -self._value) self,
else: return -self._expressions[0].value file_info: FileInfo,
case ContextOperator.Factorial: value: str,
if len(self._expressions) > 0: ):
if self._value is None: return 1 self._file_info = file_info
else: return math.factorial(int(self._value)) self._value = value
else: return math.factorial(int(self._expressions[0].value))
case ContextOperator.Exponential: def value(self, context: "Context") -> int | float:
if self._value == 'π': return math.pi
elif '.' in self._value: return float(self._value)
else: return int(self._value)
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Context Literal ({self._value})\n"
return s
class ContextVariable(ContextExpression):
_file_info: FileInfo
_name: str
def __init__(
self,
file_info: FileInfo,
name: str,
):
self._file_info = file_info
self._name = name
@property
def name(self) -> str: return self._name
def value(self, context: "Context") -> int | float:
return context.lookup_var(self)
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Context Variable ({self.name})\n"
return s
class ContextUnaryExpression(ContextExpression):
_file_info: FileInfo
_expression: ContextExpression
_operator: UnaryOperator
def __init__(
self,
file_info: FileInfo,
expression: ContextExpression,
operator: UnaryOperator,
):
self._file_info = file_info
self._expression = expression
self._operator = operator
@property
def expression(self) -> ContextExpression: return self._expression
@property
def operator(self) -> UnaryOperator: return self._operator
def value(self, context: "Context") -> int | float:
match self.operator:
case UnaryOperator.Negate:
return - self.expression.value(context)
case UnaryOperator.Factorial:
return math.factorial(
math.floor(self.expression.value(context)))
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Context Unary Expression ({self._operator})\n"
s += self._expression.tree_str(f"{pre_cont}└─", f"{pre_cont} ")
return s
class ContextBinaryExpression(ContextExpression):
_file_info: FileInfo
_expression1: ContextExpression
_expression2: ContextExpression
_operator: BinaryOperator
def __init__(
self,
file_info: FileInfo,
expression1: ContextExpression,
expression2: ContextExpression,
operator: BinaryOperator,
):
self._file_info = file_info
self._expression1 = expression1
self._expression2 = expression2
self._operator = operator
@property
def expression1(self) -> ContextExpression: return self._expression1
@property
def expression2(self) -> ContextExpression: return self._expression2
@property
def operator(self) -> BinaryOperator: return self._operator
def value(self, context: "Context") -> int | float:
a, b = self.expression1.value(context), self.expression2.value(context)
match self.operator:
case BinaryOperator.Exponential:
return a ** b return a ** b
case ContextOperator.Division: case BinaryOperator.Subscript:
raise GraphRuntimeError("ERROR", self.file_info)
case BinaryOperator.Division:
if b == 0:
raise DivideByZeroErrorRuntime(
self.expression2.file_info,
self.file_info,
)
return a / b return a / b
case ContextOperator.Modulus: case BinaryOperator.Modulus:
if b == 0:
raise DivideByZeroErrorRuntime(
self.expression2.file_info,
self.file_info,
)
return a % b return a % b
case ContextOperator.Multiplication: case BinaryOperator.Multiplication:
return a * b return a * b
case ContextOperator.Subtraction: case BinaryOperator.Subtraction:
return a - b return a - b
case ContextOperator.Addition: case BinaryOperator.Addition:
return a + b return a + b
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Context Binary Expression ({self._operator})\n"
s += self._expression1.tree_str(f"{pre_cont}├─", f"{pre_cont}")
s += self._expression2.tree_str(f"{pre_cont}└─", f"{pre_cont} ")
return s
class ContextFunctionDomain:
_start: int | float
_start_inclusive: bool
_end: int | float
_end_inclusive: bool
def __init__(
self,
start: int | float,
start_inclusive: bool,
end: int | float,
end_inclusive: bool,
):
self._start = start
self._start_inclusive = start_inclusive
self._end = end
self._end_inclusive = end_inclusive
def __str__(self) -> str:
return (
('[' if self._start_inclusive else '(') +
f"{self._start}, {self._end}" +
(']' if self._start_inclusive else ')')
)
def __contains__(self, other: int | float):
return (
(self._start_inclusive and self._start <= other) or
((not self._start_inclusive) and self._start < other) or
(self._end_inclusive and self._end >= other) or
((not self._end_inclusive) and self._end > other)
)
class ContextFunctionCallable:
_parameters: int
_function: Callable
_domain: list[ContextFunctionDomain | None]
def __init__(
self,
parameters: int,
function: Callable,
domain: list[ContextFunctionDomain | None] | None = None,
):
self._parameters = parameters
self._function = function
self._domain = domain or []
@property
def parameters(self) -> int: return self._parameters
def check_domain(self, args: Iterable[int | float]) -> tuple[int, str]:
for i, (d, a) in enumerate(zip(self._domain, args)):
if d is not None and not (a in d): return i, str(d)
return -1, ''
def __call__(self, *args) -> int | float:
return self._function(*args)
class ContextFunctionCall(ContextExpression):
_file_info: FileInfo
_identifier: str
_identifier_file_info: FileInfo
_arguments: list[ContextExpression]
def __init__(
self,
file_info: FileInfo,
identifier: str,
identifier_file_info: FileInfo,
arguments: list[ContextExpression],
):
self._file_info = file_info
self._identifier = identifier
self._identifier_file_info = identifier_file_info
self._arguments = arguments
@property
def identifier(self) -> str: return self._identifier
@property
def identifier_file_info(self) -> FileInfo:
return self._identifier_file_info
@property
def arguments(self) -> list[ContextExpression]: return self._arguments[:]
def value(self, context: "Context") -> int | float:
args = [a.value(context) for a in self._arguments]
func = context.lookup_func(self)
if len(args) > func.parameters:
raise ParameterCountErrorRuntime(
self.identifier,
func.parameters,
len(args),
self._arguments[func.parameters].file_info,
self.file_info,
)
elif len(args) < func.parameters:
raise ParameterCountErrorRuntime(
self.identifier,
func.parameters,
len(args),
self.identifier_file_info,
self.file_info,
)
else:
bad_arg, domain = func.check_domain(args)
if bad_arg > 0:
raise FunctionDomainErrorRuntime(
args[bad_arg],
domain,
self._arguments[bad_arg].file_info,
self.file_info,
)
return func(*args)
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Context Function Call ({self.identifier})\n"
for arg in self._arguments[:-1]:
s += arg.tree_str(f"{pre_cont}├─", f"{pre_cont}")
s += self._arguments[-1].tree_str(f"{pre_cont}└─", f"{pre_cont} ")
return s
class ContextFunction: class ContextFunction:
@ -1757,8 +2163,8 @@ class ContextFunction:
def lookup(self, value: str) -> int | float: def lookup(self, value: str) -> int | float:
if value in self._values: if value in self._values:
return self._values[value].value return self._values[value].value(context)
else: return self._context.lookup(value) else: return self._context.lookup_var(value)
def call(self, value: str, args: list[ContextExpression]) -> int | float: def call(self, value: str, args: list[ContextExpression]) -> int | float:
return self._context.call(value, args) return self._context.call(value, args)
@ -1775,45 +2181,43 @@ class ContextAnimation:
_direction: AnimationDirection _direction: AnimationDirection
_reversed: bool _reversed: bool
def __iter__(self): return self def step(self, context: "Context"):
def __next__(self):
if self._direction == AnimationDirection.Increase or ( if self._direction == AnimationDirection.Increase or (
self._direction == AnimationDirection.Bounce and not self._reversed self._direction == AnimationDirection.Bounce and not self._reversed
): ):
self._current += self._step.value self._current += self._step.value(context)
if self._range_end_inclusive: if self._range_end_inclusive:
if self._current > self._range_end.value: if self._current > self._range_end.value(context):
self._current = self._range_start.value self._current = self._range_start.value(context)
if self._direction == AnimationDirection.Bounce: if self._direction == AnimationDirection.Bounce:
self._reversed = True self._reversed = True
if not self._range_start_inclusive: if not self._range_start_inclusive:
self._current += self._step.value self._current += self._step.value(context)
else: else:
if self._current >= self._range_end.value: if self._current >= self._range_end.value(context):
self._current = self._range_start.value self._current = self._range_start.value(context)
if self._direction == AnimationDirection.Bounce: if self._direction == AnimationDirection.Bounce:
self._reversed = True self._reversed = True
if not self._range_start_inclusive: if not self._range_start_inclusive:
self._current += self._step.value self._current += self._step.value(context)
if self._direction == AnimationDirection.Decrease or ( if self._direction == AnimationDirection.Decrease or (
self._direction == AnimationDirection.Bounce and self._reversed self._direction == AnimationDirection.Bounce and self._reversed
): ):
self._current -= self._step.value self._current -= self._step.value(context)
if self._range_start_inclusive: if self._range_start_inclusive:
if self._current < self._range_start.value: if self._current < self._range_start.value(context):
self._current = self._range_end.value self._current = self._range_end.value(context)
if self._direction == AnimationDirection.Bounce: if self._direction == AnimationDirection.Bounce:
self._reversed = False self._reversed = False
if not self._range_end_inclusive: if not self._range_end_inclusive:
self._current -= self._step.value self._current -= self._step.value(context)
else: else:
if self._current <= self._range_start.value: if self._current <= self._range_start.value(context):
self._current = self._range_end.value self._current = self._range_end.value(context)
if self._direction == AnimationDirection.Bounce: if self._direction == AnimationDirection.Bounce:
self._reversed = False self._reversed = False
if not self._range_end_inclusive: if not self._range_end_inclusive:
self._current -= self._step.value self._current -= self._step.value(context)
return self.value return self.value
@property @property
@ -1851,7 +2255,7 @@ class ContextGraph:
def __iter__(self): return self def __iter__(self): return self
def __next__(self) -> tuple[ def step(self, context: "Context") -> tuple[
int | float, int | float,
int | float, int | float,
int | float, int | float,
@ -1903,15 +2307,15 @@ class ContextGraph:
def color_space(self) -> ColorSpace: def color_space(self) -> ColorSpace:
if isinstance(self._color_red, ContextFunction): if isinstance(self._color_red, ContextFunction):
return ColorSpace.RGB return ColorSpace.RGB
elif isinstance(self._color_green, ContextFunction): elif isinstance(self._color_green, ContextFunctionCallable):
return ColorSpace.RGB return ColorSpace.RGB
elif isinstance(self._color_blue, ContextFunction): elif isinstance(self._color_blue, ContextFunctionCallable):
return ColorSpace.RGB return ColorSpace.RGB
elif isinstance(self._color_hue, ContextFunction): elif isinstance(self._color_hue, ContextFunctionCallable):
return ColorSpace.HSL return ColorSpace.HSL
elif isinstance(self._color_saturation, ContextFunction): elif isinstance(self._color_saturation, ContextFunctionCallable):
return ColorSpace.HSL return ColorSpace.HSL
elif isinstance(self._color_luminosity, ContextFunction): elif isinstance(self._color_luminosity, ContextFunctionCallable):
return ColorSpace.HSL return ColorSpace.HSL
else: else:
return ColorSpace.Grey return ColorSpace.Grey
@ -1969,6 +2373,7 @@ funcs_1 = {
class Context: class Context:
_constants: dict[str, int | float] _constants: dict[str, int | float]
_functions: dict[str, ContextFunctionCallable]
_animations: dict[str, ContextAnimation] _animations: dict[str, ContextAnimation]
_graphs: list[ContextGraph] _graphs: list[ContextGraph]
@ -1976,18 +2381,195 @@ class Context:
for anim in self._animations.values(): for anim in self._animations.values():
next(anim) next(anim)
def lookup(self, value: str) -> int | float: def lookup_var(self, variable: ContextVariable) -> int | float:
return self._constants[value] if variable.name not in self._constants:
raise UndefinedVariableIdentifierRuntime(
variable.name, variable.file_info)
return self._constants[variable.name]
def call(self, value: str, args: list[ContextExpression]) -> int | float: def lookup_func(self, function: ContextFunctionCall) -> ContextFunctionCallable:
if value in funcs_1 and len(args) == 1: if function.identifier not in self._functions:
return funcs_1[value](args[0].value) raise UndefinedFunctionIdentifierRuntime(
raise KeyError function.identifier, function.file_info)
return self._functions[function.identifier]
def _simplify_expression(
expression: Expression,
constants: dict[str, ContextLiteral],
functions: dict[str, ContextFunctionCallable],
constant: bool = False,
) -> ContextExpression:
if isinstance(expression, LiteralExpression):
if isinstance(expression.value, (Punctuation, Identifier)):
if expression.value.value in ['π',]:
return ContextLiteral(
expression.file_info, expression.value.value)
elif expression.value.value in constants:
return constants[expression.value.value]
elif constant:
raise UndefinedVariableIdentifier(
expression.value.value,
expression.file_info
)
else:
return ContextVariable(
expression.file_info, expression.value.value)
else:
return ContextLiteral(
expression.file_info, expression.value.value)
elif isinstance(expression, UnaryExpression):
value = _simplify_expression(
expression.expression, constants, functions, constant)
if isinstance(value, ContextLiteral):
value = float(value._value)
match expression.operator:
case UnaryOperator.Negate:
return ContextLiteral(
expression.file_info, str(-value))
case UnaryOperator.Factorial:
return ContextLiteral(
expression.file_info,
str(math.factorial(math.floor(value))),
)
else:
if constant:
raise UnderDefinedConstantDefinition(
value.file_info,
expression.file_info,
)
return ContextUnaryExpression(
expression.file_info,
value,
expression.operator,
)
elif isinstance(expression, BinaryExpression):
value1 = _simplify_expression(
expression.expression1, constants, functions, constant)
value2 = _simplify_expression(
expression.expression2, constants, functions, constant)
if (
isinstance(value1, ContextLiteral) and
isinstance(value2, ContextLiteral)
):
value1 = float(value1._value)
value2 = float(value2._value)
match expression.operator:
case BinaryOperator.Exponential:
return ContextLiteral(
expression.file_info, str(value1 ** value2))
case BinaryOperator.Subscript:
raise SemanticError("ERROR", expression.file_info)
case BinaryOperator.Division:
if value2 == 0:
raise DivideByZeroError(
expression.expression2.file_info,
expression.file_info,
)
return ContextLiteral(
expression.file_info, str(value1 / value2))
case BinaryOperator.Modulus:
if value2 == 0:
raise DivideByZeroError(
expression.expression2.file_info,
expression.file_info,
)
return ContextLiteral(
expression.file_info, str(value1 % value2))
case BinaryOperator.Multiplication:
return ContextLiteral(
expression.file_info, str(value1 * value2))
case BinaryOperator.Subtraction:
return ContextLiteral(
expression.file_info, str(value1 - value2))
case BinaryOperator.Addition:
return ContextLiteral(
expression.file_info, str(value1 + value2))
elif constant and not isinstance(value1, ContextLiteral):
raise UnderDefinedConstantDefinition(
value1.file_info,
expression.file_info,
)
elif constant and not isinstance(value2, ContextLiteral):
raise UnderDefinedConstantDefinition(
value2.file_info,
expression.file_info,
)
else:
return ContextBinaryExpression(
expression.file_info,
value1,
value2,
expression.operator,
)
elif isinstance(expression, FunctionCall):
args = [_simplify_expression(a, constants, functions, constant)
for a in expression.arguments]
if expression.identifier.value not in functions:
raise UndefinedFunctionIdentifier(
expression.identifier.value,
expression.identifier.file_info,
)
func = functions[expression.identifier.value]
if len(args) > func.parameters:
raise ParameterCountError(
expression.identifier.value,
func.parameters,
len(args),
expression.arguments[func.parameters].file_info,
expression.file_info,
)
elif len(args) < func.parameters:
raise ParameterCountError(
expression.identifier.value,
func.parameters,
len(args),
expression.identifier.file_info,
expression.file_info,
)
else:
if all(isinstance(a, ContextLiteral) for a in args):
args = [a._value for a in args] # type: ignore
bad_arg, domain = func.check_domain(args)
if bad_arg > 0:
raise FunctionDomainError(
args[bad_arg],
domain,
expression.arguments[bad_arg].file_info,
expression.file_info,
)
return ContextLiteral(
expression.file_info, str(func(*args)))
elif constant:
bad_arg = [isinstance(a, ContextLiteral) for a in args]\
.index(False)
raise UnderDefinedConstantDefinition(
expression.arguments[bad_arg].file_info,
expression.file_info,
)
else:
return ContextFunctionCall(
expression.file_info,
expression.identifier.value,
expression.identifier.file_info,
args,
)
else: raise SemanticError("Expression Error", expression.file_info)
def semantics_analyzer(file: File) -> Context: def semantics_analyzer(file: File) -> Context:
screen: Screen | None = None screen: Screen | None = None
constraints: dict[str, int | float] = {} constants: dict[str, ContextLiteral] = {}
functions: dict[str, ContextFunctionCallable] = {
"sin": ContextFunctionCallable(1, math.sin),
"asin": ContextFunctionCallable(1, math.asin),
"cos": ContextFunctionCallable(1, math.cos),
"acos": ContextFunctionCallable(1, math.acos),
"tan": ContextFunctionCallable(1, math.tan),
"atan": ContextFunctionCallable(1, math.atan),
"ln": ContextFunctionCallable(1, math.log),
"log": ContextFunctionCallable(1, math.log10),
}
animations: dict[str, ContextAnimation] = {} animations: dict[str, ContextAnimation] = {}
graphs: list[ContextGraph] = [] graphs: list[ContextGraph] = []
for child in file.children: for child in file.children:
@ -1996,7 +2578,15 @@ def semantics_analyzer(file: File) -> Context:
raise DuplicateScreen(child.file_info) raise DuplicateScreen(child.file_info)
screen = child screen = child
elif isinstance(child, Constant): elif isinstance(child, Constant):
pass value = _simplify_expression(
child.expression,
constants,
functions,
True,
)
if not isinstance(value, ContextLiteral):
raise UnderDefinedConstantDefinition(child.file_info, None)
constants[child.identifier.value] = value
elif isinstance(child, Animation): elif isinstance(child, Animation):
pass pass
elif isinstance(child, Graph): elif isinstance(child, Graph):