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
|
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):
|
||||||
|
|
Loading…
Reference in New Issue