Handle string input to Program.fromAssembly() (#3038)
* 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
This commit is contained in:
parent
444a349ef4
commit
f1a75bacb2
@ -1,8 +1,10 @@
|
|||||||
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
|
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from fontTools.misc.textTools import num2binary, binary2num, readHex, strjoin
|
from fontTools.misc.textTools import num2binary, binary2num, readHex, strjoin
|
||||||
import array
|
import array
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
from typing import List
|
||||||
import re
|
import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -1064,30 +1066,35 @@ def _skipWhite(data, pos):
|
|||||||
|
|
||||||
|
|
||||||
class Program(object):
|
class Program(object):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def fromBytecode(self, bytecode):
|
def fromBytecode(self, bytecode: bytes) -> None:
|
||||||
self.bytecode = array.array("B", bytecode)
|
self.bytecode = array.array("B", bytecode)
|
||||||
if hasattr(self, "assembly"):
|
if hasattr(self, "assembly"):
|
||||||
del self.assembly
|
del self.assembly
|
||||||
|
|
||||||
def fromAssembly(self, assembly):
|
def fromAssembly(self, assembly: List[str] | str) -> None:
|
||||||
self.assembly = assembly
|
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"):
|
if hasattr(self, "bytecode"):
|
||||||
del self.bytecode
|
del self.bytecode
|
||||||
|
|
||||||
def getBytecode(self):
|
def getBytecode(self) -> bytes:
|
||||||
if not hasattr(self, "bytecode"):
|
if not hasattr(self, "bytecode"):
|
||||||
self._assemble()
|
self._assemble()
|
||||||
return self.bytecode.tobytes()
|
return self.bytecode.tobytes()
|
||||||
|
|
||||||
def getAssembly(self, preserve=True):
|
def getAssembly(self, preserve=True) -> List[str]:
|
||||||
if not hasattr(self, "assembly"):
|
if not hasattr(self, "assembly"):
|
||||||
self._disassemble(preserve=preserve)
|
self._disassemble(preserve=preserve)
|
||||||
return self.assembly
|
return self.assembly
|
||||||
|
|
||||||
def toXML(self, writer, ttFont):
|
def toXML(self, writer, ttFont) -> None:
|
||||||
if (
|
if (
|
||||||
not hasattr(ttFont, "disassembleInstructions")
|
not hasattr(ttFont, "disassembleInstructions")
|
||||||
or ttFont.disassembleInstructions
|
or ttFont.disassembleInstructions
|
||||||
@ -1128,7 +1135,7 @@ class Program(object):
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
if m:
|
if m:
|
||||||
nValues = int(m.group(1))
|
nValues = int(m.group(1))
|
||||||
line = []
|
line: List[str] = []
|
||||||
j = 0
|
j = 0
|
||||||
for j in range(nValues):
|
for j in range(nValues):
|
||||||
if j and not (j % 25):
|
if j and not (j % 25):
|
||||||
@ -1155,7 +1162,7 @@ class Program(object):
|
|||||||
writer.endtag("bytecode")
|
writer.endtag("bytecode")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
|
|
||||||
def fromXML(self, name, attrs, content, ttFont):
|
def fromXML(self, name, attrs, content, ttFont) -> None:
|
||||||
if name == "assembly":
|
if name == "assembly":
|
||||||
self.fromAssembly(strjoin(content))
|
self.fromAssembly(strjoin(content))
|
||||||
self._assemble()
|
self._assemble()
|
||||||
@ -1164,11 +1171,9 @@ class Program(object):
|
|||||||
assert name == "bytecode"
|
assert name == "bytecode"
|
||||||
self.fromBytecode(readHex(content))
|
self.fromBytecode(readHex(content))
|
||||||
|
|
||||||
def _assemble(self):
|
def _assemble(self) -> None:
|
||||||
assembly = getattr(self, "assembly", [])
|
assembly = " ".join(getattr(self, "assembly", []))
|
||||||
if isinstance(assembly, type([])):
|
bytecode: List[int] = []
|
||||||
assembly = " ".join(assembly)
|
|
||||||
bytecode = []
|
|
||||||
push = bytecode.append
|
push = bytecode.append
|
||||||
lenAssembly = len(assembly)
|
lenAssembly = len(assembly)
|
||||||
pos = _skipWhite(assembly, 0)
|
pos = _skipWhite(assembly, 0)
|
||||||
@ -1311,7 +1316,7 @@ class Program(object):
|
|||||||
assert max(bytecode) < 256 and min(bytecode) >= 0
|
assert max(bytecode) < 256 and min(bytecode) >= 0
|
||||||
self.bytecode = array.array("B", bytecode)
|
self.bytecode = array.array("B", bytecode)
|
||||||
|
|
||||||
def _disassemble(self, preserve=False):
|
def _disassemble(self, preserve=False) -> None:
|
||||||
assembly = []
|
assembly = []
|
||||||
i = 0
|
i = 0
|
||||||
bytecode = getattr(self, "bytecode", [])
|
bytecode = getattr(self, "bytecode", [])
|
||||||
@ -1376,7 +1381,7 @@ class Program(object):
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
self.assembly = assembly
|
self.assembly = assembly
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self) -> bool:
|
||||||
"""
|
"""
|
||||||
>>> p = Program()
|
>>> p = Program()
|
||||||
>>> bool(p)
|
>>> bool(p)
|
||||||
@ -1406,12 +1411,12 @@ class Program(object):
|
|||||||
|
|
||||||
__nonzero__ = __bool__
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> bool:
|
||||||
if type(self) != type(other):
|
if type(self) != type(other):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return self.__dict__ == other.__dict__
|
return self.__dict__ == other.__dict__
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other) -> bool:
|
||||||
result = self.__eq__(other)
|
result = self.__eq__(other)
|
||||||
return result if result is NotImplemented else not result
|
return result if result is NotImplemented else not result
|
||||||
|
|
||||||
|
@ -12,6 +12,13 @@ DATA_DIR = os.path.join(CURR_DIR, "data")
|
|||||||
TTPROGRAM_TTX = os.path.join(DATA_DIR, "ttProgram.ttx")
|
TTPROGRAM_TTX = os.path.join(DATA_DIR, "ttProgram.ttx")
|
||||||
# TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")
|
# TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")
|
||||||
|
|
||||||
|
ASSEMBLY = [
|
||||||
|
"PUSH[ ]",
|
||||||
|
"0 4 3",
|
||||||
|
"INSTCTRL[ ]",
|
||||||
|
"POP[ ]",
|
||||||
|
]
|
||||||
|
|
||||||
BYTECODE = deHexStr(
|
BYTECODE = deHexStr(
|
||||||
"403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a"
|
"403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a"
|
||||||
"191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0"
|
"191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0"
|
||||||
@ -90,6 +97,18 @@ class ProgramTest(unittest.TestCase):
|
|||||||
assert p.assembly.pop() == "SVTCA[0]"
|
assert p.assembly.pop() == "SVTCA[0]"
|
||||||
assert not bool(p)
|
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):
|
def test_roundtrip(self):
|
||||||
p = Program()
|
p = Program()
|
||||||
p.fromBytecode(BYTECODE)
|
p.fromBytecode(BYTECODE)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user