diff --git a/projects/archiver/org_photos.py b/projects/archiver/org_photos.py index fd96cd5..c7bfe4e 100644 --- a/projects/archiver/org_photos.py +++ b/projects/archiver/org_photos.py @@ -25,6 +25,7 @@ from shutil import copy2 as copyfile import stat import sys import typing as t +import os from .util import * @@ -350,7 +351,7 @@ def img_info(p: Path) -> ImgInfo: def is_readonly(src: Path) -> bool: statinfo = os.stat(src, dir_fd=None, follow_symlinks=True) - if statinfo.st_flags & stat.UF_IMMUTABLE: + if getattr(statinfo, 'st_flags', 0) & stat.UF_IMMUTABLE: return True return False @@ -364,8 +365,11 @@ def main(): if is_readonly(src): print(f" warning: {src} is read-only, unable to remove") else: - src.unlink() - print(" unlink: ok") + try: + src.unlink() + print(" unlink: ok") + except PermissionError: + print(" unlink: fail") def _copy(src, target): print(f" rename: {target}") @@ -398,8 +402,7 @@ def main(): continue elif ext in ["thm", "lrv", "ico", "sav"] or src.name.startswith("._"): - if opts.destructive: - src.unlink() + _delete(src) continue elif ext in KNOWN_IMG_TYPES: @@ -437,11 +440,7 @@ def main(): elif checksum_path_blocks(src) == checksum_path_blocks(target): print(f" ok: {target}") # src != target && id(src) == id(target); delete src - if opts.destructive: - if is_readonly(src): - print(f" warning: {src} is read-only, unable to remove") - else: - src.unlink() + _delete(src) else: # src != target && id(src) != id(target); replace target with src? diff --git a/projects/shoggoth/src/python/ichor/assembler.py b/projects/shoggoth/src/python/ichor/assembler.py index 688a931..9941e98 100644 --- a/projects/shoggoth/src/python/ichor/assembler.py +++ b/projects/shoggoth/src/python/ichor/assembler.py @@ -15,10 +15,39 @@ def gensym(prefix = None) -> isa.Label: class FuncBuilder(object): def __init__(self) -> None: self._opcodes = [] + self._slots = {} + self._stack = 0 + self._straightline = True def _write(self, op): self._opcodes.append(op) + # Bookkeeping for statically analyzing the stack + if not self._straightline: + return + + match op: + case isa.Label(_): + # Labels abort + self._straightline = False + + case isa.GOTO(_) | isa.ATEST(_): + # Control ops abort + self._straightline = False + + case isa.DROP(n): + self._stack -= n + + case isa.DUP(n): + self._stack += n + + case isa.ROT(_): + pass + + case _: + self._stack -= getattr(op, 'nargs', 0) + self._stack += 1 + def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]): def flatten(o): @@ -30,6 +59,7 @@ class FuncBuilder(object): if isinstance(op, (isa.Opcode, isa.Label)): self._opcodes.append(op) + else: for e in op: self.write(e) @@ -37,6 +67,26 @@ class FuncBuilder(object): def make_label(self, prefix: Optional[str] = ""): return gensym(prefix) + def mark_label(self, prefix: Optional[str] = ""): + l = self.make_label(prefix=prefix) + self.write(l) + return l + + def mark_slot(self, target: Optional[int] = None, prefix: Optional[str] = ""): + if target is not None: + s = gensym(prefix) + self._slots[s] = target + return s + + elif self._straightline: + s = gensym(prefix) + self._slots[s] = self._stack + return s + + else: + raise Exception("Can't automatically generate slots after control flow (label/goto), provide `target=`.") + + def build(self) -> List[isa.Opcode]: """Assemble the written body into fully resolved opcodes.""" @@ -64,6 +114,9 @@ class FuncBuilder(object): case isa.ATEST(isa.Label(_) as l): assembled.append(isa.ATEST(labels[l])) + case isa.SLOT(isa.Label(_) as l): + assembled.append(isa.SLOT(self._slots[l])) + case o: assembled.append(o) @@ -75,30 +128,3 @@ def assemble(builder_cls=FuncBuilder, /, **opcodes: List[isa.Opcode]) -> List[is for o in opcodes: builder.write(o) return builder.build() - - -class LocalBuilder(FuncBuilder): - def __init__(self): - super().__init__() - self._stack = 0 - self._labels = {} - - def _write(self, op): - super()._write(op) - - match op: - case isa.DROP(n): - self._stack -= n - case isa.DUP(n): - self._stack += n - case isa.ROT(_): - pass - case _: - self._stack -= getattr(op, 'nargs', 0) - self._stack += 1 - - def write_local(self, label: isa.Label): - pass - - def get_local(self, label: isa.Label): - pass diff --git a/projects/shoggoth/test/python/ichor/test_assembler.py b/projects/shoggoth/test/python/ichor/test_assembler.py index f68ef76..a6feb69 100644 --- a/projects/shoggoth/test/python/ichor/test_assembler.py +++ b/projects/shoggoth/test/python/ichor/test_assembler.py @@ -46,3 +46,17 @@ def test_self_label(builder: FuncBuilder): isa.DROP(0), isa.GOTO(1), ] + + +def test_local_label(builder: FuncBuilder): + s0 = builder.mark_slot() + builder.write(isa.SLOT(s0)) + + s999 = builder.mark_slot(target=999) + builder.write(isa.SLOT(s999)) + + instrs = builder.build() + assert instrs == [ + isa.SLOT(0), + isa.SLOT(999), + ]