Skip to content

Commit

Permalink
Handle string input to Program.fromAssembly() (#3038)
Browse files Browse the repository at this point in the history
* Add tests for fromAssembly() output (#3036)
* Handle string input to fromAssembly() (Fixes #3036)
* Fixups suggested by @anthrotype
* Add some more typing annotations
* Program.assembly always returns List[str] now
* Add annotation for bytecode
* Move code from setter to fromAssembly
* Remove property
* Fix attribute names
  • Loading branch information
jenskutilek committed Mar 14, 2023
1 parent 444a349 commit f1a75ba
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 18 deletions.
41 changes: 23 additions & 18 deletions Lib/fontTools/ttLib/tables/ttProgram.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
from __future__ import annotations

from fontTools.misc.textTools import num2binary, binary2num, readHex, strjoin
import array
from io import StringIO
from typing import List
import re
import logging

Expand Down Expand Up @@ -1064,30 +1066,35 @@ def _skipWhite(data, pos):


class Program(object):
def __init__(self):
def __init__(self) -> None:
pass

def fromBytecode(self, bytecode):
def fromBytecode(self, bytecode: bytes) -> None:
self.bytecode = array.array("B", bytecode)
if hasattr(self, "assembly"):
del self.assembly

def fromAssembly(self, assembly):
self.assembly = assembly
def fromAssembly(self, assembly: List[str] | str) -> None:
if isinstance(assembly, list):
self.assembly = assembly
elif isinstance(assembly, str):
self.assembly = assembly.splitlines()
else:
raise TypeError(f"expected str or List[str], got {type(assembly).__name__}")
if hasattr(self, "bytecode"):
del self.bytecode

def getBytecode(self):
def getBytecode(self) -> bytes:
if not hasattr(self, "bytecode"):
self._assemble()
return self.bytecode.tobytes()

def getAssembly(self, preserve=True):
def getAssembly(self, preserve=True) -> List[str]:
if not hasattr(self, "assembly"):
self._disassemble(preserve=preserve)
return self.assembly

def toXML(self, writer, ttFont):
def toXML(self, writer, ttFont) -> None:
if (
not hasattr(ttFont, "disassembleInstructions")
or ttFont.disassembleInstructions
Expand Down Expand Up @@ -1128,7 +1135,7 @@ def toXML(self, writer, ttFont):
i = i + 1
if m:
nValues = int(m.group(1))
line = []
line: List[str] = []
j = 0
for j in range(nValues):
if j and not (j % 25):
Expand All @@ -1155,7 +1162,7 @@ def toXML(self, writer, ttFont):
writer.endtag("bytecode")
writer.newline()

def fromXML(self, name, attrs, content, ttFont):
def fromXML(self, name, attrs, content, ttFont) -> None:
if name == "assembly":
self.fromAssembly(strjoin(content))
self._assemble()
Expand All @@ -1164,11 +1171,9 @@ def fromXML(self, name, attrs, content, ttFont):
assert name == "bytecode"
self.fromBytecode(readHex(content))

def _assemble(self):
assembly = getattr(self, "assembly", [])
if isinstance(assembly, type([])):
assembly = " ".join(assembly)
bytecode = []
def _assemble(self) -> None:
assembly = " ".join(getattr(self, "assembly", []))
bytecode: List[int] = []
push = bytecode.append
lenAssembly = len(assembly)
pos = _skipWhite(assembly, 0)
Expand Down Expand Up @@ -1311,7 +1316,7 @@ def _assemble(self):
assert max(bytecode) < 256 and min(bytecode) >= 0
self.bytecode = array.array("B", bytecode)

def _disassemble(self, preserve=False):
def _disassemble(self, preserve=False) -> None:
assembly = []
i = 0
bytecode = getattr(self, "bytecode", [])
Expand Down Expand Up @@ -1376,7 +1381,7 @@ def _disassemble(self, preserve=False):
i = i + 1
self.assembly = assembly

def __bool__(self):
def __bool__(self) -> bool:
"""
>>> p = Program()
>>> bool(p)
Expand Down Expand Up @@ -1406,12 +1411,12 @@ def __bool__(self):

__nonzero__ = __bool__

def __eq__(self, other):
def __eq__(self, other) -> bool:
if type(self) != type(other):
return NotImplemented
return self.__dict__ == other.__dict__

def __ne__(self, other):
def __ne__(self, other) -> bool:
result = self.__eq__(other)
return result if result is NotImplemented else not result

Expand Down
19 changes: 19 additions & 0 deletions Tests/ttLib/tables/ttProgram_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@
TTPROGRAM_TTX = os.path.join(DATA_DIR, "ttProgram.ttx")
# TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")

ASSEMBLY = [
"PUSH[ ]",
"0 4 3",
"INSTCTRL[ ]",
"POP[ ]",
]

BYTECODE = deHexStr(
"403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a"
"191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0"
Expand Down Expand Up @@ -90,6 +97,18 @@ def test__bool__(self):
assert p.assembly.pop() == "SVTCA[0]"
assert not bool(p)

def test_from_assembly_list(self):
p = Program()
p.fromAssembly(ASSEMBLY)
asm = p.getAssembly()
assert ASSEMBLY == asm

def test_from_assembly_str(self):
p = Program()
p.fromAssembly("\n".join(ASSEMBLY))
asm = p.getAssembly()
assert ASSEMBLY == asm

def test_roundtrip(self):
p = Program()
p.fromBytecode(BYTECODE)
Expand Down

0 comments on commit f1a75ba

Please sign in to comment.