Skip to content
5 changes: 3 additions & 2 deletions cocas/assembler/assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ def assemble_files(target: str,
for j in lib.code_locations.values():
if j.file == fp:
j.file = dip
else:
j.file = get_debug_info_path(Path(j.file), debug, relative_path, realpath)
elif j.file is not None:
p = get_debug_info_path(Path(j.file), debug, relative_path, realpath)
j.file = str(p) if p is not None else None
objects.append((filepath, obj))
return objects
85 changes: 82 additions & 3 deletions cocas/assembler/ast_builder.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import codecs
import warnings
from base64 import b64decode
from collections.abc import Mapping, MutableSequence
from pathlib import Path
from typing import cast

from antlr4 import CommonTokenStream, InputStream

from cocas.assembler.constants import DBG_LOC_INST, DBG_SOURCE_INST
from cocas.object_module import CodeLocation

from .ast_nodes import (
Expand All @@ -22,6 +25,7 @@
RegisterNode,
RelocatableExpressionNode,
RelocatableSectionNode,
SectionNode,
TemplateSectionNode,
UntilLoopNode,
WhileLoopNode,
Expand All @@ -32,7 +36,7 @@

# noinspection PyPep8Naming
class BuildAstVisitor(AsmParserVisitor):
allowed_top_instructions = []
allowed_top_instructions = [DBG_SOURCE_INST]

def __init__(self, filepath: str):
super().__init__()
Expand All @@ -43,8 +47,11 @@ def __init__(self, filepath: str):
self.in_macro = False
self.current_macro_file = ""
self.current_macro_line = 0
self.generated: bool = False

def _ctx_location(self, ctx) -> CodeLocation:
if self.generated:
return CodeLocation()
if self.in_macro:
return CodeLocation(self.current_macro_file, self.current_macro_line)
return CodeLocation(self.source_path, ctx.start.line - self.line_offset)
Expand All @@ -61,7 +68,9 @@ def visitProgram(self, ctx: AsmParser.ProgramContext) -> ProgramNode:
elif isinstance(child, AsmParser.Line_markContext):
self.visitLine_mark(child)
elif isinstance(child, AsmParser.Top_lineContext):
ret.shared_externals, ret.top_instructions = self.visitTop_line(child)
shared, top = self.visitTop_line(child)
ret.shared_externals.extend(shared)
ret.top_instructions.extend(top)
return ret

def visitTop_line(self, ctx: AsmParser.Top_lineContext) -> tuple[list[LabelNode], list[InstructionNode]]:
Expand Down Expand Up @@ -305,6 +314,7 @@ def visitInstructionLine(self, ctx: AsmParser.InstructionLineContext) -> list[No
if ctx.labels_declaration() is not None:
ret += self.visitLabels_declaration(ctx.labels_declaration())
op = ctx.instruction().getText()
self.generated |= op == DBG_SOURCE_INST
args = self.visitArguments(ctx.arguments()) if ctx.arguments() is not None else []
ret.append(InstructionNode(op, args))
return ret
Expand All @@ -313,7 +323,72 @@ 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: Path):
Ren = RelocatableExpressionNode


def resolve_debug_locs(files: Mapping[int, str], sections: MutableSequence[SectionNode]) -> None:
for section in sections:
lines_iter = enumerate(iter(section.lines))
loc_insts: list[int] = []

for index, line_node in lines_iter:
if not isinstance(line_node, InstructionNode) or line_node.mnemonic != DBG_LOC_INST:
continue
loc_insts.append(index)
match line_node.arguments:
case [Ren(_, _, _, file), Ren(_, _, _, line), Ren(_, _, _, column)]:
if (path := files.get(file)) is None:
raise AssemblerException(AssemblerExceptionTag.ASM, line_node.location.file,
line_node.location.column,
f"{DBG_LOC_INST} refers to file with index {file}; where is no {DBG_SOURCE_INST} with such index")
current_loc = CodeLocation(path, line, column)
case _:
raise AssemblerException(AssemblerExceptionTag.ASM, line_node.location.file,
line_node.location.column,
f"{DBG_LOC_INST} expects a number and a string, while {line_node.arguments} provided")

_, next_line_node = next(lines_iter, (None, None))
if next_line_node is None:
raise AssemblerException(AssemblerExceptionTag.ASM, line_node.location.file, line_node.location.column,
f"expected an instruction line after {DBG_LOC_INST}, while it's missing")
if not isinstance(next_line_node, InstructionNode):
raise AssemblerException(AssemblerExceptionTag.ASM, line_node.location.file, line_node.location.column,
f"expected an instruction line after {DBG_LOC_INST}, while {type(next_line_node)} has been found")
if next_line_node.mnemonic == DBG_LOC_INST:
raise AssemblerException(AssemblerExceptionTag.ASM, line_node.location.file, line_node.location.column,
f"there are two consecutive {DBG_LOC_INST} instructions; there is no point to do that")
next_line_node.location = current_loc

for index in reversed(loc_insts):
_ = section.lines.pop(index)


def resolve_debug_pseudos(pn: ProgramNode) -> None:
files: dict[int, str] = {}
source_insts: list[int] = []

for index, ti in enumerate(pn.top_instructions):
if ti.mnemonic != DBG_SOURCE_INST:
continue
match ti.arguments:
case [Ren(_, _, _, index), str() as path]:
if index in files:
raise AssemblerException(AssemblerExceptionTag.ASM, ti.location.file, ti.location.column,
f"duplicate {DBG_SOURCE_INST} for index {index}")
files[index] = path
case _:
raise AssemblerException(AssemblerExceptionTag.ASM, ti.location.file, ti.location.column,
f"{DBG_SOURCE_INST} expects a number and a string, while {ti.arguments} provided")
source_insts.append(index)

for index in reversed(source_insts):
_ = pn.top_instructions.pop(index)

resolve_debug_locs(files, cast(list[SectionNode], pn.absolute_sections))
resolve_debug_locs(files, cast(list[SectionNode], pn.relocatable_sections))


def build_ast(input_stream: InputStream, filepath: Path) -> ProgramNode:
str_path = filepath.absolute().as_posix()
lexer = AsmLexer(input_stream)
lexer.removeErrorListeners()
Expand All @@ -326,4 +401,8 @@ def build_ast(input_stream: InputStream, filepath: Path):
cst = parser.program()
bav = BuildAstVisitor(str_path)
result = bav.visit(cst)
if not isinstance(result, ProgramNode):
raise AssemblerException(AssemblerExceptionTag.ASM, str_path, 0,
"failed to build a program AST from provided code")
resolve_debug_pseudos(result)
return result
8 changes: 3 additions & 5 deletions cocas/assembler/ast_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ class LabelNode(Node):

@dataclass
class LocatableNode(Node):
def __post_init__(self):
self.location: CodeLocation = CodeLocation()
location: CodeLocation = field(default_factory=CodeLocation, init=False, compare=False)


@dataclass
class ExportLocationNode(LocatableNode):
def __post_init__(self):
self.location: CodeLocation = CodeLocation()
pass


@dataclass
Expand Down Expand Up @@ -126,4 +124,4 @@ class ProgramNode(Node):
relocatable_sections: list[RelocatableSectionNode]
absolute_sections: list[AbsoluteSectionNode]
shared_externals: list[LabelNode] = field(default_factory=list)
top_instructions: list[LabelNode] = field(default_factory=list)
top_instructions: list[InstructionNode] = field(default_factory=list)
4 changes: 4 additions & 0 deletions cocas/assembler/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__all__ = ("DBG_SOURCE_INST", "DBG_LOC_INST")

DBG_SOURCE_INST = "dbg_source"
DBG_LOC_INST = "dbg_loc"
3 changes: 2 additions & 1 deletion cocas/linker/debug_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def debug_export(code_locations: dict[int, CodeLocation]) -> str:
: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())))
code_locations = {addr: cl for addr, cl in code_locations.items() if cl.file is not None}
files = sorted({cl.file for cl in code_locations.values()})
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)
Expand Down
Loading