1999-12-16 21:34:53 +00:00
|
|
|
"""ttLib.tables.ttProgram.py -- Assembler/disassembler for TrueType bytecode programs."""
|
|
|
|
|
2014-01-14 15:07:50 +08:00
|
|
|
from __future__ import print_function, division, absolute_import
|
2013-11-27 17:27:45 -05:00
|
|
|
from fontTools.misc.py23 import *
|
|
|
|
from fontTools.misc.textTools import num2binary, binary2num, readHex
|
1999-12-16 21:34:53 +00:00
|
|
|
import array
|
2013-11-27 05:47:34 -05:00
|
|
|
import re
|
2016-01-24 14:45:05 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
# first, the list of instructions that eat bytes or words from the instruction stream
|
|
|
|
|
|
|
|
streamInstructions = [
|
2015-04-26 00:54:30 -04:00
|
|
|
#
|
|
|
|
# opcode mnemonic argBits descriptive name pops pushes eats from instruction stream pushes
|
|
|
|
#
|
|
|
|
(0x40, 'NPUSHB', 0, 'PushNBytes', 0, -1), # n, b1, b2,...bn b1,b2...bn
|
|
|
|
(0x41, 'NPUSHW', 0, 'PushNWords', 0, -1), # n, w1, w2,...w w1,w2...wn
|
|
|
|
(0xb0, 'PUSHB', 3, 'PushBytes', 0, -1), # b0, b1,..bn b0, b1, ...,bn
|
|
|
|
(0xb8, 'PUSHW', 3, 'PushWords', 0, -1), # w0,w1,..wn w0 ,w1, ...wn
|
1999-12-16 21:34:53 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
2015-04-26 00:54:30 -04:00
|
|
|
# next, the list of "normal" instructions
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
instructions = [
|
2015-04-26 00:54:30 -04:00
|
|
|
#
|
|
|
|
#, opcode mnemonic argBits descriptive name pops pushes eats from instruction stream pushes
|
|
|
|
#
|
|
|
|
(0x7f, 'AA', 0, 'AdjustAngle', 1, 0), # p -
|
|
|
|
(0x64, 'ABS', 0, 'Absolute', 1, 1), # n |n|
|
|
|
|
(0x60, 'ADD', 0, 'Add', 2, 1), # n2, n1 (n1 + n2)
|
|
|
|
(0x27, 'ALIGNPTS', 0, 'AlignPts', 2, 0), # p2, p1 -
|
|
|
|
(0x3c, 'ALIGNRP', 0, 'AlignRelativePt', -1, 0), # p1, p2, ... , ploopvalue -
|
|
|
|
(0x5a, 'AND', 0, 'LogicalAnd', 2, 1), # e2, e1 b
|
|
|
|
(0x2b, 'CALL', 0, 'CallFunction', 1, 0), # f -
|
|
|
|
(0x67, 'CEILING', 0, 'Ceiling', 1, 1), # n ceil(n)
|
|
|
|
(0x25, 'CINDEX', 0, 'CopyXToTopStack', 1, 1), # k ek
|
|
|
|
(0x22, 'CLEAR', 0, 'ClearStack', -1, 0), # all items on the stack -
|
|
|
|
(0x4f, 'DEBUG', 0, 'DebugCall', 1, 0), # n -
|
|
|
|
(0x73, 'DELTAC1', 0, 'DeltaExceptionC1', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
|
|
|
|
(0x74, 'DELTAC2', 0, 'DeltaExceptionC2', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
|
|
|
|
(0x75, 'DELTAC3', 0, 'DeltaExceptionC3', -1, 0), # argn, cn, argn-1,cn-1, , arg1, c1 -
|
|
|
|
(0x5d, 'DELTAP1', 0, 'DeltaExceptionP1', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
|
|
|
|
(0x71, 'DELTAP2', 0, 'DeltaExceptionP2', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
|
|
|
|
(0x72, 'DELTAP3', 0, 'DeltaExceptionP3', -1, 0), # argn, pn, argn-1, pn-1, , arg1, p1 -
|
|
|
|
(0x24, 'DEPTH', 0, 'GetDepthStack', 0, 1), # - n
|
|
|
|
(0x62, 'DIV', 0, 'Divide', 2, 1), # n2, n1 (n1 * 64)/ n2
|
|
|
|
(0x20, 'DUP', 0, 'DuplicateTopStack', 1, 2), # e e, e
|
|
|
|
(0x59, 'EIF', 0, 'EndIf', 0, 0), # - -
|
|
|
|
(0x1b, 'ELSE', 0, 'Else', 0, 0), # - -
|
|
|
|
(0x2d, 'ENDF', 0, 'EndFunctionDefinition', 0, 0), # - -
|
|
|
|
(0x54, 'EQ', 0, 'Equal', 2, 1), # e2, e1 b
|
|
|
|
(0x57, 'EVEN', 0, 'Even', 1, 1), # e b
|
|
|
|
(0x2c, 'FDEF', 0, 'FunctionDefinition', 1, 0), # f -
|
|
|
|
(0x4e, 'FLIPOFF', 0, 'SetAutoFlipOff', 0, 0), # - -
|
|
|
|
(0x4d, 'FLIPON', 0, 'SetAutoFlipOn', 0, 0), # - -
|
|
|
|
(0x80, 'FLIPPT', 0, 'FlipPoint', -1, 0), # p1, p2, ..., ploopvalue -
|
|
|
|
(0x82, 'FLIPRGOFF', 0, 'FlipRangeOff', 2, 0), # h, l -
|
|
|
|
(0x81, 'FLIPRGON', 0, 'FlipRangeOn', 2, 0), # h, l -
|
|
|
|
(0x66, 'FLOOR', 0, 'Floor', 1, 1), # n floor(n)
|
|
|
|
(0x46, 'GC', 1, 'GetCoordOnPVector', 1, 1), # p c
|
|
|
|
(0x88, 'GETINFO', 0, 'GetInfo', 1, 1), # selector result
|
|
|
|
(0x0d, 'GFV', 0, 'GetFVector', 0, 2), # - px, py
|
|
|
|
(0x0c, 'GPV', 0, 'GetPVector', 0, 2), # - px, py
|
|
|
|
(0x52, 'GT', 0, 'GreaterThan', 2, 1), # e2, e1 b
|
|
|
|
(0x53, 'GTEQ', 0, 'GreaterThanOrEqual', 2, 1), # e2, e1 b
|
|
|
|
(0x89, 'IDEF', 0, 'InstructionDefinition', 1, 0), # f -
|
|
|
|
(0x58, 'IF', 0, 'If', 1, 0), # e -
|
|
|
|
(0x8e, 'INSTCTRL', 0, 'SetInstrExecControl', 2, 0), # s, v -
|
|
|
|
(0x39, 'IP', 0, 'InterpolatePts', -1, 0), # p1, p2, ... , ploopvalue -
|
|
|
|
(0x0f, 'ISECT', 0, 'MovePtToIntersect', 5, 0), # a1, a0, b1, b0, p -
|
|
|
|
(0x30, 'IUP', 1, 'InterpolateUntPts', 0, 0), # - -
|
|
|
|
(0x1c, 'JMPR', 0, 'Jump', 1, 0), # offset -
|
|
|
|
(0x79, 'JROF', 0, 'JumpRelativeOnFalse', 2, 0), # e, offset -
|
|
|
|
(0x78, 'JROT', 0, 'JumpRelativeOnTrue', 2, 0), # e, offset -
|
|
|
|
(0x2a, 'LOOPCALL', 0, 'LoopAndCallFunction', 2, 0), # f, count -
|
|
|
|
(0x50, 'LT', 0, 'LessThan', 2, 1), # e2, e1 b
|
|
|
|
(0x51, 'LTEQ', 0, 'LessThenOrEqual', 2, 1), # e2, e1 b
|
|
|
|
(0x8b, 'MAX', 0, 'Maximum', 2, 1), # e2, e1 max(e1, e2)
|
|
|
|
(0x49, 'MD', 1, 'MeasureDistance', 2, 1), # p2,p1 d
|
|
|
|
(0x2e, 'MDAP', 1, 'MoveDirectAbsPt', 1, 0), # p -
|
|
|
|
(0xc0, 'MDRP', 5, 'MoveDirectRelPt', 1, 0), # p -
|
|
|
|
(0x3e, 'MIAP', 1, 'MoveIndirectAbsPt', 2, 0), # n, p -
|
|
|
|
(0x8c, 'MIN', 0, 'Minimum', 2, 1), # e2, e1 min(e1, e2)
|
|
|
|
(0x26, 'MINDEX', 0, 'MoveXToTopStack', 1, 1), # k ek
|
|
|
|
(0xe0, 'MIRP', 5, 'MoveIndirectRelPt', 2, 0), # n, p -
|
|
|
|
(0x4b, 'MPPEM', 0, 'MeasurePixelPerEm', 0, 1), # - ppem
|
|
|
|
(0x4c, 'MPS', 0, 'MeasurePointSize', 0, 1), # - pointSize
|
|
|
|
(0x3a, 'MSIRP', 1, 'MoveStackIndirRelPt', 2, 0), # d, p -
|
|
|
|
(0x63, 'MUL', 0, 'Multiply', 2, 1), # n2, n1 (n1 * n2)/64
|
|
|
|
(0x65, 'NEG', 0, 'Negate', 1, 1), # n -n
|
|
|
|
(0x55, 'NEQ', 0, 'NotEqual', 2, 1), # e2, e1 b
|
|
|
|
(0x5c, 'NOT', 0, 'LogicalNot', 1, 1), # e ( not e )
|
|
|
|
(0x6c, 'NROUND', 2, 'NoRound', 1, 1), # n1 n2
|
|
|
|
(0x56, 'ODD', 0, 'Odd', 1, 1), # e b
|
|
|
|
(0x5b, 'OR', 0, 'LogicalOr', 2, 1), # e2, e1 b
|
|
|
|
(0x21, 'POP', 0, 'PopTopStack', 1, 0), # e -
|
|
|
|
(0x45, 'RCVT', 0, 'ReadCVT', 1, 1), # location value
|
|
|
|
(0x7d, 'RDTG', 0, 'RoundDownToGrid', 0, 0), # - -
|
|
|
|
(0x7a, 'ROFF', 0, 'RoundOff', 0, 0), # - -
|
|
|
|
(0x8a, 'ROLL', 0, 'RollTopThreeStack', 3, 3), # a,b,c b,a,c
|
|
|
|
(0x68, 'ROUND', 2, 'Round', 1, 1), # n1 n2
|
|
|
|
(0x43, 'RS', 0, 'ReadStore', 1, 1), # n v
|
|
|
|
(0x3d, 'RTDG', 0, 'RoundToDoubleGrid', 0, 0), # - -
|
|
|
|
(0x18, 'RTG', 0, 'RoundToGrid', 0, 0), # - -
|
|
|
|
(0x19, 'RTHG', 0, 'RoundToHalfGrid', 0, 0), # - -
|
|
|
|
(0x7c, 'RUTG', 0, 'RoundUpToGrid', 0, 0), # - -
|
|
|
|
(0x77, 'S45ROUND', 0, 'SuperRound45Degrees', 1, 0), # n -
|
|
|
|
(0x7e, 'SANGW', 0, 'SetAngleWeight', 1, 0), # weight -
|
|
|
|
(0x85, 'SCANCTRL', 0, 'ScanConversionControl', 1, 0), # n -
|
|
|
|
(0x8d, 'SCANTYPE', 0, 'ScanType', 1, 0), # n -
|
|
|
|
(0x48, 'SCFS', 0, 'SetCoordFromStackFP', 2, 0), # c, p -
|
|
|
|
(0x1d, 'SCVTCI', 0, 'SetCVTCutIn', 1, 0), # n -
|
|
|
|
(0x5e, 'SDB', 0, 'SetDeltaBaseInGState', 1, 0), # n -
|
|
|
|
(0x86, 'SDPVTL', 1, 'SetDualPVectorToLine', 2, 0), # p2, p1 -
|
|
|
|
(0x5f, 'SDS', 0, 'SetDeltaShiftInGState',1, 0), # n -
|
|
|
|
(0x0b, 'SFVFS', 0, 'SetFVectorFromStack', 2, 0), # y, x -
|
|
|
|
(0x04, 'SFVTCA', 1, 'SetFVectorToAxis', 0, 0), # - -
|
|
|
|
(0x08, 'SFVTL', 1, 'SetFVectorToLine', 2, 0), # p2, p1 -
|
|
|
|
(0x0e, 'SFVTPV', 0, 'SetFVectorToPVector', 0, 0), # - -
|
|
|
|
(0x34, 'SHC', 1, 'ShiftContourByLastPt', 1, 0), # c -
|
|
|
|
(0x32, 'SHP', 1, 'ShiftPointByLastPoint',-1, 0), # p1, p2, ..., ploopvalue -
|
|
|
|
(0x38, 'SHPIX', 0, 'ShiftZoneByPixel', -1, 0), # d, p1, p2, ..., ploopvalue -
|
|
|
|
(0x36, 'SHZ', 1, 'ShiftZoneByLastPoint', 1, 0), # e -
|
|
|
|
(0x17, 'SLOOP', 0, 'SetLoopVariable', 1, 0), # n -
|
|
|
|
(0x1a, 'SMD', 0, 'SetMinimumDistance', 1, 0), # distance -
|
|
|
|
(0x0a, 'SPVFS', 0, 'SetPVectorFromStack', 2, 0), # y, x -
|
|
|
|
(0x02, 'SPVTCA', 1, 'SetPVectorToAxis', 0, 0), # - -
|
|
|
|
(0x06, 'SPVTL', 1, 'SetPVectorToLine', 2, 0), # p2, p1 -
|
|
|
|
(0x76, 'SROUND', 0, 'SuperRound', 1, 0), # n -
|
|
|
|
(0x10, 'SRP0', 0, 'SetRefPoint0', 1, 0), # p -
|
|
|
|
(0x11, 'SRP1', 0, 'SetRefPoint1', 1, 0), # p -
|
|
|
|
(0x12, 'SRP2', 0, 'SetRefPoint2', 1, 0), # p -
|
|
|
|
(0x1f, 'SSW', 0, 'SetSingleWidth', 1, 0), # n -
|
|
|
|
(0x1e, 'SSWCI', 0, 'SetSingleWidthCutIn', 1, 0), # n -
|
|
|
|
(0x61, 'SUB', 0, 'Subtract', 2, 1), # n2, n1 (n1 - n2)
|
|
|
|
(0x00, 'SVTCA', 1, 'SetFPVectorToAxis', 0, 0), # - -
|
|
|
|
(0x23, 'SWAP', 0, 'SwapTopStack', 2, 2), # e2, e1 e1, e2
|
|
|
|
(0x13, 'SZP0', 0, 'SetZonePointer0', 1, 0), # n -
|
|
|
|
(0x14, 'SZP1', 0, 'SetZonePointer1', 1, 0), # n -
|
|
|
|
(0x15, 'SZP2', 0, 'SetZonePointer2', 1, 0), # n -
|
|
|
|
(0x16, 'SZPS', 0, 'SetZonePointerS', 1, 0), # n -
|
|
|
|
(0x29, 'UTP', 0, 'UnTouchPt', 1, 0), # p -
|
|
|
|
(0x70, 'WCVTF', 0, 'WriteCVTInFUnits', 2, 0), # n, l -
|
|
|
|
(0x44, 'WCVTP', 0, 'WriteCVTInPixels', 2, 0), # v, l -
|
|
|
|
(0x42, 'WS', 0, 'WriteStore', 2, 0), # v, l -
|
1999-12-16 21:34:53 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def bitRepr(value, bits):
|
|
|
|
s = ""
|
|
|
|
for i in range(bits):
|
|
|
|
s = "01"[value & 0x1] + s
|
|
|
|
value = value >> 1
|
|
|
|
return s
|
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
|
|
|
|
_mnemonicPat = re.compile("[A-Z][A-Z0-9]*$")
|
|
|
|
|
|
|
|
def _makeDict(instructionList):
|
1999-12-16 21:34:53 +00:00
|
|
|
opcodeDict = {}
|
2000-02-01 15:30:15 +00:00
|
|
|
mnemonicDict = {}
|
|
|
|
for op, mnemonic, argBits, name, pops, pushes in instructionList:
|
|
|
|
assert _mnemonicPat.match(mnemonic)
|
2015-01-06 02:38:13 +01:00
|
|
|
mnemonicDict[mnemonic] = op, argBits, name
|
2000-02-01 15:30:15 +00:00
|
|
|
if argBits:
|
1999-12-16 21:34:53 +00:00
|
|
|
argoffset = op
|
2000-02-01 15:30:15 +00:00
|
|
|
for i in range(1 << argBits):
|
2015-01-06 02:38:13 +01:00
|
|
|
opcodeDict[op+i] = mnemonic, argBits, argoffset, name
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
2015-01-06 02:38:13 +01:00
|
|
|
opcodeDict[op] = mnemonic, 0, 0, name
|
2000-02-01 15:30:15 +00:00
|
|
|
return opcodeDict, mnemonicDict
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
streamOpcodeDict, streamMnemonicDict = _makeDict(streamInstructions)
|
|
|
|
opcodeDict, mnemonicDict = _makeDict(instructions)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2009-11-08 06:39:37 +00:00
|
|
|
class tt_instructions_error(Exception):
|
|
|
|
def __init__(self, error):
|
|
|
|
self.error = error
|
|
|
|
def __str__(self):
|
|
|
|
return "TT instructions error: %s" % repr(self.error)
|
1999-12-16 21:34:53 +00:00
|
|
|
|
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
_comment = r"/\*.*?\*/"
|
|
|
|
_instruction = r"([A-Z][A-Z0-9]*)\s*\[(.*?)\]"
|
|
|
|
_number = r"-?[0-9]+"
|
|
|
|
_token = "(%s)|(%s)|(%s)" % (_instruction, _number, _comment)
|
|
|
|
|
|
|
|
_tokenRE = re.compile(_token)
|
|
|
|
_whiteRE = re.compile(r"\s*")
|
|
|
|
|
2015-01-06 02:38:13 +01:00
|
|
|
_pushCountPat = re.compile(r"[A-Z][A-Z0-9]*\s*\[.*?\]\s*/\* ([0-9]+).*?\*/")
|
2000-02-01 15:54:37 +00:00
|
|
|
|
|
|
|
|
2013-12-04 01:15:46 -05:00
|
|
|
def _skipWhite(data, pos):
|
2000-02-01 15:30:15 +00:00
|
|
|
m = _whiteRE.match(data, pos)
|
|
|
|
newPos = m.regs[0][1]
|
|
|
|
assert newPos >= pos
|
|
|
|
return newPos
|
|
|
|
|
|
|
|
|
2013-11-28 14:26:58 -05:00
|
|
|
class Program(object):
|
2015-04-26 02:01:01 -04:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def __init__(self):
|
|
|
|
pass
|
2015-04-26 02:01:01 -04:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def fromBytecode(self, bytecode):
|
2000-02-01 15:30:15 +00:00
|
|
|
self.bytecode = array.array("B", bytecode)
|
|
|
|
if hasattr(self, "assembly"):
|
|
|
|
del self.assembly
|
2015-04-26 02:01:01 -04:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def fromAssembly(self, assembly):
|
|
|
|
self.assembly = assembly
|
2000-02-01 15:30:15 +00:00
|
|
|
if hasattr(self, "bytecode"):
|
|
|
|
del self.bytecode
|
2015-04-26 02:01:01 -04:00
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
def getBytecode(self):
|
|
|
|
if not hasattr(self, "bytecode"):
|
|
|
|
self._assemble()
|
|
|
|
return self.bytecode.tostring()
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2016-03-20 22:26:22 +01:00
|
|
|
def getAssembly(self, preserve=True):
|
1999-12-16 21:34:53 +00:00
|
|
|
if not hasattr(self, "assembly"):
|
2015-04-14 11:51:03 -07:00
|
|
|
self._disassemble(preserve=preserve)
|
1999-12-16 21:34:53 +00:00
|
|
|
return self.assembly
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
def toXML(self, writer, ttFont):
|
2013-12-07 16:06:10 -05:00
|
|
|
if not hasattr (ttFont, "disassembleInstructions") or ttFont.disassembleInstructions:
|
2015-09-24 09:32:49 +01:00
|
|
|
try:
|
|
|
|
assembly = self.getAssembly()
|
|
|
|
except:
|
|
|
|
import traceback
|
|
|
|
tmp = StringIO()
|
|
|
|
traceback.print_exc(file=tmp)
|
|
|
|
msg = "An exception occurred during the decompilation of glyph program:\n\n"
|
|
|
|
msg += tmp.getvalue()
|
2016-01-24 14:45:05 +00:00
|
|
|
log.error(msg)
|
2015-09-24 09:32:49 +01:00
|
|
|
writer.begintag("bytecode")
|
|
|
|
writer.newline()
|
|
|
|
writer.comment(msg.strip())
|
|
|
|
writer.newline()
|
|
|
|
writer.dumphex(self.getBytecode())
|
|
|
|
writer.endtag("bytecode")
|
|
|
|
else:
|
|
|
|
writer.begintag("assembly")
|
2000-02-01 15:30:15 +00:00
|
|
|
writer.newline()
|
2015-09-24 09:32:49 +01:00
|
|
|
i = 0
|
|
|
|
nInstr = len(assembly)
|
|
|
|
while i < nInstr:
|
|
|
|
instr = assembly[i]
|
|
|
|
writer.write(instr)
|
2000-02-01 15:30:15 +00:00
|
|
|
writer.newline()
|
2015-09-24 09:32:49 +01:00
|
|
|
m = _pushCountPat.match(instr)
|
|
|
|
i = i + 1
|
|
|
|
if m:
|
|
|
|
nValues = int(m.group(1))
|
|
|
|
line = []
|
|
|
|
j = 0
|
|
|
|
for j in range(nValues):
|
|
|
|
if j and not (j % 25):
|
|
|
|
writer.write(' '.join(line))
|
|
|
|
writer.newline()
|
|
|
|
line = []
|
|
|
|
line.append(assembly[i+j])
|
|
|
|
writer.write(' '.join(line))
|
|
|
|
writer.newline()
|
|
|
|
i = i + j + 1
|
|
|
|
writer.endtag("assembly")
|
2000-02-01 15:30:15 +00:00
|
|
|
else:
|
|
|
|
writer.begintag("bytecode")
|
|
|
|
writer.newline()
|
|
|
|
writer.dumphex(self.getBytecode())
|
|
|
|
writer.endtag("bytecode")
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-27 03:19:32 -05:00
|
|
|
def fromXML(self, name, attrs, content, ttFont):
|
2000-02-01 15:30:15 +00:00
|
|
|
if name == "assembly":
|
2013-11-27 21:17:35 -05:00
|
|
|
self.fromAssembly(strjoin(content))
|
2000-02-01 15:30:15 +00:00
|
|
|
self._assemble()
|
|
|
|
del self.assembly
|
|
|
|
else:
|
|
|
|
assert name == "bytecode"
|
|
|
|
self.fromBytecode(readHex(content))
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-12-04 01:15:46 -05:00
|
|
|
def _assemble(self):
|
2000-02-01 15:30:15 +00:00
|
|
|
assembly = self.assembly
|
2013-11-27 04:15:34 -05:00
|
|
|
if isinstance(assembly, type([])):
|
2013-11-27 05:47:34 -05:00
|
|
|
assembly = ' '.join(assembly)
|
2000-02-01 15:30:15 +00:00
|
|
|
bytecode = []
|
|
|
|
push = bytecode.append
|
|
|
|
lenAssembly = len(assembly)
|
2013-12-04 01:15:46 -05:00
|
|
|
pos = _skipWhite(assembly, 0)
|
2000-02-01 15:30:15 +00:00
|
|
|
while pos < lenAssembly:
|
|
|
|
m = _tokenRE.match(assembly, pos)
|
|
|
|
if m is None:
|
2013-11-27 02:42:28 -05:00
|
|
|
raise tt_instructions_error("Syntax error in TT program (%s)" % assembly[pos-5:pos+15])
|
2000-02-01 15:30:15 +00:00
|
|
|
dummy, mnemonic, arg, number, comment = m.groups()
|
|
|
|
pos = m.regs[0][1]
|
|
|
|
if comment:
|
2015-01-06 02:38:13 +01:00
|
|
|
pos = _skipWhite(assembly, pos)
|
2000-02-01 15:30:15 +00:00
|
|
|
continue
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2013-11-27 05:47:34 -05:00
|
|
|
arg = arg.strip()
|
2013-11-14 20:24:16 -05:00
|
|
|
if mnemonic.startswith("INSTR"):
|
|
|
|
# Unknown instruction
|
|
|
|
op = int(mnemonic[5:])
|
|
|
|
push(op)
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
elif mnemonic not in ("PUSH", "NPUSHB", "NPUSHW", "PUSHB", "PUSHW"):
|
2015-01-06 02:38:13 +01:00
|
|
|
op, argBits, name = mnemonicDict[mnemonic]
|
2013-11-27 02:40:30 -05:00
|
|
|
if len(arg) != argBits:
|
2013-11-27 02:42:28 -05:00
|
|
|
raise tt_instructions_error("Incorrect number of argument bits (%s[%s])" % (mnemonic, arg))
|
2000-02-01 15:30:15 +00:00
|
|
|
if arg:
|
|
|
|
arg = binary2num(arg)
|
|
|
|
push(op + arg)
|
|
|
|
else:
|
|
|
|
push(op)
|
|
|
|
else:
|
|
|
|
args = []
|
2013-12-04 01:15:46 -05:00
|
|
|
pos = _skipWhite(assembly, pos)
|
2000-02-01 15:30:15 +00:00
|
|
|
while pos < lenAssembly:
|
|
|
|
m = _tokenRE.match(assembly, pos)
|
|
|
|
if m is None:
|
2013-11-27 02:42:28 -05:00
|
|
|
raise tt_instructions_error("Syntax error in TT program (%s)" % assembly[pos:pos+15])
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
dummy, _mnemonic, arg, number, comment = m.groups()
|
2000-02-01 15:30:15 +00:00
|
|
|
if number is None and comment is None:
|
|
|
|
break
|
|
|
|
pos = m.regs[0][1]
|
2013-12-04 01:15:46 -05:00
|
|
|
pos = _skipWhite(assembly, pos)
|
2000-02-01 15:30:15 +00:00
|
|
|
if comment is not None:
|
|
|
|
continue
|
|
|
|
args.append(int(number))
|
|
|
|
nArgs = len(args)
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
if mnemonic == "PUSH":
|
2013-12-17 05:06:37 -05:00
|
|
|
# Automatically choose the most compact representation
|
|
|
|
nWords = 0
|
|
|
|
while nArgs:
|
|
|
|
while nWords < nArgs and nWords < 255 and not (0 <= args[nWords] <= 255):
|
|
|
|
nWords += 1
|
|
|
|
nBytes = 0
|
|
|
|
while nWords+nBytes < nArgs and nBytes < 255 and 0 <= args[nWords+nBytes] <= 255:
|
|
|
|
nBytes += 1
|
2013-12-17 05:10:30 -05:00
|
|
|
if nBytes < 2 and nWords + nBytes < 255 and nWords + nBytes != nArgs:
|
2013-12-17 05:06:37 -05:00
|
|
|
# Will write bytes as words
|
|
|
|
nWords += nBytes
|
|
|
|
continue
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
|
2013-12-17 05:06:37 -05:00
|
|
|
# Write words
|
|
|
|
if nWords:
|
|
|
|
if nWords <= 8:
|
2015-01-08 14:45:51 +01:00
|
|
|
op, argBits, name = streamMnemonicDict["PUSHW"]
|
2013-12-17 05:06:37 -05:00
|
|
|
op = op + nWords - 1
|
|
|
|
push(op)
|
|
|
|
else:
|
2015-01-08 14:45:51 +01:00
|
|
|
op, argBits, name = streamMnemonicDict["NPUSHW"]
|
2013-12-17 05:06:37 -05:00
|
|
|
push(op)
|
|
|
|
push(nWords)
|
|
|
|
for value in args[:nWords]:
|
|
|
|
assert -32768 <= value < 32768, "PUSH value out of range %d" % value
|
|
|
|
push((value >> 8) & 0xff)
|
|
|
|
push(value & 0xff)
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
|
2013-12-17 05:06:37 -05:00
|
|
|
# Write bytes
|
|
|
|
if nBytes:
|
|
|
|
pass
|
|
|
|
if nBytes <= 8:
|
2015-01-08 14:45:51 +01:00
|
|
|
op, argBits, name = streamMnemonicDict["PUSHB"]
|
2013-12-17 05:06:37 -05:00
|
|
|
op = op + nBytes - 1
|
|
|
|
push(op)
|
|
|
|
else:
|
2015-01-08 14:45:51 +01:00
|
|
|
op, argBits, name = streamMnemonicDict["NPUSHB"]
|
2013-12-17 05:06:37 -05:00
|
|
|
push(op)
|
|
|
|
push(nBytes)
|
|
|
|
for value in args[nWords:nWords+nBytes]:
|
|
|
|
push(value)
|
|
|
|
|
|
|
|
nTotal = nWords + nBytes
|
|
|
|
args = args[nTotal:]
|
|
|
|
nArgs -= nTotal
|
|
|
|
nWords = 0
|
|
|
|
else:
|
|
|
|
# Write exactly what we've been asked to
|
|
|
|
words = mnemonic[-1] == "W"
|
2015-01-06 02:38:13 +01:00
|
|
|
op, argBits, name = streamMnemonicDict[mnemonic]
|
2013-12-17 05:06:37 -05:00
|
|
|
if mnemonic[0] != "N":
|
|
|
|
assert nArgs <= 8, nArgs
|
|
|
|
op = op + nArgs - 1
|
|
|
|
push(op)
|
|
|
|
else:
|
|
|
|
assert nArgs < 256
|
|
|
|
push(op)
|
|
|
|
push(nArgs)
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
if words:
|
2013-12-17 05:06:37 -05:00
|
|
|
for value in args:
|
When writing PUSH instructions, obey requested opcode
Previously fonttools was choosing between the optimal opcode
from PUSHB, NPUSHB, PUSHW, and NPUSHW. We now respect
whatever was requested and err if the format doesn't support
the data.
Morevoer, if the number of numbers to push is more than 255,
we add multiple push instructions to push all the numbers.
Finally, add a new pseudo-opcode "PUSH" that automatically
chooses the best format.
This, by itself, reduces roundtrip noise by not optimizing
the bytecode as it was before. In a followup commit I'll
change the bytecode disassembler to always produce PUSH
in the textual instructions instead of the four variants.
That way, we get both the optimization during assembling,
and reduced noise in XML.
Part of https://github.com/behdad/fonttools/issues/73
2013-12-17 03:47:44 -05:00
|
|
|
assert -32768 <= value < 32768, "PUSHW value out of range %d" % value
|
|
|
|
push((value >> 8) & 0xff)
|
|
|
|
push(value & 0xff)
|
|
|
|
else:
|
|
|
|
for value in args:
|
|
|
|
assert 0 <= value < 256, "PUSHB value out of range %d" % value
|
|
|
|
push(value)
|
2013-12-17 05:06:37 -05:00
|
|
|
|
2013-12-04 01:15:46 -05:00
|
|
|
pos = _skipWhite(assembly, pos)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
if bytecode:
|
|
|
|
assert max(bytecode) < 256 and min(bytecode) >= 0
|
|
|
|
self.bytecode = array.array("B", bytecode)
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2014-01-19 17:30:19 +08:00
|
|
|
def _disassemble(self, preserve=False):
|
1999-12-16 21:34:53 +00:00
|
|
|
assembly = []
|
|
|
|
i = 0
|
|
|
|
bytecode = self.bytecode
|
|
|
|
numBytecode = len(bytecode)
|
|
|
|
while i < numBytecode:
|
|
|
|
op = bytecode[i]
|
|
|
|
try:
|
2015-01-06 02:38:13 +01:00
|
|
|
mnemonic, argBits, argoffset, name = opcodeDict[op]
|
1999-12-16 21:34:53 +00:00
|
|
|
except KeyError:
|
2013-11-14 20:16:41 -05:00
|
|
|
if op in streamOpcodeDict:
|
2013-12-17 04:19:14 -05:00
|
|
|
values = []
|
|
|
|
|
|
|
|
# Merge consecutive PUSH operations
|
|
|
|
while bytecode[i] in streamOpcodeDict:
|
|
|
|
op = bytecode[i]
|
2015-01-06 02:38:13 +01:00
|
|
|
mnemonic, argBits, argoffset, name = streamOpcodeDict[op]
|
2013-12-17 04:19:14 -05:00
|
|
|
words = mnemonic[-1] == "W"
|
|
|
|
if argBits:
|
|
|
|
nValues = op - argoffset + 1
|
|
|
|
else:
|
|
|
|
i = i + 1
|
|
|
|
nValues = bytecode[i]
|
2013-11-14 20:16:41 -05:00
|
|
|
i = i + 1
|
2013-12-17 04:19:14 -05:00
|
|
|
assert nValues > 0
|
|
|
|
if not words:
|
|
|
|
for j in range(nValues):
|
|
|
|
value = bytecode[i]
|
|
|
|
values.append(repr(value))
|
|
|
|
i = i + 1
|
|
|
|
else:
|
|
|
|
for j in range(nValues):
|
|
|
|
# cast to signed int16
|
|
|
|
value = (bytecode[i] << 8) | bytecode[i+1]
|
|
|
|
if value >= 0x8000:
|
|
|
|
value = value - 0x10000
|
|
|
|
values.append(repr(value))
|
|
|
|
i = i + 2
|
2014-01-19 17:30:19 +08:00
|
|
|
if preserve:
|
|
|
|
break
|
2013-12-17 04:19:14 -05:00
|
|
|
|
2014-01-19 17:30:19 +08:00
|
|
|
if not preserve:
|
|
|
|
mnemonic = "PUSH"
|
2013-12-17 04:19:14 -05:00
|
|
|
nValues = len(values)
|
2013-11-14 20:16:41 -05:00
|
|
|
if nValues == 1:
|
2015-01-05 17:49:38 -08:00
|
|
|
assembly.append("%s[ ] /* 1 value pushed */" % mnemonic)
|
2013-12-17 04:02:10 -05:00
|
|
|
else:
|
2015-01-05 17:49:38 -08:00
|
|
|
assembly.append("%s[ ] /* %s values pushed */" % (mnemonic, nValues))
|
2013-12-17 04:19:14 -05:00
|
|
|
assembly.extend(values)
|
2000-02-01 15:30:15 +00:00
|
|
|
else:
|
2013-11-14 20:24:16 -05:00
|
|
|
assembly.append("INSTR%d[ ]" % op)
|
|
|
|
i = i + 1
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
2000-02-01 15:30:15 +00:00
|
|
|
if argBits:
|
2015-01-05 17:49:38 -08:00
|
|
|
assembly.append(mnemonic + "[%s] /* %s */" % (num2binary(op - argoffset, argBits), name))
|
1999-12-16 21:34:53 +00:00
|
|
|
else:
|
2015-01-05 17:49:38 -08:00
|
|
|
assembly.append(mnemonic + "[ ] /* %s */" % name)
|
1999-12-16 21:34:53 +00:00
|
|
|
i = i + 1
|
|
|
|
self.assembly = assembly
|
|
|
|
|
2015-06-25 18:04:07 +01:00
|
|
|
def __bool__(self):
|
2015-06-25 19:22:42 +01:00
|
|
|
"""
|
|
|
|
>>> p = Program()
|
|
|
|
>>> bool(p)
|
|
|
|
False
|
|
|
|
>>> bc = array.array("B", [0])
|
|
|
|
>>> p.fromBytecode(bc)
|
|
|
|
>>> bool(p)
|
|
|
|
True
|
|
|
|
>>> p.bytecode.pop()
|
|
|
|
0
|
|
|
|
>>> bool(p)
|
|
|
|
False
|
|
|
|
|
|
|
|
>>> p = Program()
|
|
|
|
>>> asm = ['SVTCA[0]']
|
|
|
|
>>> p.fromAssembly(asm)
|
|
|
|
>>> bool(p)
|
|
|
|
True
|
|
|
|
>>> p.assembly.pop()
|
|
|
|
'SVTCA[0]'
|
|
|
|
>>> bool(p)
|
|
|
|
False
|
|
|
|
"""
|
2015-06-25 18:04:07 +01:00
|
|
|
return ((hasattr(self, 'assembly') and len(self.assembly) > 0) or
|
2015-08-09 00:33:50 -07:00
|
|
|
(hasattr(self, 'bytecode') and len(self.bytecode) > 0))
|
2015-06-25 18:04:07 +01:00
|
|
|
|
|
|
|
__nonzero__ = __bool__
|
|
|
|
|
2016-03-11 13:31:16 +00:00
|
|
|
def __eq__(self, other):
|
|
|
|
if type(self) != type(other):
|
|
|
|
return NotImplemented
|
|
|
|
return self.__dict__ == other.__dict__
|
|
|
|
|
|
|
|
def __ne__(self, other):
|
|
|
|
result = self.__eq__(other)
|
|
|
|
return result if result is NotImplemented else not result
|
|
|
|
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2015-04-14 11:51:03 -07:00
|
|
|
def _test():
|
|
|
|
"""
|
|
|
|
>>> _test()
|
|
|
|
True
|
|
|
|
"""
|
|
|
|
|
2015-04-14 13:25:19 -07:00
|
|
|
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/-"""
|
2015-04-26 02:01:01 -04:00
|
|
|
|
2000-02-01 15:30:15 +00:00
|
|
|
p = Program()
|
|
|
|
p.fromBytecode(bc)
|
2015-04-14 11:51:03 -07:00
|
|
|
asm = p.getAssembly(preserve=True)
|
2008-09-16 14:14:44 +00:00
|
|
|
p.fromAssembly(asm)
|
2013-11-27 04:57:33 -05:00
|
|
|
print(bc == p.getBytecode())
|
1999-12-16 21:34:53 +00:00
|
|
|
|
2015-04-14 11:51:03 -07:00
|
|
|
if __name__ == "__main__":
|
2015-04-26 00:54:30 -04:00
|
|
|
import sys
|
|
|
|
import doctest
|
2015-04-14 11:51:03 -07:00
|
|
|
sys.exit(doctest.testmod().failed)
|