Worked on semantics
This commit is contained in:
parent
38ee57547d
commit
7e33f6294c
754
compiler.py
754
compiler.py
|
@ -3,7 +3,7 @@
|
|||
|
||||
from enum import Enum
|
||||
import math
|
||||
from typing import ClassVar, Sequence
|
||||
from typing import Any, Callable, ClassVar, Iterable, Sequence
|
||||
from textwrap import indent
|
||||
|
||||
|
||||
|
@ -508,16 +508,20 @@ class Expression:
|
|||
class LiteralExpression(Expression):
|
||||
|
||||
_file_info: FileInfo
|
||||
_value: NumberLiteral | Punctuation
|
||||
_value: NumberLiteral | Punctuation | Identifier
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_info: FileInfo,
|
||||
value: NumberLiteral | Punctuation,
|
||||
value: NumberLiteral | Punctuation | Identifier,
|
||||
):
|
||||
self._file_info = file_info
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def value(self) -> NumberLiteral | Punctuation | Identifier:
|
||||
return self._value
|
||||
|
||||
def has_pi(self) -> bool: return self._value.value == 'π'
|
||||
|
||||
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
|
||||
|
@ -546,6 +550,12 @@ class UnaryExpression(Expression):
|
|||
self._expression = expression
|
||||
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 tree_str(self, pre: str = "", pre_cont: str = "") -> str:
|
||||
|
@ -583,6 +593,15 @@ class BinaryExpression(Expression):
|
|||
self._expression2 = expression2
|
||||
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:
|
||||
return self._expression1.has_pi() or self._expression2.has_pi()
|
||||
|
||||
|
@ -626,6 +645,12 @@ class FunctionCall(Expression):
|
|||
self._identifier = identifier
|
||||
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:
|
||||
s: str = f"{pre} Function Call ({self._identifier.value})\n"
|
||||
for arg in self._arguments[:-1]:
|
||||
|
@ -653,6 +678,12 @@ class Constant:
|
|||
@property
|
||||
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:
|
||||
s: str = f"{pre} Constant ({self._identifier.value})\n"
|
||||
s += self._expression.tree_str(f"{pre_cont}└─", f"{pre_cont} ")
|
||||
|
@ -1687,65 +1718,440 @@ class DuplicateScreen(SemanticError):
|
|||
)
|
||||
|
||||
|
||||
class ContextOperator(Enum):
|
||||
NoOp = "0"
|
||||
Identifier = "i"
|
||||
FunctionCall = "f"
|
||||
Negate = "n"
|
||||
Factorial = "!"
|
||||
Exponential = "^"
|
||||
Division = "/"
|
||||
Modulus = "%"
|
||||
Multiplication = "*"
|
||||
Subtraction = "-"
|
||||
Addition = "+"
|
||||
class UndefinedVariableIdentifier(SemanticError):
|
||||
|
||||
def __init__(self, name: str, file_info: FileInfo):
|
||||
super().__init__(
|
||||
f"Undefined Variable {name}.",
|
||||
file_info,
|
||||
)
|
||||
|
||||
|
||||
class UndefinedFunctionIdentifier(SemanticError):
|
||||
|
||||
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:
|
||||
|
||||
_context: "Context | ContextFunction"
|
||||
_value: None | int | float
|
||||
_name: None | str
|
||||
_operator: ContextOperator
|
||||
_expressions: "list[ContextExpression]"
|
||||
_file_info: FileInfo
|
||||
|
||||
@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:
|
||||
def file_info(self) -> FileInfo: return self._file_info
|
||||
|
||||
def value(self, context: "Context") -> int | float:
|
||||
raise RuntimeError
|
||||
|
||||
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
|
||||
s: str = f"{pre} Context Expression\n"
|
||||
return s
|
||||
|
||||
|
||||
class ContextLiteral(ContextExpression):
|
||||
|
||||
_file_info: FileInfo
|
||||
_value: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_info: FileInfo,
|
||||
value: str,
|
||||
):
|
||||
self._file_info = file_info
|
||||
self._value = value
|
||||
|
||||
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
|
||||
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
|
||||
case ContextOperator.Modulus:
|
||||
case BinaryOperator.Modulus:
|
||||
if b == 0:
|
||||
raise DivideByZeroErrorRuntime(
|
||||
self.expression2.file_info,
|
||||
self.file_info,
|
||||
)
|
||||
return a % b
|
||||
case ContextOperator.Multiplication:
|
||||
case BinaryOperator.Multiplication:
|
||||
return a * b
|
||||
case ContextOperator.Subtraction:
|
||||
case BinaryOperator.Subtraction:
|
||||
return a - b
|
||||
case ContextOperator.Addition:
|
||||
case BinaryOperator.Addition:
|
||||
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:
|
||||
|
||||
|
@ -1757,8 +2163,8 @@ class ContextFunction:
|
|||
|
||||
def lookup(self, value: str) -> int | float:
|
||||
if value in self._values:
|
||||
return self._values[value].value
|
||||
else: return self._context.lookup(value)
|
||||
return self._values[value].value(context)
|
||||
else: return self._context.lookup_var(value)
|
||||
|
||||
def call(self, value: str, args: list[ContextExpression]) -> int | float:
|
||||
return self._context.call(value, args)
|
||||
|
@ -1775,45 +2181,43 @@ class ContextAnimation:
|
|||
_direction: AnimationDirection
|
||||
_reversed: bool
|
||||
|
||||
def __iter__(self): return self
|
||||
|
||||
def __next__(self):
|
||||
def step(self, context: "Context"):
|
||||
if self._direction == AnimationDirection.Increase or (
|
||||
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._current > self._range_end.value:
|
||||
self._current = self._range_start.value
|
||||
if self._current > self._range_end.value(context):
|
||||
self._current = self._range_start.value(context)
|
||||
if self._direction == AnimationDirection.Bounce:
|
||||
self._reversed = True
|
||||
if not self._range_start_inclusive:
|
||||
self._current += self._step.value
|
||||
self._current += self._step.value(context)
|
||||
else:
|
||||
if self._current >= self._range_end.value:
|
||||
self._current = self._range_start.value
|
||||
if self._current >= self._range_end.value(context):
|
||||
self._current = self._range_start.value(context)
|
||||
if self._direction == AnimationDirection.Bounce:
|
||||
self._reversed = True
|
||||
if not self._range_start_inclusive:
|
||||
self._current += self._step.value
|
||||
self._current += self._step.value(context)
|
||||
if self._direction == AnimationDirection.Decrease or (
|
||||
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._current < self._range_start.value:
|
||||
self._current = self._range_end.value
|
||||
if self._current < self._range_start.value(context):
|
||||
self._current = self._range_end.value(context)
|
||||
if self._direction == AnimationDirection.Bounce:
|
||||
self._reversed = False
|
||||
if not self._range_end_inclusive:
|
||||
self._current -= self._step.value
|
||||
self._current -= self._step.value(context)
|
||||
else:
|
||||
if self._current <= self._range_start.value:
|
||||
self._current = self._range_end.value
|
||||
if self._current <= self._range_start.value(context):
|
||||
self._current = self._range_end.value(context)
|
||||
if self._direction == AnimationDirection.Bounce:
|
||||
self._reversed = False
|
||||
if not self._range_end_inclusive:
|
||||
self._current -= self._step.value
|
||||
self._current -= self._step.value(context)
|
||||
return self.value
|
||||
|
||||
@property
|
||||
|
@ -1851,7 +2255,7 @@ class ContextGraph:
|
|||
|
||||
def __iter__(self): return self
|
||||
|
||||
def __next__(self) -> tuple[
|
||||
def step(self, context: "Context") -> tuple[
|
||||
int | float,
|
||||
int | float,
|
||||
int | float,
|
||||
|
@ -1903,15 +2307,15 @@ class ContextGraph:
|
|||
def color_space(self) -> ColorSpace:
|
||||
if isinstance(self._color_red, ContextFunction):
|
||||
return ColorSpace.RGB
|
||||
elif isinstance(self._color_green, ContextFunction):
|
||||
elif isinstance(self._color_green, ContextFunctionCallable):
|
||||
return ColorSpace.RGB
|
||||
elif isinstance(self._color_blue, ContextFunction):
|
||||
elif isinstance(self._color_blue, ContextFunctionCallable):
|
||||
return ColorSpace.RGB
|
||||
elif isinstance(self._color_hue, ContextFunction):
|
||||
elif isinstance(self._color_hue, ContextFunctionCallable):
|
||||
return ColorSpace.HSL
|
||||
elif isinstance(self._color_saturation, ContextFunction):
|
||||
elif isinstance(self._color_saturation, ContextFunctionCallable):
|
||||
return ColorSpace.HSL
|
||||
elif isinstance(self._color_luminosity, ContextFunction):
|
||||
elif isinstance(self._color_luminosity, ContextFunctionCallable):
|
||||
return ColorSpace.HSL
|
||||
else:
|
||||
return ColorSpace.Grey
|
||||
|
@ -1969,6 +2373,7 @@ funcs_1 = {
|
|||
class Context:
|
||||
|
||||
_constants: dict[str, int | float]
|
||||
_functions: dict[str, ContextFunctionCallable]
|
||||
_animations: dict[str, ContextAnimation]
|
||||
_graphs: list[ContextGraph]
|
||||
|
||||
|
@ -1976,18 +2381,195 @@ class Context:
|
|||
for anim in self._animations.values():
|
||||
next(anim)
|
||||
|
||||
def lookup(self, value: str) -> int | float:
|
||||
return self._constants[value]
|
||||
def lookup_var(self, variable: ContextVariable) -> int | float:
|
||||
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:
|
||||
if value in funcs_1 and len(args) == 1:
|
||||
return funcs_1[value](args[0].value)
|
||||
raise KeyError
|
||||
def lookup_func(self, function: ContextFunctionCall) -> ContextFunctionCallable:
|
||||
if function.identifier not in self._functions:
|
||||
raise UndefinedFunctionIdentifierRuntime(
|
||||
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:
|
||||
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] = {}
|
||||
graphs: list[ContextGraph] = []
|
||||
for child in file.children:
|
||||
|
@ -1996,7 +2578,15 @@ def semantics_analyzer(file: File) -> Context:
|
|||
raise DuplicateScreen(child.file_info)
|
||||
screen = child
|
||||
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):
|
||||
pass
|
||||
elif isinstance(child, Graph):
|
||||
|
|
Loading…
Reference in New Issue