From a79644aa238e3a9465d5ca44de2ceb7a764d93d3 Mon Sep 17 00:00:00 2001
From: Reid 'arrdem' McKenzie <me@arrdem.com>
Date: Tue, 9 Aug 2022 10:05:26 -0600
Subject: [PATCH] Integrate marking labels/slots

---
 projects/archiver/org_photos.py               | 19 +++--
 .../shoggoth/src/python/ichor/assembler.py    | 80 ++++++++++++-------
 .../test/python/ichor/test_assembler.py       | 14 ++++
 3 files changed, 76 insertions(+), 37 deletions(-)

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),
+    ]