Files
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

144 lines
4.4 KiB
Python

import collections
import re
from .Errors import ArgumentOverflowError
def strip_line(line: str) -> str:
"""
Strips indent and comment from line, if present.
"""
line = line.strip()
return re.sub(r" *[;#].*\n?", "", line)
def parse_hex_string_to_value(string: str) -> int:
"""
Parse an hexadecimal string into a numeric value, handling some operators
"""
string = string.replace("$", "")
if "+" in string:
split = string.split("+")
return int(split[0], 16) + int(split[1], 16)
elif "-" in string:
split = string.split("-")
return int(split[0], 16) - int(split[1], 16)
elif "*" in string:
split = string.split("*")
return int(split[0], 16) * int(split[1], 16)
elif "|" in string:
split = string.split("|")
return int(split[0], 16) | int(split[1], 16)
else:
return int(string, 16)
def parse_bin_string_to_value(string: str) -> int:
"""
Parse a binary string into a numeric value as small endian, no operator is supported
"""
return int(string[:0:-1], 2)
def value_to_byte_array(value: int, expected_size: int) -> list[int]:
"""
Converts a value into a little endian byte array
(e.g. "0x4Fa7DEadBEef" => [0xef, 0xbe, 0xad, 0xde, 0xa7, 0x4f])
"""
output = []
while value > 0:
output.append(value & 0xFF)
value >>= 8
if len(output) > expected_size:
# Rebuild the initial value.
# It is slower but it's ok to spend a lot of time during errors if it saves time during normal behavior
initial_value = value
output.reverse()
for i in output:
initial_value <<= 8
initial_value += i
raise ArgumentOverflowError(initial_value, expected_size)
while len(output) < expected_size:
output.append(0x00)
return output
def parse_byte(string: str) -> int:
if string.startswith("$"):
return parse_hex_byte(string)
elif string.startswith("%"):
return parse_bin_string_to_value(string)
else:
raise Exception(f"Invalid byte string : {string}")
def parse_hex_byte(string: str) -> int:
"""
Converts a byte literal hexadecimal string into a byte value
(e.g. "$4F" => 0x4f)
"""
value = parse_hex_string_to_value(string)
return value_to_byte_array(value, 1)[0]
def parse_hex_word(string: str) -> list[int]:
"""
Converts a word literal hexadecimal string into a little endian byte array
(e.g. "$4Fa7" => [0xa7, 0x4f])
"""
value = parse_hex_string_to_value(string)
return value_to_byte_array(value, 2)
def parse_argument(arg: str, mnemonic_subtree: collections.abc.Mapping) -> tuple[str, list[int]]:
"""
Parse an argument to extract a generic form and potential extra bytes
"$1a" => ("$8", [0x1a])
"($c43f)" => ("($16)", [0x3f,0xc4])
"bc" => ("bc", [])
"$04+$29" => ("$8", [0x2d])
"""
arg = arg.strip()
enclosed_in_parentheses = arg.startswith("(") and arg.endswith(")")
if enclosed_in_parentheses:
arg = arg[1:-1]
# If argument is a literal, determine the expected size of that literal using the
# mnemonic subtree that was passed as parameter
if arg.startswith("$") or arg.startswith("%"):
value = 0
try:
if arg.startswith("$"):
value = parse_hex_string_to_value(arg)
else:
value = parse_bin_string_to_value(arg)
except ValueError:
pass
for size in [8, 16]:
generic_arg = f"${size}"
if enclosed_in_parentheses:
generic_arg = f"({generic_arg})"
if generic_arg in mnemonic_subtree:
return generic_arg, value_to_byte_array(value, int(size / 8))
# If we reached that point, this means we need to keep the symbol as it is: it can be a register name,
# or an invalid name which will get rejected at a later point
if enclosed_in_parentheses:
return f"({arg})", []
else:
return arg, []
def hex_str(value: int, size: int = 1, min_length: int = 0) -> str:
if value < 0:
if size == 1:
value += 0x100
elif size == 2:
value += 0x10000
else:
raise Exception("Invalid size (should be 1 or 2)")
if min_length == 0:
min_length = size * 2
return hex(value)[2:].rjust(min_length, "0")