ytd_12-bit_computer/pytd12dk/compiler/semantical_analyzer.py

1456 lines
45 KiB
Python

# Kyler Olsen
# Mar 2024
from enum import Enum
from typing import ClassVar
from .compiler_types import CompilerError, FileInfo
from . import syntactical_analyzer as sya
type SymbolDefinitionTypes = (
InternalDefinition |
sya.FunctionParameter |
sya.LetStatement |
ForPreDef |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock |
FunctionReturnDefinition
)
type SymbolReferenceTypes = (
sya.Identifier |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock
)
type Identifier = (
sya.Identifier |
CompoundIdentifier |
AddressOfIdentifier |
DereferenceIdentifier
)
type NestableCodeBlock = ForBlock | WhileBlock | DoBlock | IfBlock
type IntermediateStatement = (
sya.Expression |
sya.LetStatement |
sya.LoopStatements |
sya.NestableCodeBlock |
InternalDefinition |
Identifier
)
type Statement = (
sya.Expression |
sya.LetStatement |
sya.LoopStatements |
NestableCodeBlock |
InternalDefinition |
Identifier
)
type BlockHolder = (
ElseBlock |
ForPreDef |
ForBlock |
WhileBlock |
DoBlock |
IfBlock |
FunctionBlock
)
BaseValues: tuple[type, ...] = (
sya.BuiltInConst,
sya.NumberLiteral,
sya.CharLiteral,
sya.StringLiteral,
sya.Identifier,
sya.FunctionCall,
)
NestableCodeBlocks: tuple[type, ...] = (
sya.ForBlock,
sya.WhileBlock,
sya.DoBlock,
sya.IfBlock,
)
HasOperands: tuple[type, ...] = (
sya.UnaryExpression,
sya.BinaryExpression,
sya.TernaryExpression,
)
AddRefTypes: tuple[type, ...] = (
sya.UnaryExpression,
sya.BinaryExpression,
sya.TernaryExpression,
sya.FunctionCall,
sya.Identifier,
)
IncrementOperators: tuple[
sya.PostfixUnaryOperatorEnum |
sya.PrefixUnaryOperatorEnum, ...
] = (
sya.PostfixUnaryOperatorEnum.Increment,
sya.PostfixUnaryOperatorEnum.Decrement,
sya.PrefixUnaryOperatorEnum.Increment,
sya.PrefixUnaryOperatorEnum.Decrement,
)
PointerOperators: tuple[sya.PrefixUnaryOperatorEnum, ...] = (
sya.PrefixUnaryOperatorEnum.AddressOf,
sya.PrefixUnaryOperatorEnum.Dereference,
)
AssignmentOperators: tuple[sya.BinaryOperatorEnum, ...] = (
sya.BinaryOperatorEnum.Assignment,
sya.BinaryOperatorEnum.AdditionAssignment,
sya.BinaryOperatorEnum.SubtractionAssignment,
sya.BinaryOperatorEnum.MultiplicationAssignment,
sya.BinaryOperatorEnum.DivisionAssignment,
sya.BinaryOperatorEnum.ModulusAssignment,
sya.BinaryOperatorEnum.BitwiseANDAssignment,
sya.BinaryOperatorEnum.BitwiseORAssignment,
sya.BinaryOperatorEnum.BitwiseXORAssignment,
sya.BinaryOperatorEnum.LeftShiftAssignment,
sya.BinaryOperatorEnum.RightShiftAssignment,
)
OperandKeys: tuple[str, ...] = ("operand","operand1","operand2","operand3",)
class SemanticError(CompilerError):
_compiler_error_type = "Semantic"
class VariableAlreadyDeclared(SemanticError):
def __init__(
self,
new: SymbolDefinitionTypes,
existing: SymbolDefinitionTypes,
):
message = (
f"The variable '{new.identifier.content}' was already "
f"declared at {str(existing.file_info)}" # type: ignore
)
super().__init__(message, new.file_info) # type: ignore
class UndeclaredVariable(SemanticError):
def __init__(
self,
variable: sya.Identifier,
):
message = (
f"The variable '{variable.content}' is undeclared."
)
super().__init__(message, variable.file_info) # type: ignore
class InvalidOperand(SemanticError):
def __init__(
self,
operator: (
sya.TernaryExpression |
sya.BinaryExpression |
sya.UnaryExpression |
sya.Operator
),
operand: IntermediateStatement | Statement,
):
if isinstance(operator, (
sya.TernaryExpression,
sya.BinaryExpression,
sya.UnaryExpression,
)):
message = (
f"The operand at '{operand}' is invalid for the "
f"operator '{operator.operator.content.value}'."
)
else:
message = (
f"The operand at '{operand}' is invalid for the "
f"operator '{operator.content.value}'."
)
super().__init__(
message,
operand.file_info, # type: ignore
operator.file_info, # type: ignore
)
class CompoundIdentifier:
_owner: Identifier
_member: Identifier
_file_info: FileInfo
def __init__(
self,
owner: Identifier,
member: Identifier,
file_info: FileInfo,
):
self._owner = owner
self._member = member
self._file_info = file_info
@property
def owner(self) -> Identifier: return self._owner
@property
def member(self) -> Identifier: return self._member
@property
def content(self) -> str:
return self.owner.content + '.' + self.member.content
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} CompoundIdentifier\n"
s += f"{pre_cont}├─ Owner\n"
s += self._owner.tree_str(pre_cont + "│ └─", pre_cont + " ")
s += f"{pre_cont}└─ Member\n"
s += self._member.tree_str(pre_cont + " └─", pre_cont + " ")
return s
class AddressOfIdentifier:
_operand: Identifier
_file_info: FileInfo
def __init__(
self,
operand: Identifier,
file_info: FileInfo,
):
self._operand = operand
self._file_info = file_info
@property
def operand(self) -> Identifier: return self._operand
@property
def content(self) -> str: return self._operand.content
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = (
f"{pre} Unary Expression: PrefixUnaryOperator.AddressOf\n"
)
s += self._operand.tree_str(pre_cont + "└─", pre_cont + " ")
return s
class DereferenceIdentifier:
_operand: Identifier
_file_info: FileInfo
def __init__(
self,
operand: Identifier,
file_info: FileInfo,
):
self._operand = operand
self._file_info = file_info
@property
def operand(self) -> Identifier: return self._operand
@property
def content(self) -> str: return self._operand.content
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = (
f"{pre} Unary Expression: PrefixUnaryOperator.Dereference\n"
)
s += self._operand.tree_str(pre_cont + "└─", pre_cont + " ")
return s
class InternalDefinition:
_index: ClassVar[int] = 0
_identifier: sya.Identifier
_operand: sya.Expression
def __init__(
self,
operand: sya.Expression,
):
self._identifier = sya.Identifier(
f"`{InternalDefinition._index}",
FileInfo("",0,0,0,0)
)
self._operand = operand
InternalDefinition._index += 1
@property
def identifier(self) -> sya.Identifier:
return self._identifier
@property
def operand(self) -> sya.Expression:
return self._operand
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Internal Definition: {self._identifier}\n"
s += self._operand.tree_str(pre_cont + "└─", pre_cont + " ")
return s
class SymbolType(Enum):
struct = "struct"
enum = "enum"
function = "function"
variable = "variable"
return_variable = "return variable"
class Symbol:
_name: str
_static: bool
_symbol_type: SymbolType
_definition: SymbolDefinitionTypes
_references: list[SymbolReferenceTypes]
def __init__(
self,
name: str,
symbol_type: SymbolType,
definition: SymbolDefinitionTypes,
):
self._name = name
self._symbol_type = symbol_type
self._definition = definition
self._references = []
def __hash__(self) -> int:
return id(self)
@property
def name(self) -> str: return self._name
@property
def symbol_type(self) -> SymbolType: return self._symbol_type
@property
def references(self) -> list[SymbolReferenceTypes]:
return self._references[:]
@property
def definition(self) -> SymbolDefinitionTypes: return self._definition
def add_reference(self, ref: SymbolReferenceTypes):
self._references.append(ref)
class SymbolTable:
_parent_table: "SymbolTable | None"
_symbols: list[Symbol]
def __init__(self, parent_table: "SymbolTable | None" = None):
self._parent_table = parent_table
self._symbols = []
def __getitem__(self, key: str) -> Symbol: return self.get(key)
def __setitem__(self, key: str, value: Symbol):
if key != value.name:
raise KeyError
self.set(value)
@property
def symbols(self) -> list[Symbol]: return self._symbols[:]
def get(self, key: str, symbol_type: SymbolType | None = None) -> Symbol:
for symbol in self._symbols:
if symbol.name == key and symbol_type is None:
return symbol
elif symbol.name == key and symbol.symbol_type == symbol_type:
return symbol
if self._parent_table is None:
raise KeyError
else:
return self._parent_table.get(key, symbol_type)
def set(self, value: Symbol):
for i, symbol in enumerate(self._symbols):
if symbol.name == value.name:
self._symbols[i] = value
break
else:
if self._parent_table is None:
raise KeyError
else:
self._parent_table.set(value)
def add(self, value: Symbol):
for symbol in self._symbols:
if symbol.name == value.name:
raise KeyError
else:
self._symbols.append(value)
def table_str(self, title: str, pre: str = "", pre_cont: str = "") -> str:
if len(self._symbols):
names: list[str] = []
types: list[SymbolType] = []
counts: list[int] = []
for symbol in self._symbols:
names.append(symbol.name)
types.append(symbol.symbol_type)
counts.append(len(symbol.references))
name_width = max(len(i) for i in names)
type_width = max(len(i.value) for i in types)
count_width = max(len(str(i)) for i in counts)
title_width = name_width + 2 + type_width + 2 + count_width
s = f"{pre} o{title.center(title_width, '-')}o\n"
for i in range(len(self._symbols)):
s += f"{pre_cont} |{(names[i] + ':').ljust(name_width + 1)} "
s += f"{types[i].value.ljust(type_width)}; "
s += f"{str(counts[i]).rjust(count_width)}|\n"
s += f"{pre_cont} o{'-' * title_width}o\n"
return s
else: return f"{pre} o-{title}-o\n"
class ForSymbolTable(SymbolTable):
_parent_table: SymbolTable
_symbols: list[Symbol]
def __init__(self, parent_table: SymbolTable):
self._parent_table = parent_table
self._symbols = []
def set(self, value: Symbol): self._parent_table.set(value)
def add(self, value: Symbol): self._parent_table.add(value)
def for_add(self, value: Symbol):
for symbol in self._symbols:
if symbol.name == value.name:
raise KeyError
else:
self._symbols.append(value)
class CodeBlock:
_code: list[Statement]
def __init__(self, code: list[Statement]):
self._code = code[:]
@property
def code(self) -> list[Statement]: return self._code[:]
def tree_str(
self,
pre: str = "",
pre_cont: str = "",
) -> str:
s: str = ""
if self._code:
s += f"{pre} Code Block\n"
for code in self._code[:-1]:
s += code.tree_str(pre_cont + "├─", pre_cont + "")
s += self._code[-1].tree_str(pre_cont + "└─", pre_cont + " ")
return s
@staticmethod
def _sa(
code: list[sya.Statement],
symbol_table: SymbolTable,
members: list[sya.LetStatement],
) -> "CodeBlock":
def add_ref_if(statement: sya.Expression):
if isinstance(statement, HasOperands):
for key in OperandKeys:
if (
hasattr(statement, key) and
isinstance(
getattr(statement, key),
sya.Identifier,
)
):
try:
symbol = symbol_table.get(
getattr(statement, key).content)
except KeyError:
raise UndeclaredVariable(getattr(statement, key))
else:
symbol.add_reference(getattr(statement, key))
elif (
hasattr(statement, key) and
isinstance(
getattr(statement, key),
sya.FunctionCall,
)
):
try:
symbol = symbol_table.get(
getattr(statement, key).identifier.content,
SymbolType.function,
)
except KeyError:
raise UndeclaredVariable(
getattr(statement, key).identifier)
else:
symbol.add_reference(
getattr(statement, key).identifier)
for arg in getattr(statement, key).args:
try:
symbol = symbol_table.get(arg.value.content)
except KeyError:
raise UndeclaredVariable(arg.value)
else:
symbol.add_reference(arg.value)
elif (
isinstance(
statement,
sya.BinaryExpression,
) and
(
statement.operator ==
sya.BinaryOperatorEnum.Assignment
) and
hasattr(statement, key) and
isinstance(
getattr(statement, key),
sya.BinaryExpression,
)
):
add_ref_if(getattr(statement, key))
elif isinstance(statement, sya.FunctionCall):
try:
symbol = symbol_table.get(
statement.identifier.content, SymbolType.function)
except KeyError:
raise UndeclaredVariable(statement.identifier)
else:
symbol.add_reference(statement.identifier)
for arg in statement.args:
try:
symbol = symbol_table.get(
arg.value.content) # type: ignore
except KeyError:
raise UndeclaredVariable(arg.value) # type: ignore
else:
symbol.add_reference(arg.value) # type: ignore
elif isinstance(statement, sya.Identifier):
try:
symbol = symbol_table.get(statement.content)
except KeyError:
raise UndeclaredVariable(statement)
else:
symbol.add_reference(statement)
code_out: list[Statement] = []
for root_statement in code:
for statement in _flatten_statement(root_statement):
if isinstance(statement, sya.LetStatement):
try:
symbol_table.add(Symbol(
statement.identifier.content,
SymbolType.variable,
statement,
))
except KeyError:
raise VariableAlreadyDeclared(
statement,
symbol_table.get(
statement.identifier.content
).definition,
)
if statement.static:
members.append(statement)
else:
code_out.append(statement)
elif isinstance(statement, InternalDefinition):
add_ref_if(statement.operand)
symbol_table.add(Symbol(
statement.identifier.content,
SymbolType.variable,
statement,
))
code_out.append(statement)
elif isinstance(statement, AddRefTypes):
add_ref_if(statement) # type: ignore
code_out.append(statement) # type: ignore
elif isinstance(statement, sya.IfBlock):
code_out.append(
IfBlock._sa(statement, symbol_table, members))
elif isinstance(statement, sya.DoBlock):
code_out.append(
DoBlock._sa(statement, symbol_table, members))
elif isinstance(statement, sya.WhileBlock):
code_out.append(
WhileBlock._sa(statement, symbol_table, members))
elif isinstance(statement, sya.ForBlock):
code_out.append(
ForBlock._sa(statement, symbol_table, members))
else:
code_out.append(statement)
return CodeBlock(code_out)
class ElseBlock:
_code: CodeBlock
_file_info: FileInfo
def __init__(
self,
code: CodeBlock,
file_info: FileInfo,
):
self._code = code
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
return self._code.tree_str(pre + " Else", pre_cont + "")
@staticmethod
def _sa(
else_block: sya.ElseBlock,
symbol_table: SymbolTable,
members: list[sya.LetStatement],
) -> "ElseBlock":
code = CodeBlock._sa(else_block.code, symbol_table, members)
return ElseBlock(code, else_block.file_info)
class ForPreDef:
_identifier: Identifier
_type: sya.DataType
_pointer: bool
_assignment: CodeBlock | None
_file_info: FileInfo
def __init__(
self,
identifier: Identifier,
type: sya.DataType,
pointer: bool,
assignment: CodeBlock | None,
file_info: FileInfo,
):
self._identifier = identifier
self._type = type
self._pointer = pointer
self._assignment = assignment
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
@property
def identifier(self) -> Identifier: return self._identifier
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Definition: {self._identifier}\n"
if self._assignment: s += f"{pre_cont}├─ Type: "
else: s += f"{pre_cont}└─ Type: "
if self._pointer: s+= "@"
s += f"{self._type}\n"
if self._assignment:
s += self._assignment.tree_str(pre_cont + "└─ Value", pre_cont + " ")
return s
class ForBlock:
_pre_statement: CodeBlock | ForPreDef
_condition: CodeBlock
_code: CodeBlock
_post_statement: CodeBlock
_else: ElseBlock | None
_file_info: FileInfo
def __init__(
self,
pre_statement: CodeBlock | ForPreDef,
condition: CodeBlock,
code: CodeBlock,
post_statement: CodeBlock,
else_block: ElseBlock | None,
file_info: FileInfo,
):
self._pre_statement = pre_statement
self._condition = condition
self._code = code
self._post_statement = post_statement
self._else = else_block
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} For Loop\n"
s += self._pre_statement.tree_str(
f"{pre_cont}├─ Pre-Statement", f"{pre_cont}")
s += self._condition.tree_str(
f"{pre_cont}├─ Condition", f"{pre_cont}")
s += self._post_statement.tree_str(
f"{pre_cont}├─ Post-Statement", f"{pre_cont}")
if self._else is not None:
s += self._code.tree_str(
pre_cont + "├─", pre_cont + "")
s += self._else.tree_str(pre_cont + "└─", pre_cont + " ")
else:
s += self._code.tree_str(
pre_cont + "└─", pre_cont + " ")
return s
@staticmethod
def _sa(
for_block: sya.ForBlock,
parent_table: SymbolTable,
members: list[sya.LetStatement],
) -> "ForBlock":
symbol_table = ForSymbolTable(parent_table)
if isinstance(for_block.pre_statement, sya.ForPreDef):
assignment = CodeBlock._sa(for_block.code, symbol_table, members)
pre_statement = ForPreDef(
for_block.pre_statement.identifier,
for_block.pre_statement.data_type,
for_block.pre_statement.pointer,
assignment,
for_block.pre_statement.file_info,
)
try: symbol_table.for_add(Symbol(
pre_statement.identifier.content,
SymbolType.variable,
pre_statement,
))
except KeyError: raise VariableAlreadyDeclared(
pre_statement,
symbol_table.get(pre_statement.identifier.content).definition,
)
else: pre_statement = CodeBlock._sa(
[for_block.pre_statement], symbol_table, members)
condition = CodeBlock._sa([for_block.condition], symbol_table, members)
code = CodeBlock._sa(for_block.code, symbol_table, members)
post_statement = CodeBlock._sa(
[for_block.post_statement], symbol_table, members)
if for_block.else_block is None: else_block = None
else: else_block = ElseBlock._sa(
for_block.else_block, symbol_table, members)
return ForBlock(
pre_statement,
condition,
code,
post_statement,
else_block,
for_block.file_info,
)
class WhileBlock:
_condition: CodeBlock
_code: CodeBlock
_else: ElseBlock | None
_file_info: FileInfo
def __init__(
self,
condition: CodeBlock,
code: CodeBlock,
else_block: ElseBlock | None,
file_info: FileInfo,
):
self._condition = condition
self._code = code
self._else = else_block
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} While Loop\n"
if self._code or self._else is not None:
s += self._condition.tree_str(
f"{pre_cont}├─ Condition", f"{pre_cont}")
else:
s += self._condition.tree_str(
f"{pre_cont}└─ Condition", f"{pre_cont} ")
if self._else is not None:
s += self._code.tree_str(
pre_cont + "├─", pre_cont + "")
s += self._else.tree_str(pre_cont + "└─", pre_cont + " ")
else:
s += self._code.tree_str(
pre_cont + "└─", pre_cont + " ")
return s
@staticmethod
def _sa(
while_block: sya.WhileBlock,
symbol_table: SymbolTable,
members: list[sya.LetStatement],
) -> "WhileBlock":
condition = CodeBlock._sa(
[while_block.condition], symbol_table, members)
code = CodeBlock._sa(while_block.code, symbol_table, members)
if while_block.else_block is None: else_block = None
else: else_block = ElseBlock._sa(
while_block.else_block, symbol_table, members)
return WhileBlock(
condition,
code,
else_block,
while_block.file_info,
)
class DoBlock:
_first_code: CodeBlock
_condition: CodeBlock
_second_code: CodeBlock | None
_else: ElseBlock | None
_file_info: FileInfo
def __init__(
self,
first_code: CodeBlock,
condition: CodeBlock,
second_code: CodeBlock | None,
else_block: ElseBlock | None,
file_info: FileInfo,
):
self._first_code = first_code
self._condition = condition
if second_code:
self._second_code = second_code
else:
self._second_code = None
self._else = else_block
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Do Loop\n"
s += self._first_code.tree_str(
pre_cont + "├─ First Do", pre_cont + "")
if self._second_code or self._else is not None:
s += self._condition.tree_str(
f"{pre_cont}├─ Condition", f"{pre_cont}")
else:
s += self._condition.tree_str(
f"{pre_cont}└─ Condition", f"{pre_cont} ")
if self._second_code is not None:
if self._else is not None:
s += self._second_code.tree_str(
pre_cont + "├─ Second Do", pre_cont + "")
else:
s += self._second_code.tree_str(
pre_cont + "└─ Second Do", pre_cont + " ")
if self._else is not None:
s += self._else.tree_str(pre_cont + "└─", pre_cont + " ")
return s
@staticmethod
def _sa(
do_block: sya.DoBlock,
symbol_table: SymbolTable,
members: list[sya.LetStatement],
) -> "DoBlock":
condition = CodeBlock._sa([do_block.condition], symbol_table, members)
first_code = CodeBlock._sa(do_block.first_code, symbol_table, members)
if do_block.second_code is None: second_code = None
else: second_code = CodeBlock._sa(
do_block.second_code, symbol_table, members)
if do_block.else_block is None: else_block = None
else: else_block = ElseBlock._sa(
do_block.else_block, symbol_table, members)
return DoBlock(
condition,
first_code,
second_code,
else_block,
do_block.file_info,
)
class IfBlock:
_condition: CodeBlock
_code: CodeBlock
_else: ElseBlock | None
_file_info: FileInfo
def __init__(
self,
condition: CodeBlock,
code: CodeBlock,
else_block: ElseBlock | None,
file_info: FileInfo,
):
self._condition = condition
self._code = code
self._else = else_block
self._file_info = file_info
@property
def file_info(self) -> FileInfo: return self._file_info
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} If Statement\n"
if self._code or self._else is not None:
s += self._condition.tree_str(
f"{pre_cont}├─ Condition", f"{pre_cont}")
else:
s += self._condition.tree_str(
f"{pre_cont}└─ Condition", f"{pre_cont} ")
s += self._code.tree_str(
pre_cont + "├─ If", pre_cont + "")
if self._else is not None:
s += self._else.tree_str(pre_cont + "└─", pre_cont + " ")
return s
@staticmethod
def _sa(
if_block: sya.IfBlock,
symbol_table: SymbolTable,
members: list[sya.LetStatement],
) -> "IfBlock":
condition = CodeBlock._sa([if_block.condition], symbol_table, members)
code = CodeBlock._sa(if_block.code, symbol_table, members)
if if_block.else_block is None: else_block = None
else: else_block = ElseBlock._sa(
if_block.else_block, symbol_table, members)
return IfBlock(
condition,
code,
else_block,
if_block.file_info,
)
class FunctionReturnDefinition:
_identifier: sya.Identifier
_return_type_pointer: bool
_return_type: sya.DataType | None
def __init__(
self,
identifier: sya.Identifier,
return_type_pointer: bool,
return_type: sya.DataType | None,
):
self._identifier = identifier
self._return_type_pointer = return_type_pointer
self._return_type = return_type
@property
def identifier(self) -> sya.Identifier:
return self._identifier
@property
def return_type_pointer(self) -> bool: return self._return_type_pointer
@property
def return_type(self) -> sya.DataType | None:
return self._return_type
class FunctionBlock:
_identifier: sya.Identifier
_params: list[sya.FunctionParameter]
_return_type: FunctionReturnDefinition
_members: list[sya.LetStatement]
_code: CodeBlock
_file_info: FileInfo
_symbol_table: SymbolTable
def __init__(
self,
identifier: sya.Identifier,
params: list[sya.FunctionParameter],
return_type: FunctionReturnDefinition,
members: list[sya.LetStatement],
code: CodeBlock,
file_info: FileInfo,
symbol_table: SymbolTable,
):
self._identifier = identifier
self._params = params[:]
self._return_type = return_type
self._members = members[:]
self._code = code
self._file_info = file_info
self._symbol_table = symbol_table
@property
def identifier(self) -> sya.Identifier:
return self._identifier
@property
def params(self) -> list[sya.FunctionParameter]:
return self._params[:]
@property
def return_type(self) -> FunctionReturnDefinition: return self._return_type
@property
def members(self) -> list[sya.LetStatement]:
return self._members[:]
@property
def code(self) -> CodeBlock: return self._code
@property
def file_info(self) -> FileInfo: return self._file_info
@property
def symbol_table(self) -> SymbolTable: return self._symbol_table
def tree_str(self, pre: str = "", pre_cont: str = "") -> str:
s: str = f"{pre} Function: {self._identifier}\n"
if (
self._params or
self._code or
self._return_type is not None or
self._members
):
s += self._symbol_table.table_str(
self.identifier.content, pre_cont + "├─", pre_cont + "")
else:
s += self._symbol_table.table_str(
self.identifier.content, pre_cont + "└─", pre_cont + " ")
if self._params:
if self._code or self._return_type is not None or self._members:
s += f"{pre_cont}├─ Parameters\n"
params_pre = f"{pre_cont}"
else:
s += f"{pre_cont}└─ Parameters\n"
params_pre = f"{pre_cont} "
for param in self._params[:-1]:
s += param.tree_str(params_pre + "├─", params_pre + "")
s += self._params[-1].tree_str(params_pre + "└─", params_pre + " ")
if self.return_type._return_type is not None:
if self._code or self._members:
s += f"{pre_cont}├─ Return Type: "
else:
s += f"{pre_cont}└─ Return Type: "
if self.return_type._return_type_pointer: s+= "@"
s += f"{self.return_type._return_type}\n"
if self._members:
if self._code:
s += f"{pre_cont}├─ Members: "
else:
s += f"{pre_cont}└─ Members: "
for code in self._members[:-1]:
s += code.tree_str(pre_cont + " ├─", pre_cont + "")
s += self._members[-1].tree_str(
pre_cont + " └─", pre_cont + " ")
s += self._code.tree_str(pre_cont + "└─ Function", pre_cont + " ")
return s
@staticmethod
def _sa(
func: sya.FunctionBlock,
parent_table: SymbolTable,
) -> "FunctionBlock":
symbol_table = SymbolTable(parent_table)
members: list[sya.LetStatement] = []
function_return = FunctionReturnDefinition(
func.identifier, func.return_type_pointer, func.return_type)
if function_return.return_type is not None:
symbol_table.add(Symbol(
function_return.identifier.content,
SymbolType.return_variable,
function_return,
))
for param in func.params:
try:
symbol_table.add(Symbol(
param.identifier.content, SymbolType.variable, param))
except KeyError:
raise VariableAlreadyDeclared(
param,
symbol_table.get(param.identifier.content).definition,
)
code = CodeBlock._sa(func.code, symbol_table, members)
return FunctionBlock(
func.identifier,
func.params,
function_return,
members,
code,
func.file_info,
symbol_table,
)
class File:
_children: list[
sya.Directive |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock
]
_file_info: FileInfo
_symbol_table: SymbolTable
def __init__(
self,
children: list[
sya.Directive |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock
],
file_info: FileInfo,
symbol_table: SymbolTable,
):
self._children = children[:]
self._file_info = file_info
self._symbol_table = symbol_table
@property
def children(self) -> list[
sya.Directive |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock
]: return self._children[:]
@property
def file_info(self) -> FileInfo: return self._file_info
@property
def symbol_table(self) -> SymbolTable: return self._symbol_table
def tree_str(self) -> str:
s: str = " File\n"
if self._children:
s += self._symbol_table.table_str("GLOBAL", "├─", "")
for child in self._children[:-1]:
s += child.tree_str("├─", "")
s += self._children[-1].tree_str("└─", " ")
else:
s += self._symbol_table.table_str("GLOBAL", "└─", " ")
return s
@staticmethod
def _sa(syntax_tree: sya.File) -> "File":
symbol_table = SymbolTable()
children: list[
sya.Directive |
sya.StructBlock |
FunctionBlock |
sya.EnumBlock
] = []
for child in syntax_tree.children:
symbol: Symbol | None = None
if isinstance(child, sya.StructBlock):
symbol = Symbol(
child.identifier.content,
SymbolType.struct,
child,
)
elif isinstance(child, sya.FunctionBlock):
symbol = Symbol(
child.identifier.content,
SymbolType.function,
child, # type: ignore
)
elif isinstance(child, sya.EnumBlock):
symbol = Symbol(
child.identifier.content,
SymbolType.enum,
child,
)
if symbol is not None:
symbol_table.add(symbol)
for child in syntax_tree.children:
new_child: sya.StructBlock | FunctionBlock | sya.EnumBlock
if isinstance(child, sya.FunctionBlock):
new_child = FunctionBlock._sa(child, symbol_table)
symbol_table.get(
child.identifier.content
)._definition = new_child # type: ignore
# TODO: analyze structs
elif isinstance(child, sya.StructBlock):
new_child = child
elif isinstance(child, sya.EnumBlock):
new_child = _sa_enum(child)
elif isinstance(child, sya.Directive):
continue
children.append(new_child)
file = File(children, syntax_tree._file_info, symbol_table)
return file
def _sa_enum(block: sya.EnumBlock) -> sya.EnumBlock:
members: list[sya.EnumMember] = []
used_numbers: set[int] = set()
for member in block.members:
if member.value is not None:
used_numbers.add(member.value.value)
i = 1
for member in block.members:
while i in used_numbers:
i += 1
if member.value is not None:
members.append(sya.EnumMember(
member.identifier,
member.value,
member.file_info
))
i = member.value.value + 1
else:
used_numbers.add(i)
members.append(sya.EnumMember(
member.identifier,
sya.NumberLiteral(str(i), member.file_info),
member.file_info
))
i += 1
return sya.EnumBlock(
block.identifier,
sorted(
sorted(members, key=lambda o: o.identifier.content),
key=lambda o: o.value.value # type: ignore
),
block.file_info,
)
def _compound_identifier(
statement: sya.BinaryExpression,
operator: sya.Operator,
) -> CompoundIdentifier:
if (
statement.operator.content ==
sya.BinaryOperatorEnum.MemberOf
): return CompoundIdentifier(
_assert_identifier(statement.operand1, statement.operator, True),
_assert_identifier(statement.operand2, statement.operator, True),
statement.file_info,
)
else: raise InvalidOperand(operator, statement)
def _augment_identifier(
statement: sya.UnaryExpression,
operator: sya.Operator,
) -> AddressOfIdentifier | DereferenceIdentifier:
if (
statement.operator.content ==
sya.PrefixUnaryOperatorEnum.AddressOf
): return AddressOfIdentifier(
_assert_identifier(statement.operand, statement.operator, True),
statement.file_info,
)
elif (
statement.operator.content ==
sya.PrefixUnaryOperatorEnum.Dereference
): return DereferenceIdentifier(
_assert_identifier(statement.operand, statement.operator, True),
statement.file_info,
)
else: raise InvalidOperand(operator, statement)
def _assert_identifier(
statement: sya.Statement,
operator: sya.Operator,
harsh: bool = False
) -> Identifier:
if isinstance(statement, sya.Identifier):
return statement
elif isinstance(statement, sya.UnaryExpression):
if (
isinstance(statement.operand, sya.BinaryExpression)
and not harsh
):
return statement # type: ignore
return _augment_identifier(statement, operator)
elif isinstance(statement, sya.BinaryExpression):
return _compound_identifier(statement, operator)
else: raise InvalidOperand(operator, statement)
def _create_internal_definition(
statement: sya.Expression,
) -> list[IntermediateStatement]:
flattened = _flatten_statement(statement)
internal_definition = InternalDefinition(
flattened[-1]) # type: ignore
return flattened[:-1] + [
internal_definition, internal_definition.identifier]
def _flatten_statement(
statement: sya.Statement,
) -> list[IntermediateStatement]:
if isinstance(statement, sya.UnaryExpression):
if statement.operator.content in IncrementOperators:
return [sya.UnaryExpression(
statement.operator,
_assert_identifier( # type: ignore
statement.operand, statement.operator),
statement.file_info,
)]
elif statement.operator.content in PointerOperators:
return [_assert_identifier(statement, statement.operator)]
elif isinstance(statement.operand, BaseValues):
return [statement]
else:
flattened = _create_internal_definition(statement.operand)
return flattened[:-1] + [
sya.UnaryExpression(
statement.operator,
flattened[-1], # type: ignore
statement.file_info,
)
]
elif isinstance(statement, sya.BinaryExpression):
if (
statement.operator.content ==
sya.BinaryOperatorEnum.MemberOf
): return [CompoundIdentifier(
_assert_identifier(statement.operand1, statement.operator),
_assert_identifier(statement.operand2, statement.operator),
statement.file_info,
)]
elif (
statement.operator.content ==
sya.BinaryOperatorEnum.Assignment
):
flattened = _flatten_statement(statement.operand2)
return flattened[:-1] + [sya.BinaryExpression(
statement.operator,
_assert_identifier( # type: ignore
statement.operand1,
statement.operator,
),
flattened[-1], # type: ignore
statement.file_info,
)]
elif statement.operator.content in AssignmentOperators:
if isinstance(statement.operand2, BaseValues):
return [sya.BinaryExpression(
statement.operator,
_assert_identifier( # type: ignore
statement.operand1,
statement.operator,
),
statement.operand2,
statement.file_info,
)]
else:
flattened = _create_internal_definition(statement.operand2)
return flattened[:-1] + [sya.BinaryExpression(
statement.operator,
_assert_identifier( # type: ignore
statement.operand1,
statement.operator,
),
flattened[-1], # type: ignore
statement.file_info,
)]
else:
if isinstance(statement.operand1, BaseValues):
flattened1 = [statement.operand1]
else: flattened1 = _create_internal_definition(statement.operand1)
if isinstance(statement.operand2, BaseValues):
flattened2 = [statement.operand2]
else: flattened2 = _create_internal_definition(statement.operand2)
return flattened1[:-1] + flattened2[:-1] + [
sya.BinaryExpression(
statement.operator,
flattened1[-1], # type: ignore
flattened2[-1], # type: ignore
statement.file_info,
)
]
elif isinstance(statement, sya.TernaryExpression):
if isinstance(statement.operand1, BaseValues):
flattened1 = [statement.operand1]
else: flattened1 = _create_internal_definition(statement.operand1)
if isinstance(statement.operand2, BaseValues):
flattened2 = [statement.operand2]
else: flattened2 = _create_internal_definition(statement.operand2)
if isinstance(statement.operand3, BaseValues):
flattened3 = [statement.operand3]
else: flattened3 = _create_internal_definition(statement.operand3)
return flattened1[:-1] + flattened2[:-1] + flattened3[:-1] + [
sya.TernaryExpression(
statement.operator,
flattened1[-1], # type: ignore
flattened2[-1], # type: ignore
flattened3[-1], # type: ignore
statement.file_info,
)
]
else: return [statement]
def semantical_analyzer(syntax_tree: sya.File) -> File:
return File._sa(syntax_tree)