diff --git a/cocas/__init__.py b/cocas/__init__.py new file mode 100644 index 00000000..39c72cb9 --- /dev/null +++ b/cocas/__init__.py @@ -0,0 +1 @@ +from . import assembler, linker, object_file, object_module diff --git a/cocas/abstract_code_segments.py b/cocas/abstract_code_segments.py deleted file mode 100644 index 6119ef8a..00000000 --- a/cocas/abstract_code_segments.py +++ /dev/null @@ -1,86 +0,0 @@ -from dataclasses import dataclass, field -from math import lcm -from typing import TYPE_CHECKING - -from cocas.error import CdmException, CdmExceptionTag -from cocas.location import CodeLocation - -if TYPE_CHECKING: - from assembler import ObjectSectionRecord - - from cocas.code_block import Section - - -class CodeSegmentsInterface: - @dataclass - class CodeSegment: - size: int = field(init=False) - position: int = field(init=False) - - def __post_init__(self): - # ugly hack to store code location in segments - # now this whole project is one big and ugly hack - self.location: CodeLocation = CodeLocation() - - def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], - templates: dict[str, dict[str, int]]): - pass - - @dataclass - class VaryingLengthSegment(CodeSegment): - def update_varying_length(self, pos, section: "Section", labels: dict[str, int], - templates: dict[str, dict[str, int]]) -> int: - pass - - @staticmethod - def update_surroundings(diff: int, pos: int, section: "Section", labels: dict[str, int]): - for label_name in section.labels: - if section.labels[label_name] > pos: - section.labels[label_name] += diff - if label_name in labels: - labels[label_name] += diff - old_locations = section.code_locations - section.code_locations = dict() - for PC, location in old_locations.items(): - if PC > pos: - PC += diff - section.code_locations[PC] = location - - class AlignmentPaddingSegment(VaryingLengthSegment): - def __init__(self, alignment: int, location: CodeLocation): - self.location = location - self.alignment = alignment - self.size = alignment - - def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], - templates: dict[str, dict[str, int]]): - object_record.data += bytes(self.size) - if section.name != '$abs': - object_record.alignment = lcm(object_record.alignment, self.alignment) - - def update_varying_length(self, pos, section: "Section", labels: dict[str, int], _): - new_size = (-pos) % self.alignment - if new_size == self.alignment: - new_size = 0 - diff = new_size - self.size - self.size = new_size - self.__class__.update_surroundings(diff, pos, section, labels) - return diff - - class AlignedSegment(CodeSegment): - alignment: int - - def __init__(self, alignment: int): - self.alignment = alignment - - def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], - templates: dict[str, dict[str, int]]): - if (section.address + len(object_record.data)) % self.alignment != 0: - _error(self, f'Segment must be {self.alignment}-byte aligned') - super().fill(object_record, section, labels, templates) - if section.name != '$abs': - object_record.alignment = lcm(object_record.alignment, self.alignment) - - -def _error(segment: CodeSegmentsInterface.CodeSegment, message: str): - raise CdmException(CdmExceptionTag.ASM, segment.location.file, segment.location.line, message) diff --git a/cocas/abstract_instructions.py b/cocas/abstract_instructions.py deleted file mode 100644 index 144f6d31..00000000 --- a/cocas/abstract_instructions.py +++ /dev/null @@ -1,23 +0,0 @@ -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.ast_nodes import InstructionNode -from cocas.location import CodeLocation - - -class TargetInstructionsInterface: - @staticmethod - def assemble_instruction(line: InstructionNode, temp_storage) \ - -> list[CodeSegmentsInterface.CodeSegment]: - pass - - @staticmethod - def finish(temp_storage: dict): - return - - @staticmethod - def make_branch_instruction(location: CodeLocation, branch_mnemonic: str, label_name: str, inverse: bool) \ - -> list[CodeSegmentsInterface.CodeSegment]: - pass - - @staticmethod - def assembly_directives() -> set[str]: - pass diff --git a/cocas/abstract_params.py b/cocas/abstract_params.py deleted file mode 100644 index d01cf1be..00000000 --- a/cocas/abstract_params.py +++ /dev/null @@ -1,23 +0,0 @@ -import abc - - -class TargetParamsInterface(abc.ABC): - @staticmethod - @abc.abstractmethod - def name(): - pass - - @staticmethod - @abc.abstractmethod - def max_entry_size() -> int: - pass - - @staticmethod - @abc.abstractmethod - def default_alignment() -> int: - pass - - @staticmethod - @abc.abstractmethod - def object_file_header() -> str: - pass diff --git a/cocas/antlr-generate.sh b/cocas/antlr-generate.sh index be7817e4..d69c1c2d 100755 --- a/cocas/antlr-generate.sh +++ b/cocas/antlr-generate.sh @@ -1,2 +1,4 @@ -antlr4 -Dlanguage=Python3 -visitor -no-listener -Xexact-output-dir -o generated \ - ./grammar/AsmLexer.g4 ./grammar/AsmParser.g4 ./grammar/Macro.g4 ./grammar/ObjectFileParser.g4 ./grammar/ObjectFileLexer.g4 +antlr4 -Dlanguage=Python3 -visitor -no-listener -Xexact-output-dir -o object_file/generated \ + object_file/grammar/ObjectFileParser.g4 object_file/grammar/ObjectFileLexer.g4 +antlr4 -Dlanguage=Python3 -visitor -no-listener -Xexact-output-dir -o assembler/generated \ + assembler/grammar/AsmLexer.g4 assembler/grammar/AsmParser.g4 assembler/grammar/Macro.g4 diff --git a/cocas/assembler/__init__.py b/cocas/assembler/__init__.py new file mode 100644 index 00000000..65a90df9 --- /dev/null +++ b/cocas/assembler/__init__.py @@ -0,0 +1,5 @@ +"""Assembler module. Converts source files into object module structures""" + +from .assembler import assemble_files, assemble_module +from .exceptions import AssemblerException, AssemblerExceptionTag +from .targets import list_assembler_targets diff --git a/cocas/assembler/assembler.py b/cocas/assembler/assembler.py new file mode 100644 index 00000000..7755154e --- /dev/null +++ b/cocas/assembler/assembler.py @@ -0,0 +1,91 @@ +import codecs +from itertools import chain +from pathlib import Path +from typing import Any, Optional + +import antlr4 + +from cocas.object_module import ObjectModule + +from .ast_builder import build_ast +from .macro_processor import ExpandMacrosVisitor, process_macros, read_mlb +from .object_generator import generate_object_module +from .targets import TargetInstructions, import_target, mlb_path + + +def assemble_module(input_stream: antlr4.InputStream, + target_instructions: TargetInstructions, + macros_library: ExpandMacrosVisitor, + filepath: Path) -> ObjectModule: + """ + Convert lines of an assembler file to object code + + :param input_stream: contents of file + :param target_instructions: information how to convert mnemonics to code segments + :param macros_library: standard macros of assembler + :param filepath: path of the file to use in error handling + """ + macro_expanded_input_stream = process_macros(input_stream, macros_library, filepath) + r = build_ast(macro_expanded_input_stream, filepath) + return generate_object_module(r, target_instructions) + + +def get_debug_info_path(filepath: Path, + debug: Optional[Any], + relative_path: Optional[Path], + realpath: bool) -> Optional[Path]: + if debug: + debug_info_path = filepath.expanduser().absolute() + if realpath: + debug_info_path = debug_info_path.resolve() + if relative_path: + debug_info_path = debug_info_path.relative_to(relative_path) + else: + debug_info_path = None + return debug_info_path + + +def assemble_files(target: str, + files: list[Path], + debug: bool, + relative_path: Optional[Path], + absolute_path: Optional[Path], + realpath: bool) -> list[tuple[Path, ObjectModule]]: + """ + Open and assemble multiple files into object modules + + :param target: name of processor, should be valid + :param files: list of assembler files' paths to process + :param debug: if debug information should be collected + :param relative_path: if debug paths should be relative to some path + :param absolute_path: if relative paths should be converted to absolute + :param realpath: if paths should be converted to canonical + :return: list of pairs [source file path, object module] + """ + _ = absolute_path + target_instructions = import_target(target) + macros_library = read_mlb(mlb_path(target)) + objects = [] + + for filepath in files: + with filepath.open('rb') as file: + data = file.read() + data = codecs.decode(data, 'utf8', 'strict') + if not data.endswith('\n'): + data += '\n' + input_stream = antlr4.InputStream(data) + obj = assemble_module(input_stream, target_instructions, macros_library, filepath) + + debug_info_path = get_debug_info_path(filepath, debug, relative_path, realpath) + if debug_info_path: + obj.source_file_path = debug_info_path + fp = filepath.absolute().as_posix() + dip = debug_info_path.as_posix() + for i in chain(obj.asects, obj.rsects): + for j in i.code_locations.values(): + if j.file == fp: + j.file = dip + else: + j.file = get_debug_info_path(Path(j.file), debug, relative_path, realpath) + objects.append((filepath, obj)) + return objects diff --git a/cocas/ast_builder.py b/cocas/assembler/ast_builder.py similarity index 91% rename from cocas/ast_builder.py rename to cocas/assembler/ast_builder.py index f0327b50..dd5bee69 100644 --- a/cocas/ast_builder.py +++ b/cocas/assembler/ast_builder.py @@ -1,8 +1,11 @@ from base64 import b64decode +from pathlib import Path from antlr4 import CommonTokenStream, InputStream -from cocas.ast_nodes import ( +from cocas.object_module import CodeLocation + +from .ast_nodes import ( AbsoluteSectionNode, BreakStatementNode, ConditionalStatementNode, @@ -21,11 +24,8 @@ UntilLoopNode, WhileLoopNode, ) -from cocas.error import AntlrErrorListener, CdmException, CdmExceptionTag -from cocas.generated.AsmLexer import AsmLexer -from cocas.generated.AsmParser import AsmParser -from cocas.generated.AsmParserVisitor import AsmParserVisitor -from cocas.location import CodeLocation +from .exceptions import AntlrErrorListener, AssemblerException, AssemblerExceptionTag +from .generated import AsmLexer, AsmParser, AsmParserVisitor # noinspection PyPep8Naming @@ -126,8 +126,8 @@ def visitConnective_condition(self, ctx: AsmParser.Connective_conditionContext): cond = self.visitCondition(ctx.condition()) cond.conjunction = ctx.conjunction().getText() if cond.conjunction != 'and' and cond.conjunction != 'or': - raise CdmException(CdmExceptionTag.ASM, self.source_path, ctx.start.line - self.line_offset, - 'Expected "and" or "or" in compound condition') + raise AssemblerException(AssemblerExceptionTag.ASM, self.source_path, ctx.start.line - self.line_offset, + 'Expected "and" or "or" in compound condition') return cond def visitCondition(self, ctx: AsmParser.ConditionContext): @@ -218,8 +218,8 @@ def visitStandaloneLabel(self, ctx: AsmParser.StandaloneLabelContext) -> LabelDe label_decl = self.visitLabel_declaration(ctx.label_declaration()) label_decl.external = ctx.Ext() is not None if label_decl.entry and label_decl.external: - raise CdmException(CdmExceptionTag.ASM, self.source_path, ctx.start.line - self.line_offset, - f'Label {label_decl.label.name} cannot be both external and entry') + raise AssemblerException(AssemblerExceptionTag.ASM, self.source_path, ctx.start.line - self.line_offset, + f'Label {label_decl.label.name} cannot be both external and entry') return label_decl def visitLabel_declaration(self, ctx: AsmParser.Label_declarationContext) -> LabelDeclarationNode: @@ -253,16 +253,17 @@ def visitArguments(self, ctx: AsmParser.ArgumentsContext): return [self.visitArgument(i) for i in ctx.children if isinstance(i, AsmParser.ArgumentContext)] -def build_ast(input_stream: InputStream, filepath: str): +def build_ast(input_stream: InputStream, filepath: Path): + str_path = filepath.absolute().as_posix() lexer = AsmLexer(input_stream) lexer.removeErrorListeners() - lexer.addErrorListener(AntlrErrorListener(CdmExceptionTag.ASM, filepath)) + lexer.addErrorListener(AntlrErrorListener(AssemblerExceptionTag.ASM, str_path)) token_stream = CommonTokenStream(lexer) token_stream.fill() parser = AsmParser(token_stream) parser.removeErrorListeners() - parser.addErrorListener(AntlrErrorListener(CdmExceptionTag.ASM, filepath)) + parser.addErrorListener(AntlrErrorListener(AssemblerExceptionTag.ASM, str_path)) cst = parser.program() - bav = BuildAstVisitor(filepath) + bav = BuildAstVisitor(str_path) result = bav.visit(cst) return result diff --git a/cocas/ast_nodes.py b/cocas/assembler/ast_nodes.py similarity index 95% rename from cocas/ast_nodes.py rename to cocas/assembler/ast_nodes.py index c988aded..861685c9 100644 --- a/cocas/ast_nodes.py +++ b/cocas/assembler/ast_nodes.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from typing import Optional -from cocas.location import CodeLocation +from cocas.object_module import CodeLocation @dataclass @@ -40,7 +40,7 @@ class RelocatableExpressionNode(LocatableNode): @dataclass -class LabelDeclarationNode(Node): +class LabelDeclarationNode(LocatableNode): label: LabelNode entry: bool external: bool diff --git a/cocas/code_block.py b/cocas/assembler/code_block.py similarity index 77% rename from cocas/code_block.py rename to cocas/assembler/code_block.py index 6177b2c1..604ab53c 100644 --- a/cocas/code_block.py +++ b/cocas/assembler/code_block.py @@ -1,9 +1,9 @@ from dataclasses import dataclass from typing import Any, Callable, Type -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.abstract_instructions import TargetInstructionsInterface -from cocas.ast_nodes import ( +from cocas.object_module import CodeLocation, ObjectSectionRecord + +from .ast_nodes import ( AbsoluteSectionNode, BreakStatementNode, ConditionalStatementNode, @@ -11,26 +11,24 @@ InstructionNode, LabelDeclarationNode, LocatableNode, + Node, RelocatableSectionNode, SectionNode, UntilLoopNode, WhileLoopNode, ) -from cocas.error import CdmException, CdmExceptionTag, CdmTempException -from cocas.location import CodeLocation +from .exceptions import AssemblerException, AssemblerExceptionTag, CdmTempException +from .targets import ICodeSegment, TargetInstructions @dataclass class CodeBlock: - def __init__(self, address: int, lines: list, - target_instructions: Type[TargetInstructionsInterface], - code_segments: Type[CodeSegmentsInterface]): + def __init__(self, address: int, lines: list, target_instructions: TargetInstructions): self.target_instructions = target_instructions - self.code_segments = code_segments self.address = address self.size: int = 0 self.loop_stack: list = [] - self.segments: list[code_segments.CodeSegment] = [] + self.segments: list[ICodeSegment] = [] self.labels: dict[str, int] = dict() self.ents: set[str] = set() self.exts: set[str] = set() @@ -42,7 +40,8 @@ def __init__(self, address: int, lines: list, target_instructions.finish(temp_storage) except CdmTempException as e: # if it isn't ok, must be at least one line - raise CdmException(CdmExceptionTag.ASM, lines[-1].location.file, lines[-1].location.line, e.message) + raise AssemblerException(AssemblerExceptionTag.ASM, lines[-1].location.file, + lines[-1].location.line, e.message) def append_label(self, label_name): self.labels[label_name] = self.address + self.size @@ -54,7 +53,7 @@ def append_branch_instruction(self, location, mnemonic, label_name, inverse=Fals self.size += sum(map(lambda x: x.size, br)) def assemble_lines(self, lines: list, temp_storage): - ast_node_handlers: dict[Type, Callable[[Any, Any], None]] = { + ast_node_handlers: dict[Type[Node], Callable[[Any, Any], None]] = { LabelDeclarationNode: self.assemble_label_declaration, InstructionNode: self.assemble_instruction, ConditionalStatementNode: self.assemble_conditional_statement, @@ -73,7 +72,8 @@ def assemble_label_declaration(self, line: LabelDeclarationNode, __): if (label_name in self.labels or label_name in self.ents or label_name in self.exts): - raise Exception(f'Duplicate label "{label_name}" declaration') + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + f'Duplicate label "{label_name}" declaration') if line.external: self.exts.add(label_name) @@ -152,22 +152,22 @@ def assemble_until_loop(self, line: UntilLoopNode, temp_storage): def assemble_break_statement(self, line: BreakStatementNode, _): if len(self.loop_stack) == 0: - raise Exception('"break" not allowed outside of a loop') + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + '"break" not allowed outside of a loop') _, finally_label = self.loop_stack[-1] self.append_branch_instruction(line.location, 'anything', finally_label) def assemble_continue_statement(self, line: ContinueStatementNode, _): if len(self.loop_stack) == 0: - raise Exception('"continue" not allowed outside of a loop') + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + '"continue" not allowed outside of a loop') cond_label, _ = self.loop_stack[-1] self.append_branch_instruction(line.location, 'anything', cond_label) @dataclass class Section(CodeBlock): - def __init__(self, sn: SectionNode, - target_instructions: Type[TargetInstructionsInterface], - code_segments: Type[CodeSegmentsInterface]): + def __init__(self, sn: SectionNode, target_instructions: TargetInstructions): if isinstance(sn, AbsoluteSectionNode): self.name = '$abs' address = sn.address @@ -175,5 +175,12 @@ def __init__(self, sn: SectionNode, self.name = sn.name address = 0 else: - raise Exception('Section is neither Absolute nor Relative, can it happen? It was elif instead of else here') - super().__init__(address, sn.lines, target_instructions, code_segments) + raise Exception('Section is neither abs nor rel, can it happen? It was elif instead of else here') + super().__init__(address, sn.lines, target_instructions) + + def to_object_section_record(self, labels: dict[str, int], templates: dict[str, dict[str, int]]): + entries = dict(p for p in self.labels.items() if p[0] in self.ents) + out = ObjectSectionRecord(self.name, self.address, bytearray(), entries, [], self.code_locations) + for seg in self.segments: + seg.fill(out, self, labels, templates) + return out diff --git a/cocas/assembler/exceptions.py b/cocas/assembler/exceptions.py new file mode 100644 index 00000000..35117857 --- /dev/null +++ b/cocas/assembler/exceptions.py @@ -0,0 +1,43 @@ +from enum import Enum + +from antlr4.error.ErrorListener import ErrorListener + +from .generated import AsmParser + + +class AssemblerExceptionTag(Enum): + """Shows if an exception caused in macros or in usual source code""" + MACRO = "Macro" + TPLATE = "Template" + ASM = "Assembler" + + +# this exception should be used when we don't know code location now, +# but this info is somewhere up the call stack +# it should be re-raised +# it s here to avoid except Exception +class CdmTempException(Exception): + def __init__(self, message: str): + self.message = message + + +class AssemblerException(Exception): + """Exception raised when given source code is invalid""" + + def __init__(self, tag: AssemblerExceptionTag, file: str, line: int, description: str): + self.tag = tag + self.file = file + self.line = line + self.description = description + + +class AntlrErrorListener(ErrorListener): + def __init__(self, tag, file): + self.file = file + self.tag = tag + + def syntaxError(self, recognizer, offending_symbol, line, column, msg, e): + if isinstance(recognizer, AsmParser): + line = line - recognizer.current_offset + self.file = recognizer.current_file + raise AssemblerException(self.tag, self.file, line, msg) diff --git a/cocas/generated/AsmLexer.py b/cocas/assembler/generated/AsmLexer.py similarity index 99% rename from cocas/generated/AsmLexer.py rename to cocas/assembler/generated/AsmLexer.py index 7290aeac..3bd5a593 100644 --- a/cocas/generated/AsmLexer.py +++ b/cocas/assembler/generated/AsmLexer.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/AsmLexer.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/AsmLexer.g4 by ANTLR 4.13.1 from antlr4 import * from io import StringIO import sys diff --git a/cocas/generated/AsmParser.py b/cocas/assembler/generated/AsmParser.py similarity index 99% rename from cocas/generated/AsmParser.py rename to cocas/assembler/generated/AsmParser.py index 61f9f254..c7331133 100644 --- a/cocas/generated/AsmParser.py +++ b/cocas/assembler/generated/AsmParser.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/AsmParser.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/AsmParser.g4 by ANTLR 4.13.1 # encoding: utf-8 from antlr4 import * from io import StringIO diff --git a/cocas/generated/AsmParserVisitor.py b/cocas/assembler/generated/AsmParserVisitor.py similarity index 99% rename from cocas/generated/AsmParserVisitor.py rename to cocas/assembler/generated/AsmParserVisitor.py index eb4e32e3..82ffc4b4 100644 --- a/cocas/generated/AsmParserVisitor.py +++ b/cocas/assembler/generated/AsmParserVisitor.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/AsmParser.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/AsmParser.g4 by ANTLR 4.13.1 from antlr4 import * if "." in __name__: from .AsmParser import AsmParser diff --git a/cocas/generated/MacroLexer.py b/cocas/assembler/generated/MacroLexer.py similarity index 98% rename from cocas/generated/MacroLexer.py rename to cocas/assembler/generated/MacroLexer.py index df56c648..838a3dc0 100644 --- a/cocas/generated/MacroLexer.py +++ b/cocas/assembler/generated/MacroLexer.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/Macro.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/Macro.g4 by ANTLR 4.13.1 from antlr4 import * from io import StringIO import sys diff --git a/cocas/generated/MacroParser.py b/cocas/assembler/generated/MacroParser.py similarity index 99% rename from cocas/generated/MacroParser.py rename to cocas/assembler/generated/MacroParser.py index 603ba17b..111081e0 100644 --- a/cocas/generated/MacroParser.py +++ b/cocas/assembler/generated/MacroParser.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/Macro.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/Macro.g4 by ANTLR 4.13.1 # encoding: utf-8 from antlr4 import * from io import StringIO diff --git a/cocas/generated/MacroVisitor.py b/cocas/assembler/generated/MacroVisitor.py similarity index 98% rename from cocas/generated/MacroVisitor.py rename to cocas/assembler/generated/MacroVisitor.py index 8f74ac35..e9143ab5 100644 --- a/cocas/generated/MacroVisitor.py +++ b/cocas/assembler/generated/MacroVisitor.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/Macro.g4 by ANTLR 4.13.1 +# Generated from assembler/grammar/Macro.g4 by ANTLR 4.13.1 from antlr4 import * if "." in __name__: from .MacroParser import MacroParser diff --git a/cocas/assembler/generated/__init__.py b/cocas/assembler/generated/__init__.py new file mode 100644 index 00000000..e422e78d --- /dev/null +++ b/cocas/assembler/generated/__init__.py @@ -0,0 +1,6 @@ +from .AsmLexer import AsmLexer +from .AsmParser import AsmParser +from .AsmParserVisitor import AsmParserVisitor +from .MacroLexer import MacroLexer +from .MacroParser import MacroParser +from .MacroVisitor import MacroVisitor diff --git a/cocas/grammar/AsmLexer.g4 b/cocas/assembler/grammar/AsmLexer.g4 similarity index 100% rename from cocas/grammar/AsmLexer.g4 rename to cocas/assembler/grammar/AsmLexer.g4 diff --git a/cocas/grammar/AsmParser.g4 b/cocas/assembler/grammar/AsmParser.g4 similarity index 100% rename from cocas/grammar/AsmParser.g4 rename to cocas/assembler/grammar/AsmParser.g4 diff --git a/cocas/grammar/Macro.g4 b/cocas/assembler/grammar/Macro.g4 similarity index 100% rename from cocas/grammar/Macro.g4 rename to cocas/assembler/grammar/Macro.g4 diff --git a/cocas/macro_processor.py b/cocas/assembler/macro_processor.py similarity index 89% rename from cocas/macro_processor.py rename to cocas/assembler/macro_processor.py index 122dbe1e..f7735694 100644 --- a/cocas/macro_processor.py +++ b/cocas/assembler/macro_processor.py @@ -1,15 +1,16 @@ import re from base64 import b64encode from dataclasses import dataclass +from pathlib import Path +from typing import Optional from antlr4 import CommonTokenStream, FileStream, InputStream from antlr4.TokenStreamRewriter import TokenStreamRewriter -from cocas.error import AntlrErrorListener, CdmException, CdmExceptionTag, CdmTempException -from cocas.generated.MacroLexer import MacroLexer -from cocas.generated.MacroParser import MacroParser -from cocas.generated.MacroVisitor import MacroVisitor -from cocas.location import CodeLocation +from cocas.object_module import CodeLocation + +from .exceptions import AntlrErrorListener, AssemblerException, AssemblerExceptionTag, CdmTempException +from .generated import MacroLexer, MacroParser, MacroVisitor def unique(params: list[str]): @@ -102,7 +103,9 @@ def sub_all(ps): # noinspection PyPep8Naming class ExpandMacrosVisitor(MacroVisitor): - def __init__(self, rewriter: TokenStreamRewriter, mlb_macros, filepath: str): + def __init__(self, rewriter: Optional[TokenStreamRewriter], mlb_macros, filepath: str): + # rewriter should be None if then will be called .visit(MlbContext) + # rewriter should be valid if then will be called .visit(ProgramContext) self.nonce = 0 self.macros = {name: mlb_macros[name].copy() for name in mlb_macros} self.rewriter = rewriter @@ -179,7 +182,7 @@ def visitMlb(self, ctx: MacroParser.MlbContext): try: self.add_macro(self.visitMlb_macro(child)) except CdmTempException as e: - raise CdmException(CdmExceptionTag.MACRO, self.filepath, child.start.line, e.message) + raise AssemblerException(AssemblerExceptionTag.MACRO, self.filepath, child.start.line, e.message) return self.macros def visitProgram(self, ctx: MacroParser.ProgramContext): @@ -202,7 +205,7 @@ def visitProgram(self, ctx: MacroParser.ProgramContext): self.rewriter.insertBeforeToken(child.start, expanded_text) self.rewriter.delete(self.rewriter.DEFAULT_PROGRAM_NAME, child.start, child.stop) except CdmTempException as e: - raise CdmException(CdmExceptionTag.MACRO, self.filepath, child.start.line, e.message) + raise AssemblerException(AssemblerExceptionTag.MACRO, self.filepath, child.start.line, e.message) def visitMacro(self, ctx: MacroParser.MacroContext): header = ctx.macro_header() @@ -287,30 +290,30 @@ def visitMacro_piece(self, ctx: MacroParser.Macro_pieceContext): return MacroNonce() -# filepath should be absolute -def read_mlb(filepath): - input_stream = FileStream(filepath) +def read_mlb(filepath: Path): + str_path = filepath.absolute().as_posix() + input_stream = FileStream(str_path) lexer = MacroLexer(input_stream) token_stream = CommonTokenStream(lexer) parser = MacroParser(token_stream) cst = parser.mlb() - return ExpandMacrosVisitor(None, dict(), filepath).visit(cst) + emv = ExpandMacrosVisitor(None, dict(), str_path) + return emv.visit(cst) -# filepath should be absolute -def process_macros(input_stream: InputStream, library_macros, filepath: str): +def process_macros(input_stream: InputStream, library_macros, filepath: Path): + str_path = filepath.absolute().as_posix() lexer = MacroLexer(input_stream) lexer.removeErrorListeners() - # Adds a class that will be called somehow from antlr. And it will raise exceptions with MACRO and filepath - lexer.addErrorListener(AntlrErrorListener(CdmExceptionTag.MACRO, filepath)) + lexer.addErrorListener(AntlrErrorListener(AssemblerExceptionTag.MACRO, str_path)) token_stream = CommonTokenStream(lexer) parser = MacroParser(token_stream) parser.removeErrorListeners() - parser.addErrorListener(AntlrErrorListener(CdmExceptionTag.MACRO, filepath)) + parser.addErrorListener(AntlrErrorListener(AssemblerExceptionTag.MACRO, str_path)) cst = parser.program() rewriter = TokenStreamRewriter(token_stream) - emv = ExpandMacrosVisitor(rewriter, library_macros, filepath) + emv = ExpandMacrosVisitor(rewriter, library_macros, str_path) emv.visit(cst) - new_test = rewriter.getDefaultText() - return InputStream(new_test) + new_text = rewriter.getDefaultText() + return InputStream(new_text) diff --git a/cocas/assembler.py b/cocas/assembler/object_generator.py similarity index 54% rename from cocas/assembler.py rename to cocas/assembler/object_generator.py index 7cb8bab8..313fc9f2 100644 --- a/cocas/assembler.py +++ b/cocas/assembler/object_generator.py @@ -1,23 +1,16 @@ from dataclasses import dataclass -from pathlib import Path -from typing import Type -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.abstract_instructions import TargetInstructionsInterface -from cocas.ast_nodes import InstructionNode, LabelDeclarationNode, ProgramNode, TemplateSectionNode -from cocas.code_block import Section -from cocas.error import CdmExceptionTag -from cocas.location import CodeLocation -from cocas.object_module import ObjectModule, ObjectSectionRecord +from cocas.object_module import CodeLocation, ObjectModule -TAG = CdmExceptionTag.ASM +from .ast_nodes import InstructionNode, LabelDeclarationNode, ProgramNode, TemplateSectionNode +from .code_block import Section +from .exceptions import AssemblerException, AssemblerExceptionTag +from .targets import IVaryingLengthSegment, TargetInstructions @dataclass class Template: - def __init__(self, sn: TemplateSectionNode, code_segments: Type[CodeSegmentsInterface], - target_instructions: Type[TargetInstructionsInterface]): - self.code_segments = code_segments + def __init__(self, sn: TemplateSectionNode, target_instructions: TargetInstructions): self.name: str = sn.name self.labels: dict[str, int] = dict() @@ -27,19 +20,22 @@ def __init__(self, sn: TemplateSectionNode, code_segments: Type[CodeSegmentsInte if isinstance(line, LabelDeclarationNode): label_name = line.label.name if label_name in self.labels: - raise Exception(f'Duplicate label "{label_name}" declaration') - + raise AssemblerException(AssemblerExceptionTag.TPLATE, line.location.file, line.location.line, + f'Duplicate label "{label_name}" declaration') if line.external: - raise Exception('External labels not allowed in templates') + raise AssemblerException(AssemblerExceptionTag.TPLATE, line.location.file, line.location.line, + 'External labels not allowed in templates') elif line.entry: - raise Exception('Ents not allowed in templates') + raise AssemblerException(AssemblerExceptionTag.TPLATE, line.location.file, line.location.line, + 'Ents not allowed in templates') else: self.labels[label_name] = size elif isinstance(line, InstructionNode): if line.mnemonic not in target_instructions.assembly_directives(): - raise Exception('Only these directives allowed in templates: ' + - ', '.join(target_instructions.assembly_directives())) + raise AssemblerException(AssemblerExceptionTag.TPLATE, line.location.file, line.location.line, + 'Only these directives allowed in templates: ' + + ', '.join(target_instructions.assembly_directives())) for seg in target_instructions.assemble_instruction(line, temp_storage): size += seg.size @@ -55,7 +51,7 @@ def gather_local_labels(sects: list[Section]): @dataclass class VaryingLengthEntry: - seg: CodeSegmentsInterface.VaryingLengthSegment + seg: IVaryingLengthSegment sect: Section pos: int location: CodeLocation @@ -69,7 +65,7 @@ def update_varying_length(sections: list[Section], known_labels: dict[str, int], for sect in sections: pos = sect.address for seg in sect.segments: - if isinstance(seg, CodeSegmentsInterface.VaryingLengthSegment): + if isinstance(seg, IVaryingLengthSegment): a = VaryingLengthEntry(seg, sect, pos, seg.location) var_len_entries.append(a) pos += seg.size @@ -86,12 +82,12 @@ def update_varying_length(sections: list[Section], known_labels: dict[str, int], changed = True -def assemble(pn: ProgramNode, target_instructions, code_segments, debug_info_path: Path) -> ObjectModule: - templates = [Template(t, code_segments, target_instructions) for t in pn.template_sections] +def generate_object_module(pn: ProgramNode, target_instructions: TargetInstructions) -> ObjectModule: + templates = [Template(t, target_instructions) for t in pn.template_sections] template_fields = dict([(t.name, t.labels) for t in templates]) - asects = [Section(asect, target_instructions, code_segments) for asect in pn.absolute_sections] - rsects = [Section(rsect, target_instructions, code_segments) for rsect in pn.relocatable_sections] + asects = [Section(asect, target_instructions) for asect in pn.absolute_sections] + rsects = [Section(rsect, target_instructions) for rsect in pn.relocatable_sections] asects.sort(key=lambda s: s.address) update_varying_length(asects, {}, template_fields) @@ -99,8 +95,6 @@ def assemble(pn: ProgramNode, target_instructions, code_segments, debug_info_pat for rsect in rsects: update_varying_length([rsect], asects_labels, template_fields) - obj = ObjectModule(debug_info_path) - obj.asects = [ObjectSectionRecord.from_section(asect, asects_labels, template_fields) for asect in asects] - obj.rsects = [ObjectSectionRecord.from_section(rsect, asects_labels, template_fields) for rsect in rsects] - + obj = ObjectModule([asect.to_object_section_record(asects_labels, template_fields) for asect in asects], + [rsect.to_object_section_record(asects_labels, template_fields) for rsect in rsects]) return obj diff --git a/cocas/assembler/targets/__init__.py b/cocas/assembler/targets/__init__.py new file mode 100644 index 00000000..7ee6dbf7 --- /dev/null +++ b/cocas/assembler/targets/__init__.py @@ -0,0 +1,24 @@ +import importlib +from pathlib import Path + +from .abstract_code_segments import IAlignedSegment, IAlignmentPaddingSegment, ICodeSegment, IVaryingLengthSegment +from .target_instructions_protocol import TargetInstructions + + +def list_assembler_targets() -> set[str]: + """Returns a set of supported assembler targets. Takes submodules of assembler/target module""" + targets_dir = Path(__file__).parent.absolute() + targets = map(lambda x: x.name, filter(lambda x: x.is_dir(), targets_dir.glob("[!_]*"))) + return set(targets) + + +def import_target(target: str) -> TargetInstructions: + module = importlib.import_module(f'.{target}', __package__) + if isinstance(module, TargetInstructions): + return module + else: + raise TypeError("Module is not a valid target") + + +def mlb_path(target: str) -> Path: + return Path(__file__).parent / target / "standard.mlb" diff --git a/cocas/assembler/targets/abstract_code_segments.py b/cocas/assembler/targets/abstract_code_segments.py new file mode 100644 index 00000000..12e7d035 --- /dev/null +++ b/cocas/assembler/targets/abstract_code_segments.py @@ -0,0 +1,99 @@ +from abc import ABC, abstractmethod +from math import lcm +from typing import TYPE_CHECKING + +from cocas.object_module import CodeLocation, ObjectSectionRecord + +from ..exceptions import AssemblerException, AssemblerExceptionTag + +if TYPE_CHECKING: + from ..code_block import Section + + +class ICodeSegment(ABC): + @property + @abstractmethod + def size(self) -> int: + pass + + @property + @abstractmethod + def location(self) -> CodeLocation: + pass + + @abstractmethod + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + pass + + +class IVaryingLengthSegment(ICodeSegment, ABC): + @abstractmethod + def update_varying_length(self, pos, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]) -> int: + pass + + @staticmethod + def update_surroundings(diff: int, pos: int, section: "Section", labels: dict[str, int]): + for label_name in section.labels: + if section.labels[label_name] > pos: + section.labels[label_name] += diff + if label_name in labels: + labels[label_name] += diff + old_locations = section.code_locations + section.code_locations = dict() + for PC, location in old_locations.items(): + if PC > pos: + PC += diff + section.code_locations[PC] = location + + +class IAlignmentPaddingSegment(IVaryingLengthSegment): + + @property + def size(self) -> int: + return self._size + + @property + def location(self) -> CodeLocation: + return self._location + + def __init__(self, alignment: int, location: CodeLocation): + self.alignment = alignment + self._size = alignment + self._location = location + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + object_record.data += bytes(self.size) + if section.name != '$abs': + object_record.alignment = lcm(object_record.alignment, self.alignment) + + def update_varying_length(self, pos, section: "Section", labels: dict[str, int], _) -> int: + new_size = (-pos) % self.alignment + if new_size == self.alignment: + new_size = 0 + diff = new_size - self.size + self._size = new_size + self.__class__.update_surroundings(diff, pos, section, labels) + return diff + + +class IAlignedSegment(ICodeSegment, ABC): + @property + @abstractmethod + def alignment(self) -> int: + pass + + @abstractmethod + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + if (section.address + len(object_record.data)) % self.alignment != 0: + _error(self, f'Segment must be {self.alignment}-byte aligned') + super().fill(object_record, section, labels, templates) + if section.name != '$abs': + object_record.alignment = lcm(object_record.alignment, self.alignment) + + +def _error(segment: ICodeSegment, message: str): + raise AssemblerException(AssemblerExceptionTag.ASM, segment.location.file, segment.location.line, message) diff --git a/cocas/assembler/targets/cdm16/__init__.py b/cocas/assembler/targets/cdm16/__init__.py new file mode 100644 index 00000000..cfee7571 --- /dev/null +++ b/cocas/assembler/targets/cdm16/__init__.py @@ -0,0 +1 @@ +from .target_instructions import assemble_instruction, assembly_directives, finish, make_branch_instruction diff --git a/cocas/assembler/targets/cdm16/code_segments.py b/cocas/assembler/targets/cdm16/code_segments.py new file mode 100644 index 00000000..bc5c8541 --- /dev/null +++ b/cocas/assembler/targets/cdm16/code_segments.py @@ -0,0 +1,369 @@ +from abc import ABC +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Optional + +import bitstruct + +from cocas.object_module import CodeLocation, ExternalEntry, ObjectSectionRecord + +from ...ast_nodes import LabelNode, RegisterNode, RelocatableExpressionNode, TemplateFieldNode +from ...exceptions import AssemblerException, AssemblerExceptionTag +from .. import IAlignedSegment, IAlignmentPaddingSegment, ICodeSegment, IVaryingLengthSegment + +if TYPE_CHECKING: + from ...code_block import Section + + +def pack(fmt, *args): + b = bitstruct.pack(fmt, *args) + return bitstruct.byteswap('2', b) + + +def _error(segment: ICodeSegment, message: str): + raise AssemblerException(AssemblerExceptionTag.ASM, segment.location.file, segment.location.line, message) + + +class CodeSegment(ICodeSegment, ABC): + pass + + +class VaryingLengthSegment(IVaryingLengthSegment, CodeSegment, ABC): + pass + + +class AlignmentPaddingSegment(IAlignmentPaddingSegment, CodeSegment): + pass + + +class AlignedSegment(IAlignedSegment, CodeSegment, ABC): + def __init__(self, alignment: int): + self._alignment = alignment + + @property + def alignment(self) -> int: + return self._alignment + + +class InstructionSegment(AlignedSegment, ABC): + def __init__(self, location: CodeLocation): + super().__init__(2) + self._location = location + + @property + def location(self) -> CodeLocation: + return self._location + + +class FixedLengthInstructionSegment(InstructionSegment, ABC): + @property + def size(self) -> int: + return self._size + + def __init__(self, size: int, location: CodeLocation): + super().__init__(location) + self._size = size + + +class BytesSegment(CodeSegment): + data: bytes + + @property + def size(self) -> int: + return self._size + + @property + def location(self) -> CodeLocation: + return self._location + + def __init__(self, data: bytes, location: CodeLocation): + self.data = data + self._size = len(data) + self._location = location + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + object_record.data += self.data + + +class InstructionBytesSegment(BytesSegment, FixedLengthInstructionSegment): + def __init__(self, data: bytes, location: CodeLocation): + BytesSegment.__init__(self, data, location) + FixedLengthInstructionSegment.__init__(self, len(data), location) + + +class ExpressionSegment(CodeSegment): + expr: RelocatableExpressionNode + + @property + def size(self) -> int: + return self._size + + @property + def location(self) -> CodeLocation: + return self._location + + def __init__(self, expr: RelocatableExpressionNode, location: CodeLocation): + self.expr = expr + self._size = 2 + self._location = location + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + parsed = parse_expression(self.expr, section, labels, templates, self) + forbid_multilabel_expressions(parsed, self) + value = calculate_expression(parsed, section, labels) + offset = section.address + len(object_record.data) + if not -32768 <= value < 65536: + _error(self, 'Number out of range') + object_record.data.extend((value % 65536).to_bytes(2, 'little')) + add_rel_ext_entries(parsed, offset, object_record) + + +class LdiSegment(InstructionSegment, VaryingLengthSegment): + expr: RelocatableExpressionNode + + @property + def size(self) -> int: + return self._size + + @size.setter + def size(self, value): + self._size = value + + def __init__(self, register: RegisterNode, expr: RelocatableExpressionNode, location: CodeLocation): + InstructionSegment.__init__(self, location) + self.reg: int = register.number + self.expr = expr + self._size = 2 + self.size_locked = False + self.checked = False + self.parsed: Optional[ParsedExpression] = None + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + if self.size == 4: + object_record.data.extend(pack("u3p6u4u3", 0b001, 2, self.reg)) + ExpressionSegment(self.expr, self.location).fill(object_record, section, labels, templates) + else: + value = calculate_expression(self.parsed, section, labels) + object_record.data.extend(pack("u3u3s7u3", 0b011, 5, value, self.reg)) + + def update_varying_length(self, pos, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + if self.size_locked: + return + bad = False + if not self.checked: + self.parsed = parse_expression(self.expr, section, labels, templates, self) + forbid_multilabel_expressions(self.parsed, self) + bad = self.parsed.ext_labels or self.parsed.relocate_additions != 0 + self.checked = True + value = calculate_expression(self.parsed, section, labels) + if bad or not -64 <= value < 64: + self.size = 4 + self.size_locked = True + self.__class__.update_surroundings(2, pos, section, labels) + return 2 + + +class Branch(InstructionSegment, VaryingLengthSegment): + @property + def size(self) -> int: + return self._size + + @size.setter + def size(self, value): + self._size = value + + expr: RelocatableExpressionNode + + def __init__(self, location: CodeLocation, branch_code: int, expr: RelocatableExpressionNode, + operation='branch'): + InstructionSegment.__init__(self, location) + self.type = operation + self.branch_code = branch_code + self.expr = expr + self._size = 2 + self.size_locked = False + self.checked = False + self.parsed: Optional[ParsedExpression] = None + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + value = calculate_expression(self.parsed, section, labels) + if value % 2 != 0: + _error(self, "Destination address must be 2-byte aligned") + if self.size == 4: + if self.type == 'branch': + object_record.data.extend(pack("u5p7u4", 0x00001, self.branch_code)) + elif self.type == 'jsr': + object_record.data.extend(pack("u5p7u4", 0x00000, 8)) + ExpressionSegment(self.expr, self.location).fill(object_record, section, labels, templates) + else: + dist = value - (section.address + len(object_record.data) + 2) + if self.type == 'branch': + val = dist // 2 % 512 + sign = 0 if dist < 0 else 1 + object_record.data.extend(pack("u2u1u4u9", 0b11, sign, self.branch_code, val)) + elif self.type == 'jsr': + val = dist // 2 % 1024 + object_record.data.extend(pack("u3u3u10", 0b100, 3, val)) + + def update_varying_length(self, pos, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + if self.size_locked: + return + bad = False + if not self.checked: + self.parsed = parse_expression(self.expr, section, labels, templates, self) + if self.expr.sub_terms: + _error(self, 'Cannot subtract labels in branch value expressions') + elif len(self.expr.add_terms) > 1: + _error(self, 'Cannot use multiple labels in branch value expressions') + const = not self.expr.add_terms and not self.expr.sub_terms + bad = self.parsed.ext_labels or (self.parsed.asect or const) and section.name != '$abs' + self.checked = True + value = calculate_expression(self.parsed, section, labels) + dist = value - pos - 2 + if bad or not -1024 <= dist < 1024: + self.size = 4 + self.size_locked = True + self.__class__.update_surroundings(2, pos, section, labels) + return 2 + + +class Imm6(FixedLengthInstructionSegment): + expr: RelocatableExpressionNode + + def __init__(self, location: CodeLocation, negative: bool, op_number: int, register: RegisterNode, + expr: RelocatableExpressionNode, word=False): + super().__init__(2, location) + self.word = word + self.op_number = op_number + self.sign = -1 if negative else 1 + self.reg: int = register.number + self.expr = expr + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + parsed = parse_expression(self.expr, section, labels, templates, self) + value = calculate_expression(parsed, section, labels) * self.sign + if parsed.ext_labels: + _error(self, 'No external labels allowed in immediate form') + elif parsed.relocate_additions != 0: + _error(self, 'Can use rsect labels only to find distance in immediate form') + if self.word: + if value % 2 != 0: + _error(self, "Destination address must be 2-byte aligned") + value //= 2 + if not -64 <= value < 64: + _error(self, 'Value is out of bounds for immediate form') + object_record.data.extend(pack("u3u3s7u3", 0b011, self.op_number, value, self.reg)) + + +class Imm9(FixedLengthInstructionSegment): + expr: RelocatableExpressionNode + + def __init__(self, location: CodeLocation, negative: bool, op_number: int, expr: RelocatableExpressionNode): + super().__init__(2, location) + self.op_number = op_number + self.sign = -1 if negative else 1 + self.expr = expr + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + parsed = parse_expression(self.expr, section, labels, templates, self) + value = calculate_expression(parsed, section, labels) * self.sign + if parsed.ext_labels: + _error(self, 'No external labels allowed in immediate form') + elif parsed.relocate_additions != 0: + _error(self, 'Can use rsect labels only to find distance in immediate form') + elif not -512 <= value < 512: + _error(self, 'Value is out of bounds for immediate form') + object_record.data.extend(pack("u3u3s10", 0b100, self.op_number, value)) + + +@dataclass +class ParsedExpression: + value: int + relocate_additions: int = field(default=0) + asect: dict[str, int] = field(default_factory=dict) + rel_labels: dict[str, int] = field(default_factory=dict) + ext_labels: dict[str, int] = field(default_factory=dict) + + +def parse_expression(expr: RelocatableExpressionNode, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]], segment: CodeSegment) -> ParsedExpression: + if expr.byte_specifier is not None: + _error(segment, 'No byte specifiers allowed in CdM-16') + result = ParsedExpression(expr.const_term) + for term, sign in [(t, 1) for t in expr.add_terms] + [(t, -1) for t in expr.sub_terms]: + if isinstance(term, LabelNode): + if term.name in section.exts: + result.ext_labels[term.name] = result.ext_labels.get(term.name, 0) + sign + elif term.name in section.labels and section.name != '$abs': + result.rel_labels[term.name] = result.rel_labels.get(term.name, 0) + sign + elif term.name in section.labels: + result.asect[term.name] = result.asect.get(term.name, 0) + sign + elif term.name in labels: + result.asect[term.name] = result.asect.get(term.name, 0) + sign + else: + _error(segment, f'Label "{term.name}" not found') + elif isinstance(term, TemplateFieldNode): + result.value += templates[term.template_name][term.field_name] * sign + for label, n in result.rel_labels.items(): + result.relocate_additions += n + result.asect = {label: n for label, n in result.asect.items() if n != 0} + result.ext_labels = {label: n for label, n in result.ext_labels.items() if n != 0} + result.rel_labels = {label: n for label, n in result.rel_labels.items() if n != 0} + return result + + +def calculate_expression(parsed: ParsedExpression, section: "Section", labels: dict[str, int]) -> int: + value = parsed.value + for label, n in parsed.asect.items(): + if label in section.labels: + value += section.labels[label] * n + else: + value += labels[label] * n + for label, n in parsed.rel_labels.items(): + rel_address = section.labels[label] + value += rel_address * n + return value + + +def forbid_multilabel_expressions(parsed: ParsedExpression, segment: CodeSegment): + if len(parsed.ext_labels) > 1: + _error(segment, 'Cannot use multiple external labels in an address expression') + elif len(parsed.ext_labels) == 1: + label, n = next(iter(parsed.ext_labels.items())) + if n < 0: + _error(segment, 'Cannot subtract external labels in an address expression') + elif n > 1: + _error(segment, 'Cannot add external label multiple times in an address expression') + elif parsed.relocate_additions != 0: + _error(segment, 'Cannot add both external and relocatable section labels') + elif parsed.relocate_additions < 0: + _error(segment, 'Can subtract rsect labels only to get distance from another added rsect label') + elif parsed.relocate_additions > 1: + _error(segment, 'Can add rsect labels multiple times only to find distance ' + 'from another subtracted rsect label') + + +def add_rel_ext_entries(parsed: ParsedExpression, offset: int, object_record: "ObjectSectionRecord"): + for label in parsed.ext_labels: + if parsed.ext_labels[label] != 0: + entry = object_record.external.setdefault(label, []) + n = abs(parsed.ext_labels[label]) + sign = parsed.ext_labels[label] // n + for i in range(n): + entry.append(ExternalEntry(offset, range(0, 2), sign)) + if parsed.relocate_additions != 0: + sign = parsed.relocate_additions // abs(parsed.relocate_additions) + for i in range(abs(parsed.relocate_additions)): + object_record.relocatable.append(ExternalEntry(offset, range(0, 2), sign)) diff --git a/cocas/targets/cdm16/standard.mlb b/cocas/assembler/targets/cdm16/standard.mlb similarity index 100% rename from cocas/targets/cdm16/standard.mlb rename to cocas/assembler/targets/cdm16/standard.mlb diff --git a/cocas/assembler/targets/cdm16/target_instructions.py b/cocas/assembler/targets/cdm16/target_instructions.py new file mode 100644 index 00000000..2c9056ed --- /dev/null +++ b/cocas/assembler/targets/cdm16/target_instructions.py @@ -0,0 +1,414 @@ +import re +from copy import copy +from dataclasses import dataclass +from typing import Callable, Union, get_args, get_origin + +from ...ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode +from ...exceptions import AssemblerException, AssemblerExceptionTag, CdmTempException +from .. import ICodeSegment +from .code_segments import ( + AlignmentPaddingSegment, + Branch, + BytesSegment, + ExpressionSegment, + Imm6, + Imm9, + InstructionBytesSegment, + LdiSegment, + pack, +) + + +def assert_args(args, *types): + ts = [((t,) if get_origin(t) is None else get_args(t)) for t in types] + for i in range(len(args)): + for j in ts[i]: + if isinstance(args[i], j): + break + else: + raise CdmTempException(f'Incompatible argument type {type(args[i])}') + if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 7: + raise CdmTempException(f'Invalid register number r{args[i].number}') + + +def assert_count_args(args, *types): + if len(args) != len(types): + raise CdmTempException(f'Expected {len(types)} arguments, found {len(args)}') + assert_args(args, *types) + + +def handle_frame_pointer(line: InstructionNode): + for i in range(len(line.arguments)): + arg = line.arguments[i] + if isinstance(arg, RelocatableExpressionNode): + if not arg.const_term and not arg.sub_terms and not arg.byte_specifier and len(arg.add_terms) == 1 \ + and isinstance(arg.add_terms[0], LabelNode) and arg.add_terms[0].name == 'fp': + line.arguments[i] = RegisterNode(7) + + +def assemble_instruction(line: InstructionNode, temp_storage: dict) -> list[ICodeSegment]: + handle_frame_pointer(line) + try: + for h in handlers: + if line.mnemonic in h.instructions: + return h.handler(line, temp_storage, h.instructions[line.mnemonic]) + if line.mnemonic.startswith('b'): + return branch(line) + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + f'Unknown instruction "{line.mnemonic}"') + except CdmTempException as e: + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, e.message) + + +def finish(temp_storage: dict): + if len(temp_storage.get("save_restore_stack", [])) != 0: + raise CdmTempException("Expected restore statement") + + +def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ + -> list[ICodeSegment]: + instruction = InstructionNode('b' + branch_mnemonic, + [RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0)]) + instruction.location = location + return branch(instruction, inverse) + + +def ds(line: InstructionNode, _, __): + assert_args(line.arguments, RelocatableExpressionNode) + arg = line.arguments[0] + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Const number expected') + if arg.const_term < 0: + raise CdmTempException('Cannot specify negative size in "ds"') + return [BytesSegment(bytes(arg.const_term), line.location)] + + +def dc(line: InstructionNode, _, __): + if len(line.arguments) == 0: + raise CdmTempException('At least one argument must be provided') + segments = [] + size = 0 + command = line.mnemonic + for arg in line.arguments: + if isinstance(arg, RelocatableExpressionNode): + if command == 'db': + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Only constants allowed for 1 byte') + if -128 < arg.const_term < 256: + segments.append(BytesSegment((arg.const_term % 256).to_bytes(1, 'little'), + line.location)) + size += 2 + else: + raise CdmTempException(f'Number is not a byte: {arg.const_term}') + else: + segments.append(ExpressionSegment(arg, line.location)) + elif isinstance(arg, str): + if command == 'dw': + raise CdmTempException('Currently "dw" doesn\'t support strings') + encoded = arg.encode('utf-8') + segments.append(BytesSegment(encoded, line.location)) + size += len(encoded) + else: + raise CdmTempException(f'Incompatible argument type: {type(arg)}') + return segments + + +def align(line: InstructionNode, _, __): + if len(line.arguments) > 0: + assert_args(line.arguments, RelocatableExpressionNode) + arg: RelocatableExpressionNode = line.arguments[0] + if arg.add_terms or arg.sub_terms: + raise CdmTempException('Const number expected') + alignment = arg.const_term + else: + alignment = 2 + if alignment <= 0: + raise CdmTempException('Alignment should be positive') + elif alignment == 1: + return [] + return [AlignmentPaddingSegment(alignment, line.location)] + + +def save(line: InstructionNode, temp_storage: dict, __) -> list[ICodeSegment]: + assert_args(line.arguments, RegisterNode) + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + save_restore_stack.append(line.arguments[0]) + temp_storage["save_restore_stack"] = save_restore_stack + return assemble_instruction(InstructionNode("push", [line.arguments[0]]), temp_storage) + + +def restore(line: InstructionNode, temp_storage: dict, __) -> list[ICodeSegment]: + assert_args(line.arguments, RegisterNode) + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + if len(save_restore_stack) == 0: + raise CdmTempException("Every restore statement must be preceded by a save statement") + reg = save_restore_stack.pop() + if len(line.arguments) > 0: + assert_args(line.arguments, RegisterNode) + reg = line.arguments[0] + return assemble_instruction(InstructionNode("pop", [reg]), temp_storage) + + +def ldi(line: InstructionNode, _, __) -> list[ICodeSegment]: + assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) + return [LdiSegment(line.arguments[0], line.arguments[1], line.location)] + + +@dataclass +class BranchCode: + condition: list[str] + code: int + inverse: list[str] + inv_code: int + + +branch_codes: list[BranchCode] = [BranchCode(['eq', 'z'], 0, ['ne', 'nz'], 1), + BranchCode(['hs', 'cs'], 2, ['lo', 'cc'], 3), + BranchCode(['mi'], 4, ['pl'], 5), + BranchCode(['vs'], 6, ['vc'], 7), + BranchCode(['hi'], 8, ['ls'], 9), + BranchCode(['ge'], 10, ['lt'], 11), + BranchCode(['gt'], 12, ['le'], 13), + BranchCode(['anything', 'true', 'r'], 14, ['false'], 15)] + + +def branch(line: InstructionNode, inverse=False) -> list[ICodeSegment]: + cond = re.match(r'b(\w*)', line.mnemonic)[1] + for pair in branch_codes: + if cond in pair.condition: + branch_code = pair.code if not inverse else pair.inv_code + break + elif cond in pair.inverse: + branch_code = pair.inv_code if not inverse else pair.code + break + else: + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + f'Invalid branch condition: {cond}') + assert_count_args(line.arguments, RelocatableExpressionNode) + return [Branch(line.location, branch_code, line.arguments[0])] + + +def op0(line: InstructionNode, _, op_number: int): + assert_count_args(line.arguments) + return [InstructionBytesSegment(pack("u5p7u4", 0b00000, op_number), line.location)] + + +def const_only(arg: RelocatableExpressionNode): + if arg.add_terms or arg.sub_terms: + raise CdmTempException('Constant number expected as shift value') + return arg.const_term + + +def shifts(line: InstructionNode, _, op_number: int): + args = line.arguments + if len(args) == 3: + assert_args(args, RegisterNode, RegisterNode, RelocatableExpressionNode) + rs = args[0].number + rd = args[1].number + val = const_only(args[2]) + elif len(args) == 2 and isinstance(args[1], RegisterNode): + assert_args(args, RegisterNode, RegisterNode) + rs = args[0].number + rd = args[1].number + val = 1 + elif len(args) == 2: + assert_args(args, RegisterNode, RelocatableExpressionNode) + rs = args[0].number + rd = args[0].number + val = const_only(args[1]) + elif len(args) == 1: + assert_args(args, RegisterNode) + rs = args[0].number + rd = args[0].number + val = 1 + else: + raise CdmTempException(f'Expected 1-3 arguments, found {len(args)}') + if not 0 <= val <= 8: + raise CdmTempException('Shift value out of range') + if val == 0: + return [] + return [ + InstructionBytesSegment(pack("u4u3u3u3u3", 0b0001, op_number, val - 1, rs, rd), line.location)] + + +def op1(line: InstructionNode, _, op_number: int): + assert_count_args(line.arguments, RegisterNode) + reg = line.arguments[0].number + return [InstructionBytesSegment(pack("u3p6u4u3", 0b001, op_number, reg), line.location)] + + +def op2(line: InstructionNode, _, op_number: int): + assert_count_args(line.arguments, RegisterNode, RegisterNode) + rs = line.arguments[0].number + rd = line.arguments[1].number + return [InstructionBytesSegment(pack("u5p1u4u3u3", 0b01000, op_number, rs, rd), line.location)] + + +def alu3_ind(line: InstructionNode, _, op_number: int): + assert_count_args(line.arguments, RegisterNode, RegisterNode) + rs = line.arguments[0].number + rd = line.arguments[1].number + return [InstructionBytesSegment(pack("u5p2u3u3u3", 0b01001, op_number, rs, rd), line.location)] + + +def mem(line: InstructionNode, _, op_number: int): + if len(line.arguments) == 2: + assert_args(line.arguments, RegisterNode, RegisterNode) + addr1 = line.arguments[0].number + arg = line.arguments[1].number + return [ + InstructionBytesSegment(pack("u5p1u4u3u3", 0b01010, op_number, addr1, arg), line.location)] + elif len(line.arguments) == 3: + assert_args(line.arguments, RegisterNode, RegisterNode, RegisterNode) + addr1 = line.arguments[0].number + addr2 = line.arguments[1].number + arg = line.arguments[2].number + return [InstructionBytesSegment(pack("u4u3u3u3u3", 0b1010, op_number, addr1, addr2, arg), + line.location)] + else: + raise CdmTempException(f'Expected 2 or 3 arguments, found {len(line.arguments)}') + + +def alu2(line: InstructionNode, _, op_number: int): + if len(line.arguments) == 2: + assert_args(line.arguments, RegisterNode, RegisterNode) + rd = line.arguments[1].number + elif len(line.arguments) == 1: + assert_args(line.arguments, RegisterNode) + rd = line.arguments[0].number + else: + raise CdmTempException(f'Expected 1 or 2 arguments, found {len(line.arguments)}') + rs = line.arguments[0].number + return [InstructionBytesSegment(pack("u5p2u3u3u3", 0b01011, op_number, rs, rd), line.location)] + + +def imm6(line: InstructionNode, _, op_number: int) -> list[ICodeSegment]: + assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) + return [Imm6(line.location, False, op_number, *line.arguments)] + + +def imm6_word(line: InstructionNode, _, op_number: int) -> list[ICodeSegment]: + assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) + return [Imm6(line.location, False, op_number, *line.arguments, word=True)] + + +def alu3(line: InstructionNode, _, op_number: int): + if len(line.arguments) == 3: + assert_args(line.arguments, RegisterNode, RegisterNode, RegisterNode) + arg1 = line.arguments[0].number + arg2 = line.arguments[1].number + dest = line.arguments[2].number + return [InstructionBytesSegment(pack("u4u3u3u3u3", 0b1011, op_number, arg2, arg1, dest), + line.location)] + elif len(line.arguments) == 2: + assert_args(line.arguments, RegisterNode, RegisterNode) + arg1 = line.arguments[0].number + arg2 = line.arguments[1].number + return [InstructionBytesSegment(pack("u4u3u3u3u3", 0b1011, op_number, arg2, arg1, arg2), + line.location)] + else: + raise CdmTempException(f'Expected 2 or 3 arguments, found {len(line.arguments)}') + + +def special(line: InstructionNode, temp_storage: dict, _): + if line.mnemonic == 'add': + if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): + assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) + return [Imm6(line.location, False, 6, *line.arguments)] + else: + return alu3(line, temp_storage, 4) + elif line.mnemonic == 'sub': + if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): + assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) + return [Imm6(line.location, True, 6, *line.arguments)] + else: + return alu3(line, temp_storage, 6) + elif line.mnemonic == 'cmp': + if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): + return [Imm6(line.location, False, 7, *line.arguments)] + else: + return alu3_ind(line, temp_storage, 6) + elif line.mnemonic == 'int': + assert_count_args(line.arguments, RelocatableExpressionNode) + arg = line.arguments[0] + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Const number expected') + if arg.const_term < 0: + raise CdmTempException('Interrupt number must be not negative') + return [InstructionBytesSegment(pack("u3u4s9", 0b100, 0, arg.const_term), line.location)] + elif line.mnemonic == 'reset': + if len(line.arguments) == 0: + arg = RelocatableExpressionNode(None, [], [], 0) + elif len(line.arguments) == 1: + assert_args(line.arguments, RelocatableExpressionNode) + arg = line.arguments[0] + else: + raise CdmTempException(f'Expected 0 or 1 arguments, found {len(line.arguments)}') + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Const number expected') + if arg.const_term < 0: + raise CdmTempException('Vector number must be not negative') + return [InstructionBytesSegment(pack("u3u4s9", 0b100, 1, arg.const_term), line.location)] + elif line.mnemonic == 'addsp': + assert_count_args(line.arguments, Union[RelocatableExpressionNode, RegisterNode]) + if isinstance(line.arguments[0], RelocatableExpressionNode): + arg = copy(line.arguments[0]) + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Const number expected') + if arg.const_term % 2 == 1: + raise CdmTempException('Only even numbers can be added to stack pointer') + arg.const_term //= 2 + return [Imm9(line.location, False, 2, arg)] + else: + reg = line.arguments[0].number + return [InstructionBytesSegment(pack("u3p6u4u3", 0b001, 10, reg), line.location)] + elif line.mnemonic == 'jsr': + if len(line.arguments) == 1 and isinstance(line.arguments[0], RegisterNode): + jsrr = copy(line) + jsrr.mnemonic = 'jsrr' + return assemble_instruction(jsrr, temp_storage) + assert_count_args(line.arguments, RelocatableExpressionNode) + return [Branch(line.location, 0, line.arguments[0], operation='jsr')] + elif line.mnemonic == 'push': + assert_count_args(line.arguments, Union[RegisterNode, RelocatableExpressionNode]) + if isinstance(line.arguments[0], RegisterNode): + reg = line.arguments[0].number + return [InstructionBytesSegment(pack("u3p6u4u3", 0b001, 0, reg), line.location)] + else: + return [Imm9(line.location, False, 1, *line.arguments)] + + +@dataclass +class Handler: + handler: Callable[[InstructionNode, dict, int], list[ICodeSegment]] + instructions: dict[str, int] + + +handlers: list[Handler] + +handlers = [ + Handler(ds, {'ds': -1}), + Handler(dc, {'dc': -1, 'db': -1, 'dw': -1}), + Handler(align, {'align': -1}), + Handler(save, {'save': -1}), + Handler(restore, {'restore': -1}), + Handler(ldi, {'ldi': -1}), + Handler(op0, {'halt': 4, 'wait': 5, 'ei': 6, 'di': 7, 'rti': 9, 'pupc': 10, + 'popc': 11, 'pusp': 12, 'posp': 13, 'pups': 14, 'pops': 15}), + Handler(shifts, {'shl': 0, 'shr': 1, 'shra': 2, 'rol': 3, 'ror': 4, 'rcl': 5, 'rcr': 6}), + Handler(op1, {'pop': 1, 'jsrr': 3, 'ldsp': 4, 'stsp': 5, 'ldps': 6, 'stps': 7, 'ldpc': 8, 'stpc': 9}), + Handler(op2, {'move': 0}), + Handler(alu3_ind, {'bit': 0}), + Handler(mem, {'ldw': 0, 'ldb': 1, 'ldsb': 2, 'lcw': 3, 'lcb': 4, 'lcsb': 5, 'stw': 6, 'stb': 7}), + Handler(alu2, {'neg': 0, 'not': 1, 'sxt': 2, 'scl': 3}), + Handler(imm6, {'lsb': 1, 'lssb': 2, 'ssb': 4}), + Handler(imm6_word, {'lsw': 0, 'ssw': 3}), + Handler(alu3, {'and': 0, 'or': 1, 'xor': 2, 'bic': 3, 'addc': 5, 'subc': 7}), + Handler(special, {'add': -1, 'sub': -1, 'cmp': -1, 'int': -1, 'reset': -1, 'addsp': -1, 'jsr': -1, 'push': -1}) +] + + +def assembly_directives(): + return {'ds', 'dc', 'db', 'dw'} diff --git a/cocas/assembler/targets/cdm8/__init__.py b/cocas/assembler/targets/cdm8/__init__.py new file mode 100644 index 00000000..cfee7571 --- /dev/null +++ b/cocas/assembler/targets/cdm8/__init__.py @@ -0,0 +1 @@ +from .target_instructions import assemble_instruction, assembly_directives, finish, make_branch_instruction diff --git a/cocas/assembler/targets/cdm8/code_segments.py b/cocas/assembler/targets/cdm8/code_segments.py new file mode 100644 index 00000000..d8b2db1c --- /dev/null +++ b/cocas/assembler/targets/cdm8/code_segments.py @@ -0,0 +1,153 @@ +from abc import ABC +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from cocas.object_module import CodeLocation, ExternalEntry, ObjectSectionRecord + +from ...ast_nodes import LabelNode, RelocatableExpressionNode, TemplateFieldNode +from ...exceptions import AssemblerException, AssemblerExceptionTag +from .. import IAlignmentPaddingSegment, ICodeSegment + +if TYPE_CHECKING: + from ...code_block import Section + + +def _error(segment: ICodeSegment, message: str): + raise AssemblerException(AssemblerExceptionTag.ASM, segment.location.file, segment.location.line, message) + + +# noinspection DuplicatedCode +class CodeSegment(ICodeSegment, ABC): + pass + + +class AlignmentPaddingSegment(IAlignmentPaddingSegment, CodeSegment): + pass + + +class BytesSegment(CodeSegment): + data: bytes + + @property + def size(self) -> int: + return self._size + + @property + def location(self) -> CodeLocation: + return self._location + + def __init__(self, data: bytes, location: CodeLocation): + self.data = data + self._size = len(data) + self._location = location + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + super().fill(object_record, section, labels, templates) + object_record.data += self.data + + +class ExpressionSegment(CodeSegment): + expr: RelocatableExpressionNode + size = 1 + + @property + def location(self) -> CodeLocation: + return self._location + + def __init__(self, expr: RelocatableExpressionNode, location: CodeLocation): + self.expr = expr + self._location = location + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + parsed = parse_expression(self.expr, section, labels, templates, self) + forbid_multilabel_expressions(parsed, self) + value = calculate_expression(parsed, section, labels) + offset = section.address + len(object_record.data) + if not -128 <= value < 256: + _error(self, 'Number out of range') + object_record.data.extend((value % 256).to_bytes(1, 'little')) + add_rel_ext_entries(parsed, offset, object_record) + + +# noinspection DuplicatedCode +@dataclass +class ParsedExpression: + value: int + relocate_additions: int = field(default=0) + asect: dict[str, int] = field(default_factory=dict) + rel_labels: dict[str, int] = field(default_factory=dict) + ext_labels: dict[str, int] = field(default_factory=dict) + + +def parse_expression(expr: RelocatableExpressionNode, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]], segment: CodeSegment) -> ParsedExpression: + if expr.byte_specifier is not None: + _error(segment, 'No byte specifiers allowed in CdM-8') + result = ParsedExpression(expr.const_term) + for term, sign in [(t, 1) for t in expr.add_terms] + [(t, -1) for t in expr.sub_terms]: + if isinstance(term, LabelNode): + if term.name in section.exts: + result.ext_labels[term.name] = result.ext_labels.get(term.name, 0) + sign + elif term.name in section.labels and section.name != '$abs': + result.rel_labels[term.name] = result.rel_labels.get(term.name, 0) + sign + elif term.name in section.labels: + result.asect[term.name] = result.asect.get(term.name, 0) + sign + elif term.name in labels: + result.asect[term.name] = result.asect.get(term.name, 0) + sign + else: + _error(segment, f'Label "{term.name}" not found') + elif isinstance(term, TemplateFieldNode): + result.value += templates[term.template_name][term.field_name] * sign + for label, n in result.rel_labels.items(): + result.relocate_additions += n + result.asect = {label: n for label, n in result.asect.items() if n != 0} + result.ext_labels = {label: n for label, n in result.ext_labels.items() if n != 0} + result.rel_labels = {label: n for label, n in result.rel_labels.items() if n != 0} + return result + + +def calculate_expression(parsed: ParsedExpression, section: "Section", labels: dict[str, int]) -> int: + value = parsed.value + for label, n in parsed.asect.items(): + if label in section.labels: + value += section.labels[label] * n + else: + value += labels[label] * n + for label, n in parsed.rel_labels.items(): + rel_address = section.labels[label] + value += rel_address * n + return value + + +def forbid_multilabel_expressions(parsed: ParsedExpression, segment: CodeSegment): + if len(parsed.ext_labels) > 1: + _error(segment, 'Cannot use multiple external labels in an address expression') + elif len(parsed.ext_labels) == 1: + label, n = next(iter(parsed.ext_labels.items())) + if n < 0: + _error(segment, 'Cannot subtract external labels in an address expression') + elif n > 1: + _error(segment, 'Cannot add external label multiple times in an address expression') + elif parsed.relocate_additions != 0: + _error(segment, 'Cannot add both external and relocatable section labels') + elif parsed.relocate_additions < 0: + _error(segment, 'Can subtract rsect labels only to get distance from another added rsect label') + elif parsed.relocate_additions > 1: + _error(segment, 'Can add rsect labels multiple times only to find distance ' + 'from another subtracted rsect label') + + +def add_rel_ext_entries(parsed: ParsedExpression, offset: int, object_record: "ObjectSectionRecord"): + for label in parsed.ext_labels: + if parsed.ext_labels[label] != 0: + entry = object_record.external.setdefault(label, []) + n = abs(parsed.ext_labels[label]) + sign = parsed.ext_labels[label] // n + for i in range(n): + entry.append(ExternalEntry(offset, range(0, 1), sign)) + if parsed.relocate_additions != 0: + sign = parsed.relocate_additions // abs(parsed.relocate_additions) + for i in range(abs(parsed.relocate_additions)): + object_record.relocatable.append(ExternalEntry(offset, range(0, 1), sign)) diff --git a/cocas/targets/cdm8/standard.mlb b/cocas/assembler/targets/cdm8/standard.mlb similarity index 100% rename from cocas/targets/cdm8/standard.mlb rename to cocas/assembler/targets/cdm8/standard.mlb diff --git a/cocas/assembler/targets/cdm8/target_instructions.py b/cocas/assembler/targets/cdm8/target_instructions.py new file mode 100644 index 00000000..a6b8a5ee --- /dev/null +++ b/cocas/assembler/targets/cdm8/target_instructions.py @@ -0,0 +1,255 @@ +import re +from dataclasses import dataclass +from typing import Callable, get_args, get_origin + +from bitstruct import pack + +from ...ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode +from ...exceptions import AssemblerException, AssemblerExceptionTag, CdmTempException +from .. import ICodeSegment +from .code_segments import AlignmentPaddingSegment, BytesSegment, ExpressionSegment + + +def assert_args(args, *types): + ts = [((t,) if get_origin(t) is None else get_args(t)) for t in types] + for i in range(len(args)): + for j in ts[i]: + if isinstance(args[i], j): + break + else: + raise CdmTempException(f'Incompatible argument type {type(args[i])}') + if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 7: + raise CdmTempException(f'Invalid register number r{args[i].number}') + + +def assert_count_args(args, *types): + if len(args) != len(types): + raise CdmTempException(f'Expected {len(types)} arguments, found {len(args)}') + assert_args(args, *types) + + +def handle_frame_pointer(line: InstructionNode): + for i in range(len(line.arguments)): + arg = line.arguments[i] + if isinstance(arg, RelocatableExpressionNode): + if not arg.const_term and not arg.sub_terms and not arg.byte_specifier and len(arg.add_terms) == 1 \ + and isinstance(arg.add_terms[0], LabelNode) and arg.add_terms[0].name == 'fp': + line.arguments[i] = RegisterNode(7) + + +def assemble_instruction(line: InstructionNode, temp_storage: dict) -> list[ICodeSegment]: + try: + for h in handlers: + if line.mnemonic in h.instructions: + return h.handler(line, temp_storage, h.instructions[line.mnemonic]) + if line.mnemonic.startswith('b'): + return branch(line) + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + f'Unknown instruction "{line.mnemonic}"') + except CdmTempException as e: + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, e.message) + + +def finish(temp_storage: dict): + if len(temp_storage.get("save_restore_stack", [])) != 0: + raise CdmTempException("Expected restore statement") + + +def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ + -> list[ICodeSegment]: + instruction = InstructionNode('b' + branch_mnemonic, + [RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0)]) + instruction.location = location + return branch(instruction, inverse) + + +def ds(line: InstructionNode, _, __): + assert_args(line.arguments, RelocatableExpressionNode) + arg = line.arguments[0] + if arg.const_term < 0: + raise CdmTempException('Cannot specify negative size in "ds"') + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Const number expected') + return [BytesSegment(bytes(arg.const_term), line.location)] + + +def dc(line: InstructionNode, _, __): + if len(line.arguments) == 0: + raise CdmTempException('At least one argument must be provided') + segments = [] + size = 0 + for arg in line.arguments: + if isinstance(arg, RelocatableExpressionNode): + segments.append(ExpressionSegment(arg, line.location)) + elif isinstance(arg, str): + encoded = arg.encode('utf-8') + segments.append(BytesSegment(encoded, line.location)) + size += len(encoded) + else: + raise CdmTempException(f'Incompatible argument type: {type(arg)}') + return segments + + +def align(line: InstructionNode, _, __): + assert_args(line.arguments, RelocatableExpressionNode) + arg: RelocatableExpressionNode = line.arguments[0] + if arg.add_terms or arg.sub_terms: + raise CdmTempException('Const number expected') + alignment = arg.const_term + if alignment <= 0: + raise CdmTempException('Alignment should be positive') + elif alignment == 1: + return [] + return [AlignmentPaddingSegment(alignment, line.location)] + + +def save(line: InstructionNode, temp_storage: dict, __) -> list[ICodeSegment]: + assert_args(line.arguments, RegisterNode) + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + save_restore_stack.append(line.arguments[0]) + temp_storage["save_restore_stack"] = save_restore_stack + return assemble_instruction(InstructionNode("push", [line.arguments[0]]), temp_storage) + + +def restore(line: InstructionNode, temp_storage: dict, __) -> list[ICodeSegment]: + assert_args(line.arguments, RegisterNode) + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + if len(save_restore_stack) == 0: + raise CdmTempException("Every restore statement must be preceded by a save statement") + reg = save_restore_stack.pop() + if len(line.arguments) > 0: + assert_args(line.arguments, RegisterNode) + reg = line.arguments[0] + return assemble_instruction(InstructionNode("pop", [reg]), temp_storage) + + +@dataclass +class BranchCode: + condition: list[str] + code: int + inverse: list[str] + inv_code: int + + +branch_codes: list[BranchCode] = [BranchCode(['eq', 'z'], 0, ['ne', 'nz'], 1), + BranchCode(['hs', 'cs'], 2, ['lo', 'cc'], 3), + BranchCode(['mi'], 4, ['pl'], 5), + BranchCode(['vs'], 6, ['vc'], 7), + BranchCode(['hi'], 8, ['ls'], 9), + BranchCode(['ge'], 10, ['lt'], 11), + BranchCode(['gt'], 12, ['le'], 13), + BranchCode(['anything', 'true', 'r'], 14, ['false'], 15)] + + +def branch(line: InstructionNode, inverse=False) -> list[ICodeSegment]: + cond = re.match(r'b(\w*)', line.mnemonic)[1] + for pair in branch_codes: + if cond in pair.condition: + branch_code = pair.code if not inverse else pair.inv_code + break + elif cond in pair.inverse: + branch_code = pair.inv_code if not inverse else pair.code + break + else: + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, + f'Invalid branch condition: {cond}') + assert_count_args(line.arguments, RelocatableExpressionNode) + return [BytesSegment(pack('u4u4', 0xE, branch_code), line.location), + ExpressionSegment(line.arguments[0], line.location)] + + +def zero(line: InstructionNode, _, opcode: int): + assert_count_args(line.arguments) + return [BytesSegment(bytearray([opcode]), line.location)] + + +def unary(line: InstructionNode, _, opcode: int): + assert_args(line.arguments, RegisterNode) + data = pack('u6u2', opcode // 4, line.arguments[0].number) + return [BytesSegment(bytearray(data), line.location)] + + +def binary(line: InstructionNode, _, opcode: int): + assert_args(line.arguments, RegisterNode, RegisterNode) + data = pack('u4u2u2', opcode // 16, line.arguments[0].number, line.arguments[1].number) + return [BytesSegment(bytearray(data), line.location)] + + +def imm(line: InstructionNode, _, opcode: int): + assert_args(line.arguments, RelocatableExpressionNode) + return [BytesSegment(bytearray([opcode]), line.location), + ExpressionSegment(line.arguments[0], line.location)] + + +def unary_imm(line: InstructionNode, _, opcode: int): + assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) + data = pack('u6u2', opcode // 4, line.arguments[0].number) + return [BytesSegment(bytearray(data), line.location), + ExpressionSegment(line.arguments[1], line.location)] + + +@dataclass +class Handler: + handler: Callable[[InstructionNode, dict, int], list[ICodeSegment]] + instructions: dict[str, int] + + +handlers: list[Handler] +handlers = [ + Handler(ds, {'ds': -1}), + Handler(dc, {'dc': -1}), + Handler(align, {'align': -1}), + Handler(save, {'save': -1}), + Handler(restore, {'restore': -1}), + Handler(zero, { + 'pushall': 0xCE, + 'popall': 0xCF, + 'rts': 0xD7, + 'halt': 0xD4, + 'wait': 0xD5, + 'ioi': 0xD8, + 'rti': 0xD9, + 'crc': 0xDA + }), + Handler(unary, { + 'not': 0x80, + 'neg': 0x84, + 'dec': 0x88, + 'inc': 0x8C, + 'shr': 0x90, + 'shla': 0x94, + 'shra': 0x98, + 'rol': 0x9C, + 'push': 0xC0, + 'pop': 0xC4 + }), + Handler(binary, { + 'move': 0x00, + 'add': 0x10, + 'addc': 0x20, + 'sub': 0x30, + 'and': 0x40, + 'or': 0x50, + 'xor': 0x60, + 'cmp': 0x70, + 'st': 0xA0, + 'ld': 0xB0, + 'ldc': 0xF0 + }), + Handler(imm, { + 'jsr': 0xD6, + 'osix': 0xDB, + 'addsp': 0xCC, + 'setsp': 0xCD + }), + Handler(unary_imm, { + 'ldsa': 0xC8, + 'ldi': 0xD0 + }) +] + + +def assembly_directives(): + return {'ds', 'dc'} diff --git a/cocas/assembler/targets/cdm8e/__init__.py b/cocas/assembler/targets/cdm8e/__init__.py new file mode 100644 index 00000000..cfee7571 --- /dev/null +++ b/cocas/assembler/targets/cdm8e/__init__.py @@ -0,0 +1 @@ +from .target_instructions import assemble_instruction, assembly_directives, finish, make_branch_instruction diff --git a/cocas/assembler/targets/cdm8e/code_segments.py b/cocas/assembler/targets/cdm8e/code_segments.py new file mode 100644 index 00000000..b44fef7f --- /dev/null +++ b/cocas/assembler/targets/cdm8e/code_segments.py @@ -0,0 +1,282 @@ +from abc import ABC +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +from cocas.object_module import CodeLocation, ExternalEntry, ObjectSectionRecord + +from ...ast_nodes import LabelNode, RelocatableExpressionNode, TemplateFieldNode +from ...exceptions import AssemblerException, AssemblerExceptionTag +from .. import ICodeSegment, IVaryingLengthSegment +from .simple_instructions import simple_instructions + +TAG = AssemblerExceptionTag.ASM + +if TYPE_CHECKING: + from ...code_block import Section + + +class CodeSegment(ICodeSegment, ABC): + _location = CodeLocation() + + @property + def location(self) -> CodeLocation: + return self._location + + @location.setter + def location(self, value): + self._location = value + + +@dataclass +class RelocatableExpressionSegment(CodeSegment, ABC): + expr: RelocatableExpressionNode = field(init=True) + + +@dataclass +class VaryingLengthSegment(IVaryingLengthSegment, CodeSegment, ABC): + is_expanded: bool = field(init=False, default=False) + expanded_size: int = field(init=False) + + +class BytesSegment(CodeSegment): + data: bytearray + + def __init__(self, data: bytearray): + self.data = data + self._size = len(data) + + @property + def size(self) -> int: + return self._size + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + object_record.data += self.data + + +@dataclass +class ShortExpressionSegment(RelocatableExpressionSegment): + size = 1 + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + val, val_long, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) + + is_rel = (val_sect == section.name != '$abs') + if self.expr.byte_specifier is None and (is_rel or ext is not None): + _error(self, 'Expected a 1-byte expression') + if not -2 ** 7 <= val < 2 ** 8: + _error(self, 'Number out of range') + + if is_rel: + add_rel_record(object_record, section, val_long, self) + if ext is not None: + add_ext_record(object_record, ext, section, val_long, self) + object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) + + +@dataclass +class ConstExpressionSegment(RelocatableExpressionSegment): + positive: bool = False + size = 1 + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + val, _, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) + if val_sect is not None or ext is not None: + _error(self, 'Number expected but label found') + if not -2 ** 7 <= val < 2 ** 8 or (self.positive and val < 0): + _error(self, 'Number out of range') + object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) + + +@dataclass +class LongExpressionSegment(RelocatableExpressionSegment): + size = 2 + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + val, val_long, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) + + if not -2 ** 15 <= val < 2 ** 16: + _error(self, 'Number out of range') + + if val_sect: + add_rel_record(object_record, section, val_long, self) + if ext is not None: + add_ext_record(object_record, ext, section, val_long, self) + object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) + + +@dataclass +class OffsetExpressionSegment(RelocatableExpressionSegment): + size = 1 + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + val, _, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) + + is_rel = (val_sect == section.name != '$abs') + if ext is not None: + _error(self, 'Invalid destination address (external label used)') + if section.name != '$abs' and not is_rel: + _error(self, 'Invalid destination address (absolute address from rsect)') + if self.expr.byte_specifier is not None and is_rel: + _error(self, 'Invalid destination address (byte of relocatable address)') + + val -= section.address + len(object_record.data) + if not -2 ** 7 <= val < 2 ** 7: + _error(self, 'Destination address is too far') + + object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) + + +class GotoSegment(RelocatableExpressionSegment, VaryingLengthSegment): + def __init__(self, branch_mnemonic: str, expr: RelocatableExpressionNode): + # noinspection PyArgumentList + RelocatableExpressionSegment.__init__(self, expr) + VaryingLengthSegment.__init__(self) + self.branch_mnemonic = branch_mnemonic + + base_size = 2 + expanded_size = 5 + + @property + def size(self) -> int: + if self.is_expanded: + return self.expanded_size + else: + return self.base_size + + def update_varying_length(self, pos: int, section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + try: + if self.is_expanded: + return + + addr, _, res_sect, ext = eval_rel_expr_seg(self, section, labels, templates) + is_rel = (res_sect == section.name != '$abs') + if (not -2 ** 7 <= addr - (pos + 1) < 2 ** 7 + or (section.name != '$abs' and not is_rel) + or (self.expr.byte_specifier is not None and is_rel) + or (ext is not None)): + + shift_length = self.expanded_size - self.base_size + self.is_expanded = True + old_locations = section.code_locations + section.code_locations = dict() + for PC, location in old_locations.items(): + if PC > pos: + PC += shift_length + section.code_locations[PC] = location + + for label_name in section.labels: + if section.labels[label_name] > pos: + section.labels[label_name] += shift_length + if label_name in labels: + labels[label_name] += shift_length + return shift_length + except AssemblerException as e: + raise e + except Exception as e: + raise AssemblerException(TAG, self.location.file, self.location.line, str(e)) + + def fill(self, object_record: "ObjectSectionRecord", section: "Section", labels: dict[str, int], + templates: dict[str, dict[str, int]]): + mnemonic = f'b{self.branch_mnemonic}' + if mnemonic not in simple_instructions['branch']: + _error(self, f'Invalid branch mnemonic: {mnemonic}') + if self.is_expanded: + branch_opcode = simple_instructions['branch'][ + f'bn{self.branch_mnemonic}'] + jmp_opcode = simple_instructions['long']['jmp'] + object_record.data += bytearray([branch_opcode, 4, jmp_opcode]) + LongExpressionSegment(self.expr).fill(object_record, section, labels, templates) + else: + branch_opcode = simple_instructions['branch'][mnemonic] + object_record.data += bytearray([branch_opcode]) + OffsetExpressionSegment(self.expr).fill(object_record, section, labels, templates) + + +def _error(segment: ICodeSegment, message: str): + raise AssemblerException(TAG, segment.location.file, segment.location.line, message) + + +def eval_rel_expr_seg(seg: RelocatableExpressionSegment, s: "Section", + labels: dict[str, int], templates: dict[str, dict[str, int]]): + val_long = seg.expr.const_term + used_exts = dict() + s_dim = 0 + local_dim = 0 + for term, m in [(t, 1) for t in seg.expr.add_terms] + [(t, -1) for t in seg.expr.sub_terms]: + if isinstance(term, LabelNode): + if term.name in labels: + local_dim += m + val_long += labels[term.name] * m + elif term.name in s.labels: + s_dim += m + val_long += s.labels[term.name] * m + elif term.name in s.exts: + used_exts.setdefault(term.name, 0) + used_exts[term.name] += m + else: + _error(seg, f'Label "{term.name}" not found') + elif isinstance(term, TemplateFieldNode): + val_long += templates[term.template_name][term.field_name] * m + + val_lo, val_hi = val_long.to_bytes(2, 'little', signed=(val_long < 0)) + if seg.expr.byte_specifier == 'low': + val = val_lo + elif seg.expr.byte_specifier == 'high': + val = val_hi + elif seg.expr.byte_specifier is not None: + _error(seg, f'Invalid byte specifier "{seg.expr.byte_specifier}". Possible options are "low" and "high"') + return + else: + val = val_long + + used_exts = dict(filter(lambda x: x[1] != 0, used_exts.items())) + if len(used_exts) > 1: + _error(seg, 'Cannot use multiple external labels in an address expression') + + if len(used_exts) == 0: + if s_dim == 0 and local_dim == 0: + return val, val_long, None, None + elif s_dim == 0 and local_dim == 1: + return val, val_long, '$abs', None + elif s_dim == 1 and local_dim == 0: + return val, val_long, s.name, None + else: + ext, ext_dim = used_exts.popitem() + if local_dim == 0 and s_dim == 0 and ext_dim == 1: + return val, val_long, None, ext + + _error(seg, 'Result is not a label or a number') + + +def add_ext_record(obj_rec: "ObjectSectionRecord", ext: str, s: "Section", val: int, + seg: RelocatableExpressionSegment): + val %= 65536 + val_lo, _ = val.to_bytes(2, 'little', signed=False) + offset = s.address + len(obj_rec.data) + if seg.expr.byte_specifier == 'low': + obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(0, 1), full_bytes=False)) + elif seg.expr.byte_specifier == 'high': + obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(1, 2), full_bytes=False)) + obj_rec.lower_parts[offset] = obj_rec.lower_parts.get(offset, 0) + val_lo + else: + obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(0, 2), full_bytes=True)) + + +def add_rel_record(obj_rec: "ObjectSectionRecord", s: "Section", val: int, + seg: RelocatableExpressionSegment): + val %= 65536 + val_lo, _ = val.to_bytes(2, 'little', signed=False) + offset = s.address + len(obj_rec.data) + if seg.expr.byte_specifier == 'low': + obj_rec.relocatable.append(ExternalEntry(offset, range(0, 1), full_bytes=False)) + elif seg.expr.byte_specifier == 'high': + obj_rec.relocatable.append(ExternalEntry(offset, range(1, 2), full_bytes=False)) + obj_rec.lower_parts[offset] = obj_rec.lower_parts.get(offset, 0) + val_lo + else: + obj_rec.relocatable.append(ExternalEntry(offset, range(0, 2), full_bytes=True)) diff --git a/cocas/assembler/targets/cdm8e/simple_instructions.py b/cocas/assembler/targets/cdm8e/simple_instructions.py new file mode 100644 index 00000000..e3cab8eb --- /dev/null +++ b/cocas/assembler/targets/cdm8e/simple_instructions.py @@ -0,0 +1,108 @@ +simple_instructions = { + 'zero': { + 'pushall': 0xCE, + 'popall': 0xCF, + 'rts': 0xD7, + 'halt': 0xD4, + 'wait': 0xD5, + 'ioi': 0xD8, + 'rti': 0xD9, + 'crc': 0xDA, + }, + 'unary': { + 'not': 0x80, + 'neg': 0x84, + 'dec': 0x88, + 'inc': 0x8C, + 'shr': 0x90, + 'shla': 0x94, + 'shra': 0x98, + 'rol': 0x9C, + 'push': 0xC0, + 'pop': 0xC4, + }, + 'binary': { + 'move': 0x00, + 'add': 0x10, + 'addc': 0x20, + 'sub': 0x30, + 'and': 0x40, + 'or': 0x50, + 'xor': 0x60, + 'cmp': 0x70, + 'st': 0xA0, + 'ld': 0xB0, + 'ldc': 0xF0, + }, + 'branch': { + 'beq': 0xE0, + 'bz': 0xE0, + 'bnne': 0xE0, + 'bnnz': 0xE0, + + 'bne': 0xE1, + 'bnz': 0xE1, + 'bneq': 0xE1, + + 'bhs': 0xE2, + 'bcs': 0xE2, + 'bnlo': 0xE2, + 'bncc': 0xE2, + + 'blo': 0xE3, + 'bcc': 0xE3, + 'bnhs': 0xE3, + 'bncs': 0xE3, + + 'bmi': 0xE4, + 'bnpl': 0xE4, + + 'bpl': 0xE5, + 'bnmi': 0xE5, + + 'bvs': 0xE6, + 'bnvc': 0xE6, + + 'bvc': 0xE7, + 'bnvs': 0xE7, + + 'bhi': 0xE8, + 'bnls': 0xE8, + + 'bls': 0xE9, + 'bnhi': 0xE9, + + 'bge': 0xEA, + 'bnlt': 0xEA, + + 'blt': 0xEB, + 'bnge': 0xEB, + + 'bgt': 0xEC, + 'bnle': 0xEC, + + 'ble': 0xED, + 'bngt': 0xED, + + 'br': 0xEE, + 'banything': 0xEE, + 'btrue': 0xEE, + 'bnfalse': 0xEE, + + 'nop': 0xEF, + 'bnanything': 0xEF, + 'bntrue': 0xEF, + 'bfalse': 0xEF, + }, + 'long': { + 'jsr': 0xD6, + 'jmp': 0xDD, + }, + 'ldsa': {'ldsa': 0xC8}, + 'ldi': {'ldi': 0xD0}, + 'osix': {'osix': 0xDB}, + 'spmove': { + 'addsp': 0xCC, + 'setsp': 0xCD, + }, +} diff --git a/cocas/targets/cdm8e/standard.mlb b/cocas/assembler/targets/cdm8e/standard.mlb similarity index 100% rename from cocas/targets/cdm8e/standard.mlb rename to cocas/assembler/targets/cdm8e/standard.mlb diff --git a/cocas/assembler/targets/cdm8e/target_instructions.py b/cocas/assembler/targets/cdm8e/target_instructions.py new file mode 100644 index 00000000..d23a9591 --- /dev/null +++ b/cocas/assembler/targets/cdm8e/target_instructions.py @@ -0,0 +1,247 @@ +from typing import Union, get_args, get_origin + +import bitstruct + +from ...ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode +from ...exceptions import AssemblerException, AssemblerExceptionTag, CdmTempException +from .. import ICodeSegment +from .code_segments import ( + BytesSegment, + ConstExpressionSegment, + GotoSegment, + LongExpressionSegment, + OffsetExpressionSegment, + ShortExpressionSegment, +) +from .simple_instructions import simple_instructions + + +def assert_args(args, *types, single_type=False): + ts = [(t if get_origin(t) is None else get_args(t)) for t in types] + if single_type: + if len(ts) != 1: + raise TypeError('Exactly one type must be specified when single_type is True') + ts = ts * len(args) + elif len(args) != len(ts): + raise CdmTempException(f'Expected {len(ts)} arguments, but found {len(args)}') + + for i in range(len(args)): + # noinspection PyTypeHints + if not isinstance(args[i], ts[i]): + raise CdmTempException(f'Incompatible argument type {type(args[i])}') + if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 3: + raise CdmTempException(f'Invalid register number r{args[i].number}') + + +def assemble_instruction(line: InstructionNode, temp_storage) \ + -> list[ICodeSegment]: + try: + if line.mnemonic in assembly_directives(): + handler = assembler_directives[line.mnemonic] + segments = handler(line.arguments) + elif line.mnemonic in cpu_instructions: + opcode, handler = cpu_instructions[line.mnemonic] + segments = handler(opcode, line.arguments) + elif line.mnemonic in special_instructions: + return special_instructions[line.mnemonic](line.arguments, temp_storage, line.location) + else: + raise CdmTempException(f'Unknown instruction "{line.mnemonic}"') + for segment in segments: + segment.location = line.location + return segments + except CdmTempException as e: + raise AssemblerException(AssemblerExceptionTag.ASM, line.location.file, line.location.line, e.message) + + +def finish(temp_storage: dict): + if len(temp_storage.get("save_restore_stack", [])) != 0: + raise CdmTempException("Expected restore statement") + + +def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ + -> list[ICodeSegment]: + arg2 = RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0) + if inverse: + branch_mnemonic = 'n' + branch_mnemonic + seg = GotoSegment(branch_mnemonic, arg2) + seg.location = location + return [seg] + + +def goto_handler(arguments: list, _, location): + assert_args(arguments, RelocatableExpressionNode, RelocatableExpressionNode) + br_mnemonic: RelocatableExpressionNode + br_mnemonic = arguments[0] + if br_mnemonic.byte_specifier is not None or len(br_mnemonic.sub_terms) != 0 \ + or len(br_mnemonic.add_terms) != 1 or not isinstance(br_mnemonic.add_terms[0], LabelNode): + raise CdmTempException('Branch mnemonic must be single word') + goto = GotoSegment(br_mnemonic.add_terms[0].name, arguments[1]) + goto.location = location + return [goto] + + +def save_handler(arguments: list, temp_storage: dict, _): + assert_args(arguments, RegisterNode) + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + save_restore_stack.append(arguments[0]) + temp_storage["save_restore_stack"] = save_restore_stack + return assemble_instruction(InstructionNode("push", [arguments[0]]), temp_storage) + + +def restore_handler(arguments: list, temp_storage: dict, _): + save_restore_stack: list[RegisterNode] + save_restore_stack = temp_storage.get("save_restore_stack", []) + if len(save_restore_stack) == 0: + raise CdmTempException("Every restore statement must be preceded by a save statement") + reg = save_restore_stack.pop() + if len(arguments) > 0: + assert_args(arguments, RegisterNode) + reg = arguments[0] + return assemble_instruction(InstructionNode("pop", [reg]), temp_storage) + + +special_instructions = { + 'goto': goto_handler, + 'save': save_handler, + 'restore': restore_handler +} + +simple_instructions = simple_instructions + + +def assembly_directives(): + return {'ds', 'dc'} + + +def binary_handler(opcode: int, arguments: list): + assert_args(arguments, RegisterNode, RegisterNode) + data = bitstruct.pack("u4u2u2", opcode // 16, arguments[0].number, arguments[1].number) + return [BytesSegment(bytearray(data))] + + +def unary_handler(opcode: int, arguments: list): + assert_args(arguments, RegisterNode) + data = bitstruct.pack('u6u2', opcode // 4, arguments[0].number) + return [BytesSegment(bytearray(data))] + + +def zero_handler(opcode: int, arguments: list): + assert_args(arguments) + return [BytesSegment(bytearray([opcode]))] + + +def branch_handler(opcode: int, arguments: list): + assert_args(arguments, RelocatableExpressionNode) + arg = arguments[0] + + return [BytesSegment(bytearray([opcode])), OffsetExpressionSegment(arg)] + + +def long_handler(opcode: int, arguments: list): + assert_args(arguments, RelocatableExpressionNode) + arg = arguments[0] + + return [BytesSegment(bytearray([opcode])), LongExpressionSegment(arg)] + + +def ldsa_handler(opcode: int, arguments: list): + assert_args(arguments, RegisterNode, RelocatableExpressionNode) + reg, arg = arguments + cmd_piece = unary_handler(opcode, [reg])[0] + + return [BytesSegment(cmd_piece.data), ShortExpressionSegment(arg)] + + +def ldi_handler(opcode: int, arguments: list): + # check types + assert_args(arguments, RegisterNode, Union[RelocatableExpressionNode, str]) + reg, arg = arguments + cmd_piece = unary_handler(opcode, [reg])[0] + + if isinstance(arg, str): + arg_data = bytearray(arg, 'utf8') + if len(arg_data) != 1: + raise CdmTempException('Argument must be a string of length 1') + cmd_piece.data.extend(arg_data) + return [BytesSegment(cmd_piece.data)] + elif isinstance(arg, RelocatableExpressionNode): + return [BytesSegment(cmd_piece.data), ShortExpressionSegment(arg)] + + +def osix_handler(opcode: int, arguments: list): + assert_args(arguments, RelocatableExpressionNode) + arg = arguments[0] + + return [BytesSegment(bytearray([opcode])), ConstExpressionSegment(arg, positive=True)] + + +def spmove_handler(opcode: int, arguments: list): + assert_args(arguments, RelocatableExpressionNode) + arg = arguments[0] + + return [BytesSegment(bytearray([opcode])), ConstExpressionSegment(arg)] + + +def dc_handler(arguments: list): + assert_args(arguments, Union[RelocatableExpressionNode, str], single_type=True) + if len(arguments) == 0: + raise CdmTempException('At least one argument must be provided') + + segments = [] + for arg in arguments: + if isinstance(arg, str): + segments.append(BytesSegment(bytearray(arg, 'utf8'))) + elif isinstance(arg, RelocatableExpressionNode): + if arg.byte_specifier is None: + added_labels = list(filter(lambda t: isinstance(t, LabelNode), arg.add_terms)) + subtracted_labels = list(filter(lambda t: isinstance(t, LabelNode), arg.sub_terms)) + if len(added_labels) == len(subtracted_labels): + segments.append(ShortExpressionSegment(arg)) + else: + segments.append(LongExpressionSegment(arg)) + else: + segments.append(ShortExpressionSegment(arg)) + return segments + + +def ds_handler(arguments: list): + assert_args(arguments, RelocatableExpressionNode) + arg = arguments[0] + + if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: + raise CdmTempException('Number expected') + if arg.const_term < 0: + raise CdmTempException('Cannot specify negative size in "ds"') + return [BytesSegment(bytearray(arg.const_term))] + + +command_handlers = { + 'zero': zero_handler, + 'unary': unary_handler, + 'binary': binary_handler, + 'branch': branch_handler, + 'long': long_handler, + 'ldsa': ldsa_handler, + 'ldi': ldi_handler, + 'osix': osix_handler, + 'spmove': spmove_handler, + + 'dc': dc_handler, + 'ds': ds_handler, +} + +cpu_instructions = {} +assembler_directives = {} + + +def initialize(): + for category, instructions in simple_instructions.items(): + for mnemonic, opcode in instructions.items(): + cpu_instructions[mnemonic] = (opcode, command_handlers[category]) + + for directive in assembly_directives(): + assembler_directives[directive] = command_handlers[directive] + + +initialize() diff --git a/cocas/assembler/targets/target_instructions_protocol.py b/cocas/assembler/targets/target_instructions_protocol.py new file mode 100644 index 00000000..4d11009f --- /dev/null +++ b/cocas/assembler/targets/target_instructions_protocol.py @@ -0,0 +1,24 @@ +from typing import TYPE_CHECKING, Protocol, runtime_checkable + +from cocas.object_module import CodeLocation + +from .abstract_code_segments import ICodeSegment + +if TYPE_CHECKING: + from ..ast_nodes import InstructionNode + + +@runtime_checkable +class TargetInstructions(Protocol): + def assemble_instruction(self, line: "InstructionNode", temp_storage) -> list[ICodeSegment]: + ... + + def finish(self, temp_storage: dict): + ... + + def make_branch_instruction(self, location: CodeLocation, branch_mnemonic: str, label_name: str, inverse: bool) \ + -> list[ICodeSegment]: + ... + + def assembly_directives(self) -> set[str]: + ... diff --git a/cocas/error.py b/cocas/error.py deleted file mode 100644 index 51c3deb3..00000000 --- a/cocas/error.py +++ /dev/null @@ -1,62 +0,0 @@ -from enum import Enum -from typing import Union - -from antlr4.error.ErrorListener import ErrorListener -from colorama import Fore, Style - -from cocas.generated import AsmParser - - -class CdmExceptionTag(Enum): - MACRO = "Macro" - ASM = "Assembler" - OBJ = "Object files" - LINK = "Linker" - - -# this exception should be used when we don't know code location now, -# but this info is somewhere up the call stack -# it should be re-raised -# it s here to avoid except Exception -class CdmTempException(Exception): - def __init__(self, message: str): - self.message = message - - -class CdmLinkException(Exception): - def __init__(self, message: str): - self.message = message - - -class CdmException(Exception): - def __init__(self, tag: Union[str, CdmExceptionTag], file: str, line: int, description: str): - if isinstance(tag, CdmExceptionTag): - tag = tag.value - self.tag = tag - self.file = file - self.line = line - self.description = description - - def log(self): - print( - f'[{self.tag}] {Fore.RED}ERROR{Fore.RESET} at line {Style.BRIGHT}{self.line}{Style.RESET_ALL} of ' - f'{Style.BRIGHT}{self.file}{Style.RESET_ALL}') - print(f'{self.description}') - - -def log_error(tag: str, message: str): - print( - f'[{tag}] {Fore.RED}ERROR{Fore.RESET}') - print(message) - - -class AntlrErrorListener(ErrorListener): - def __init__(self, tag, file): - self.file = file - self.tag = tag - - def syntaxError(self, recognizer, offending_symbol, line, column, msg, e): - if isinstance(recognizer, AsmParser.AsmParser): - line = line - recognizer.current_offset - self.file = recognizer.current_file - raise CdmException(self.tag, self.file, line, msg) diff --git a/cocas/exception_handlers.py b/cocas/exception_handlers.py new file mode 100644 index 00000000..2988ec06 --- /dev/null +++ b/cocas/exception_handlers.py @@ -0,0 +1,36 @@ +from sys import stderr + +from colorama import Fore, Style + +from cocas.assembler import AssemblerException +from cocas.linker import LinkerException +from cocas.object_file import ObjectFileException + + +def log_error(tag: str, message: str): + print(f'[{tag}] {Fore.RED}ERROR{Fore.RESET}', file=stderr) + print(message, file=stderr) + + +def log_os_error(e: OSError): + message = e.strerror + if e.filename is not None: + message += f': {Style.BRIGHT}{e.filename}{Style.NORMAL}' + log_error("Main", message) + exit(1) + + +def log_asm_exception(e: AssemblerException): + print(f'[{e.tag.value}] {Fore.RED}ERROR{Fore.RESET} at {Style.BRIGHT}{e.file}{Style.RESET_ALL}, ' + f'line {Style.BRIGHT}{e.line}{Style.RESET_ALL}', file=stderr) + print(f'{e.description}', file=stderr) + + +def log_object_file_exception(e: ObjectFileException): + print(f'[Object file] {Fore.RED}ERROR{Fore.RESET} at {Style.BRIGHT}{e.file}{Style.RESET_ALL}, ' + f'line {Style.BRIGHT}{e.line}{Style.RESET_ALL}', file=stderr) + print(f'{e.description}', file=stderr) + + +def log_link_exception(e: LinkerException): + log_error("Linker", e.message) diff --git a/cocas/external_entry.py b/cocas/external_entry.py deleted file mode 100644 index dafbe338..00000000 --- a/cocas/external_entry.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass, field - - -@dataclass -class ExternalEntry: - offset: int - entry_bytes: range - sign: int = field(default=1) - full_bytes: bool = field(default=True) - - def __str__(self): - s = f'{self.sign * self.offset:02x}' - if not self.full_bytes: - s += f':{self.entry_bytes.start}:{self.entry_bytes.stop}' - return s - - def __repr__(self): - return str(self) - - def as_tuple(self): - return self.offset, self.entry_bytes, self.sign diff --git a/cocas/linker/__init__.py b/cocas/linker/__init__.py new file mode 100644 index 00000000..1da837de --- /dev/null +++ b/cocas/linker/__init__.py @@ -0,0 +1,7 @@ +"""Functions to link multiple object modules and write image and debug information to files""" + +from .debug_export import debug_export, write_debug_export +from .exceptions import LinkerException +from .image import write_image +from .linker import link, target_link +from .targets import list_linker_targets diff --git a/cocas/debug_export.py b/cocas/linker/debug_export.py similarity index 56% rename from cocas/debug_export.py rename to cocas/linker/debug_export.py index 22dfb63d..5b87ef8e 100644 --- a/cocas/debug_export.py +++ b/cocas/linker/debug_export.py @@ -1,8 +1,10 @@ import bisect import json import re +from pathlib import Path +from typing import Union -from cocas.location import CodeLocation +from cocas.object_module import CodeLocation def default_json_(o, files): @@ -28,9 +30,28 @@ def code_location_json(files, cl: CodeLocation): def debug_export(code_locations: dict[int, CodeLocation]) -> str: + """ + Convert debug information objects to json format + + :param code_locations: mapping from address in binary image to location in source code + :return: string with json representation of debug information, code locations are sorted + """ files = sorted(set(map(lambda x: x.file, code_locations.values()))) - dump = json.dumps({"files": files, "codeLocations": code_locations}, + sorted_cl = {key: value for (key, value) in sorted(code_locations.items())} + dump = json.dumps({"files": files, "codeLocations": sorted_cl}, default=default_json(files), indent=4, ensure_ascii=False) pattern = re.compile(r"{\n\s+\"__no_breaks_begin\": \[],\n\s+([\S\s]+?),\n\s+\"__no_breaks_end\": \[]\s+}") dump = re.sub(pattern, lambda m: "{" + re.sub(r"\n\s+", " ", m.group(1)) + "}", dump) return dump + + +def write_debug_export(filepath: Union[Path, str], code_locations: dict[int, CodeLocation]): + """ + Convert debug information objects to json format and write to file + + :param filepath: path to file to write + :param code_locations: mapping from address in binary image to location in source code + :return: json file with debug information, code locations are sorted + """ + with open(filepath, 'w') as f: + f.write(debug_export(code_locations)) diff --git a/cocas/linker/exceptions.py b/cocas/linker/exceptions.py new file mode 100644 index 00000000..8b44231a --- /dev/null +++ b/cocas/linker/exceptions.py @@ -0,0 +1,5 @@ +class LinkerException(Exception): + """Exception raised when sections cannot be correctly linked""" + + def __init__(self, message: str): + self.message = message diff --git a/cocas/linker/image.py b/cocas/linker/image.py new file mode 100644 index 00000000..1aecd333 --- /dev/null +++ b/cocas/linker/image.py @@ -0,0 +1,27 @@ +from pathlib import Path +from typing import Union + + +def write_image(filename: Union[Path, str], arr: bytearray): + """ + Write the contents or array into file in logisim-compatible format + + :param filename: path to output file + :param arr: bytearray to be written + """ + with open(filename, mode='w') as f: + f.write("v2.0 raw\n") + zeroes = 0 + for i, byte in enumerate(arr): + if byte == 0: + zeroes += 1 + else: + if zeroes != 0: + if zeroes > 4: + f.write(f'{zeroes}*00\n') + f.write(f'# {i:#2x}\n') + else: + for _ in range(zeroes): + f.write('00\n') + zeroes = 0 + f.write(f'{byte:02x}\n') diff --git a/cocas/linker.py b/cocas/linker/linker.py similarity index 64% rename from cocas/linker.py rename to cocas/linker/linker.py index 4c920dc4..774e65bc 100644 --- a/cocas/linker.py +++ b/cocas/linker/linker.py @@ -1,13 +1,14 @@ import itertools -from typing import Any +from math import inf +from typing import Any, Optional -from cocas.abstract_params import TargetParamsInterface -from cocas.error import CdmLinkException -from cocas.location import CodeLocation -from cocas.object_module import ExternalEntry, ObjectModule, ObjectSectionRecord +from cocas.object_module import CodeLocation, ObjectModule, ObjectSectionRecord +from .exceptions import LinkerException +from .targets import TargetParams, import_target -def init_bins(asects: list[ObjectSectionRecord]): + +def init_bins(asects: list[ObjectSectionRecord], image_size: Optional[int]): rsect_bins = [] last_bin_begin = 0 for i in range(len(asects)): @@ -20,16 +21,21 @@ def init_bins(asects: list[ObjectSectionRecord]): addr2 = asects[i].address len1 = len(asects[i - 1].data) len2 = len(asects[i].data) - raise CdmLinkException(f'Overlapping sections at {addr1} (size {len1}) and {addr2} (size {len2})') + raise LinkerException(f'Overlapping sections at {addr1} (size {len1}) and {addr2} (size {len2})') last_bin_begin = asects[i].address + len(asects[i].data) - - if last_bin_begin < 2 ** 16: - rsect_bins.append((last_bin_begin, 2 ** 16 - last_bin_begin)) + if image_size and last_bin_begin > image_size: + raise LinkerException(f'Absolute section at address {asects[i].address} (size {len(asects[i].data)}) ' + f'exceeds image size limit {image_size}') + if image_size: + if last_bin_begin < image_size: + rsect_bins.append((last_bin_begin, image_size - last_bin_begin)) + else: + rsect_bins.append((last_bin_begin, inf)) return rsect_bins -def place_sects(rsects: list[ObjectSectionRecord], rsect_bins: list): +def place_sects(rsects: list[ObjectSectionRecord], rsect_bins: list, image_size) -> dict[str, int]: sect_addresses = {'$abs': 0} for rsect in rsects: rsect_size = len(rsect.data) @@ -37,14 +43,14 @@ def place_sects(rsects: list[ObjectSectionRecord], rsect_bins: list): bin_begin, bin_size = rsect_bins[i] if bin_size >= rsect_size: address = (bin_begin + rsect.alignment - 1) // rsect.alignment * rsect.alignment - if address + rsect_size < bin_begin + bin_size: + if address + rsect_size <= bin_begin + bin_size: if rsect.name in sect_addresses: - raise CdmLinkException(f'Duplicate sections "{rsect.name}"') + raise LinkerException(f'Duplicate sections "{rsect.name}"') sect_addresses[rsect.name] = address rsect_bins[i] = (address + rsect_size, bin_size - rsect_size) break else: - raise CdmLinkException(f'Section "{rsect.name}" exceeds image size limit') + raise LinkerException(f'Section "{rsect.name}" exceeds image size limit {image_size}') return sect_addresses @@ -53,7 +59,7 @@ def gather_ents(sects: list[ObjectSectionRecord], sect_addresses: dict[str, int] for sect in sects: for ent_name in sect.entries: if ent_name in ents: - raise CdmLinkException(f'Duplicate entries "{ent_name}"') + raise LinkerException(f'Duplicate entries "{ent_name}"') ents[ent_name] = sect.entries[ent_name] + sect_addresses[sect.name] return ents @@ -82,7 +88,7 @@ def find_referenced_sects(exts_by_sect: dict[str, set[str]], sect_by_ent: dict[s if used_sects_queue[i] in exts_by_sect: for ext_name in exts_by_sect[used_sects_queue[i]]: if ext_name not in sect_by_ent: - raise CdmLinkException(f'Unresolved ext "{ext_name}"') + raise LinkerException(f'Unresolved ext "{ext_name}"') new_sect = sect_by_ent[ext_name] if new_sect not in used_sects: used_sects_queue.append(new_sect) @@ -91,9 +97,17 @@ def find_referenced_sects(exts_by_sect: dict[str, set[str]], sect_by_ent: dict[s return used_sects -def link(objects: list[tuple[Any, ObjectModule]], _: TargetParamsInterface): - asects = list(itertools.chain.from_iterable([obj.asects for _, obj in objects])) - rsects = list(itertools.chain.from_iterable([obj.rsects for _, obj in objects])) +def link(objects: list[tuple[Any, ObjectModule]], image_size: Optional[int] = None) -> \ + tuple[bytearray, dict[int, CodeLocation]]: + """ + Link object modules into one image + + :param objects: list of pairs (file path, object module) + :param image_size: maximum size of image for current target or None if no limit + :return: pair [bytearray of image data, mapping from image addresses to locations in source files] + """ + asects: list[ObjectSectionRecord] = list(itertools.chain.from_iterable([obj.asects for _, obj in objects])) + rsects: list[ObjectSectionRecord] = list(itertools.chain.from_iterable([obj.rsects for _, obj in objects])) exts_by_sect = find_exts_by_sect(asects + rsects) sect_by_ent = find_sect_by_ent(asects + rsects) @@ -103,8 +117,8 @@ def link(objects: list[tuple[Any, ObjectModule]], _: TargetParamsInterface): rsects.sort(key=lambda s: -len(s.data)) asects.sort(key=lambda s: s.address) - rsect_bins = init_bins(asects) - sect_addresses = place_sects(rsects, rsect_bins) + rsect_bins = init_bins(asects, image_size) + sect_addresses = place_sects(rsects, rsect_bins, image_size) ents = gather_ents(asects + rsects, sect_addresses) image = bytearray(2 ** 16) code_locations: dict[int, CodeLocation] = {} @@ -121,7 +135,7 @@ def link(objects: list[tuple[Any, ObjectModule]], _: TargetParamsInterface): image_end = image_begin + len(rsect.data) image[image_begin:image_end] = rsect.data entry_bytes: range - for offset, entry_bytes, sign in map(ExternalEntry.as_tuple, rsect.relative): + for offset, entry_bytes, sign in map(lambda x: x.as_tuple(), rsect.relocatable): pos = image_begin + offset lower_limit = 1 << 8 * entry_bytes.start val = int.from_bytes(image[pos:pos + len(entry_bytes)], 'little', signed=False) * lower_limit @@ -136,7 +150,7 @@ def link(objects: list[tuple[Any, ObjectModule]], _: TargetParamsInterface): for sect in asects + rsects: for ext_name in sect.external: - for offset, entry_bytes, sign in map(ExternalEntry.as_tuple, sect.external[ext_name]): + for offset, entry_bytes, sign in map(lambda x: x.as_tuple(), sect.external[ext_name]): pos = sect_addresses[sect.name] + offset lower_limit = 1 << 8 * entry_bytes.start val = int.from_bytes(image[pos:pos + len(entry_bytes)], 'little', signed=False) * lower_limit @@ -149,3 +163,16 @@ def link(objects: list[tuple[Any, ObjectModule]], _: TargetParamsInterface): sect.lower_parts[pos] = val % lower_limit return image, code_locations + + +def target_link(objects: list[tuple[Any, ObjectModule]], target_name: str) -> \ + tuple[bytearray, dict[int, CodeLocation]]: + """ + Link object modules with checking constraints for the target + + :param objects: list of pairs (file path, object module) + :param target_name: name of the target + :return: pair [bytearray of image data, mapping from image addresses to locations in source files] + """ + params: TargetParams = import_target(target_name) + return link(objects, params.image_size) diff --git a/cocas/linker/targets/__init__.py b/cocas/linker/targets/__init__.py new file mode 100644 index 00000000..50002767 --- /dev/null +++ b/cocas/linker/targets/__init__.py @@ -0,0 +1,22 @@ +import json +from dataclasses import dataclass +from pathlib import Path + + +def list_linker_targets() -> set[str]: + """Returns a set of supported linker targets. Takes files from of linker/target module""" + targets_dir = Path(__file__).parent.absolute() + targets = map(lambda x: x.name[:-5], targets_dir.glob("*.json")) + return set(targets) + + +@dataclass +class TargetParams: + name: str + image_size: int + + +def import_target(target: str) -> TargetParams: + path = Path(__file__).parent / (target + '.json') + with path.open('r') as f: + return json.load(f, object_hook=lambda d: TargetParams(**d)) diff --git a/cocas/linker/targets/cdm16.json b/cocas/linker/targets/cdm16.json new file mode 100644 index 00000000..7f2693b9 --- /dev/null +++ b/cocas/linker/targets/cdm16.json @@ -0,0 +1,4 @@ +{ + "name": "CdM-16", + "image_size": 65536 +} \ No newline at end of file diff --git a/cocas/linker/targets/cdm8.json b/cocas/linker/targets/cdm8.json new file mode 100644 index 00000000..b5ad53f5 --- /dev/null +++ b/cocas/linker/targets/cdm8.json @@ -0,0 +1,4 @@ +{ + "name": "CdM-8", + "image_size": 256 +} \ No newline at end of file diff --git a/cocas/linker/targets/cdm8e.json b/cocas/linker/targets/cdm8e.json new file mode 100644 index 00000000..1aa2f758 --- /dev/null +++ b/cocas/linker/targets/cdm8e.json @@ -0,0 +1,4 @@ +{ + "name": "CdM-8e", + "image_size": 65536 +} \ No newline at end of file diff --git a/cocas/location.py b/cocas/location.py deleted file mode 100644 index 38286d9f..00000000 --- a/cocas/location.py +++ /dev/null @@ -1,13 +0,0 @@ -from dataclasses import dataclass -from typing import Union - - -@dataclass -class CodeLocation: - """ - Store information about to which place in the source file the instruction belongs. - File is None if and only if there is no information for this line. - """ - file: Union[str, None] = None - line: int = 0 - column: int = 0 diff --git a/cocas/main.py b/cocas/main.py index 48ab1564..558db0b5 100755 --- a/cocas/main.py +++ b/cocas/main.py @@ -1,76 +1,37 @@ import argparse -import codecs -import importlib -import os -import pathlib -import pkgutil +import itertools +from pathlib import Path +from sys import stderr from typing import Union -import antlr4 import colorama -from cocas.assembler import assemble -from cocas.ast_builder import build_ast -from cocas.debug_export import debug_export -from cocas.error import CdmException, CdmExceptionTag, CdmLinkException, log_error -from cocas.linker import link -from cocas.macro_processor import process_macros, read_mlb -from cocas.object_file import import_object -from cocas.object_module import export_objects - - -def write_image(filename: str, arr: bytearray): - """ - Write the contents or array into file in logisim-compatible format - - :param filename: Path to output file - :param arr: Bytearray to be written - """ - f = open(filename, mode='wb') - f.write(bytes("v2.0 raw\n", 'UTF-8')) - zeroes = 0 - for i, byte in enumerate(arr): - if byte == 0: - zeroes += 1 - else: - if zeroes != 0: - if zeroes > 4: - f.write(bytes(f'{zeroes}*00\n', 'UTF-8')) - f.write(bytes(f'# {i:#2x}\n', 'UTF-8')) - else: - for _ in range(zeroes): - f.write(bytes('00\n', 'UTF-8')) - zeroes = 0 - f.write(bytes(f'{byte:02x}\n', 'UTF-8')) - f.close() - - -def handle_os_error(e: OSError): - message = e.strerror - if e.filename is not None: - message += f': {colorama.Style.BRIGHT}{e.filename}{colorama.Style.NORMAL}' - log_error("MAIN", message) - exit(1) +from cocas import exception_handlers as handlers +from cocas.assembler import AssemblerException, assemble_files, list_assembler_targets +from cocas.exception_handlers import log_error +from cocas.linker import LinkerException, list_linker_targets, target_link, write_debug_export, write_image +from cocas.object_file import ObjectFileException, list_object_targets, read_object_files, write_object_file +from cocas.object_module import ObjectModule def main(): colorama.init() - targets_dir = os.path.join(os.path.dirname(__file__), "targets") - available_targets = list(map(lambda i: i.name, pkgutil.iter_modules([targets_dir]))) + available_targets = list_assembler_targets() & list_object_targets() & list_linker_targets() parser = argparse.ArgumentParser('cocas') - parser.add_argument('sources', type=pathlib.Path, nargs='*', help='source files') - parser.add_argument('-t', '--target', type=str, default='cdm-16', help='target processor, CdM-16 is default') + parser.add_argument('sources', type=Path, nargs='*', help='source files') + parser.add_argument('-t', '--target', type=str, default='cdm-16', + help='target processor, CdM-16 is default. May omit "cdm"') parser.add_argument('-T', '--list-targets', action='count', help='list available targets and exit') parser.add_argument('-c', '--compile', action='store_true', help='compile into object files without linking') parser.add_argument('-m', '--merge', action='store_true', help='merge object files into one') - parser.add_argument('-o', '--output', type=str, help='specify output file name') + parser.add_argument('-o', '--output', type=Path, help='specify output file name') debug_group = parser.add_argument_group('debug') - debug_group.add_argument('--debug', type=str, nargs='?', const=True, help='export debug information') + debug_group.add_argument('--debug', type=Path, nargs='?', const=True, help='export debug information') debug_path_group = debug_group.add_mutually_exclusive_group() - debug_path_group.add_argument('--relative-path', type=pathlib.Path, + debug_path_group.add_argument('--relative-path', type=Path, help='convert source files paths to relative in debug info and object files') - debug_path_group.add_argument('--absolute-path', type=pathlib.Path, + debug_path_group.add_argument('--absolute-path', type=Path, help='convert all debug paths to absolute, concatenating with given path') debug_group.add_argument('--realpath', action='store_true', help='canonicalize paths by following symlinks and resolving . and ..') @@ -79,194 +40,95 @@ def main(): print('Available targets: ' + ', '.join(available_targets)) return target: str = args.target.replace('-', '').lower() + if target[:1].isdecimal(): + target = 'cdm' + target if target not in available_targets: - print('Error: unknown target ' + target) - print('Available targets: ' + ', '.join(available_targets)) + log_error("Main", 'Unknown target ' + target) + print('Available targets: ' + ', '.join(available_targets), file=stderr) return 2 if len(args.sources) == 0: - print('Error: no source files provided') + log_error("Main", 'No source files provided') return 2 if args.compile and args.merge: - print('Error: cannot use --compile and --merge options at same time') + log_error("Main", 'Cannot use --compile and --merge options at same time') return 2 - target_instructions = importlib.import_module(f'cocas.targets.{target}.target_instructions', - 'cocas').TargetInstructions - code_segments = importlib.import_module(f'cocas.targets.{target}.code_segments', 'cocas').CodeSegments - target_params = importlib.import_module(f'cocas.targets.{target}.target_params', 'cocas').TargetParams - - library_macros = read_mlb(str(pathlib.Path(__file__).parent.joinpath(f'targets/{target}/standard.mlb').absolute())) - objects = [] - realpath = bool(args.realpath) if args.relative_path: - relative_path: Union[pathlib.Path, None] = args.relative_path.absolute() + relative_path: Union[Path, None] = args.relative_path.absolute() if realpath: relative_path = relative_path.resolve() else: relative_path = None if args.absolute_path: - absolute_path: Union[pathlib.Path, None] = args.absolute_path.absolute() + absolute_path: Union[Path, None] = args.absolute_path.absolute() if realpath: absolute_path = absolute_path.resolve() else: absolute_path = None - filepath: pathlib.Path + asm_files = [] + obj_files = [] + filepath: Path for filepath in args.sources: - try: - with filepath.open('rb') as file: - data = file.read() - except OSError as e: - handle_os_error(e) - data = codecs.decode(data, 'utf8', 'strict') - if not data.endswith('\n'): - data += '\n' - - try: - filetype = filepath.suffix - if not filetype: - if args.merge: - filetype = '.obj' - else: - filetype = '.asm' - - if filetype in ['.obj', '.lib', ]: - if args.compile: - print("Error: object files should not be provided with --compile option") - return 2 - input_stream = antlr4.InputStream(data) - for obj in import_object(input_stream, str(filepath), target_params): - if realpath: - dip = obj.debug_info_path - obj.debug_info_path = obj.debug_info_path.resolve() - for i in obj.asects + obj.rsects: - for j in i.code_locations.values(): - f = pathlib.Path(j.file) - if f == dip: - j.file = obj.debug_info_path.as_posix() - else: - j.file = pathlib.Path(j.file).resolve().as_posix() - if relative_path: - dip = obj.debug_info_path - if obj.debug_info_path.is_absolute(): - try: - obj.debug_info_path = obj.debug_info_path.absolute().relative_to(relative_path) - except ValueError as e: - print('Error:', e) - return 2 - for i in obj.asects + obj.rsects: - for j in i.code_locations.values(): - f = pathlib.Path(j.file) - if f == dip: - j.file = obj.debug_info_path.as_posix() - else: - if f.is_absolute(): - try: - j.file = f.absolute().relative_to(relative_path).as_posix() - except ValueError as e: - print('Error:', e) - return 2 - elif absolute_path: - obj.debug_info_path = absolute_path / obj.debug_info_path - if realpath: - obj.debug_info_path = obj.debug_info_path.resolve() - for i in obj.asects + obj.rsects: - for j in i.code_locations.values(): - f = absolute_path / pathlib.Path(j.file) - j.file = f.as_posix() - objects.append((filepath, obj)) - else: # filetype == '.asm' - if args.merge: - print("Error: source files should not be provided with --merge option") - return 2 - if args.debug: - debug_info_path = filepath.expanduser().absolute() - if realpath: - debug_info_path = debug_info_path.resolve() - if relative_path: - try: - debug_info_path = debug_info_path.relative_to(relative_path) - except ValueError as e: - print('Error:', e) - return 2 - else: - debug_info_path = None - input_stream = antlr4.InputStream(data) - macro_expanded_input_stream = process_macros(input_stream, library_macros, str(filepath)) - r = build_ast(macro_expanded_input_stream, str(filepath)) - obj = assemble(r, target_instructions, code_segments, debug_info_path) - if debug_info_path: - fp = filepath.as_posix() - dip = debug_info_path.as_posix() - for i in obj.asects + obj.rsects: - for j in i.code_locations.values(): - if j.file == fp: - j.file = dip - else: - try: - f = pathlib.Path(j.file).absolute() - if realpath: - f = f.resolve() - j.file = f.relative_to(relative_path).as_posix() - except ValueError as e: - print('Error:', e) - return 2 - objects.append((filepath, obj)) - except CdmException as e: - e.log() - return 1 - - if args.merge or args.compile and args.output: - if args.output: - obj_path = pathlib.Path(args.output) + filetype = filepath.suffix or args.merge and '.obj' or '.asm' + if filetype in ['.obj', '.lib', ]: + obj_files.append(filepath) else: - obj_path = pathlib.Path('merged.obj') - lines = export_objects([tup[1] for tup in objects], target_params, (args.debug or args.merge)) - try: - with obj_path.open('w') as file: - file.writelines(lines) - except OSError as e: - handle_os_error(e) - elif args.compile: - for path, obj in objects: - obj_path = path.with_suffix('.obj').name - lines = export_objects([obj], target_params, args.debug) - try: - with open(obj_path, 'w') as file: - file.writelines(lines) - except OSError as e: - handle_os_error(e) - else: - try: - data, code_locations = link(objects, target_params) - except CdmLinkException as e: - log_error(CdmExceptionTag.LINK.value, e.message) - return 1 - try: + asm_files.append(filepath) + if asm_files and args.merge: + log_error("Main", 'Source files should not be provided with --merge option') + return 2 + if obj_files and args.compile: + log_error("Main", 'Object files should not be provided with --compile option') + return 2 + objects: list[tuple[Path, ObjectModule]] + try: + objects: list[tuple[Path, ObjectModule]] = list(itertools.chain( + assemble_files(target, asm_files, bool(args.debug), relative_path, absolute_path, realpath), + read_object_files(target, obj_files, bool(args.debug), relative_path, absolute_path, realpath) + )) + except AssemblerException as e: + handlers.log_asm_exception(e) + return 1 + except ObjectFileException as e: + handlers.log_object_file_exception(e) + return 1 + except OSError as e: + handlers.log_os_error(e) + return 3 + + try: + if args.merge: + write_object_file('merged.obj', [tup[1] for tup in objects], target, (args.debug or args.merge)) + elif args.compile and args.output: + write_object_file(args.output, [tup[1] for tup in objects], target, args.debug) + elif args.compile: + for path, obj in objects: + write_object_file(path.with_suffix('.obj').name, [obj], target, args.debug) + else: + # data, code_locations = link(objects) + data, code_locations = target_link(objects, target) if args.output: write_image(args.output, data) else: write_image("out.img", data) - except OSError as e: - handle_os_error(e) - - if args.debug: - if args.debug is True: - if args.output: - filename = pathlib.Path(args.output).with_suffix('.dbg.json') + if args.debug: + if args.debug is True: + if args.output: + filename = Path(args.output).with_suffix('.dbg.json') + else: + filename = Path('out.dbg.json') else: - filename = 'out.dbg.json' - else: - filename = args.debug - code_locations = {key: value for (key, value) in sorted(code_locations.items())} - debug_info = debug_export(code_locations) - try: - with open(filename, 'w') as f: - f.write(debug_info) - except OSError as e: - handle_os_error(e) + filename = args.debug + write_debug_export(filename, code_locations) + except LinkerException as e: + handlers.log_link_exception(e) + return 1 + except OSError as e: + handlers.log_os_error(e) + return 3 if __name__ == '__main__': diff --git a/cocas/object_file/__init__.py b/cocas/object_file/__init__.py new file mode 100644 index 00000000..d23d1dcf --- /dev/null +++ b/cocas/object_file/__init__.py @@ -0,0 +1,6 @@ +"""Convert between object modules and object file format""" + +from .exceptions import ObjectFileException +from .object_export import export_object, write_object_file +from .object_import import import_object, read_object_files +from .targets import list_object_targets diff --git a/cocas/object_file/exceptions.py b/cocas/object_file/exceptions.py new file mode 100644 index 00000000..97ce7141 --- /dev/null +++ b/cocas/object_file/exceptions.py @@ -0,0 +1,18 @@ +from antlr4.error.ErrorListener import ErrorListener + + +class ObjectFileException(Exception): + """Exception raised when given object file is invalid""" + + def __init__(self, file: str, line: int, description: str): + self.file = file + self.line = line + self.description = description + + +class AntlrErrorListener(ErrorListener): + def __init__(self, file): + self.file = file + + def syntaxError(self, recognizer, offending_symbol, line, column, msg, e): + raise ObjectFileException(self.file, line, msg) diff --git a/cocas/generated/ObjectFileLexer.py b/cocas/object_file/generated/ObjectFileLexer.py similarity index 98% rename from cocas/generated/ObjectFileLexer.py rename to cocas/object_file/generated/ObjectFileLexer.py index 5ba4ae3a..e71fd2f5 100644 --- a/cocas/generated/ObjectFileLexer.py +++ b/cocas/object_file/generated/ObjectFileLexer.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/ObjectFileLexer.g4 by ANTLR 4.13.1 +# Generated from object_file/grammar/ObjectFileLexer.g4 by ANTLR 4.13.1 from antlr4 import * from io import StringIO import sys diff --git a/cocas/generated/ObjectFileParser.py b/cocas/object_file/generated/ObjectFileParser.py similarity index 99% rename from cocas/generated/ObjectFileParser.py rename to cocas/object_file/generated/ObjectFileParser.py index fa10f17e..6cfca085 100644 --- a/cocas/generated/ObjectFileParser.py +++ b/cocas/object_file/generated/ObjectFileParser.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/ObjectFileParser.g4 by ANTLR 4.13.1 +# Generated from object_file/grammar/ObjectFileParser.g4 by ANTLR 4.13.1 # encoding: utf-8 from antlr4 import * from io import StringIO diff --git a/cocas/generated/ObjectFileParserVisitor.py b/cocas/object_file/generated/ObjectFileParserVisitor.py similarity index 98% rename from cocas/generated/ObjectFileParserVisitor.py rename to cocas/object_file/generated/ObjectFileParserVisitor.py index fa0c9760..698be135 100644 --- a/cocas/generated/ObjectFileParserVisitor.py +++ b/cocas/object_file/generated/ObjectFileParserVisitor.py @@ -1,4 +1,4 @@ -# Generated from ./grammar/ObjectFileParser.g4 by ANTLR 4.13.1 +# Generated from object_file/grammar/ObjectFileParser.g4 by ANTLR 4.13.1 from antlr4 import * if "." in __name__: from .ObjectFileParser import ObjectFileParser diff --git a/cocas/object_file/generated/__init__.py b/cocas/object_file/generated/__init__.py new file mode 100644 index 00000000..5c5f02c7 --- /dev/null +++ b/cocas/object_file/generated/__init__.py @@ -0,0 +1,3 @@ +from .ObjectFileLexer import ObjectFileLexer +from .ObjectFileParser import ObjectFileParser +from .ObjectFileParserVisitor import ObjectFileParserVisitor diff --git a/cocas/grammar/ObjectFileLexer.g4 b/cocas/object_file/grammar/ObjectFileLexer.g4 similarity index 100% rename from cocas/grammar/ObjectFileLexer.g4 rename to cocas/object_file/grammar/ObjectFileLexer.g4 diff --git a/cocas/grammar/ObjectFileParser.g4 b/cocas/object_file/grammar/ObjectFileParser.g4 similarity index 100% rename from cocas/grammar/ObjectFileParser.g4 rename to cocas/object_file/grammar/ObjectFileParser.g4 diff --git a/cocas/object_module.py b/cocas/object_file/object_export.py similarity index 56% rename from cocas/object_module.py rename to cocas/object_file/object_export.py index 5b9cad31..2b6923a7 100644 --- a/cocas/object_module.py +++ b/cocas/object_file/object_export.py @@ -1,44 +1,10 @@ -import pathlib from collections import defaultdict -from dataclasses import dataclass from pathlib import Path from typing import Union -from cocas.abstract_params import TargetParamsInterface -from cocas.code_block import Section -from cocas.external_entry import ExternalEntry -from cocas.location import CodeLocation - - -class ObjectSectionRecord: - def __init__(self, name: str, address: int, data: bytearray, - entries: dict[str, int], relative: list[ExternalEntry], - code_locations: dict[int, CodeLocation], alignment=1): - self.name = name - self.address = address - self.data = data - self.entries = entries - self.relative = relative - self.code_locations = code_locations - self.alignment = alignment - self.external: defaultdict[str, list[ExternalEntry]] = defaultdict(list) - self.lower_parts: dict[int, int] = dict() - - @classmethod - def from_section(cls, section: Section, labels: dict[str, int], templates: dict[str, dict[str, int]]): - entries = dict(p for p in section.labels.items() if p[0] in section.ents) - out = cls(section.name, section.address, bytearray(), entries, [], section.code_locations) - for seg in section.segments: - seg.fill(out, section, labels, templates) - return out - - -@dataclass -class ObjectModule: - def __init__(self, debug_info_path: Union[Path, None]): - self.debug_info_path: pathlib.Path = debug_info_path - self.asects: list[ObjectSectionRecord] = [] - self.rsects: list[ObjectSectionRecord] = [] +from cocas.object_module import CodeLocation, ExternalEntry, ObjectModule + +from .targets import TargetParams, import_target def data_to_str(array: bytearray): @@ -71,23 +37,24 @@ def export_code_locations(cl: dict[int, CodeLocation]) -> list[str]: return res -def export_objects(objs: list[ObjectModule], target_params: TargetParamsInterface, debug: bool) -> list[str]: +def export_object(objs: list[ObjectModule], target: str, debug: bool) -> list[str]: """ Export multiple object modules in object file format :param objs: objects to export - :param target_params: information about selected target + :param target: name of selected processor target, must be valid :param debug: if needed to export debug information :return: list of strings of object file, ended by new line """ + target_params: TargetParams = import_target(target) result = [] - if target_params.object_file_header(): - result.append(f'TARG {target_params.object_file_header()}\n') + if target_params.header: + result.append(f'TARG {target_params.header}\n') for obj in objs: if len(objs) > 1: result.append('\n') - if debug and obj.debug_info_path: - result.append(f'FILE {Path(obj.debug_info_path).as_posix()}\n') + if debug and obj.source_file_path: + result.append(f'FILE {Path(obj.source_file_path).as_posix()}\n') for asect in obj.asects: s = data_to_str(asect.data) @@ -99,13 +66,13 @@ def export_objects(objs: list[ObjectModule], target_params: TargetParamsInterfac result.append(f'NTRY {label} {address:02x}\n') for rsect in obj.rsects: result.append(f'NAME {rsect.name}\n') - if rsect.alignment != target_params.default_alignment(): + if rsect.alignment != target_params.default_alignment: result.append(f'ALIG {rsect.alignment:02x}\n') s = data_to_str(rsect.data) result.append(f'DATA {s}\n') if debug: result += export_code_locations(rsect.code_locations) - result.append(f'REL {" ".join(map(str, rsect.relative))}\n') + result.append(f'REL {" ".join(map(str, rsect.relocatable))}\n') for label, address in rsect.entries.items(): result.append(f'NTRY {label} {address:02x}\n') external = defaultdict(list) @@ -117,3 +84,18 @@ def export_objects(objs: list[ObjectModule], target_params: TargetParamsInterfac result.append(f'XTRN {label}: {" ".join(map(sect_entry_to_str, entries))}\n') pass return result + + +def write_object_file(filepath: Union[Path, str], objs: list[ObjectModule], target: str, debug: bool): + """ + Export and write to a file a group of object modules + + :param filepath: path to the output object file + :param objs: objects to export + :param target: name of selected processor target, must be valid + :param debug: if needed to export debug information + :return: list of strings of object file, ended by new line + """ + lines = export_object(objs, target, debug) + with open(filepath, 'w') as file: + file.writelines(lines) diff --git a/cocas/object_file.py b/cocas/object_file/object_import.py similarity index 57% rename from cocas/object_file.py rename to cocas/object_file/object_import.py index 19d89e67..08571321 100644 --- a/cocas/object_file.py +++ b/cocas/object_file/object_import.py @@ -1,40 +1,39 @@ import bisect +import codecs from pathlib import Path -from typing import List +from typing import Optional, Union +import antlr4 from antlr4 import CommonTokenStream, InputStream -from cocas.abstract_params import TargetParamsInterface -from cocas.error import AntlrErrorListener, CdmException, CdmExceptionTag -from cocas.external_entry import ExternalEntry -from cocas.generated.ObjectFileLexer import ObjectFileLexer -from cocas.generated.ObjectFileParser import ObjectFileParser -from cocas.generated.ObjectFileParserVisitor import ObjectFileParserVisitor -from cocas.location import CodeLocation -from cocas.object_module import ObjectModule, ObjectSectionRecord +from cocas.object_module import CodeLocation, ExternalEntry, ObjectModule, ObjectSectionRecord + +from .exceptions import AntlrErrorListener, ObjectFileException +from .generated import ObjectFileLexer, ObjectFileParser, ObjectFileParserVisitor +from .targets import TargetParams, import_target class ImportObjectFileVisitor(ObjectFileParserVisitor): - def __init__(self, filepath, target_params: TargetParamsInterface): + def __init__(self, filepath, target: str): super().__init__() self.file = filepath - self.target_params = target_params + self.target_params: TargetParams = import_target(target) def visitObject_file(self, ctx: ObjectFileParser.Object_fileContext) -> list[ObjectModule]: - exp_header = self.target_params.object_file_header() - target_name = self.target_params.name() + exp_header = self.target_params.header + target_name = self.target_params.name if ctx.targ_record(): header = self.visitTarg_record(ctx.targ_record()) if header != exp_header: if exp_header: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Wrong target header {header}, expected {exp_header}') + raise ObjectFileException(self.file, ctx.start.line, + f'Wrong target header {header}, expected {exp_header}') else: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Expected no header for {target_name} target, got {header}') + raise ObjectFileException(self.file, ctx.start.line, + f'Expected no header for {target_name} target, got {header}') elif exp_header: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Expected non-empty target header for {target_name}, got empty') + raise ObjectFileException(self.file, ctx.start.line, + f'Expected non-empty target header for {target_name}, got empty') modules = [] for i in ctx.object_block(): @@ -56,8 +55,7 @@ def visitObject_block(self, ctx: ObjectFileParser.Object_blockContext) -> Object for block in ctx.rsect_block(): name, rsect = self.visitRsect_block(block) if name in rsects: - raise CdmException(CdmExceptionTag.OBJ, self.file, block.start.line, - f'Repeating section: {name}') + raise ObjectFileException(self.file, block.start.line, f'Repeating section: {name}') rsects[name] = rsect xtrn: ObjectFileParser.Xtrn_recordContext @@ -66,28 +64,25 @@ def visitObject_block(self, ctx: ObjectFileParser.Object_blockContext) -> Object for sect, entry in entries: if sect == '$abs': if not asects: - raise CdmException(CdmExceptionTag.OBJ, self.file, xtrn.start.line, - 'No absolute sections found, but needed for xtrn entry') + raise ObjectFileException(self.file, xtrn.start.line, + 'No absolute sections found, but needed for xtrn entry') # what is this? ind = max(bisect.bisect_right(asect_addr, entry.offset) - 1, 0) asects[asect_addr[ind]].external[label].append(entry) elif sect in rsects: rsects[sect].external[label].append(entry) else: - raise CdmException(CdmExceptionTag.OBJ, self.file, xtrn.start.line, - f'Section not found: {sect}') + raise ObjectFileException(self.file, xtrn.start.line, f'Section not found: {sect}') if filename: f = Path(filename) - om = ObjectModule(f) for i in (asects | rsects).values(): for j in i.code_locations.values(): j.file = f.as_posix() + om = ObjectModule(list(asects.values()), list(rsects.values()), f) else: - om = ObjectModule(None) for i in (asects | rsects).values(): i.code_locations = {} - om.asects = list(asects.values()) - om.rsects = list(rsects.values()) + om = ObjectModule(list(asects.values()), list(rsects.values()), None) return om def visitAsect_block(self, ctx: ObjectFileParser.Asect_blockContext): @@ -114,7 +109,7 @@ def visitRsect_block(self, ctx: ObjectFileParser.Rsect_blockContext): if ctx.alig_record(): align = self.visitAlig_record(ctx.alig_record()) else: - align = self.target_params.default_alignment() + align = self.target_params.default_alignment data = self.visitData_record(ctx.data_record()) if ctx.rel_record(): rel = self.visitRel_record(ctx.rel_record()) @@ -175,11 +170,9 @@ def parse_byte(self, byte: str, ctx: ObjectFileParser.DataContext): try: value = int(byte, 16) except ValueError: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Not a hex number: {byte}') + raise ObjectFileException(self.file, ctx.start.line, f'Not a hex number: {byte}') if not 0 <= value <= 255: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'To big hex number: {byte}, expected byte') + raise ObjectFileException(self.file, ctx.start.line, f'To big hex number: {byte}, expected byte') return value def visitData(self, ctx: ObjectFileParser.DataContext): @@ -189,15 +182,13 @@ def visitAbs_address(self, ctx: ObjectFileParser.Abs_addressContext): try: return int(ctx.getText(), 16) except ValueError: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Not a hex number: {ctx.getText()}') + raise ObjectFileException(self.file, ctx.start.line, f'Not a hex number: {ctx.getText()}') def visitNumber(self, ctx: ObjectFileParser.NumberContext): try: return int(ctx.getText(), 16) except ValueError: - raise CdmException(CdmExceptionTag.OBJ, self.file, ctx.start.line, - f'Not a hex number: {ctx.getText()}') + raise ObjectFileException(self.file, ctx.start.line, f'Not a hex number: {ctx.getText()}') def visitEntry_usage(self, ctx: ObjectFileParser.Entry_usageContext): addr = self.visitNumber(ctx.number()) @@ -205,7 +196,7 @@ def visitEntry_usage(self, ctx: ObjectFileParser.Entry_usageContext): if ctx.range_(): return ExternalEntry(addr, self.visitRange(ctx.range_()), sign, False) else: - entry_size = self.target_params.max_entry_size() + entry_size = self.target_params.max_entry_size return ExternalEntry(addr, range(0, entry_size), sign, True) def visitRange(self, ctx: ObjectFileParser.RangeContext): @@ -225,17 +216,87 @@ def visitMinus(self, ctx: ObjectFileParser.MinusContext): pass -def import_object(input_stream: InputStream, filepath: str, - target_params: TargetParamsInterface) -> List[ObjectModule]: +def import_object(input_stream: InputStream, filepath: Path, target: str) -> list[ObjectModule]: + """ + Open multiple object files and create object modules + + :param input_stream: contents of file + :param filepath: path of the file to use in error handling + :param target: name of processor target + :return: list of object modules contained in object file + """ + str_path = filepath.absolute().as_posix() lexer = ObjectFileLexer(input_stream) lexer.removeErrorListeners() - lexer.addErrorListener(AntlrErrorListener(CdmExceptionTag.OBJ, filepath)) + lexer.addErrorListener(AntlrErrorListener(str_path)) token_stream = CommonTokenStream(lexer) token_stream.fill() parser = ObjectFileParser(token_stream) parser.removeErrorListeners() - parser.addErrorListener(AntlrErrorListener(CdmExceptionTag.OBJ, filepath)) + parser.addErrorListener(AntlrErrorListener(str_path)) ctx = parser.object_file() - visitor = ImportObjectFileVisitor(filepath, target_params) + visitor = ImportObjectFileVisitor(str_path, target) result = visitor.visit(ctx) return result + + +def read_object_files(target: str, + files: list[Union[str, Path]], + debug: bool, + relative_path: Optional[Path], + absolute_path: Optional[Path], + realpath: bool) -> list[tuple[Path, ObjectModule]]: + """ + Open multiple object files and create object modules + + :param target: name of processor target + :param files: list of object files' paths to process + :param debug: if debug information should be exported + :param relative_path: if debug paths should be converted to relative to some path + :param absolute_path: if relative paths should be converted to absolute + :param realpath: if paths should be converted to canonical + :return: list of pairs [object file path, object module] + """ + _ = debug + objects = [] + for filepath in files: + with open(filepath, 'rb') as file: + data = file.read() + data = codecs.decode(data, 'utf8', 'strict') + if not data.endswith('\n'): + data += '\n' + + input_stream = antlr4.InputStream(data) + for obj in import_object(input_stream, Path(filepath), target): + if realpath: + dip = obj.source_file_path + obj.source_file_path = obj.source_file_path.resolve() + for i in obj.asects + obj.rsects: + for j in i.code_locations.values(): + f = Path(j.file) + if f == dip: + j.file = obj.source_file_path.as_posix() + else: + j.file = Path(j.file).resolve().as_posix() + if relative_path: + dip = obj.source_file_path + if obj.source_file_path.is_absolute(): + obj.source_file_path = obj.source_file_path.absolute().relative_to(relative_path) + for i in obj.asects + obj.rsects: + for j in i.code_locations.values(): + f = Path(j.file) + if f == dip: + j.file = obj.source_file_path.as_posix() + else: + if f.is_absolute(): + j.file = f.absolute().relative_to(relative_path).as_posix() + elif absolute_path: + obj.source_file_path = absolute_path / obj.source_file_path + if realpath: + obj.source_file_path = obj.source_file_path.resolve() + for i in obj.asects + obj.rsects: + for j in i.code_locations.values(): + f = absolute_path / Path(j.file) + j.file = f.as_posix() + objects.append((filepath, obj)) + return objects diff --git a/cocas/object_file/targets/__init__.py b/cocas/object_file/targets/__init__.py new file mode 100644 index 00000000..12ff51e7 --- /dev/null +++ b/cocas/object_file/targets/__init__.py @@ -0,0 +1,24 @@ +import json +from dataclasses import dataclass +from pathlib import Path + + +def list_object_targets() -> set[str]: + """Returns a set of supported object file targets. Takes files from of object_file/target module""" + targets_dir = Path(__file__).parent.absolute() + targets = map(lambda x: x.name[:-5], targets_dir.glob("*.json")) + return set(targets) + + +@dataclass +class TargetParams: + name: str + header: str + default_alignment: int + max_entry_size: int + + +def import_target(target: str) -> TargetParams: + path = Path(__file__).parent / (target + '.json') + with path.open('r') as f: + return json.load(f, object_hook=lambda d: TargetParams(**d)) diff --git a/cocas/object_file/targets/cdm16.json b/cocas/object_file/targets/cdm16.json new file mode 100644 index 00000000..4b1ef858 --- /dev/null +++ b/cocas/object_file/targets/cdm16.json @@ -0,0 +1,6 @@ +{ + "name": "CdM-16", + "header": "CDM16", + "default_alignment": 2, + "max_entry_size": 2 +} \ No newline at end of file diff --git a/cocas/object_file/targets/cdm8.json b/cocas/object_file/targets/cdm8.json new file mode 100644 index 00000000..863a75c6 --- /dev/null +++ b/cocas/object_file/targets/cdm8.json @@ -0,0 +1,6 @@ +{ + "name": "CdM-8", + "header": "", + "default_alignment": 1, + "max_entry_size": 1 +} \ No newline at end of file diff --git a/cocas/object_file/targets/cdm8e.json b/cocas/object_file/targets/cdm8e.json new file mode 100644 index 00000000..695dad69 --- /dev/null +++ b/cocas/object_file/targets/cdm8e.json @@ -0,0 +1,6 @@ +{ + "name": "CdM-8e", + "header": "CDM8E", + "default_alignment": 1, + "max_entry_size": 2 +} \ No newline at end of file diff --git a/cocas/object_module/__init__.py b/cocas/object_module/__init__.py new file mode 100644 index 00000000..ba508a97 --- /dev/null +++ b/cocas/object_module/__init__.py @@ -0,0 +1,5 @@ +"""This module contains structures of universal object code and debug information that is used in all other modules""" + +from .external_entry import ExternalEntry +from .location import CodeLocation +from .object_module import ObjectModule, ObjectSectionRecord diff --git a/cocas/object_module/external_entry.py b/cocas/object_module/external_entry.py new file mode 100644 index 00000000..2d28207b --- /dev/null +++ b/cocas/object_module/external_entry.py @@ -0,0 +1,28 @@ +from dataclasses import dataclass, field + + +@dataclass +class ExternalEntry: + """Describes where and how some unknown at compile time value will be placed over the binary image. + Takes certain bytes of teh image as a number (maybe also from `lower_parts`) and adds the value. + Used for external and relocatable entries.""" + offset: int + """Position from start of the section where some value will be placed""" + entry_bytes: range + """Selection of bytes from binary representation of the added value""" + sign: int = field(default=1) + """Should value be added or subtracted (not tested with -1)""" + full_bytes: bool = field(default=True) + """Whether no bytes were excluded by entry_bytes. Used when exporting this to object file""" + + def __str__(self): + s = f'{self.sign * self.offset:02x}' + if not self.full_bytes: + s += f':{self.entry_bytes.start}:{self.entry_bytes.stop}' + return s + + def __repr__(self): + return str(self) + + def as_tuple(self): + return self.offset, self.entry_bytes, self.sign diff --git a/cocas/object_module/location.py b/cocas/object_module/location.py new file mode 100644 index 00000000..9273e566 --- /dev/null +++ b/cocas/object_module/location.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass +from typing import Union + + +@dataclass +class CodeLocation: + """Store information about to which place in the source file the instruction belongs.""" + file: Union[str, None] = None + """Name of the source file that generated this instruction. None if unknown""" + line: int = 0 + column: int = 0 diff --git a/cocas/object_module/object_module.py b/cocas/object_module/object_module.py new file mode 100644 index 00000000..3fb1c3b8 --- /dev/null +++ b/cocas/object_module/object_module.py @@ -0,0 +1,44 @@ +from collections import defaultdict +from dataclasses import dataclass, field +from pathlib import Path +from typing import Union + +from .external_entry import ExternalEntry +from .location import CodeLocation + + +@dataclass +class ObjectSectionRecord: + """Structure that represents object code and debug information of a section""" + name: str + """Name of the relocatable section or '$abs' if it is absolute""" + address: int + """Address of the section. 0 for every relocatable section""" + data: bytearray + """Compiled binary image of that section before linking""" + entries: dict[str, int] + """Exported labels of this section and their addresses""" + relocatable: list[ExternalEntry] + """Places where the address of this relocatable section should be added""" + code_locations: dict[int, CodeLocation] + """Mapping between addresses in binary image and locations in the source file""" + alignment: int = field(default=1) + """If the relocatable section should get address that is a multiple of some number""" + + external: defaultdict[str, list[ExternalEntry]] = field(default_factory=lambda: defaultdict(list)) + """List of places in section where some external label is used""" + lower_parts: dict[int, int] = field(default_factory=dict) + """If there is an external record with some least significant bytes dropped, these + least significant bytes of a constant value are saved to check for possible overflows. + The key in dict equals the offset of the entry (i.e. the beginning of the entry)""" + + +@dataclass +class ObjectModule: + """Object code representation of one translation unit (source file) with multiple sections, and debug information""" + asects: list[ObjectSectionRecord] + """Absolute sections""" + rsects: list[ObjectSectionRecord] + """Relocatable sections""" + source_file_path: Union[Path, None] = field(default=None) + """Path to the source files that is stated in object file FILE record""" diff --git a/cocas/targets/cdm16/__init__.py b/cocas/targets/cdm16/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cocas/targets/cdm16/code_segments.py b/cocas/targets/cdm16/code_segments.py deleted file mode 100644 index 9db3b992..00000000 --- a/cocas/targets/cdm16/code_segments.py +++ /dev/null @@ -1,307 +0,0 @@ -from dataclasses import dataclass, field -from typing import Optional - -import bitstruct - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.ast_nodes import LabelNode, RegisterNode, RelocatableExpressionNode, TemplateFieldNode -from cocas.code_block import Section -from cocas.error import CdmException, CdmExceptionTag -from cocas.location import CodeLocation -from cocas.object_module import ExternalEntry, ObjectSectionRecord - - -def pack(fmt, *args): - b = bitstruct.pack(fmt, *args) - return bitstruct.byteswap('2', b) - - -def _error(segment: CodeSegmentsInterface.CodeSegment, message: str): - raise CdmException(CdmExceptionTag.ASM, segment.location.file, segment.location.line, message) - - -class CodeSegments(CodeSegmentsInterface): - @dataclass - class CodeSegment(CodeSegmentsInterface.CodeSegment): - pass - - class VaryingLengthSegment(CodeSegmentsInterface.VaryingLengthSegment, CodeSegment): - pass - - class AlignmentPaddingSegment(CodeSegmentsInterface.AlignmentPaddingSegment, CodeSegment): - pass - - class AlignedSegment(CodeSegmentsInterface.AlignedSegment, CodeSegment): - pass - - class InstructionSegment(AlignedSegment): - def __init__(self, location: CodeLocation): - super().__init__(2) - self.location = location - - class BytesSegment(CodeSegment): - data: bytes - - def __init__(self, data: bytes, location: CodeLocation): - self.location = location - self.data = data - self.size = len(data) - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - object_record.data += self.data - - class InstructionBytesSegment(InstructionSegment, BytesSegment): - def __init__(self, data: bytes, location: CodeLocation): - CodeSegments.InstructionSegment.__init__(self, location) - CodeSegments.BytesSegment.__init__(self, data, location) - - class ExpressionSegment(CodeSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, expr: RelocatableExpressionNode): - self.location = location - self.expr = expr - self.size = 2 - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - CodeSegments.forbid_multilabel_expressions(parsed, self) - value = CodeSegments.calculate_expression(parsed, section, labels) - offset = section.address + len(object_record.data) - if not -32768 <= value < 65536: - _error(self, 'Number out of range') - object_record.data.extend((value % 65536).to_bytes(2, 'little')) - CodeSegments.add_relatives_externals(parsed, offset, object_record) - - class LdiSegment(InstructionSegment, VaryingLengthSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, register: RegisterNode, expr: RelocatableExpressionNode): - CodeSegments.InstructionSegment.__init__(self, location) - self.reg: int = register.number - self.expr = expr - self.size = 2 - self.size_locked = False - self.checked = False - self.parsed: Optional[CodeSegments.ParsedExpression] = None - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - if self.size == 4: - object_record.data.extend(pack("u3p6u4u3", 0b001, 2, self.reg)) - CodeSegments.ExpressionSegment(self.location, self.expr).fill(object_record, section, labels, templates) - else: - value = CodeSegments.calculate_expression(self.parsed, section, labels) - object_record.data.extend(pack("u3u3s7u3", 0b011, 5, value, self.reg)) - - def update_varying_length(self, pos, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - if self.size_locked: - return - bad = False - if not self.checked: - self.parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - CodeSegments.forbid_multilabel_expressions(self.parsed, self) - bad = self.parsed.external or self.parsed.relative_additions != 0 - self.checked = True - value = CodeSegments.calculate_expression(self.parsed, section, labels) - if bad or not -64 <= value < 64: - self.size = 4 - self.size_locked = True - self.__class__.update_surroundings(2, pos, section, labels) - return 2 - - class Branch(InstructionSegment, VaryingLengthSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, branch_code: int, expr: RelocatableExpressionNode, - operation='branch'): - CodeSegments.InstructionSegment.__init__(self, location) - self.type = operation - self.branch_code = branch_code - self.expr = expr - self.size = 2 - self.size_locked = False - self.checked = False - self.parsed: Optional[CodeSegments.ParsedExpression] = None - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - value = CodeSegments.calculate_expression(self.parsed, section, labels) - if value % 2 != 0: - _error(self, "Destination address must be 2-byte aligned") - if self.size == 4: - if self.type == 'branch': - object_record.data.extend(pack("u5p7u4", 0x00001, self.branch_code)) - elif self.type == 'jsr': - object_record.data.extend(pack("u5p7u4", 0x00000, 8)) - CodeSegments.ExpressionSegment(self.location, self.expr).fill(object_record, section, labels, templates) - else: - dist = value - (section.address + len(object_record.data) + 2) - if self.type == 'branch': - val = dist // 2 % 512 - sign = 0 if dist < 0 else 1 - object_record.data.extend(pack("u2u1u4u9", 0b11, sign, self.branch_code, val)) - elif self.type == 'jsr': - val = dist // 2 % 1024 - object_record.data.extend(pack("u3u3u10", 0b100, 3, val)) - - def update_varying_length(self, pos, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - if self.size_locked: - return - bad = False - if not self.checked: - self.parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - if self.expr.sub_terms: - _error(self, 'Cannot subtract labels in branch value expressions') - elif len(self.expr.add_terms) > 1: - _error(self, 'Cannot use multiple labels in branch value expressions') - const = not self.expr.add_terms and not self.expr.sub_terms - bad = self.parsed.external or (self.parsed.asect or const) and section.name != '$abs' - self.checked = True - value = CodeSegments.calculate_expression(self.parsed, section, labels) - dist = value - pos - 2 - if bad or not -1024 <= dist < 1024: - self.size = 4 - self.size_locked = True - self.__class__.update_surroundings(2, pos, section, labels) - return 2 - - class Imm6(InstructionSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, negative: bool, op_number: int, register: RegisterNode, - expr: RelocatableExpressionNode, word=False): - super().__init__(location) - self.word = word - self.op_number = op_number - self.sign = -1 if negative else 1 - self.reg: int = register.number - self.expr = expr - self.size = 2 - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - value = CodeSegments.calculate_expression(parsed, section, labels) * self.sign - if parsed.external: - _error(self, 'No external labels allowed in immediate form') - elif parsed.relative_additions != 0: - _error(self, 'Can use rsect labels only to find distance in immediate form') - if self.word: - if value % 2 != 0: - _error(self, "Destination address must be 2-byte aligned") - value //= 2 - if not -64 <= value < 64: - _error(self, 'Value is out of bounds for immediate form') - object_record.data.extend(pack("u3u3s7u3", 0b011, self.op_number, value, self.reg)) - - class Imm9(InstructionSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, negative: bool, op_number: int, expr: RelocatableExpressionNode): - super().__init__(location) - self.op_number = op_number - self.sign = -1 if negative else 1 - self.expr = expr - self.size = 2 - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - value = CodeSegments.calculate_expression(parsed, section, labels) * self.sign - if parsed.external: - _error(self, 'No external labels allowed in immediate form') - elif parsed.relative_additions != 0: - _error(self, 'Can use rsect labels only to find distance in immediate form') - elif not -512 <= value < 512: - _error(self, 'Value is out of bounds for immediate form') - object_record.data.extend(pack("u3u3s10", 0b100, self.op_number, value)) - - @dataclass - class ParsedExpression: - value: int - relative_additions: int = field(default=0) - asect: dict[str, int] = field(default_factory=dict) - relative: dict[str, int] = field(default_factory=dict) - external: dict[str, int] = field(default_factory=dict) - - @staticmethod - def parse_expression(expr: RelocatableExpressionNode, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]], segment: CodeSegment) -> ParsedExpression: - if expr.byte_specifier is not None: - _error(segment, 'No byte specifiers allowed in CdM-16') - result = CodeSegments.ParsedExpression(expr.const_term) - for term, sign in [(t, 1) for t in expr.add_terms] + [(t, -1) for t in expr.sub_terms]: - if isinstance(term, LabelNode): - if term.name in section.exts: - result.external[term.name] = result.external.get(term.name, 0) + sign - elif term.name in section.labels and section.name != '$abs': - result.relative[term.name] = result.relative.get(term.name, 0) + sign - elif term.name in section.labels: - result.asect[term.name] = result.asect.get(term.name, 0) + sign - elif term.name in labels: - result.asect[term.name] = result.asect.get(term.name, 0) + sign - else: - _error(segment, f'Label "{term.name}" not found') - elif isinstance(term, TemplateFieldNode): - result.value += templates[term.template_name][term.field_name] * sign - for label, n in result.relative.items(): - result.relative_additions += n - result.asect = {label: n for label, n in result.asect.items() if n != 0} - result.external = {label: n for label, n in result.external.items() if n != 0} - result.relative = {label: n for label, n in result.relative.items() if n != 0} - return result - - @staticmethod - def calculate_expression(parsed: ParsedExpression, section: Section, labels: dict[str, int]) -> int: - value = parsed.value - for label, n in parsed.asect.items(): - if label in section.labels: - value += section.labels[label] * n - else: - value += labels[label] * n - for label, n in parsed.relative.items(): - rel_address = section.labels[label] - value += rel_address * n - return value - - @staticmethod - def forbid_multilabel_expressions(parsed: ParsedExpression, segment: CodeSegment): - if len(parsed.external) > 1: - _error(segment, 'Cannot use multiple external labels in an address expression') - elif len(parsed.external) == 1: - label, n = next(iter(parsed.external.items())) - if n < 0: - _error(segment, 'Cannot subtract external labels in an address expression') - elif n > 1: - _error(segment, 'Cannot add external label multiple times in an address expression') - elif parsed.relative_additions != 0: - _error(segment, 'Cannot add both external and relative section labels') - elif parsed.relative_additions < 0: - _error(segment, 'Can subtract rsect labels only to get distance from another added rsect label') - elif parsed.relative_additions > 1: - _error(segment, 'Can add rsect labels multiple times only to find distance ' - 'from another subtracted rsect label') - - @staticmethod - def add_relatives_externals(parsed: ParsedExpression, offset: int, object_record: ObjectSectionRecord): - for label in parsed.external: - if parsed.external[label] != 0: - entry = object_record.external.setdefault(label, []) - n = abs(parsed.external[label]) - sign = parsed.external[label] // n - for i in range(n): - entry.append(ExternalEntry(offset, range(0, 2), sign)) - if parsed.relative_additions != 0: - sign = parsed.relative_additions // abs(parsed.relative_additions) - for i in range(abs(parsed.relative_additions)): - object_record.relative.append(ExternalEntry(offset, range(0, 2), sign)) diff --git a/cocas/targets/cdm16/target_instructions.py b/cocas/targets/cdm16/target_instructions.py deleted file mode 100644 index 8978636d..00000000 --- a/cocas/targets/cdm16/target_instructions.py +++ /dev/null @@ -1,405 +0,0 @@ -import re -from copy import copy -from dataclasses import dataclass -from typing import Callable, Union, get_args, get_origin - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.abstract_instructions import TargetInstructionsInterface -from cocas.ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode -from cocas.error import CdmException, CdmExceptionTag, CdmTempException - -from .code_segments import CodeSegments, pack - - -def assert_args(args, *types): - ts = [((t,) if get_origin(t) is None else get_args(t)) for t in types] - for i in range(len(args)): - for j in ts[i]: - if isinstance(args[i], j): - break - else: - raise CdmTempException(f'Incompatible argument type {type(args[i])}') - if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 7: - raise CdmTempException(f'Invalid register number r{args[i].number}') - - -def assert_count_args(args, *types): - if len(args) != len(types): - raise CdmTempException(f'Expected {len(types)} arguments, found {len(args)}') - assert_args(args, *types) - - -def handle_frame_pointer(line: InstructionNode): - for i in range(len(line.arguments)): - arg = line.arguments[i] - if isinstance(arg, RelocatableExpressionNode): - if not arg.const_term and not arg.sub_terms and not arg.byte_specifier and len(arg.add_terms) == 1 \ - and isinstance(arg.add_terms[0], LabelNode) and arg.add_terms[0].name == 'fp': - line.arguments[i] = RegisterNode(7) - - -class TargetInstructions(TargetInstructionsInterface): - @staticmethod - def assemble_instruction(line: InstructionNode, temp_storage: dict) -> list[CodeSegmentsInterface.CodeSegment]: - handle_frame_pointer(line) - try: - for h in TargetInstructions.handlers: - if line.mnemonic in h.instructions: - return h.handler(line, temp_storage, h.instructions[line.mnemonic]) - if line.mnemonic.startswith('b'): - return TargetInstructions.branch(line) - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, - f'Unknown instruction "{line.mnemonic}"') - except CdmTempException as e: - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, e.message) - - @staticmethod - def finish(temp_storage: dict): - if len(temp_storage.get("save_restore_stack", [])) != 0: - raise CdmTempException("Expected restore statement") - - @staticmethod - def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ - -> list[CodeSegmentsInterface.CodeSegment]: - instruction = InstructionNode('b' + branch_mnemonic, - [RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0)]) - instruction.location = location - return TargetInstructions.branch(instruction, inverse) - - @staticmethod - def ds(line: InstructionNode, _, __): - assert_args(line.arguments, RelocatableExpressionNode) - arg = line.arguments[0] - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Const number expected') - if arg.const_term < 0: - raise CdmTempException('Cannot specify negative size in "ds"') - return [CodeSegments.BytesSegment(bytes(arg.const_term), line.location)] - - @staticmethod - def dc(line: InstructionNode, _, __): - if len(line.arguments) == 0: - raise CdmTempException('At least one argument must be provided') - segments = [] - size = 0 - command = line.mnemonic - for arg in line.arguments: - if isinstance(arg, RelocatableExpressionNode): - if command == 'db': - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Only constants allowed for 1 byte') - if -128 < arg.const_term < 256: - segments.append(CodeSegments.BytesSegment((arg.const_term % 256).to_bytes(1, 'little'), - line.location)) - size += 2 - else: - raise CdmTempException(f'Number is not a byte: {arg.const_term}') - else: - segments.append(CodeSegments.ExpressionSegment(line.location, arg)) - elif isinstance(arg, str): - if command == 'dw': - raise CdmTempException('Currently "dw" doesn\'t support strings') - encoded = arg.encode('utf-8') - segments.append(CodeSegments.BytesSegment(encoded, line.location)) - size += len(encoded) - else: - raise CdmTempException(f'Incompatible argument type: {type(arg)}') - return segments - - @staticmethod - def align(line: InstructionNode, _, __): - if len(line.arguments) > 0: - assert_args(line.arguments, RelocatableExpressionNode) - arg: RelocatableExpressionNode = line.arguments[0] - if arg.add_terms or arg.sub_terms: - raise CdmTempException('Const number expected') - alignment = arg.const_term - else: - alignment = 2 - if alignment <= 0: - raise CdmTempException('Alignment should be positive') - elif alignment == 1: - return [] - return [CodeSegments.AlignmentPaddingSegment(alignment, line.location)] - - @staticmethod - def save(line: InstructionNode, temp_storage: dict, __) -> list[CodeSegmentsInterface.CodeSegment]: - assert_args(line.arguments, RegisterNode) - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - save_restore_stack.append(line.arguments[0]) - temp_storage["save_restore_stack"] = save_restore_stack - return TargetInstructions.assemble_instruction(InstructionNode("push", [line.arguments[0]]), temp_storage) - - @staticmethod - def restore(line: InstructionNode, temp_storage: dict, __) -> list[CodeSegmentsInterface.CodeSegment]: - assert_args(line.arguments, RegisterNode) - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - if len(save_restore_stack) == 0: - raise CdmTempException("Every restore statement must be preceded by a save statement") - reg = save_restore_stack.pop() - if len(line.arguments) > 0: - assert_args(line.arguments, RegisterNode) - reg = line.arguments[0] - return TargetInstructions.assemble_instruction(InstructionNode("pop", [reg]), temp_storage) - - @staticmethod - def ldi(line: InstructionNode, _, __) -> list[CodeSegmentsInterface.CodeSegment]: - assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) - return [CodeSegments.LdiSegment(line.location, *line.arguments)] - - @dataclass - class BranchCode: - condition: list[str] - code: int - inverse: list[str] - inv_code: int - - branch_codes: list[BranchCode] = [BranchCode(['eq', 'z'], 0, ['ne', 'nz'], 1), - BranchCode(['hs', 'cs'], 2, ['lo', 'cc'], 3), - BranchCode(['mi'], 4, ['pl'], 5), - BranchCode(['vs'], 6, ['vc'], 7), - BranchCode(['hi'], 8, ['ls'], 9), - BranchCode(['ge'], 10, ['lt'], 11), - BranchCode(['gt'], 12, ['le'], 13), - BranchCode(['anything', 'true', 'r'], 14, ['false'], 15)] - - @staticmethod - def branch(line: InstructionNode, inverse=False) -> list[CodeSegmentsInterface.CodeSegment]: - cond = re.match(r'b(\w*)', line.mnemonic)[1] - for pair in TargetInstructions.branch_codes: - if cond in pair.condition: - branch_code = pair.code if not inverse else pair.inv_code - break - elif cond in pair.inverse: - branch_code = pair.inv_code if not inverse else pair.code - break - else: - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, - f'Invalid branch condition: {cond}') - assert_count_args(line.arguments, RelocatableExpressionNode) - return [CodeSegments.Branch(line.location, branch_code, line.arguments[0])] - - @staticmethod - def op0(line: InstructionNode, _, op_number: int): - assert_count_args(line.arguments) - return [CodeSegments.InstructionBytesSegment(pack("u5p7u4", 0b00000, op_number), line.location)] - - @staticmethod - def const_only(arg: RelocatableExpressionNode): - if arg.add_terms or arg.sub_terms: - raise CdmTempException('Constant number expected as shift value') - return arg.const_term - - @staticmethod - def shifts(line: InstructionNode, _, op_number: int): - args = line.arguments - if len(args) == 3: - assert_args(args, RegisterNode, RegisterNode, RelocatableExpressionNode) - rs = args[0].number - rd = args[1].number - val = TargetInstructions.const_only(args[2]) - elif len(args) == 2 and isinstance(args[1], RegisterNode): - assert_args(args, RegisterNode, RegisterNode) - rs = args[0].number - rd = args[1].number - val = 1 - elif len(args) == 2: - assert_args(args, RegisterNode, RelocatableExpressionNode) - rs = args[0].number - rd = args[0].number - val = TargetInstructions.const_only(args[1]) - elif len(args) == 1: - assert_args(args, RegisterNode) - rs = args[0].number - rd = args[0].number - val = 1 - else: - raise CdmTempException(f'Expected 1-3 arguments, found {len(args)}') - if not 0 <= val <= 8: - raise CdmTempException('Shift value out of range') - if val == 0: - return [] - return [ - CodeSegments.InstructionBytesSegment(pack("u4u3u3u3u3", 0b0001, op_number, val - 1, rs, rd), line.location)] - - @staticmethod - def op1(line: InstructionNode, _, op_number: int): - assert_count_args(line.arguments, RegisterNode) - reg = line.arguments[0].number - return [CodeSegments.InstructionBytesSegment(pack("u3p6u4u3", 0b001, op_number, reg), line.location)] - - @staticmethod - def op2(line: InstructionNode, _, op_number: int): - assert_count_args(line.arguments, RegisterNode, RegisterNode) - rs = line.arguments[0].number - rd = line.arguments[1].number - return [CodeSegments.InstructionBytesSegment(pack("u5p1u4u3u3", 0b01000, op_number, rs, rd), line.location)] - - @staticmethod - def alu3_ind(line: InstructionNode, _, op_number: int): - assert_count_args(line.arguments, RegisterNode, RegisterNode) - rs = line.arguments[0].number - rd = line.arguments[1].number - return [CodeSegments.InstructionBytesSegment(pack("u5p2u3u3u3", 0b01001, op_number, rs, rd), line.location)] - - @staticmethod - def mem(line: InstructionNode, _, op_number: int): - if len(line.arguments) == 2: - assert_args(line.arguments, RegisterNode, RegisterNode) - addr1 = line.arguments[0].number - arg = line.arguments[1].number - return [ - CodeSegments.InstructionBytesSegment(pack("u5p1u4u3u3", 0b01010, op_number, addr1, arg), line.location)] - elif len(line.arguments) == 3: - assert_args(line.arguments, RegisterNode, RegisterNode, RegisterNode) - addr1 = line.arguments[0].number - addr2 = line.arguments[1].number - arg = line.arguments[2].number - return [CodeSegments.InstructionBytesSegment(pack("u4u3u3u3u3", 0b1010, op_number, addr1, addr2, arg), - line.location)] - else: - raise CdmTempException(f'Expected 2 or 3 arguments, found {len(line.arguments)}') - - @staticmethod - def alu2(line: InstructionNode, _, op_number: int): - if len(line.arguments) == 2: - assert_args(line.arguments, RegisterNode, RegisterNode) - rd = line.arguments[1].number - elif len(line.arguments) == 1: - assert_args(line.arguments, RegisterNode) - rd = line.arguments[0].number - else: - raise CdmTempException(f'Expected 1 or 2 arguments, found {len(line.arguments)}') - rs = line.arguments[0].number - return [CodeSegments.InstructionBytesSegment(pack("u5p2u3u3u3", 0b01011, op_number, rs, rd), line.location)] - - @staticmethod - def imm6(line: InstructionNode, _, op_number: int) -> list[CodeSegmentsInterface.CodeSegment]: - assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) - return [CodeSegments.Imm6(line.location, False, op_number, *line.arguments)] - - @staticmethod - def imm6_word(line: InstructionNode, _, op_number: int) -> list[CodeSegmentsInterface.CodeSegment]: - assert_count_args(line.arguments, RegisterNode, RelocatableExpressionNode) - return [CodeSegments.Imm6(line.location, False, op_number, *line.arguments, word=True)] - - @staticmethod - def alu3(line: InstructionNode, _, op_number: int): - if len(line.arguments) == 3: - assert_args(line.arguments, RegisterNode, RegisterNode, RegisterNode) - arg1 = line.arguments[0].number - arg2 = line.arguments[1].number - dest = line.arguments[2].number - return [CodeSegments.InstructionBytesSegment(pack("u4u3u3u3u3", 0b1011, op_number, arg2, arg1, dest), - line.location)] - elif len(line.arguments) == 2: - assert_args(line.arguments, RegisterNode, RegisterNode) - arg1 = line.arguments[0].number - arg2 = line.arguments[1].number - return [CodeSegments.InstructionBytesSegment(pack("u4u3u3u3u3", 0b1011, op_number, arg2, arg1, arg2), - line.location)] - else: - raise CdmTempException(f'Expected 2 or 3 arguments, found {len(line.arguments)}') - - @staticmethod - def special(line: InstructionNode, temp_storage: dict, _): - if line.mnemonic == 'add': - if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): - assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) - return [CodeSegments.Imm6(line.location, False, 6, *line.arguments)] - else: - return TargetInstructions.alu3(line, temp_storage, 4) - elif line.mnemonic == 'sub': - if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): - assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) - return [CodeSegments.Imm6(line.location, True, 6, *line.arguments)] - else: - return TargetInstructions.alu3(line, temp_storage, 6) - elif line.mnemonic == 'cmp': - if len(line.arguments) == 2 and isinstance(line.arguments[1], RelocatableExpressionNode): - return [CodeSegments.Imm6(line.location, False, 7, *line.arguments)] - else: - return TargetInstructions.alu3_ind(line, temp_storage, 6) - elif line.mnemonic == 'int': - assert_count_args(line.arguments, RelocatableExpressionNode) - arg = line.arguments[0] - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Const number expected') - if arg.const_term < 0: - raise CdmTempException('Interrupt number must be not negative') - return [CodeSegments.InstructionBytesSegment(pack("u3u4s9", 0b100, 0, arg.const_term), line.location)] - elif line.mnemonic == 'reset': - if len(line.arguments) == 0: - arg = RelocatableExpressionNode(None, [], [], 0) - elif len(line.arguments) == 1: - assert_args(line.arguments, RelocatableExpressionNode) - arg = line.arguments[0] - else: - raise CdmTempException(f'Expected 0 or 1 arguments, found {len(line.arguments)}') - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Const number expected') - if arg.const_term < 0: - raise CdmTempException('Vector number must be not negative') - return [CodeSegments.InstructionBytesSegment(pack("u3u4s9", 0b100, 1, arg.const_term), line.location)] - elif line.mnemonic == 'addsp': - assert_count_args(line.arguments, Union[RelocatableExpressionNode, RegisterNode]) - if isinstance(line.arguments[0], RelocatableExpressionNode): - arg = copy(line.arguments[0]) - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Const number expected') - if arg.const_term % 2 == 1: - raise CdmTempException('Only even numbers can be added to stack pointer') - arg.const_term //= 2 - return [CodeSegments.Imm9(line.location, False, 2, arg)] - else: - reg = line.arguments[0].number - return [CodeSegments.InstructionBytesSegment(pack("u3p6u4u3", 0b001, 10, reg), line.location)] - elif line.mnemonic == 'jsr': - if len(line.arguments) == 1 and isinstance(line.arguments[0], RegisterNode): - jsrr = copy(line) - jsrr.mnemonic = 'jsrr' - return TargetInstructions.assemble_instruction(jsrr, temp_storage) - assert_count_args(line.arguments, RelocatableExpressionNode) - return [CodeSegments.Branch(line.location, 0, line.arguments[0], operation='jsr')] - elif line.mnemonic == 'push': - assert_count_args(line.arguments, Union[RegisterNode, RelocatableExpressionNode]) - if isinstance(line.arguments[0], RegisterNode): - reg = line.arguments[0].number - return [CodeSegments.InstructionBytesSegment(pack("u3p6u4u3", 0b001, 0, reg), line.location)] - else: - return [CodeSegments.Imm9(line.location, False, 1, *line.arguments)] - - @dataclass - class Handler: - handler: Callable[[InstructionNode, dict, int], list[CodeSegmentsInterface.CodeSegment]] - instructions: dict[str, int] - - handlers: list[Handler] - - handlers = [ - Handler(ds, {'ds': -1}), - Handler(dc, {'dc': -1, 'db': -1, 'dw': -1}), - Handler(align, {'align': -1}), - Handler(save, {'save': -1}), - Handler(restore, {'restore': -1}), - Handler(ldi, {'ldi': -1}), - Handler(op0, {'halt': 4, 'wait': 5, 'ei': 6, 'di': 7, 'rti': 9, - 'pupc': 10, 'popc': 11, 'pusp': 12, 'posp': 13, 'pups': 14, 'pops': 15}), - Handler(shifts, {'shl': 0, 'shr': 1, 'shra': 2, 'rol': 3, 'ror': 4, 'rcl': 5, 'rcr': 6}), - Handler(op1, {'pop': 1, 'jsrr': 3, 'ldsp': 4, 'stsp': 5, - 'ldps': 6, 'stps': 7, 'ldpc': 8, 'stpc': 9}), - Handler(op2, {'move': 0}), - Handler(alu3_ind, {'bit': 0}), - Handler(mem, {'ldw': 0, 'ldb': 1, 'ldsb': 2, 'lcw': 3, 'lcb': 4, 'lcsb': 5, 'stw': 6, 'stb': 7}), - Handler(alu2, {'neg': 0, 'not': 1, 'sxt': 2, 'scl': 3}), - Handler(imm6, {'lsb': 1, 'lssb': 2, 'ssb': 4}), - Handler(imm6_word, {'lsw': 0, 'ssw': 3}), - Handler(alu3, {'and': 0, 'or': 1, 'xor': 2, 'bic': 3, 'addc': 5, 'subc': 7}), - Handler(special, {'add': -1, 'sub': -1, 'cmp': -1, 'int': -1, 'reset': -1, 'addsp': -1, 'jsr': -1, 'push': -1}) - ] - - @staticmethod - def assembly_directives(): - return {'ds', 'dc', 'db', 'dw'} diff --git a/cocas/targets/cdm16/target_params.py b/cocas/targets/cdm16/target_params.py deleted file mode 100644 index b0d6e496..00000000 --- a/cocas/targets/cdm16/target_params.py +++ /dev/null @@ -1,19 +0,0 @@ -from cocas.abstract_params import TargetParamsInterface - - -class TargetParams(TargetParamsInterface): - @staticmethod - def name(): - return 'CdM-16' - - @staticmethod - def max_entry_size() -> int: - return 2 - - @staticmethod - def default_alignment() -> int: - return 2 - - @staticmethod - def object_file_header() -> str: - return 'CDM16' diff --git a/cocas/targets/cdm8/__init__.py b/cocas/targets/cdm8/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cocas/targets/cdm8/code_segments.py b/cocas/targets/cdm8/code_segments.py deleted file mode 100644 index 0dbdb231..00000000 --- a/cocas/targets/cdm8/code_segments.py +++ /dev/null @@ -1,135 +0,0 @@ -from dataclasses import dataclass, field - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.ast_nodes import LabelNode, RelocatableExpressionNode, TemplateFieldNode -from cocas.code_block import Section -from cocas.error import CdmException, CdmExceptionTag -from cocas.location import CodeLocation -from cocas.object_module import ExternalEntry, ObjectSectionRecord - - -def _error(segment: CodeSegmentsInterface.CodeSegment, message: str): - raise CdmException(CdmExceptionTag.ASM, segment.location.file, segment.location.line, message) - - -# noinspection DuplicatedCode -class CodeSegments(CodeSegmentsInterface): - @dataclass - class CodeSegment(CodeSegmentsInterface.CodeSegment): - pass - - class AlignmentPaddingSegment(CodeSegmentsInterface.AlignmentPaddingSegment, CodeSegment): - pass - - class BytesSegment(CodeSegment): - data: bytes - - def __init__(self, data: bytes, location: CodeLocation): - self.location = location - self.data = data - self.size = len(data) - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - super().fill(object_record, section, labels, templates) - object_record.data += self.data - - class ExpressionSegment(CodeSegment): - expr: RelocatableExpressionNode - - def __init__(self, location: CodeLocation, expr: RelocatableExpressionNode): - self.location = location - self.expr = expr - self.size = 1 - - def fill(self, object_record: ObjectSectionRecord, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - parsed = CodeSegments.parse_expression(self.expr, section, labels, templates, self) - CodeSegments.forbid_multilabel_expressions(parsed, self) - value = CodeSegments.calculate_expression(parsed, section, labels) - offset = section.address + len(object_record.data) - if not -128 <= value < 256: - _error(self, 'Number out of range') - object_record.data.extend((value % 256).to_bytes(1, 'little')) - CodeSegments.add_relatives_externals(parsed, offset, object_record) - - # noinspection DuplicatedCode - @dataclass - class ParsedExpression: - value: int - relative_additions: int = field(default=0) - asect: dict[str, int] = field(default_factory=dict) - relative: dict[str, int] = field(default_factory=dict) - external: dict[str, int] = field(default_factory=dict) - - @staticmethod - def parse_expression(expr: RelocatableExpressionNode, section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]], segment: CodeSegment) -> ParsedExpression: - if expr.byte_specifier is not None: - _error(segment, 'No byte specifiers allowed in CdM-8') - result = CodeSegments.ParsedExpression(expr.const_term) - for term, sign in [(t, 1) for t in expr.add_terms] + [(t, -1) for t in expr.sub_terms]: - if isinstance(term, LabelNode): - if term.name in section.exts: - result.external[term.name] = result.external.get(term.name, 0) + sign - elif term.name in section.labels and section.name != '$abs': - result.relative[term.name] = result.relative.get(term.name, 0) + sign - elif term.name in section.labels: - result.asect[term.name] = result.asect.get(term.name, 0) + sign - elif term.name in labels: - result.asect[term.name] = result.asect.get(term.name, 0) + sign - else: - _error(segment, f'Label "{term.name}" not found') - elif isinstance(term, TemplateFieldNode): - result.value += templates[term.template_name][term.field_name] * sign - for label, n in result.relative.items(): - result.relative_additions += n - result.asect = {label: n for label, n in result.asect.items() if n != 0} - result.external = {label: n for label, n in result.external.items() if n != 0} - result.relative = {label: n for label, n in result.relative.items() if n != 0} - return result - - @staticmethod - def calculate_expression(parsed: ParsedExpression, section: Section, labels: dict[str, int]) -> int: - value = parsed.value - for label, n in parsed.asect.items(): - if label in section.labels: - value += section.labels[label] * n - else: - value += labels[label] * n - for label, n in parsed.relative.items(): - rel_address = section.labels[label] - value += rel_address * n - return value - - @staticmethod - def forbid_multilabel_expressions(parsed: ParsedExpression, segment: CodeSegment): - if len(parsed.external) > 1: - _error(segment, 'Cannot use multiple external labels in an address expression') - elif len(parsed.external) == 1: - label, n = next(iter(parsed.external.items())) - if n < 0: - _error(segment, 'Cannot subtract external labels in an address expression') - elif n > 1: - _error(segment, 'Cannot add external label multiple times in an address expression') - elif parsed.relative_additions != 0: - _error(segment, 'Cannot add both external and relative section labels') - elif parsed.relative_additions < 0: - _error(segment, 'Can subtract rsect labels only to get distance from another added rsect label') - elif parsed.relative_additions > 1: - _error(segment, 'Can add rsect labels multiple times only to find distance ' - 'from another subtracted rsect label') - - @staticmethod - def add_relatives_externals(parsed: ParsedExpression, offset: int, object_record: ObjectSectionRecord): - for label in parsed.external: - if parsed.external[label] != 0: - entry = object_record.external.setdefault(label, []) - n = abs(parsed.external[label]) - sign = parsed.external[label] // n - for i in range(n): - entry.append(ExternalEntry(offset, range(0, 1), sign)) - if parsed.relative_additions != 0: - sign = parsed.relative_additions // abs(parsed.relative_additions) - for i in range(abs(parsed.relative_additions)): - object_record.relative.append(ExternalEntry(offset, range(0, 1), sign)) diff --git a/cocas/targets/cdm8/target_instructions.py b/cocas/targets/cdm8/target_instructions.py deleted file mode 100644 index abab00fd..00000000 --- a/cocas/targets/cdm8/target_instructions.py +++ /dev/null @@ -1,257 +0,0 @@ -import re -from dataclasses import dataclass -from typing import Callable, get_args, get_origin - -from bitstruct import pack - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.abstract_instructions import TargetInstructionsInterface -from cocas.ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode -from cocas.error import CdmException, CdmExceptionTag, CdmTempException - -from .code_segments import CodeSegments - - -# noinspection DuplicatedCode -def assert_args(args, *types): - ts = [((t,) if get_origin(t) is None else get_args(t)) for t in types] - for i in range(len(args)): - for j in ts[i]: - if isinstance(args[i], j): - break - else: - raise CdmTempException(f'Incompatible argument type {type(args[i])}') - if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 7: - raise CdmTempException(f'Invalid register number r{args[i].number}') - - -def assert_count_args(args, *types): - if len(args) != len(types): - raise CdmTempException(f'Expected {len(types)} arguments, found {len(args)}') - assert_args(args, *types) - - -def handle_frame_pointer(line: InstructionNode): - for i in range(len(line.arguments)): - arg = line.arguments[i] - if isinstance(arg, RelocatableExpressionNode): - if not arg.const_term and not arg.sub_terms and not arg.byte_specifier and len(arg.add_terms) == 1 \ - and isinstance(arg.add_terms[0], LabelNode) and arg.add_terms[0].name == 'fp': - line.arguments[i] = RegisterNode(7) - - -# noinspection DuplicatedCode -class TargetInstructions(TargetInstructionsInterface): - @staticmethod - def assemble_instruction(line: InstructionNode, temp_storage: dict) -> list[CodeSegmentsInterface.CodeSegment]: - try: - for h in TargetInstructions.handlers: - if line.mnemonic in h.instructions: - return h.handler(line, temp_storage, h.instructions[line.mnemonic]) - if line.mnemonic.startswith('b'): - return TargetInstructions.branch(line) - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, - f'Unknown instruction "{line.mnemonic}"') - except CdmTempException as e: - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, e.message) - - @staticmethod - def finish(temp_storage: dict): - if len(temp_storage.get("save_restore_stack", [])) != 0: - raise CdmTempException("Expected restore statement") - - @staticmethod - def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ - -> list[CodeSegmentsInterface.CodeSegment]: - instruction = InstructionNode('b' + branch_mnemonic, - [RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0)]) - instruction.location = location - return TargetInstructions.branch(instruction, inverse) - - @staticmethod - def ds(line: InstructionNode, _, __): - assert_args(line.arguments, RelocatableExpressionNode) - arg = line.arguments[0] - if arg.const_term < 0: - raise CdmTempException('Cannot specify negative size in "ds"') - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Const number expected') - return [CodeSegments.BytesSegment(bytes(arg.const_term), line.location)] - - @staticmethod - def dc(line: InstructionNode, _, __): - if len(line.arguments) == 0: - raise CdmTempException('At least one argument must be provided') - segments = [] - size = 0 - for arg in line.arguments: - if isinstance(arg, RelocatableExpressionNode): - segments.append(CodeSegments.ExpressionSegment(line.location, arg)) - elif isinstance(arg, str): - encoded = arg.encode('utf-8') - segments.append(CodeSegments.BytesSegment(encoded, line.location)) - size += len(encoded) - else: - raise CdmTempException(f'Incompatible argument type: {type(arg)}') - return segments - - @staticmethod - def align(line: InstructionNode, _, __): - assert_args(line.arguments, RelocatableExpressionNode) - arg: RelocatableExpressionNode = line.arguments[0] - if arg.add_terms or arg.sub_terms: - raise CdmTempException('Const number expected') - alignment = arg.const_term - if alignment <= 0: - raise CdmTempException('Alignment should be positive') - elif alignment == 1: - return [] - return [CodeSegments.AlignmentPaddingSegment(alignment, line.location)] - - @staticmethod - def save(line: InstructionNode, temp_storage: dict, __) -> list[CodeSegmentsInterface.CodeSegment]: - assert_args(line.arguments, RegisterNode) - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - save_restore_stack.append(line.arguments[0]) - temp_storage["save_restore_stack"] = save_restore_stack - return TargetInstructions.assemble_instruction(InstructionNode("push", [line.arguments[0]]), temp_storage) - - @staticmethod - def restore(line: InstructionNode, temp_storage: dict, __) -> list[CodeSegmentsInterface.CodeSegment]: - assert_args(line.arguments, RegisterNode) - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - if len(save_restore_stack) == 0: - raise CdmTempException("Every restore statement must be preceded by a save statement") - reg = save_restore_stack.pop() - if len(line.arguments) > 0: - assert_args(line.arguments, RegisterNode) - reg = line.arguments[0] - return TargetInstructions.assemble_instruction(InstructionNode("pop", [reg]), temp_storage) - - @dataclass - class BranchCode: - condition: list[str] - code: int - inverse: list[str] - inv_code: int - - branch_codes: list[BranchCode] = [BranchCode(['eq', 'z'], 0, ['ne', 'nz'], 1), - BranchCode(['hs', 'cs'], 2, ['lo', 'cc'], 3), - BranchCode(['mi'], 4, ['pl'], 5), - BranchCode(['vs'], 6, ['vc'], 7), - BranchCode(['hi'], 8, ['ls'], 9), - BranchCode(['ge'], 10, ['lt'], 11), - BranchCode(['gt'], 12, ['le'], 13), - BranchCode(['anything', 'true', 'r'], 14, ['false'], 15)] - - @staticmethod - def branch(line: InstructionNode, inverse=False) -> list[CodeSegmentsInterface.CodeSegment]: - cond = re.match(r'b(\w*)', line.mnemonic)[1] - for pair in TargetInstructions.branch_codes: - if cond in pair.condition: - branch_code = pair.code if not inverse else pair.inv_code - break - elif cond in pair.inverse: - branch_code = pair.inv_code if not inverse else pair.code - break - else: - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, - f'Invalid branch condition: {cond}') - assert_count_args(line.arguments, RelocatableExpressionNode) - return [CodeSegments.BytesSegment(pack('u4u4', 0xE, branch_code), line.location), - CodeSegments.ExpressionSegment(line.location, line.arguments[0])] - - @staticmethod - def zero(line: InstructionNode, _, opcode: int): - assert_count_args(line.arguments) - return [CodeSegments.BytesSegment(bytearray([opcode]), line.location)] - - @staticmethod - def unary(line: InstructionNode, _, opcode: int): - assert_args(line.arguments, RegisterNode) - data = pack('u6u2', opcode // 4, line.arguments[0].number) - return [CodeSegments.BytesSegment(bytearray(data), line.location)] - - @staticmethod - def binary(line: InstructionNode, _, opcode: int): - assert_args(line.arguments, RegisterNode, RegisterNode) - data = pack('u4u2u2', opcode // 16, line.arguments[0].number, line.arguments[1].number) - return [CodeSegments.BytesSegment(bytearray(data), line.location)] - - @staticmethod - def imm(line: InstructionNode, _, opcode: int): - assert_args(line.arguments, RelocatableExpressionNode) - return [CodeSegments.BytesSegment(bytearray([opcode]), line.location), - CodeSegments.ExpressionSegment(line.location, line.arguments[0])] - - @staticmethod - def unary_imm(line: InstructionNode, _, opcode: int): - assert_args(line.arguments, RegisterNode, RelocatableExpressionNode) - data = pack('u6u2', opcode // 4, line.arguments[0].number) - return [CodeSegments.BytesSegment(bytearray(data), line.location), - CodeSegments.ExpressionSegment(line.location, line.arguments[1])] - - @dataclass - class Handler: - handler: Callable[[InstructionNode, dict, int], list[CodeSegmentsInterface.CodeSegment]] - instructions: dict[str, int] - - handlers: list[Handler] - handlers = [ - Handler(ds, {'ds': -1}), - Handler(dc, {'dc': -1}), - Handler(align, {'align': -1}), - Handler(save, {'save': -1}), - Handler(restore, {'restore': -1}), - Handler(zero, { - 'pushall': 0xCE, - 'popall': 0xCF, - 'rts': 0xD7, - 'halt': 0xD4, - 'wait': 0xD5, - 'ioi': 0xD8, - 'rti': 0xD9, - 'crc': 0xDA - }), - Handler(unary, { - 'not': 0x80, - 'neg': 0x84, - 'dec': 0x88, - 'inc': 0x8C, - 'shr': 0x90, - 'shla': 0x94, - 'shra': 0x98, - 'rol': 0x9C, - 'push': 0xC0, - 'pop': 0xC4 - }), - Handler(binary, { - 'move': 0x00, - 'add': 0x10, - 'addc': 0x20, - 'sub': 0x30, - 'and': 0x40, - 'or': 0x50, - 'xor': 0x60, - 'cmp': 0x70, - 'st': 0xA0, - 'ld': 0xB0, - 'ldc': 0xF0 - }), - Handler(imm, { - 'jsr': 0xD6, - 'osix': 0xDB, - 'addsp': 0xCC, - 'setsp': 0xCD - }), - Handler(unary_imm, { - 'ldsa': 0xC8, - 'ldi': 0xD0 - }) - ] - - @staticmethod - def assembly_directives(): - return {'ds', 'dc'} diff --git a/cocas/targets/cdm8/target_params.py b/cocas/targets/cdm8/target_params.py deleted file mode 100644 index d45939c3..00000000 --- a/cocas/targets/cdm8/target_params.py +++ /dev/null @@ -1,19 +0,0 @@ -from cocas.abstract_params import TargetParamsInterface - - -class TargetParams(TargetParamsInterface): - @staticmethod - def name(): - return 'CdM-8' - - @staticmethod - def max_entry_size() -> int: - return 1 - - @staticmethod - def default_alignment() -> int: - return 1 - - @staticmethod - def object_file_header() -> str: - return '' diff --git a/cocas/targets/cdm8e/__init__.py b/cocas/targets/cdm8e/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cocas/targets/cdm8e/code_segments.py b/cocas/targets/cdm8e/code_segments.py deleted file mode 100644 index 97191685..00000000 --- a/cocas/targets/cdm8e/code_segments.py +++ /dev/null @@ -1,257 +0,0 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.ast_nodes import LabelNode, RelocatableExpressionNode, TemplateFieldNode -from cocas.code_block import Section -from cocas.error import CdmException, CdmExceptionTag -from cocas.object_module import ExternalEntry - -from . import target_instructions - -TAG = CdmExceptionTag.ASM - -if TYPE_CHECKING: - from cocas.assembler import ObjectSectionRecord - - -class CodeSegments(CodeSegmentsInterface): - @dataclass - class CodeSegment(CodeSegmentsInterface.CodeSegment): - pass - - @dataclass - class RelocatableExpressionSegment(CodeSegment): - expr: RelocatableExpressionNode - - @dataclass - class VaryingLengthSegment(CodeSegmentsInterface.VaryingLengthSegment, CodeSegment): - is_expanded: bool = field(init=False, default=False) - expanded_size: int = field(init=False) - - @dataclass - class BytesSegment(CodeSegment): - data: bytearray - - def __init__(self, data: bytearray): - self.data = data - self.size = len(data) - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - object_record.data += self.data - - @dataclass - class ShortExpressionSegment(RelocatableExpressionSegment): - size = 1 - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - val, val_long, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) - - is_rel = (val_sect == section.name != '$abs') - if self.expr.byte_specifier is None and (is_rel or ext is not None): - _error(self, 'Expected a 1-byte expression') - if not -2 ** 7 <= val < 2 ** 8: - _error(self, 'Number out of range') - - if is_rel: - add_rel_record(object_record, section, val_long, self) - if ext is not None: - add_ext_record(object_record, ext, section, val_long, self) - object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) - - @dataclass - class ConstExpressionSegment(RelocatableExpressionSegment): - positive: bool = False - size = 1 - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - val, _, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) - if val_sect is not None or ext is not None: - _error(self, 'Number expected but label found') - if not -2 ** 7 <= val < 2 ** 8 or (self.positive and val < 0): - _error(self, 'Number out of range') - object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) - - @dataclass - class LongExpressionSegment(RelocatableExpressionSegment): - size = 2 - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - val, val_long, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) - - if not -2 ** 15 <= val < 2 ** 16: - _error(self, 'Number out of range') - - if val_sect: - add_rel_record(object_record, section, val_long, self) - if ext is not None: - add_ext_record(object_record, ext, section, val_long, self) - object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) - - @dataclass - class OffsetExpressionSegment(RelocatableExpressionSegment): - size = 1 - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - val, _, val_sect, ext = eval_rel_expr_seg(self, section, labels, templates) - - is_rel = (val_sect == section.name != '$abs') - if ext is not None: - _error(self, 'Invalid destination address (external label used)') - if section.name != '$abs' and not is_rel: - _error(self, 'Invalid destination address (absolute address from rsect)') - if self.expr.byte_specifier is not None and is_rel: - _error(self, 'Invalid destination address (byte of relative address)') - - val -= section.address + len(object_record.data) - if not -2 ** 7 <= val < 2 ** 7: - _error(self, 'Destination address is too far') - - object_record.data.extend(val.to_bytes(self.size, 'little', signed=(val < 0))) - - @dataclass - class GotoSegment(VaryingLengthSegment): - branch_mnemonic: str - expr: RelocatableExpressionNode - size = 2 - base_size = 2 - expanded_size = 5 - - def update_varying_length(self, pos: int, section: "Section", labels: dict[str, int], - templates: dict[str, dict[str, int]]): - try: - if self.is_expanded: - return - - addr, _, res_sect, ext = eval_rel_expr_seg(self, section, labels, templates) - is_rel = (res_sect == section.name != '$abs') - if (not -2 ** 7 <= addr - (pos + 1) < 2 ** 7 - or (section.name != '$abs' and not is_rel) - or (self.expr.byte_specifier is not None and is_rel) - or (ext is not None)): - - shift_length = self.expanded_size - self.base_size - self.is_expanded = True - self.size = self.expanded_size - old_locations = section.code_locations - section.code_locations = dict() - for PC, location in old_locations.items(): - if PC > pos: - PC += shift_length - section.code_locations[PC] = location - - for label_name in section.labels: - if section.labels[label_name] > pos: - section.labels[label_name] += shift_length - if label_name in labels: - labels[label_name] += shift_length - return shift_length - except CdmException as e: - raise e - except Exception as e: - raise CdmException(TAG, self.location.file, self.location.line, str(e)) - - def fill(self, object_record: "ObjectSectionRecord", section: Section, labels: dict[str, int], - templates: dict[str, dict[str, int]]): - mnemonic = f'b{self.branch_mnemonic}' - if mnemonic not in target_instructions.TargetInstructions.simple_instructions['branch']: - _error(self, f'Invalid branch mnemonic: {mnemonic}') - if self.is_expanded: - branch_opcode = target_instructions.TargetInstructions.simple_instructions['branch'][ - f'bn{self.branch_mnemonic}'] - jmp_opcode = target_instructions.TargetInstructions.simple_instructions['long']['jmp'] - object_record.data += bytearray([branch_opcode, 4, jmp_opcode]) - CodeSegments.LongExpressionSegment(self.expr).fill(object_record, section, labels, templates) - else: - branch_opcode = target_instructions.TargetInstructions.simple_instructions['branch'][mnemonic] - object_record.data += bytearray([branch_opcode]) - CodeSegments.OffsetExpressionSegment(self.expr).fill(object_record, section, labels, templates) - - -def _error(segment: CodeSegmentsInterface.CodeSegment, message: str): - raise CdmException(TAG, segment.location.file, segment.location.line, message) - - -def eval_rel_expr_seg(seg: CodeSegments.RelocatableExpressionSegment, s: Section, - labels: dict[str, int], templates: dict[str, dict[str, int]]): - val_long = seg.expr.const_term - used_exts = dict() - s_dim = 0 - local_dim = 0 - for term, m in [(t, 1) for t in seg.expr.add_terms] + [(t, -1) for t in seg.expr.sub_terms]: - if isinstance(term, LabelNode): - if term.name in labels: - local_dim += m - val_long += labels[term.name] * m - elif term.name in s.labels: - s_dim += m - val_long += s.labels[term.name] * m - elif term.name in s.exts: - used_exts.setdefault(term.name, 0) - used_exts[term.name] += m - else: - _error(seg, f'Label "{term.name}" not found') - elif isinstance(term, TemplateFieldNode): - val_long += templates[term.template_name][term.field_name] * m - - val_lo, val_hi = val_long.to_bytes(2, 'little', signed=(val_long < 0)) - if seg.expr.byte_specifier == 'low': - val = val_lo - elif seg.expr.byte_specifier == 'high': - val = val_hi - elif seg.expr.byte_specifier is not None: - _error(seg, f'Invalid byte specifier "{seg.expr.byte_specifier}". Possible options are "low" and "high"') - return - else: - val = val_long - - used_exts = dict(filter(lambda x: x[1] != 0, used_exts.items())) - if len(used_exts) > 1: - _error(seg, 'Cannot use multiple external labels in an address expression') - - if len(used_exts) == 0: - if s_dim == 0 and local_dim == 0: - return val, val_long, None, None - elif s_dim == 0 and local_dim == 1: - return val, val_long, '$abs', None - elif s_dim == 1 and local_dim == 0: - return val, val_long, s.name, None - else: - ext, ext_dim = used_exts.popitem() - if local_dim == 0 and s_dim == 0 and ext_dim == 1: - return val, val_long, None, ext - - _error(seg, 'Result is not a label or a number') - - -def add_ext_record(obj_rec: "ObjectSectionRecord", ext: str, s: Section, val: int, - seg: CodeSegments.RelocatableExpressionSegment): - val %= 65536 - val_lo, _ = val.to_bytes(2, 'little', signed=False) - offset = s.address + len(obj_rec.data) - if seg.expr.byte_specifier == 'low': - obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(0, 1), full_bytes=False)) - elif seg.expr.byte_specifier == 'high': - obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(1, 2), full_bytes=False)) - obj_rec.lower_parts[offset] = obj_rec.lower_parts.get(offset, 0) + val_lo - else: - obj_rec.external.setdefault(ext, []).append(ExternalEntry(offset, range(0, 2), full_bytes=True)) - - -def add_rel_record(obj_rec: "ObjectSectionRecord", s: Section, val: int, - seg: CodeSegments.RelocatableExpressionSegment): - val %= 65536 - val_lo, _ = val.to_bytes(2, 'little', signed=False) - offset = s.address + len(obj_rec.data) - if seg.expr.byte_specifier == 'low': - obj_rec.relative.append(ExternalEntry(offset, range(0, 1), full_bytes=False)) - elif seg.expr.byte_specifier == 'high': - obj_rec.relative.append(ExternalEntry(offset, range(1, 2), full_bytes=False)) - obj_rec.lower_parts[offset] = obj_rec.lower_parts.get(offset, 0) + val_lo - else: - obj_rec.relative.append(ExternalEntry(offset, range(0, 2), full_bytes=True)) diff --git a/cocas/targets/cdm8e/target_instructions.py b/cocas/targets/cdm8e/target_instructions.py deleted file mode 100644 index 58a0ec9d..00000000 --- a/cocas/targets/cdm8e/target_instructions.py +++ /dev/null @@ -1,348 +0,0 @@ -from typing import Union, get_args, get_origin - -import bitstruct - -from cocas.abstract_code_segments import CodeSegmentsInterface -from cocas.abstract_instructions import TargetInstructionsInterface -from cocas.ast_nodes import InstructionNode, LabelNode, RegisterNode, RelocatableExpressionNode -from cocas.error import CdmException, CdmExceptionTag, CdmTempException - -from .code_segments import CodeSegments - - -def assert_args(args, *types, single_type=False): - ts = [(t if get_origin(t) is None else get_args(t)) for t in types] - if single_type: - if len(ts) != 1: - raise TypeError('Exactly one type must be specified when single_type is True') - ts = ts * len(args) - elif len(args) != len(ts): - raise CdmTempException(f'Expected {len(ts)} arguments, but found {len(args)}') - - for i in range(len(args)): - # noinspection PyTypeHints - if not isinstance(args[i], ts[i]): - raise CdmTempException(f'Incompatible argument type {type(args[i])}') - if isinstance(args[i], RegisterNode) and not 0 <= args[i].number <= 3: - raise CdmTempException(f'Invalid register number r{args[i].number}') - - -class TargetInstructions(TargetInstructionsInterface): - @staticmethod - def assemble_instruction(line: InstructionNode, temp_storage) \ - -> list[CodeSegmentsInterface.CodeSegment]: - try: - if line.mnemonic in TargetInstructions.assembly_directives(): - handler = assembler_directives[line.mnemonic] - segments = handler(line.arguments) - elif line.mnemonic in cpu_instructions: - opcode, handler = cpu_instructions[line.mnemonic] - segments = handler(opcode, line.arguments) - elif line.mnemonic in TargetInstructions.special_instructions: - return TargetInstructions.special_instructions[line.mnemonic](line.arguments, temp_storage, - line.location) - else: - raise CdmTempException(f'Unknown instruction "{line.mnemonic}"') - for segment in segments: - segment.location = line.location - return segments - except CdmTempException as e: - raise CdmException(CdmExceptionTag.ASM, line.location.file, line.location.line, e.message) - - @staticmethod - def finish(temp_storage: dict): - if len(temp_storage.get("save_restore_stack", [])) != 0: - raise CdmTempException("Expected restore statement") - - @staticmethod - def make_branch_instruction(location, branch_mnemonic: str, label_name: str, inverse: bool) \ - -> list[CodeSegmentsInterface.CodeSegment]: - arg2 = RelocatableExpressionNode(None, [LabelNode(label_name)], [], 0) - if inverse: - branch_mnemonic = 'n' + branch_mnemonic - return [CodeSegments.GotoSegment(branch_mnemonic, arg2)] - - @staticmethod - def goto_handler(arguments: list, _, location): - assert_args(arguments, RelocatableExpressionNode, RelocatableExpressionNode) - br_mnemonic: RelocatableExpressionNode - br_mnemonic = arguments[0] - if br_mnemonic.byte_specifier is not None or len(br_mnemonic.sub_terms) != 0 \ - or len(br_mnemonic.add_terms) != 1 or not isinstance(br_mnemonic.add_terms[0], LabelNode): - raise CdmTempException('Branch mnemonic must be single word') - goto = CodeSegments.GotoSegment(br_mnemonic.add_terms[0].name, arguments[1]) - goto.location = location - return [goto] - - @staticmethod - def save_handler(arguments: list, temp_storage: dict, _): - assert_args(arguments, RegisterNode) - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - save_restore_stack.append(arguments[0]) - temp_storage["save_restore_stack"] = save_restore_stack - return TargetInstructions.assemble_instruction(InstructionNode("push", [arguments[0]]), temp_storage) - - @staticmethod - def restore_handler(arguments: list, temp_storage: dict, _): - save_restore_stack: list[RegisterNode] - save_restore_stack = temp_storage.get("save_restore_stack", []) - if len(save_restore_stack) == 0: - raise CdmTempException("Every restore statement must be preceded by a save statement") - reg = save_restore_stack.pop() - if len(arguments) > 0: - assert_args(arguments, RegisterNode) - reg = arguments[0] - return TargetInstructions.assemble_instruction(InstructionNode("pop", [reg]), temp_storage) - - special_instructions = { - 'goto': goto_handler, - 'save': save_handler, - 'restore': restore_handler - } - - simple_instructions = { - 'zero': { - 'pushall': 0xCE, - 'popall': 0xCF, - 'rts': 0xD7, - 'halt': 0xD4, - 'wait': 0xD5, - 'ioi': 0xD8, - 'rti': 0xD9, - 'crc': 0xDA, - }, - 'unary': { - 'not': 0x80, - 'neg': 0x84, - 'dec': 0x88, - 'inc': 0x8C, - 'shr': 0x90, - 'shla': 0x94, - 'shra': 0x98, - 'rol': 0x9C, - 'push': 0xC0, - 'pop': 0xC4, - }, - 'binary': { - 'move': 0x00, - 'add': 0x10, - 'addc': 0x20, - 'sub': 0x30, - 'and': 0x40, - 'or': 0x50, - 'xor': 0x60, - 'cmp': 0x70, - 'st': 0xA0, - 'ld': 0xB0, - 'ldc': 0xF0, - }, - 'branch': { - 'beq': 0xE0, - 'bz': 0xE0, - 'bnne': 0xE0, - 'bnnz': 0xE0, - - 'bne': 0xE1, - 'bnz': 0xE1, - 'bneq': 0xE1, - - 'bhs': 0xE2, - 'bcs': 0xE2, - 'bnlo': 0xE2, - 'bncc': 0xE2, - - 'blo': 0xE3, - 'bcc': 0xE3, - 'bnhs': 0xE3, - 'bncs': 0xE3, - - 'bmi': 0xE4, - 'bnpl': 0xE4, - - 'bpl': 0xE5, - 'bnmi': 0xE5, - - 'bvs': 0xE6, - 'bnvc': 0xE6, - - 'bvc': 0xE7, - 'bnvs': 0xE7, - - 'bhi': 0xE8, - 'bnls': 0xE8, - - 'bls': 0xE9, - 'bnhi': 0xE9, - - 'bge': 0xEA, - 'bnlt': 0xEA, - - 'blt': 0xEB, - 'bnge': 0xEB, - - 'bgt': 0xEC, - 'bnle': 0xEC, - - 'ble': 0xED, - 'bngt': 0xED, - - 'br': 0xEE, - 'banything': 0xEE, - 'btrue': 0xEE, - 'bnfalse': 0xEE, - - 'nop': 0xEF, - 'bnanything': 0xEF, - 'bntrue': 0xEF, - 'bfalse': 0xEF, - }, - 'long': { - 'jsr': 0xD6, - 'jmp': 0xDD, - }, - 'ldsa': {'ldsa': 0xC8}, - 'ldi': {'ldi': 0xD0}, - 'osix': {'osix': 0xDB}, - 'spmove': { - 'addsp': 0xCC, - 'setsp': 0xCD, - }, - } - - @staticmethod - def assembly_directives(): - return {'ds', 'dc'} - - -def binary_handler(opcode: int, arguments: list): - assert_args(arguments, RegisterNode, RegisterNode) - data = bitstruct.pack("u4u2u2", opcode // 16, arguments[0].number, arguments[1].number) - return [CodeSegments.BytesSegment(bytearray(data))] - - -def unary_handler(opcode: int, arguments: list): - assert_args(arguments, RegisterNode) - data = bitstruct.pack('u6u2', opcode // 4, arguments[0].number) - return [CodeSegments.BytesSegment(bytearray(data))] - - -def zero_handler(opcode: int, arguments: list): - assert_args(arguments) - return [CodeSegments.BytesSegment(bytearray([opcode]))] - - -def branch_handler(opcode: int, arguments: list): - assert_args(arguments, RelocatableExpressionNode) - arg = arguments[0] - - return [CodeSegments.BytesSegment(bytearray([opcode])), CodeSegments.OffsetExpressionSegment(arg)] - - -def long_handler(opcode: int, arguments: list): - assert_args(arguments, RelocatableExpressionNode) - arg = arguments[0] - - return [CodeSegments.BytesSegment(bytearray([opcode])), CodeSegments.LongExpressionSegment(arg)] - - -def ldsa_handler(opcode: int, arguments: list): - assert_args(arguments, RegisterNode, RelocatableExpressionNode) - reg, arg = arguments - cmd_piece = unary_handler(opcode, [reg])[0] - - return [CodeSegments.BytesSegment(cmd_piece.data), CodeSegments.ShortExpressionSegment(arg)] - - -def ldi_handler(opcode: int, arguments: list): - # check types - assert_args(arguments, RegisterNode, Union[RelocatableExpressionNode, str]) - reg, arg = arguments - cmd_piece = unary_handler(opcode, [reg])[0] - - if isinstance(arg, str): - arg_data = bytearray(arg, 'utf8') - if len(arg_data) != 1: - raise CdmTempException('Argument must be a string of length 1') - cmd_piece.data.extend(arg_data) - return [CodeSegments.BytesSegment(cmd_piece.data)] - elif isinstance(arg, RelocatableExpressionNode): - return [CodeSegments.BytesSegment(cmd_piece.data), CodeSegments.ShortExpressionSegment(arg)] - - -def osix_handler(opcode: int, arguments: list): - assert_args(arguments, RelocatableExpressionNode) - arg = arguments[0] - - return [CodeSegments.BytesSegment(bytearray([opcode])), CodeSegments.ConstExpressionSegment(arg, positive=True)] - - -def spmove_handler(opcode: int, arguments: list): - assert_args(arguments, RelocatableExpressionNode) - arg = arguments[0] - - return [CodeSegments.BytesSegment(bytearray([opcode])), CodeSegments.ConstExpressionSegment(arg)] - - -def dc_handler(arguments: list): - assert_args(arguments, Union[RelocatableExpressionNode, str], single_type=True) - if len(arguments) == 0: - raise CdmTempException('At least one argument must be provided') - - segments = [] - for arg in arguments: - if isinstance(arg, str): - segments.append(CodeSegments.BytesSegment(bytearray(arg, 'utf8'))) - elif isinstance(arg, RelocatableExpressionNode): - if arg.byte_specifier is None: - added_labels = list(filter(lambda t: isinstance(t, LabelNode), arg.add_terms)) - subtracted_labels = list(filter(lambda t: isinstance(t, LabelNode), arg.sub_terms)) - if len(added_labels) == len(subtracted_labels): - segments.append(CodeSegments.ShortExpressionSegment(arg)) - else: - segments.append(CodeSegments.LongExpressionSegment(arg)) - else: - segments.append(CodeSegments.ShortExpressionSegment(arg)) - return segments - - -def ds_handler(arguments: list): - assert_args(arguments, RelocatableExpressionNode) - arg = arguments[0] - - if len(arg.add_terms) != 0 or len(arg.sub_terms) != 0: - raise CdmTempException('Number expected') - if arg.const_term < 0: - raise CdmTempException('Cannot specify negative size in "ds"') - return [CodeSegments.BytesSegment(bytearray(arg.const_term))] - - -command_handlers = { - 'zero': zero_handler, - 'unary': unary_handler, - 'binary': binary_handler, - 'branch': branch_handler, - 'long': long_handler, - 'ldsa': ldsa_handler, - 'ldi': ldi_handler, - 'osix': osix_handler, - 'spmove': spmove_handler, - - 'dc': dc_handler, - 'ds': ds_handler, -} - -cpu_instructions = {} -assembler_directives = {} - - -def initialize(): - for category, instructions in TargetInstructions.simple_instructions.items(): - for mnemonic, opcode in instructions.items(): - cpu_instructions[mnemonic] = (opcode, command_handlers[category]) - - for directive in TargetInstructions.assembly_directives(): - assembler_directives[directive] = command_handlers[directive] - - -initialize() diff --git a/cocas/targets/cdm8e/target_params.py b/cocas/targets/cdm8e/target_params.py deleted file mode 100644 index 9991d21f..00000000 --- a/cocas/targets/cdm8e/target_params.py +++ /dev/null @@ -1,21 +0,0 @@ -from cocas.abstract_params import TargetParamsInterface - - -class TargetParams(TargetParamsInterface): - - @staticmethod - def name(): - return 'CdM-8e' - - @staticmethod - def max_entry_size() -> int: - return 2 - - @staticmethod - def default_alignment() -> int: - return 1 - - @staticmethod - def object_file_header() -> str: - return 'CDM8E' - diff --git a/logisim/.gitignore b/logisim/.gitignore new file mode 100644 index 00000000..9eaefddd --- /dev/null +++ b/logisim/.gitignore @@ -0,0 +1 @@ +**/microcode/*.circ \ No newline at end of file diff --git a/logisim/logisim-runner/.gitignore b/logisim/logisim-runner/.gitignore index 8044aff5..361613d3 100644 --- a/logisim/logisim-runner/.gitignore +++ b/logisim/logisim-runner/.gitignore @@ -56,4 +56,4 @@ build/ .idea/ # Include Logisim library -!libs/*.jar \ No newline at end of file +!libs/logisim-generic-2.7.1.jar \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index d1cf4b4d..01933da1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,25 +1,25 @@ -# This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "antlr4-python3-runtime" -version = "4.13.0" -description = "ANTLR 4.13.0 runtime for Python 3" +version = "4.13.1" +description = "ANTLR 4.13.1 runtime for Python 3" optional = false python-versions = "*" files = [ - {file = "antlr4-python3-runtime-4.13.0.tar.gz", hash = "sha256:0d5454928ae40c8a6b653caa35046cd8492c8743b5fbc22ff4009099d074c7ae"}, - {file = "antlr4_python3_runtime-4.13.0-py3-none-any.whl", hash = "sha256:53e6e208cf4a1ad53fb8b1b4467b756375a4f827331e290618aedcf481cb1d5c"}, + {file = "antlr4-python3-runtime-4.13.1.tar.gz", hash = "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a"}, + {file = "antlr4_python3_runtime-4.13.1-py3-none-any.whl", hash = "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943"}, ] [[package]] name = "antlr4-tools" -version = "0.2" +version = "0.2.1" description = "Tools to run ANTLR4 tool and grammar interpreter/profiler" optional = false python-versions = "*" files = [ - {file = "antlr4-tools-0.2.tar.gz", hash = "sha256:9e44b636fec5c6daf890d479378b6fe684b62de32084baeabaf51c352fd9a7e0"}, - {file = "antlr4_tools-0.2-py3-none-any.whl", hash = "sha256:59a192826963c5a94e982c0b3d8e9439a84f051f47c8fae1169f6331139ebf39"}, + {file = "antlr4-tools-0.2.1.tar.gz", hash = "sha256:eabad95c7d6b2e15b9875f630e6695395f90ed557721aeef7844d5821ac1ca6a"}, + {file = "antlr4_tools-0.2.1-py3-none-any.whl", hash = "sha256:71bfe6d5f856cbf69e69c4ad1750eb8f2c84e72b07780efabced6a8e5194f2e4"}, ] [package.dependencies] @@ -27,12 +27,54 @@ install-jdk = "*" [[package]] name = "bitstruct" -version = "8.17.0" +version = "8.19.0" description = "This module performs conversions between Python values and C bit field structs represented as Python byte strings." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "bitstruct-8.17.0.tar.gz", hash = "sha256:eb94b40e4218a23aa8f90406b836a9e6ed83e48b8d112ce3f96408463bd1b874"}, + {file = "bitstruct-8.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d1f3eb18ddc33ba73f5cbb55c885584bcec51c421ac3551b79edc0ffeaecc3d"}, + {file = "bitstruct-8.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a35e0b267d12438e6a7b28850a15d4cffe767db6fc443a406d0ead97fa1d7d5b"}, + {file = "bitstruct-8.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5732aff5c8eb3a572f7b20d09fc4c213215f9e60c0e66f2910b31eb65b457744"}, + {file = "bitstruct-8.19.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc8f1871b42b705eb34b8722c3ec358fbf1b97fd37a62693564ee72648afb100"}, + {file = "bitstruct-8.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:01bdfc3adbe15b05ba27ab6dce7959caa29a000f066201944b29c64bb8888f03"}, + {file = "bitstruct-8.19.0-cp310-cp310-win32.whl", hash = "sha256:961845a29333119b70dd9aab54bc714bf9ba5efefc55cb4c747c35c1390b8842"}, + {file = "bitstruct-8.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:9fbe12d464db909f58d5e2a2485b3047a488fa1373e8f74b22d6759ee6b2437a"}, + {file = "bitstruct-8.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1300cd635814e40b1f4105aa4f404cb5d1b8cc54e06e267ba1616725f9c2beea"}, + {file = "bitstruct-8.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2fb23b5973ce1e9f349c4dc90873eeff9800fe917ffd345f39b9b964f6d119"}, + {file = "bitstruct-8.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e0c18d557474d8452c4f8b59320fd4d9efcf52eae2144bdf317d25c64dcf85"}, + {file = "bitstruct-8.19.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bba06607f956cc39ceee19fd11b542e8e66a43180d48fa36c4609443893c273e"}, + {file = "bitstruct-8.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f2fa607d111077145e6374d49be6098f33e7cee0967b42cfc117df53eee13332"}, + {file = "bitstruct-8.19.0-cp311-cp311-win32.whl", hash = "sha256:abdb7bdb5b04c2f1bbda0eae828c627252243ddc042aea6b72af8fcc63696598"}, + {file = "bitstruct-8.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:464f102999402a2624ee3106dbfa1f3745810036814a33e6bc706b7d312c480f"}, + {file = "bitstruct-8.19.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:55768b1f5e33594178f0b3e1596b89d831b006713a60caa09de61fd385bf22b1"}, + {file = "bitstruct-8.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c026a7cf8d954ef53cf4d0ae5ee3dd1ac66e24e9a474c5afe55467ab7d609f2e"}, + {file = "bitstruct-8.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7488fd4e2fde3d8111971e2040cd5b008be918381afc80387d3fdf047c801293"}, + {file = "bitstruct-8.19.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:45b66e20633f1e083e37fa396c81761e0fc688ffa06ff5559e990e37234f9e18"}, + {file = "bitstruct-8.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9c1542d5ae888ebc31614775938bfd13454f0d897dc2515363a4607efadc990b"}, + {file = "bitstruct-8.19.0-cp312-cp312-win32.whl", hash = "sha256:7ea57e4e793b595cd3e037920852f2c676b4f5f1734c41985db3f48783928e2c"}, + {file = "bitstruct-8.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c4d9b75248adee84e7e6c95bf95966f152b78363cb20a81920da2aeadc4375f"}, + {file = "bitstruct-8.19.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7b4745b099d3d85307495e25ff0f265deeea675621dcecb25ba059ee68ce88d5"}, + {file = "bitstruct-8.19.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:645da560acd20dd73a1ef220e3ddc08e108866e30a708ef2f6193e0a3725113e"}, + {file = "bitstruct-8.19.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01402fbc3dba2286b3ac9b74d5936dd984736f928aacd371458a4b0cf95f0755"}, + {file = "bitstruct-8.19.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2c5eda42d55db67072c6cf7cc79b1df1074269004bad119b79e4ad38cfa61877"}, + {file = "bitstruct-8.19.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ea093522b12ce714a3a95851a8c3dd97f620126bbe983eb261b3bf18ac945e7"}, + {file = "bitstruct-8.19.0-cp37-cp37m-win32.whl", hash = "sha256:da00da004830800323554e7a83f1f32a1f49345f5379476de4b5f6ae227ee962"}, + {file = "bitstruct-8.19.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a0ca55fba25d6c631e17933f20cf87f553d7bceec7659e3de9ef48dc85ced2bf"}, + {file = "bitstruct-8.19.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3f6e3aeb598215062c505a06135fbdfa3bb4eeb249b55f87e865a86b3fd9e99"}, + {file = "bitstruct-8.19.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df74c72feba80014b05ab6f1e1a0bb90be9f9e7eb60a9bab1e00728f7f46d79d"}, + {file = "bitstruct-8.19.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:976c39ad771c6773d6fbd14d71e62242d5b3bca7b72428fd183e1f1085d5e858"}, + {file = "bitstruct-8.19.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d2c176ff6727206805760f45c2151468aed843256aa239c14f4730b9e1d84fc7"}, + {file = "bitstruct-8.19.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7774e2a51e254ef1ba98a1ee38573c819d4ee7e396d5121c5ecae17df927501"}, + {file = "bitstruct-8.19.0-cp38-cp38-win32.whl", hash = "sha256:b86d192d658eaf35f10efb2e1940ec755cc28e081f46de294a2e91a74ea298aa"}, + {file = "bitstruct-8.19.0-cp38-cp38-win_amd64.whl", hash = "sha256:5e7f78aedec2881017026eb7f7ab79514aef09a24afd8acf5fa8c73b1cd0e9f4"}, + {file = "bitstruct-8.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2bb49acc2ccc6efd3c9613cae8f7e1316c92f832bff860a6fcb78a4275974e90"}, + {file = "bitstruct-8.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bed7b2761c18a515298145a4f67b6c71ce302453fe7d87ec6b7d2e77fd3c22b"}, + {file = "bitstruct-8.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d0cafd2e2974c4bbe349fb67951d43d221ea304218c2ee65f9fe4c62acabc2f"}, + {file = "bitstruct-8.19.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d9ba0299f624e7c8ea1eec926fc77741f82ffc5b3c3ba4f89303d33d5605f4d8"}, + {file = "bitstruct-8.19.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfa0326057c9b02c4e65e74e45b9914a7f8c59590a8e718e20a899a02b41f2e6"}, + {file = "bitstruct-8.19.0-cp39-cp39-win32.whl", hash = "sha256:14c3ebdec92c486142327d934cb451d96b411543ec6f72aeb2b4b4334e9408bf"}, + {file = "bitstruct-8.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:7836852d5c15444e87a2029f922b48717e6e199d2332d55e8738e92d8590987e"}, + {file = "bitstruct-8.19.0.tar.gz", hash = "sha256:d75ba9dded85c17e885a209a00eb8e248ee40762149f2f2a79360ca857467dac"}, ] [[package]] @@ -48,13 +90,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.1.1" +version = "1.2.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, - {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, ] [package.extras] @@ -73,35 +115,35 @@ files = [ [[package]] name = "install-jdk" -version = "1.0.4" +version = "1.1.0" description = "install-jdk allows you to easily install latest Java OpenJDK version. Supports OpenJDK builds from Adoptium (previously AdoptOpenJDK), Corretto, and Zulu. Simplify your Java development with the latest OpenJDK builds." optional = false python-versions = ">=3.6,<4.0" files = [ - {file = "install_jdk-1.0.4-py3-none-any.whl", hash = "sha256:801c7dc0f9d27972ab3af920225be73a3231d1594213e2229b4beee0c4113a52"}, - {file = "install_jdk-1.0.4.tar.gz", hash = "sha256:51786cf98f7c5f7223e08213200dde839a5d526150b635e86c42a422b087f8e1"}, + {file = "install_jdk-1.1.0-py3-none-any.whl", hash = "sha256:b63f0fcd63f7abab3443d4120ba92716397753b8a8ea3c85762a629925a9936e"}, + {file = "install_jdk-1.1.0.tar.gz", hash = "sha256:2bfd53caf660e4916df0215a5715519dcb9547fa2a5f07421fd97a8046851eaa"}, ] [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -110,13 +152,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.3.1" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, - {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -128,7 +170,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "python-dateutil" diff --git a/pyproject.toml b/pyproject.toml index 2281c445..198822c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,8 @@ testpaths = [ [tool.ruff] select = ["E", "F", "I"] exclude = [ - "cocas/generated", + "cocas/assembler/generated", + "cocas/object_file/generated", "logisim/cdm8e" ] line-length = 120 diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..a84af91f --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,61 @@ +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Include gradle-wrapper.jar +!gradle/wrapper/gradle-wrapper.jar + +# Gradle files +.gradle/ +build/ + +# IDEA Folder +.idea/ + + +# Exclude processor circuits, copied by make +**/circuits/*.circ +!**/circuits/test_circuit.circ \ No newline at end of file