Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*.pyc
.cache/
.pytest_cache/
.mypy_cache/
.pytype/
.idea/
.python-version
.tox/
Expand Down
4 changes: 4 additions & 0 deletions src/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@
# This program is Free Software and is released under the terms of
# the GNU General License
# ----------------------------------------------------------------------

from src.api import debug # noqa
from src.api import errors # noqa
from src.api import errmsg # noqa
86 changes: 54 additions & 32 deletions src/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@
import errno
import shelve
import signal

from functools import wraps
from typing import NamedTuple, List, Any

from typing import NamedTuple
from typing import List
from typing import Any
from typing import Optional
from typing import Callable
from typing import IO
from typing import Iterable

from . import constants
from . import global_
Expand Down Expand Up @@ -35,7 +43,7 @@ class DataRef(NamedTuple):
datas: List[Any]


def read_txt_file(fname):
def read_txt_file(fname: str) -> str:
"""Reads a txt file, regardless of its encoding
"""
encodings = ['utf-8-sig', 'cp1252']
Expand All @@ -54,90 +62,104 @@ def read_txt_file(fname):
return ''


def open_file(fname, mode='rb', encoding='utf-8'):
def open_file(fname: str, mode: str = 'rb', encoding: str = 'utf-8') -> IO[Any]:
""" An open() wrapper for PY2 and PY3 which allows encoding
:param fname: file name (string)
:param mode: file mode (string) optional
:param encoding: optional encoding (string). Ignored in python2 or if not in text mode
:return: an open file handle
"""
if 't' not in mode:
kwargs = {}
else:
kwargs = {'encoding': encoding}
if 't' not in mode or not encoding:
return open(fname, mode)

return open(fname, mode, **kwargs)
return open(fname, mode, encoding=encoding)


def sanitize_filename(fname):
def sanitize_filename(fname: str) -> str:
""" Given a file name (string) returns it with back-slashes reversed.
This is to make all BASIC programs compatible in all OSes
"""
return fname.replace('\\', '/')


def current_data_label():
def current_data_label() -> str:
""" Returns a data label to which all labels must point to, until
a new DATA line is declared
"""
return '__DATA__{0}'.format(len(global_.DATAS))


def flatten_list(x):
def flatten_list(x: Iterable[Any], iterables=(list, )) -> List[Any]:
""" Flattens a nested iterable and returns it as a List.
Nested iterables will be flattened recursively (default only nested lists)
"""
result = []

for l in x:
if not isinstance(l, list):
result.append(l)
for elem in x:
if not isinstance(elem, iterables):
result.append(elem)
else:
result.extend(flatten_list(l))
result.extend(flatten_list(elem))

return result


def parse_int(str_num):
def parse_int(num: Optional[str]) -> Optional[int]:
""" Given an integer number, return its value,
or None if it could not be parsed.
Allowed formats: DECIMAL, HEXA (0xnnn, $nnnn or nnnnh)
:param str_num: (string) the number to be parsed
:return: an integer number or None if it could not be parsedd
An hexadecimal number is ambiguous if it starts with a letter (i.e. A0h can be a label),
and won't be parsed. Such numbers must be prefixed with 0 digit (i.e. 0A0h)
:param num: (string) the number to be parsed
:return: an integer number or None if it could not be parsed
"""
str_num = (str_num or "").strip().upper()
if not str_num:
num = (num or "").strip().upper()
if not num:
return None

base = 10
if str_num.startswith('0X'):
if num[:2] == '0X':
base = 16
str_num = str_num[2:]
if str_num.endswith('H'):
elif num[-1] == 'H':
if num[0] not in '0123456789':
return None
base = 16
str_num = str_num[:-1]
if str_num.startswith('$'):
num = num[:-1]
elif num[0] == '$':
base = 16
str_num = str_num[1:]
num = num[1:]
elif num[0] == '%':
base = 2
num = num[1:]
elif num[-1] == 'B':
if num[0] not in '01':
return None
base = 2
num = num[:-1]

try:
return int(str_num, base)
return int(num, base)
except ValueError:
return None
pass

return None


def load_object(key):
def load_object(key: str) -> Any:
return SHELVE[key] if key in SHELVE else None


def save_object(key, obj):
def save_object(key: str, obj: Any) -> Any:
SHELVE[key] = obj
SHELVE.sync()
return obj


def get_or_create(key, fn):
def get_or_create(key: str, fn: Callable[[], Any]) -> Any:
return load_object(key) or save_object(key, fn())


def get_final_value(symbol: symbols.SYMBOL):
def get_final_value(symbol: symbols.SYMBOL) -> Any:
assert check.is_static(symbol)
result = symbol
while hasattr(result, 'value'):
Expand Down
47 changes: 25 additions & 22 deletions src/arch/zx48k/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2325,15 +2325,19 @@ def emit(mem, optimize=True):
# Optimization patterns: at this point no more than -O2
patterns = [x for x in engine.PATTERNS if x.level <= min(OPTIONS.optimization, 2)]

def output_join(output, new_chunk, optimize=True):
def output_join(output: List[str], new_chunk: List[str], optimize: bool = True):
""" Extends output instruction list
performing a little peep-hole optimization (O1)
"""
base_index = len(output)
output.extend(new_chunk)

if not optimize:
return

i = max(0, base_index - engine.MAXLEN)

while optimize and i < len(output):
while i < len(output):
if not engine.apply_match(output, patterns, index=i): # Nothing changed
i += 1
else:
Expand All @@ -2347,32 +2351,31 @@ def output_join(output, new_chunk, optimize=True):

if optimize and OPTIONS.optimization > 1:
# Remove unused labels
while True:
to_remove = []

for i, ins in enumerate(output):
ins = ins[:-1]
if ins not in TMP_LABELS:
label_used = {}
label_to_delete = {}

for i, ins in enumerate(output):
try_label = ins[:-1]
if try_label in TMP_LABELS:
if label_used.get(try_label):
label_to_delete.pop(try_label, None)
continue

for j, ins2 in enumerate(output):
if j == i:
continue
if ins in Asm.opers(ins2):
break
else:
to_remove.append(i)
label_to_delete[try_label] = i
continue

if not to_remove:
break
for op in Asm.opers(ins):
if op in TMP_LABELS:
label_used[op] = True
label_to_delete.pop(op, None)

to_remove.reverse()
for i in to_remove:
output.pop(i)
for i in sorted(label_to_delete.values(), reverse=True):
output.pop(i)

tmp = output
output = []
output_join(output, tmp)
tmp = output
output = []
output_join(output, tmp)

for i in sorted(REQUIRES):
output.append('#include once <%s>' % i)
Expand Down
Loading