ttProgram.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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, bits):
  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(instructionList):
  155. opcodeDict = {}
  156. mnemonicDict = {}
  157. for op, mnemonic, argBits, name, pops, pushes in instructionList:
  158. assert _mnemonicPat.match(mnemonic)
  159. mnemonicDict[mnemonic] = op, argBits, name
  160. if argBits:
  161. argoffset = op
  162. for i in range(1 << argBits):
  163. opcodeDict[op + i] = mnemonic, argBits, argoffset, name
  164. else:
  165. opcodeDict[op] = mnemonic, 0, 0, name
  166. return opcodeDict, mnemonicDict
  167. streamOpcodeDict, streamMnemonicDict = _makeDict(streamInstructions)
  168. opcodeDict, mnemonicDict = _makeDict(instructions)
  169. class tt_instructions_error(Exception):
  170. def __init__(self, error):
  171. self.error = error
  172. def __str__(self):
  173. return "TT instructions error: %s" % repr(self.error)
  174. _comment = r"/\*.*?\*/"
  175. _instruction = r"([A-Z][A-Z0-9]*)\s*\[(.*?)\]"
  176. _number = r"-?[0-9]+"
  177. _token = "(%s)|(%s)|(%s)" % (_instruction, _number, _comment)
  178. _tokenRE = re.compile(_token)
  179. _whiteRE = re.compile(r"\s*")
  180. _pushCountPat = re.compile(r"[A-Z][A-Z0-9]*\s*\[.*?\]\s*/\* ([0-9]+).*?\*/")
  181. _indentRE = re.compile(r"^FDEF|IF|ELSE\[ \]\t.+")
  182. _unindentRE = re.compile(r"^ELSE|ENDF|EIF\[ \]\t.+")
  183. def _skipWhite(data, pos):
  184. m = _whiteRE.match(data, pos)
  185. newPos = m.regs[0][1]
  186. assert newPos >= pos
  187. return newPos
  188. class Program(object):
  189. def __init__(self) -> None:
  190. pass
  191. def fromBytecode(self, bytecode: bytes) -> None:
  192. self.bytecode = array.array("B", bytecode)
  193. if hasattr(self, "assembly"):
  194. del self.assembly
  195. def fromAssembly(self, assembly: List[str] | str) -> None:
  196. if isinstance(assembly, list):
  197. self.assembly = assembly
  198. elif isinstance(assembly, str):
  199. self.assembly = assembly.splitlines()
  200. else:
  201. raise TypeError(f"expected str or List[str], got {type(assembly).__name__}")
  202. if hasattr(self, "bytecode"):
  203. del self.bytecode
  204. def getBytecode(self) -> bytes:
  205. if not hasattr(self, "bytecode"):
  206. self._assemble()
  207. return self.bytecode.tobytes()
  208. def getAssembly(self, preserve=True) -> List[str]:
  209. if not hasattr(self, "assembly"):
  210. self._disassemble(preserve=preserve)
  211. return self.assembly
  212. def toXML(self, writer, ttFont) -> None:
  213. if (
  214. not hasattr(ttFont, "disassembleInstructions")
  215. or ttFont.disassembleInstructions
  216. ):
  217. try:
  218. assembly = self.getAssembly()
  219. except:
  220. import traceback
  221. tmp = StringIO()
  222. traceback.print_exc(file=tmp)
  223. msg = "An exception occurred during the decompilation of glyph program:\n\n"
  224. msg += tmp.getvalue()
  225. log.error(msg)
  226. writer.begintag("bytecode")
  227. writer.newline()
  228. writer.comment(msg.strip())
  229. writer.newline()
  230. writer.dumphex(self.getBytecode())
  231. writer.endtag("bytecode")
  232. writer.newline()
  233. else:
  234. if not assembly:
  235. return
  236. writer.begintag("assembly")
  237. writer.newline()
  238. i = 0
  239. indent = 0
  240. nInstr = len(assembly)
  241. while i < nInstr:
  242. instr = assembly[i]
  243. if _unindentRE.match(instr):
  244. indent -= 1
  245. writer.write(writer.indentwhite * indent)
  246. writer.write(instr)
  247. writer.newline()
  248. m = _pushCountPat.match(instr)
  249. i = i + 1
  250. if m:
  251. nValues = int(m.group(1))
  252. line: List[str] = []
  253. j = 0
  254. for j in range(nValues):
  255. if j and not (j % 25):
  256. writer.write(writer.indentwhite * indent)
  257. writer.write(" ".join(line))
  258. writer.newline()
  259. line = []
  260. line.append(assembly[i + j])
  261. writer.write(writer.indentwhite * indent)
  262. writer.write(" ".join(line))
  263. writer.newline()
  264. i = i + j + 1
  265. if _indentRE.match(instr):
  266. indent += 1
  267. writer.endtag("assembly")
  268. writer.newline()
  269. else:
  270. bytecode = self.getBytecode()
  271. if not bytecode:
  272. return
  273. writer.begintag("bytecode")
  274. writer.newline()
  275. writer.dumphex(bytecode)
  276. writer.endtag("bytecode")
  277. writer.newline()
  278. def fromXML(self, name, attrs, content, ttFont) -> None:
  279. if name == "assembly":
  280. self.fromAssembly(strjoin(content))
  281. self._assemble()
  282. del self.assembly
  283. else:
  284. assert name == "bytecode"
  285. self.fromBytecode(readHex(content))
  286. def _assemble(self) -> None:
  287. assembly = " ".join(getattr(self, "assembly", []))
  288. bytecode: List[int] = []
  289. push = bytecode.append
  290. lenAssembly = len(assembly)
  291. pos = _skipWhite(assembly, 0)
  292. while pos < lenAssembly:
  293. m = _tokenRE.match(assembly, pos)
  294. if m is None:
  295. raise tt_instructions_error(
  296. "Syntax error in TT program (%s)" % assembly[pos - 5 : pos + 15]
  297. )
  298. dummy, mnemonic, arg, number, comment = m.groups()
  299. pos = m.regs[0][1]
  300. if comment:
  301. pos = _skipWhite(assembly, pos)
  302. continue
  303. arg = arg.strip()
  304. if mnemonic.startswith("INSTR"):
  305. # Unknown instruction
  306. op = int(mnemonic[5:])
  307. push(op)
  308. elif mnemonic not in ("PUSH", "NPUSHB", "NPUSHW", "PUSHB", "PUSHW"):
  309. op, argBits, name = mnemonicDict[mnemonic]
  310. if len(arg) != argBits:
  311. raise tt_instructions_error(
  312. "Incorrect number of argument bits (%s[%s])" % (mnemonic, arg)
  313. )
  314. if arg:
  315. arg = binary2num(arg)
  316. push(op + arg)
  317. else:
  318. push(op)
  319. else:
  320. args = []
  321. pos = _skipWhite(assembly, pos)
  322. while pos < lenAssembly:
  323. m = _tokenRE.match(assembly, pos)
  324. if m is None:
  325. raise tt_instructions_error(
  326. "Syntax error in TT program (%s)" % assembly[pos : pos + 15]
  327. )
  328. dummy, _mnemonic, arg, number, comment = m.groups()
  329. if number is None and comment is None:
  330. break
  331. pos = m.regs[0][1]
  332. pos = _skipWhite(assembly, pos)
  333. if comment is not None:
  334. continue
  335. args.append(int(number))
  336. nArgs = len(args)
  337. if mnemonic == "PUSH":
  338. # Automatically choose the most compact representation
  339. nWords = 0
  340. while nArgs:
  341. while (
  342. nWords < nArgs
  343. and nWords < 255
  344. and not (0 <= args[nWords] <= 255)
  345. ):
  346. nWords += 1
  347. nBytes = 0
  348. while (
  349. nWords + nBytes < nArgs
  350. and nBytes < 255
  351. and 0 <= args[nWords + nBytes] <= 255
  352. ):
  353. nBytes += 1
  354. if (
  355. nBytes < 2
  356. and nWords + nBytes < 255
  357. and nWords + nBytes != nArgs
  358. ):
  359. # Will write bytes as words
  360. nWords += nBytes
  361. continue
  362. # Write words
  363. if nWords:
  364. if nWords <= 8:
  365. op, argBits, name = streamMnemonicDict["PUSHW"]
  366. op = op + nWords - 1
  367. push(op)
  368. else:
  369. op, argBits, name = streamMnemonicDict["NPUSHW"]
  370. push(op)
  371. push(nWords)
  372. for value in args[:nWords]:
  373. assert -32768 <= value < 32768, (
  374. "PUSH value out of range %d" % value
  375. )
  376. push((value >> 8) & 0xFF)
  377. push(value & 0xFF)
  378. # Write bytes
  379. if nBytes:
  380. pass
  381. if nBytes <= 8:
  382. op, argBits, name = streamMnemonicDict["PUSHB"]
  383. op = op + nBytes - 1
  384. push(op)
  385. else:
  386. op, argBits, name = streamMnemonicDict["NPUSHB"]
  387. push(op)
  388. push(nBytes)
  389. for value in args[nWords : nWords + nBytes]:
  390. push(value)
  391. nTotal = nWords + nBytes
  392. args = args[nTotal:]
  393. nArgs -= nTotal
  394. nWords = 0
  395. else:
  396. # Write exactly what we've been asked to
  397. words = mnemonic[-1] == "W"
  398. op, argBits, name = streamMnemonicDict[mnemonic]
  399. if mnemonic[0] != "N":
  400. assert nArgs <= 8, nArgs
  401. op = op + nArgs - 1
  402. push(op)
  403. else:
  404. assert nArgs < 256
  405. push(op)
  406. push(nArgs)
  407. if words:
  408. for value in args:
  409. assert -32768 <= value < 32768, (
  410. "PUSHW value out of range %d" % value
  411. )
  412. push((value >> 8) & 0xFF)
  413. push(value & 0xFF)
  414. else:
  415. for value in args:
  416. assert 0 <= value < 256, (
  417. "PUSHB value out of range %d" % value
  418. )
  419. push(value)
  420. pos = _skipWhite(assembly, pos)
  421. if bytecode:
  422. assert max(bytecode) < 256 and min(bytecode) >= 0
  423. self.bytecode = array.array("B", bytecode)
  424. def _disassemble(self, preserve=False) -> None:
  425. assembly = []
  426. i = 0
  427. bytecode = getattr(self, "bytecode", [])
  428. numBytecode = len(bytecode)
  429. while i < numBytecode:
  430. op = bytecode[i]
  431. try:
  432. mnemonic, argBits, argoffset, name = opcodeDict[op]
  433. except KeyError:
  434. if op in streamOpcodeDict:
  435. values = []
  436. # Merge consecutive PUSH operations
  437. while bytecode[i] in streamOpcodeDict:
  438. op = bytecode[i]
  439. mnemonic, argBits, argoffset, name = streamOpcodeDict[op]
  440. words = mnemonic[-1] == "W"
  441. if argBits:
  442. nValues = op - argoffset + 1
  443. else:
  444. i = i + 1
  445. nValues = bytecode[i]
  446. i = i + 1
  447. assert nValues > 0
  448. if not words:
  449. for j in range(nValues):
  450. value = bytecode[i]
  451. values.append(repr(value))
  452. i = i + 1
  453. else:
  454. for j in range(nValues):
  455. # cast to signed int16
  456. value = (bytecode[i] << 8) | bytecode[i + 1]
  457. if value >= 0x8000:
  458. value = value - 0x10000
  459. values.append(repr(value))
  460. i = i + 2
  461. if preserve:
  462. break
  463. if not preserve:
  464. mnemonic = "PUSH"
  465. nValues = len(values)
  466. if nValues == 1:
  467. assembly.append("%s[ ] /* 1 value pushed */" % mnemonic)
  468. else:
  469. assembly.append(
  470. "%s[ ] /* %s values pushed */" % (mnemonic, nValues)
  471. )
  472. assembly.extend(values)
  473. else:
  474. assembly.append("INSTR%d[ ]" % op)
  475. i = i + 1
  476. else:
  477. if argBits:
  478. assembly.append(
  479. mnemonic
  480. + "[%s] /* %s */" % (num2binary(op - argoffset, argBits), name)
  481. )
  482. else:
  483. assembly.append(mnemonic + "[ ] /* %s */" % name)
  484. i = i + 1
  485. self.assembly = assembly
  486. def __bool__(self) -> bool:
  487. """
  488. >>> p = Program()
  489. >>> bool(p)
  490. False
  491. >>> bc = array.array("B", [0])
  492. >>> p.fromBytecode(bc)
  493. >>> bool(p)
  494. True
  495. >>> p.bytecode.pop()
  496. 0
  497. >>> bool(p)
  498. False
  499. >>> p = Program()
  500. >>> asm = ['SVTCA[0]']
  501. >>> p.fromAssembly(asm)
  502. >>> bool(p)
  503. True
  504. >>> p.assembly.pop()
  505. 'SVTCA[0]'
  506. >>> bool(p)
  507. False
  508. """
  509. return (hasattr(self, "assembly") and len(self.assembly) > 0) or (
  510. hasattr(self, "bytecode") and len(self.bytecode) > 0
  511. )
  512. __nonzero__ = __bool__
  513. def __eq__(self, other) -> bool:
  514. if type(self) != type(other):
  515. return NotImplemented
  516. return self.__dict__ == other.__dict__
  517. def __ne__(self, other) -> bool:
  518. result = self.__eq__(other)
  519. return result if result is NotImplemented else not result
  520. def _test():
  521. """
  522. >>> _test()
  523. True
  524. """
  525. 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/-"""
  526. p = Program()
  527. p.fromBytecode(bc)
  528. asm = p.getAssembly(preserve=True)
  529. p.fromAssembly(asm)
  530. print(bc == p.getBytecode())
  531. if __name__ == "__main__":
  532. import sys
  533. import doctest
  534. sys.exit(doctest.testmod().failed)