ttProgram.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. """ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
  2. from __future__ import annotations
  3. from fontTools.misc.textTools import num2binary, binary2num, readHex, strjoin
  4. import array
  5. from io import StringIO
  6. from typing import List
  7. import re
  8. import logging
  9. log = logging.getLogger(__name__)
  10. # fmt: off
  11. # first, the list of instructions that eat bytes or words from the instruction stream
  12. streamInstructions = [
  13. #
  14. # opcode mnemonic argBits descriptive name pops pushes eats from instruction stream pushes
  15. #
  16. (0x40, 'NPUSHB', 0, 'PushNBytes', 0, -1), # n, b1, b2,...bn b1,b2...bn
  17. (0x41, 'NPUSHW', 0, 'PushNWords', 0, -1), # n, w1, w2,...w w1,w2...wn
  18. (0xb0, 'PUSHB', 3, 'PushBytes', 0, -1), # b0, b1,..bn b0, b1, ...,bn
  19. (0xb8, 'PUSHW', 3, 'PushWords', 0, -1), # w0,w1,..wn w0 ,w1, ...wn
  20. ]
  21. # next, the list of "normal" instructions
  22. instructions = [
  23. #
  24. # opcode mnemonic argBits descriptive name pops pushes eats from instruction stream pushes
  25. #
  26. (0x7f, 'AA', 0, 'AdjustAngle', 1, 0), # p -
  27. (0x64, 'ABS', 0, 'Absolute', 1, 1), # n |n|
  28. (0x60, 'ADD', 0, 'Add', 2, 1), # n2, n1 (n1 + n2)
  29. (0x27, 'ALIGNPTS', 0, 'AlignPts', 2, 0), # p2, p1 -
  30. (0x3c, 'ALIGNRP', 0, 'AlignRelativePt', -1, 0), # p1, p2, ... , ploopvalue -
  31. (0x5a, 'AND', 0, 'LogicalAnd', 2, 1), # e2, e1 b
  32. (0x2b, 'CALL', 0, 'CallFunction', 1, 0), # f -
  33. (0x67, 'CEILING', 0, 'Ceiling', 1, 1), # n ceil(n)
  34. (0x25, 'CINDEX', 0, 'CopyXToTopStack', 1, 1), # k ek
  35. (0x22, 'CLEAR', 0, 'ClearStack', -1, 0), # all items on the stack -
  36. (0x4f, 'DEBUG', 0, 'DebugCall', 1, 0), # n -
  37. (0x73, 'DELTAC1', 0, 'DeltaExceptionC1', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
  38. (0x74, 'DELTAC2', 0, 'DeltaExceptionC2', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
  39. (0x75, 'DELTAC3', 0, 'DeltaExceptionC3', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
  40. (0x5d, 'DELTAP1', 0, 'DeltaExceptionP1', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
  41. (0x71, 'DELTAP2', 0, 'DeltaExceptionP2', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
  42. (0x72, 'DELTAP3', 0, 'DeltaExceptionP3', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
  43. (0x24, 'DEPTH', 0, 'GetDepthStack', 0, 1), # - n
  44. (0x62, 'DIV', 0, 'Divide', 2, 1), # n2, n1 (n1 * 64)/ n2
  45. (0x20, 'DUP', 0, 'DuplicateTopStack', 1, 2), # e e, e
  46. (0x59, 'EIF', 0, 'EndIf', 0, 0), # - -
  47. (0x1b, 'ELSE', 0, 'Else', 0, 0), # - -
  48. (0x2d, 'ENDF', 0, 'EndFunctionDefinition', 0, 0), # - -
  49. (0x54, 'EQ', 0, 'Equal', 2, 1), # e2, e1 b
  50. (0x57, 'EVEN', 0, 'Even', 1, 1), # e b
  51. (0x2c, 'FDEF', 0, 'FunctionDefinition', 1, 0), # f -
  52. (0x4e, 'FLIPOFF', 0, 'SetAutoFlipOff', 0, 0), # - -
  53. (0x4d, 'FLIPON', 0, 'SetAutoFlipOn', 0, 0), # - -
  54. (0x80, 'FLIPPT', 0, 'FlipPoint', -1, 0), # p1, p2, ..., ploopvalue -
  55. (0x82, 'FLIPRGOFF', 0, 'FlipRangeOff', 2, 0), # h, l -
  56. (0x81, 'FLIPRGON', 0, 'FlipRangeOn', 2, 0), # h, l -
  57. (0x66, 'FLOOR', 0, 'Floor', 1, 1), # n floor(n)
  58. (0x46, 'GC', 1, 'GetCoordOnPVector', 1, 1), # p c
  59. (0x88, 'GETINFO', 0, 'GetInfo', 1, 1), # selector result
  60. (0x91, 'GETVARIATION', 0, 'GetVariation', 0, -1), # - a1,..,an
  61. (0x0d, 'GFV', 0, 'GetFVector', 0, 2), # - px, py
  62. (0x0c, 'GPV', 0, 'GetPVector', 0, 2), # - px, py
  63. (0x52, 'GT', 0, 'GreaterThan', 2, 1), # e2, e1 b
  64. (0x53, 'GTEQ', 0, 'GreaterThanOrEqual', 2, 1), # e2, e1 b
  65. (0x89, 'IDEF', 0, 'InstructionDefinition', 1, 0), # f -
  66. (0x58, 'IF', 0, 'If', 1, 0), # e -
  67. (0x8e, 'INSTCTRL', 0, 'SetInstrExecControl', 2, 0), # s, v -
  68. (0x39, 'IP', 0, 'InterpolatePts', -1, 0), # p1, p2, ... , ploopvalue -
  69. (0x0f, 'ISECT', 0, 'MovePtToIntersect', 5, 0), # a1, a0, b1, b0, p -
  70. (0x30, 'IUP', 1, 'InterpolateUntPts', 0, 0), # - -
  71. (0x1c, 'JMPR', 0, 'Jump', 1, 0), # offset -
  72. (0x79, 'JROF', 0, 'JumpRelativeOnFalse', 2, 0), # e, offset -
  73. (0x78, 'JROT', 0, 'JumpRelativeOnTrue', 2, 0), # e, offset -
  74. (0x2a, 'LOOPCALL', 0, 'LoopAndCallFunction', 2, 0), # f, count -
  75. (0x50, 'LT', 0, 'LessThan', 2, 1), # e2, e1 b
  76. (0x51, 'LTEQ', 0, 'LessThenOrEqual', 2, 1), # e2, e1 b
  77. (0x8b, 'MAX', 0, 'Maximum', 2, 1), # e2, e1 max(e1, e2)
  78. (0x49, 'MD', 1, 'MeasureDistance', 2, 1), # p2,p1 d
  79. (0x2e, 'MDAP', 1, 'MoveDirectAbsPt', 1, 0), # p -
  80. (0xc0, 'MDRP', 5, 'MoveDirectRelPt', 1, 0), # p -
  81. (0x3e, 'MIAP', 1, 'MoveIndirectAbsPt', 2, 0), # n, p -
  82. (0x8c, 'MIN', 0, 'Minimum', 2, 1), # e2, e1 min(e1, e2)
  83. (0x26, 'MINDEX', 0, 'MoveXToTopStack', 1, 1), # k ek
  84. (0xe0, 'MIRP', 5, 'MoveIndirectRelPt', 2, 0), # n, p -
  85. (0x4b, 'MPPEM', 0, 'MeasurePixelPerEm', 0, 1), # - ppem
  86. (0x4c, 'MPS', 0, 'MeasurePointSize', 0, 1), # - pointSize
  87. (0x3a, 'MSIRP', 1, 'MoveStackIndirRelPt', 2, 0), # d, p -
  88. (0x63, 'MUL', 0, 'Multiply', 2, 1), # n2, n1 (n1 * n2)/64
  89. (0x65, 'NEG', 0, 'Negate', 1, 1), # n -n
  90. (0x55, 'NEQ', 0, 'NotEqual', 2, 1), # e2, e1 b
  91. (0x5c, 'NOT', 0, 'LogicalNot', 1, 1), # e ( not e )
  92. (0x6c, 'NROUND', 2, 'NoRound', 1, 1), # n1 n2
  93. (0x56, 'ODD', 0, 'Odd', 1, 1), # e b
  94. (0x5b, 'OR', 0, 'LogicalOr', 2, 1), # e2, e1 b
  95. (0x21, 'POP', 0, 'PopTopStack', 1, 0), # e -
  96. (0x45, 'RCVT', 0, 'ReadCVT', 1, 1), # location value
  97. (0x7d, 'RDTG', 0, 'RoundDownToGrid', 0, 0), # - -
  98. (0x7a, 'ROFF', 0, 'RoundOff', 0, 0), # - -
  99. (0x8a, 'ROLL', 0, 'RollTopThreeStack', 3, 3), # a,b,c b,a,c
  100. (0x68, 'ROUND', 2, 'Round', 1, 1), # n1 n2
  101. (0x43, 'RS', 0, 'ReadStore', 1, 1), # n v
  102. (0x3d, 'RTDG', 0, 'RoundToDoubleGrid', 0, 0), # - -
  103. (0x18, 'RTG', 0, 'RoundToGrid', 0, 0), # - -
  104. (0x19, 'RTHG', 0, 'RoundToHalfGrid', 0, 0), # - -
  105. (0x7c, 'RUTG', 0, 'RoundUpToGrid', 0, 0), # - -
  106. (0x77, 'S45ROUND', 0, 'SuperRound45Degrees', 1, 0), # n -
  107. (0x7e, 'SANGW', 0, 'SetAngleWeight', 1, 0), # weight -
  108. (0x85, 'SCANCTRL', 0, 'ScanConversionControl', 1, 0), # n -
  109. (0x8d, 'SCANTYPE', 0, 'ScanType', 1, 0), # n -
  110. (0x48, 'SCFS', 0, 'SetCoordFromStackFP', 2, 0), # c, p -
  111. (0x1d, 'SCVTCI', 0, 'SetCVTCutIn', 1, 0), # n -
  112. (0x5e, 'SDB', 0, 'SetDeltaBaseInGState', 1, 0), # n -
  113. (0x86, 'SDPVTL', 1, 'SetDualPVectorToLine', 2, 0), # p2, p1 -
  114. (0x5f, 'SDS', 0, 'SetDeltaShiftInGState', 1, 0), # n -
  115. (0x0b, 'SFVFS', 0, 'SetFVectorFromStack', 2, 0), # y, x -
  116. (0x04, 'SFVTCA', 1, 'SetFVectorToAxis', 0, 0), # - -
  117. (0x08, 'SFVTL', 1, 'SetFVectorToLine', 2, 0), # p2, p1 -
  118. (0x0e, 'SFVTPV', 0, 'SetFVectorToPVector', 0, 0), # - -
  119. (0x34, 'SHC', 1, 'ShiftContourByLastPt', 1, 0), # c -
  120. (0x32, 'SHP', 1, 'ShiftPointByLastPoint', -1, 0), # p1, p2, ..., ploopvalue -
  121. (0x38, 'SHPIX', 0, 'ShiftZoneByPixel', -1, 0), # d, p1, p2, ..., ploopvalue -
  122. (0x36, 'SHZ', 1, 'ShiftZoneByLastPoint', 1, 0), # e -
  123. (0x17, 'SLOOP', 0, 'SetLoopVariable', 1, 0), # n -
  124. (0x1a, 'SMD', 0, 'SetMinimumDistance', 1, 0), # distance -
  125. (0x0a, 'SPVFS', 0, 'SetPVectorFromStack', 2, 0), # y, x -
  126. (0x02, 'SPVTCA', 1, 'SetPVectorToAxis', 0, 0), # - -
  127. (0x06, 'SPVTL', 1, 'SetPVectorToLine', 2, 0), # p2, p1 -
  128. (0x76, 'SROUND', 0, 'SuperRound', 1, 0), # n -
  129. (0x10, 'SRP0', 0, 'SetRefPoint0', 1, 0), # p -
  130. (0x11, 'SRP1', 0, 'SetRefPoint1', 1, 0), # p -
  131. (0x12, 'SRP2', 0, 'SetRefPoint2', 1, 0), # p -
  132. (0x1f, 'SSW', 0, 'SetSingleWidth', 1, 0), # n -
  133. (0x1e, 'SSWCI', 0, 'SetSingleWidthCutIn', 1, 0), # n -
  134. (0x61, 'SUB', 0, 'Subtract', 2, 1), # n2, n1 (n1 - n2)
  135. (0x00, 'SVTCA', 1, 'SetFPVectorToAxis', 0, 0), # - -
  136. (0x23, 'SWAP', 0, 'SwapTopStack', 2, 2), # e2, e1 e1, e2
  137. (0x13, 'SZP0', 0, 'SetZonePointer0', 1, 0), # n -
  138. (0x14, 'SZP1', 0, 'SetZonePointer1', 1, 0), # n -
  139. (0x15, 'SZP2', 0, 'SetZonePointer2', 1, 0), # n -
  140. (0x16, 'SZPS', 0, 'SetZonePointerS', 1, 0), # n -
  141. (0x29, 'UTP', 0, 'UnTouchPt', 1, 0), # p -
  142. (0x70, 'WCVTF', 0, 'WriteCVTInFUnits', 2, 0), # n, l -
  143. (0x44, 'WCVTP', 0, 'WriteCVTInPixels', 2, 0), # v, l -
  144. (0x42, 'WS', 0, 'WriteStore', 2, 0), # v, l -
  145. ]
  146. # fmt: on
  147. def bitRepr(value: int, bits: int) -> str:
  148. s = ""
  149. for i in range(bits):
  150. s = "01"[value & 0x1] + s
  151. value = value >> 1
  152. return s
  153. _mnemonicPat = re.compile(r"[A-Z][A-Z0-9]*$")
  154. def _makeDict(
  155. instructionList: list[tuple[int, str, int, str, int, int]],
  156. ) -> tuple[dict, dict]:
  157. opcodeDict = {}
  158. mnemonicDict = {}
  159. for op, mnemonic, argBits, name, pops, pushes in instructionList:
  160. assert _mnemonicPat.match(mnemonic)
  161. mnemonicDict[mnemonic] = op, argBits, name
  162. if argBits:
  163. argoffset = op
  164. for i in range(1 << argBits):
  165. opcodeDict[op + i] = mnemonic, argBits, argoffset, name
  166. else:
  167. opcodeDict[op] = mnemonic, 0, 0, name
  168. return opcodeDict, mnemonicDict
  169. streamOpcodeDict, streamMnemonicDict = _makeDict(streamInstructions)
  170. opcodeDict, mnemonicDict = _makeDict(instructions)
  171. class tt_instructions_error(Exception):
  172. def __init__(self, error: str) -> None:
  173. self.error = error
  174. def __str__(self) -> str:
  175. return "TT instructions error: %s" % repr(self.error)
  176. _comment = r"/\*.*?\*/"
  177. _instruction = r"([A-Z][A-Z0-9]*)\s*\[(.*?)\]"
  178. _number = r"-?[0-9]+"
  179. _token = "(%s)|(%s)|(%s)" % (_instruction, _number, _comment)
  180. _tokenRE = re.compile(_token)
  181. _whiteRE = re.compile(r"\s*")
  182. _pushCountPat = re.compile(r"[A-Z][A-Z0-9]*\s*\[.*?\]\s*/\* ([0-9]+).*?\*/")
  183. _indentRE = re.compile(r"^FDEF|IF|ELSE\[ \]\t.+")
  184. _unindentRE = re.compile(r"^ELSE|ENDF|EIF\[ \]\t.+")
  185. def _skipWhite(data: str, pos: int) -> int:
  186. m = _whiteRE.match(data, pos)
  187. newPos = m.regs[0][1]
  188. assert newPos >= pos
  189. return newPos
  190. class Program:
  191. def __init__(self) -> None:
  192. pass
  193. def fromBytecode(self, bytecode: bytes) -> None:
  194. self.bytecode = array.array("B", bytecode)
  195. if hasattr(self, "assembly"):
  196. del self.assembly
  197. def fromAssembly(self, assembly: list[str] | str) -> None:
  198. if isinstance(assembly, list):
  199. self.assembly = assembly
  200. elif isinstance(assembly, str):
  201. self.assembly = assembly.splitlines()
  202. else:
  203. raise TypeError(f"expected str or List[str], got {type(assembly).__name__}")
  204. if hasattr(self, "bytecode"):
  205. del self.bytecode
  206. def getBytecode(self) -> bytes:
  207. if not hasattr(self, "bytecode"):
  208. self._assemble()
  209. return self.bytecode.tobytes()
  210. def getAssembly(self, preserve=True) -> List[str]:
  211. if not hasattr(self, "assembly"):
  212. self._disassemble(preserve=preserve)
  213. return self.assembly
  214. def toXML(self, writer, ttFont) -> None:
  215. if (
  216. not hasattr(ttFont, "disassembleInstructions")
  217. or ttFont.disassembleInstructions
  218. ):
  219. try:
  220. assembly = self.getAssembly()
  221. except:
  222. import traceback
  223. tmp = StringIO()
  224. traceback.print_exc(file=tmp)
  225. msg = "An exception occurred during the decompilation of glyph program:\n\n"
  226. msg += tmp.getvalue()
  227. log.error(msg)
  228. writer.begintag("bytecode")
  229. writer.newline()
  230. writer.comment(msg.strip())
  231. writer.newline()
  232. writer.dumphex(self.getBytecode())
  233. writer.endtag("bytecode")
  234. writer.newline()
  235. else:
  236. if not assembly:
  237. return
  238. writer.begintag("assembly")
  239. writer.newline()
  240. i = 0
  241. indent = 0
  242. nInstr = len(assembly)
  243. while i < nInstr:
  244. instr = assembly[i]
  245. if _unindentRE.match(instr):
  246. indent -= 1
  247. writer.write(writer.indentwhite * indent)
  248. writer.write(instr)
  249. writer.newline()
  250. m = _pushCountPat.match(instr)
  251. i = i + 1
  252. if m:
  253. nValues = int(m.group(1))
  254. line: List[str] = []
  255. j = 0
  256. for j in range(nValues):
  257. if j and not (j % 25):
  258. writer.write(writer.indentwhite * indent)
  259. writer.write(" ".join(line))
  260. writer.newline()
  261. line = []
  262. line.append(assembly[i + j])
  263. writer.write(writer.indentwhite * indent)
  264. writer.write(" ".join(line))
  265. writer.newline()
  266. i = i + j + 1
  267. if _indentRE.match(instr):
  268. indent += 1
  269. writer.endtag("assembly")
  270. writer.newline()
  271. else:
  272. bytecode = self.getBytecode()
  273. if not bytecode:
  274. return
  275. writer.begintag("bytecode")
  276. writer.newline()
  277. writer.dumphex(bytecode)
  278. writer.endtag("bytecode")
  279. writer.newline()
  280. def fromXML(self, name, attrs, content, ttFont) -> None:
  281. if name == "assembly":
  282. self.fromAssembly(strjoin(content))
  283. self._assemble()
  284. del self.assembly
  285. else:
  286. assert name == "bytecode"
  287. self.fromBytecode(readHex(content))
  288. def _assemble(self) -> None:
  289. assembly = " ".join(getattr(self, "assembly", []))
  290. bytecode: List[int] = []
  291. push = bytecode.append
  292. lenAssembly = len(assembly)
  293. pos = _skipWhite(assembly, 0)
  294. while pos < lenAssembly:
  295. m = _tokenRE.match(assembly, pos)
  296. if m is None:
  297. raise tt_instructions_error(
  298. "Syntax error in TT program (%s)" % assembly[pos - 5 : pos + 15]
  299. )
  300. dummy, mnemonic, arg, number, comment = m.groups()
  301. pos = m.regs[0][1]
  302. if comment:
  303. pos = _skipWhite(assembly, pos)
  304. continue
  305. arg = arg.strip()
  306. if mnemonic.startswith("INSTR"):
  307. # Unknown instruction
  308. op = int(mnemonic[5:])
  309. push(op)
  310. elif mnemonic not in ("PUSH", "NPUSHB", "NPUSHW", "PUSHB", "PUSHW"):
  311. op, argBits, name = mnemonicDict[mnemonic]
  312. if len(arg) != argBits:
  313. raise tt_instructions_error(
  314. "Incorrect number of argument bits (%s[%s])" % (mnemonic, arg)
  315. )
  316. if arg:
  317. arg = binary2num(arg)
  318. push(op + arg)
  319. else:
  320. push(op)
  321. else:
  322. args = []
  323. pos = _skipWhite(assembly, pos)
  324. while pos < lenAssembly:
  325. m = _tokenRE.match(assembly, pos)
  326. if m is None:
  327. raise tt_instructions_error(
  328. "Syntax error in TT program (%s)" % assembly[pos : pos + 15]
  329. )
  330. dummy, _mnemonic, arg, number, comment = m.groups()
  331. if number is None and comment is None:
  332. break
  333. pos = m.regs[0][1]
  334. pos = _skipWhite(assembly, pos)
  335. if comment is not None:
  336. continue
  337. args.append(int(number))
  338. nArgs = len(args)
  339. if mnemonic == "PUSH":
  340. # Automatically choose the most compact representation
  341. nWords = 0
  342. while nArgs:
  343. while (
  344. nWords < nArgs
  345. and nWords < 255
  346. and not (0 <= args[nWords] <= 255)
  347. ):
  348. nWords += 1
  349. nBytes = 0
  350. while (
  351. nWords + nBytes < nArgs
  352. and nBytes < 255
  353. and 0 <= args[nWords + nBytes] <= 255
  354. ):
  355. nBytes += 1
  356. if (
  357. nBytes < 2
  358. and nWords + nBytes < 255
  359. and nWords + nBytes != nArgs
  360. ):
  361. # Will write bytes as words
  362. nWords += nBytes
  363. continue
  364. # Write words
  365. if nWords:
  366. if nWords <= 8:
  367. op, argBits, name = streamMnemonicDict["PUSHW"]
  368. op = op + nWords - 1
  369. push(op)
  370. else:
  371. op, argBits, name = streamMnemonicDict["NPUSHW"]
  372. push(op)
  373. push(nWords)
  374. for value in args[:nWords]:
  375. assert -32768 <= value < 32768, (
  376. "PUSH value out of range %d" % value
  377. )
  378. push((value >> 8) & 0xFF)
  379. push(value & 0xFF)
  380. # Write bytes
  381. if nBytes:
  382. pass
  383. if nBytes <= 8:
  384. op, argBits, name = streamMnemonicDict["PUSHB"]
  385. op = op + nBytes - 1
  386. push(op)
  387. else:
  388. op, argBits, name = streamMnemonicDict["NPUSHB"]
  389. push(op)
  390. push(nBytes)
  391. for value in args[nWords : nWords + nBytes]:
  392. push(value)
  393. nTotal = nWords + nBytes
  394. args = args[nTotal:]
  395. nArgs -= nTotal
  396. nWords = 0
  397. else:
  398. # Write exactly what we've been asked to
  399. words = mnemonic[-1] == "W"
  400. op, argBits, name = streamMnemonicDict[mnemonic]
  401. if mnemonic[0] != "N":
  402. assert nArgs <= 8, nArgs
  403. op = op + nArgs - 1
  404. push(op)
  405. else:
  406. assert nArgs < 256
  407. push(op)
  408. push(nArgs)
  409. if words:
  410. for value in args:
  411. assert -32768 <= value < 32768, (
  412. "PUSHW value out of range %d" % value
  413. )
  414. push((value >> 8) & 0xFF)
  415. push(value & 0xFF)
  416. else:
  417. for value in args:
  418. assert 0 <= value < 256, (
  419. "PUSHB value out of range %d" % value
  420. )
  421. push(value)
  422. pos = _skipWhite(assembly, pos)
  423. if bytecode:
  424. assert max(bytecode) < 256 and min(bytecode) >= 0
  425. self.bytecode = array.array("B", bytecode)
  426. def _disassemble(self, preserve=False) -> None:
  427. assembly = []
  428. i = 0
  429. bytecode = getattr(self, "bytecode", [])
  430. numBytecode = len(bytecode)
  431. while i < numBytecode:
  432. op = bytecode[i]
  433. try:
  434. mnemonic, argBits, argoffset, name = opcodeDict[op]
  435. except KeyError:
  436. if op in streamOpcodeDict:
  437. values = []
  438. # Merge consecutive PUSH operations
  439. while bytecode[i] in streamOpcodeDict:
  440. op = bytecode[i]
  441. mnemonic, argBits, argoffset, name = streamOpcodeDict[op]
  442. words = mnemonic[-1] == "W"
  443. if argBits:
  444. nValues = op - argoffset + 1
  445. else:
  446. i = i + 1
  447. nValues = bytecode[i]
  448. i = i + 1
  449. assert nValues > 0
  450. if not words:
  451. for j in range(nValues):
  452. value = bytecode[i]
  453. values.append(repr(value))
  454. i = i + 1
  455. else:
  456. for j in range(nValues):
  457. # cast to signed int16
  458. value = (bytecode[i] << 8) | bytecode[i + 1]
  459. if value >= 0x8000:
  460. value = value - 0x10000
  461. values.append(repr(value))
  462. i = i + 2
  463. if preserve:
  464. break
  465. if not preserve:
  466. mnemonic = "PUSH"
  467. nValues = len(values)
  468. if nValues == 1:
  469. assembly.append("%s[ ] /* 1 value pushed */" % mnemonic)
  470. else:
  471. assembly.append(
  472. "%s[ ] /* %s values pushed */" % (mnemonic, nValues)
  473. )
  474. assembly.extend(values)
  475. else:
  476. assembly.append("INSTR%d[ ]" % op)
  477. i = i + 1
  478. else:
  479. if argBits:
  480. assembly.append(
  481. mnemonic
  482. + "[%s] /* %s */" % (num2binary(op - argoffset, argBits), name)
  483. )
  484. else:
  485. assembly.append(mnemonic + "[ ] /* %s */" % name)
  486. i = i + 1
  487. self.assembly = assembly
  488. def __bool__(self) -> bool:
  489. """
  490. >>> p = Program()
  491. >>> bool(p)
  492. False
  493. >>> bc = array.array("B", [0])
  494. >>> p.fromBytecode(bc)
  495. >>> bool(p)
  496. True
  497. >>> p.bytecode.pop()
  498. 0
  499. >>> bool(p)
  500. False
  501. >>> p = Program()
  502. >>> asm = ['SVTCA[0]']
  503. >>> p.fromAssembly(asm)
  504. >>> bool(p)
  505. True
  506. >>> p.assembly.pop()
  507. 'SVTCA[0]'
  508. >>> bool(p)
  509. False
  510. """
  511. return (hasattr(self, "assembly") and len(self.assembly) > 0) or (
  512. hasattr(self, "bytecode") and len(self.bytecode) > 0
  513. )
  514. __nonzero__ = __bool__
  515. def __eq__(self, other) -> bool:
  516. if type(self) != type(other):
  517. return NotImplemented
  518. return self.__dict__ == other.__dict__
  519. def __ne__(self, other) -> bool:
  520. result = self.__eq__(other)
  521. return result if result is NotImplemented else not result
  522. def _test():
  523. """
  524. >>> _test()
  525. True
  526. """
  527. bc = b"""@;:9876543210/.-,+*)(\'&%$#"! \037\036\035\034\033\032\031\030\027\026\025\024\023\022\021\020\017\016\015\014\013\012\011\010\007\006\005\004\003\002\001\000,\001\260\030CXEj\260\031C`\260F#D#\020 \260FN\360M/\260\000\022\033!#\0213Y-,\001\260\030CX\260\005+\260\000\023K\260\024PX\261\000@8Y\260\006+\033!#\0213Y-,\001\260\030CXN\260\003%\020\362!\260\000\022M\033 E\260\004%\260\004%#Jad\260(RX!#\020\326\033\260\003%\020\362!\260\000\022YY-,\260\032CX!!\033\260\002%\260\002%I\260\003%\260\003%Ja d\260\020PX!!!\033\260\003%\260\003%I\260\000PX\260\000PX\270\377\3428!\033\260\0208!Y\033\260\000RX\260\0368!\033\270\377\3608!YYYY-,\001\260\030CX\260\005+\260\000\023K\260\024PX\271\000\000\377\3008Y\260\006+\033!#\0213Y-,N\001\212\020\261F\031CD\260\000\024\261\000F\342\260\000\025\271\000\000\377\3608\000\260\000<\260(+\260\002%\020\260\000<-,\001\030\260\000/\260\001\024\362\260\001\023\260\001\025M\260\000\022-,\001\260\030CX\260\005+\260\000\023\271\000\000\377\3408\260\006+\033!#\0213Y-,\001\260\030CXEdj#Edi\260\031Cd``\260F#D#\020 \260F\360/\260\000\022\033!! \212 \212RX\0213\033!!YY-,\001\261\013\012C#Ce\012-,\000\261\012\013C#C\013-,\000\260F#p\261\001F>\001\260F#p\261\002FE:\261\002\000\010\015-,\260\022+\260\002%E\260\002%Ej\260@\213`\260\002%#D!!!-,\260\023+\260\002%E\260\002%Ej\270\377\300\214`\260\002%#D!!!-,\260\000\260\022+!!!-,\260\000\260\023+!!!-,\001\260\006C\260\007Ce\012-, i\260@a\260\000\213 \261,\300\212\214\270\020\000b`+\014d#da\\X\260\003aY-,\261\000\003%EhT\260\034KPZX\260\003%E\260\003%E`h \260\004%#D\260\004%#D\033\260\003% Eh \212#D\260\003%Eh`\260\003%#DY-,\260\003% Eh \212#D\260\003%Edhe`\260\004%\260\001`#D-,\260\011CX\207!\300\033\260\022CX\207E\260\021+\260G#D\260Gz\344\033\003\212E\030i \260G#D\212\212\207 \260\240QX\260\021+\260G#D\260Gz\344\033!\260Gz\344YYY\030-, \212E#Eh`D-,EjB-,\001\030/-,\001\260\030CX\260\004%\260\004%Id#Edi\260@\213a \260\200bj\260\002%\260\002%a\214\260\031C`\260F#D!\212\020\260F\366!\033!!!!Y-,\001\260\030CX\260\002%E\260\002%Ed`j\260\003%Eja \260\004%Ej \212\213e\260\004%#D\214\260\003%#D!!\033 EjD EjDY-,\001 E\260\000U\260\030CZXEh#Ei\260@\213a \260\200bj \212#a \260\003%\213e\260\004%#D\214\260\003%#D!!\033!!\260\031+Y-,\001\212\212Ed#EdadB-,\260\004%\260\004%\260\031+\260\030CX\260\004%\260\004%\260\003%\260\033+\001\260\002%C\260@T\260\002%C\260\000TZX\260\003% E\260@aDY\260\002%C\260\000T\260\002%C\260@TZX\260\004% E\260@`DYY!!!!-,\001KRXC\260\002%E#aD\033!!Y-,\001KRXC\260\002%E#`D\033!!Y-,KRXED\033!!Y-,\001 \260\003%#I\260@`\260 c \260\000RX#\260\002%8#\260\002%e8\000\212c8\033!!!!!Y\001-,KPXED\033!!Y-,\001\260\005%\020# \212\365\000\260\001`#\355\354-,\001\260\005%\020# \212\365\000\260\001a#\355\354-,\001\260\006%\020\365\000\355\354-,F#F`\212\212F# F\212`\212a\270\377\200b# \020#\212\261KK\212pE` \260\000PX\260\001a\270\377\272\213\033\260F\214Y\260\020`h\001:-, E\260\003%FRX\260\002%F ha\260\003%\260\003%?#!8\033!\021Y-, E\260\003%FPX\260\002%F ha\260\003%\260\003%?#!8\033!\021Y-,\000\260\007C\260\006C\013-,\212\020\354-,\260\014CX!\033 F\260\000RX\270\377\3608\033\260\0208YY-, \260\000UX\270\020\000c\260\003%Ed\260\003%Eda\260\000SX\260\002\033\260@a\260\003Y%EiSXED\033!!Y\033!\260\002%E\260\002%Ead\260(QXED\033!!YY-,!!\014d#d\213\270@\000b-,!\260\200QX\014d#d\213\270 \000b\033\262\000@/+Y\260\002`-,!\260\300QX\014d#d\213\270\025Ub\033\262\000\200/+Y\260\002`-,\014d#d\213\270@\000b`#!-,KSX\260\004%\260\004%Id#Edi\260@\213a \260\200bj\260\002%\260\002%a\214\260F#D!\212\020\260F\366!\033!\212\021#\022 9/Y-,\260\002%\260\002%Id\260\300TX\270\377\3708\260\0108\033!!Y-,\260\023CX\003\033\002Y-,\260\023CX\002\033\003Y-,\260\012+#\020 <\260\027+-,\260\002%\270\377\3608\260(+\212\020# \320#\260\020+\260\005CX\300\033<Y \020\021\260\000\022\001-,KS#KQZX8\033!!Y-,\001\260\002%\020\320#\311\001\260\001\023\260\000\024\020\260\001<\260\001\026-,\001\260\000\023\260\001\260\003%I\260\003\0278\260\001\023-,KS#KQZX E\212`D\033!!Y-, 9/-"""
  528. p = Program()
  529. p.fromBytecode(bc)
  530. asm = p.getAssembly(preserve=True)
  531. p.fromAssembly(asm)
  532. print(bc == p.getBytecode())
  533. if __name__ == "__main__":
  534. import sys
  535. import doctest
  536. sys.exit(doctest.testmod().failed)