from filetypes.base import *
import malcat
import codecs
import struct
from struct import unpack
import operator as opr

class BiffRecord(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        if sz:
            yield Bytes(sz, name="Data")

# http://www.aboutvb.de/bas/formate/pdf/biff.pdf
class BiffBeginOfFile(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        eof = len(self) + sz
        yield UInt16(name="BiffVersion", comment="0600 for Biff8")
        yield UInt16(name="DocumentType", values=[
            ("Global", 5), 
            ("VB Module", 6), 
            ("XLS", 0x10), 
            ("XLC", 0x20), 
            ("XLM", 0x40), 
            ("XLW", 0x100), 
        ])
        if len(self) < eof:
            yield UInt16(name="BuildIdentifier")
            yield UInt16(name="BuildYear")
        if len(self) < eof:
            yield BitsField(
            Bit(name="LastWindows", comment="Last update by excel for Windows"),
            Bit(name="LastRISC", comment="Last update by excel for RISC"),
            Bit(name="LastBeta", comment="Last update by excel Neta"),
            Bit(name="Windows", comment="File was updated by Excel for Windows"),
            Bit(name="Mac", comment="File was updated by Excel for Max"),
            Bit(name="Beta", comment="File was updated by Excel Beta"),
            NullBits(2),
            Bit(name="RISC", comment="File was updated by Excel for RISC"),
            Bit(name="OutOfMemory", comment="This file had an out-of-memory failure"),
            Bit(name="GlJmp", comment="This file had an out-of-memory failure during rendering"),
            NullBits(2),
            Bit(name="FontLimit", comment="This file hit the 255 font limit"),
            Bit(name="VerXLHigh1", comment="Specifies the highest version of the application that once saved this file"),
            Bit(name="VerXLHigh2", comment="Specifies the highest version of the application that once saved this file"),
            Bit(name="VerXLHigh3", comment="Specifies the highest version of the application that once saved this file"),
            Bit(name="VerXLHigh4", comment="Specifies the highest version of the application that once saved this file"),
            NullBits(14),
            name="FileFlags", comment="file history flags")
            yield UInt8(name="BiffVersionMinor")
        if len(self) < eof:
            yield Unused(eof - len(self), name="Unused")


class BiffCodeName(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        yield from XLString().parse()



class XLString(Struct):

    def parse(self):
        ssz = yield UInt16(name="StringSize", comment="number of characters in string")
        flags = yield BitsField(
            Bit(name="Wide", comment="String is wide"),
            NullBits(7),
            name="Attributes", comment="StringAttribute")
        if flags["Wide"]:
            yield StringUtf16le(ssz, name="String")
        else:
            yield String(ssz, name="String")

class XLUnicodeRichExtendedString(Struct):

    def parse(self):
        ssz = yield UInt16(name="StringSize", comment="number of characters in string")
        flags = yield BitsField(
            Bit(name="Wide", comment="String is wide"),
            NullBits(1),
            Bit(name="ExtSt", comment="whether the string contains phonetic string data"),
            Bit(name="RichSt", comment="whether the string is a rich string and the string has at least two character formats applied"),
            name="Attributes", comment="StringAttribute")
        if flags["RichSt"]:
            crun = yield UInt16(name="FormatRunCount", comment="number of elements in FormatRun")
        else:
            crun = 0
        if flags["ExtSt"]:
            cext = yield UInt32(name="ExtRstCount", comment="byte count of ExtRst")
        else:
            cext = 0
        if flags["Wide"]:
            yield StringUtf16le(ssz, name="String")
        else:
            yield String(ssz, name="String")
        if crun:
            yield Array(crun, UInt32(), name="FormatRun")
        if cext:
            yield Bytes(cext, name="ExtRst")

class ShortXLString(Struct):

    def __init__(self, *args, staticsz=None, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.staticsz = staticsz

    def parse(self):
        ssz = yield UInt8(name="Size", comment="number of characters in string")
        if self.staticsz is None or self.staticsz > 1:
            flags = yield BitsField(
                Bit(name="Wide", comment="String is wide"),
                NullBits(7),
            name="Attributes", comment="StringAttribute")
            if flags["Wide"]:
                if self.staticsz:
                    if self.staticsz % 2 and self.staticsz < ssz:
                        yield String(self.staticsz - 2, name="String")
                    elif ssz > 0:
                        yield StringUtf16le(min(ssz, (self.staticsz - 2) // 2), name="String")
                elif ssz > 0:
                    yield StringUtf16le(ssz, name="String")
            else:
                if self.staticsz:
                    ssz = min(ssz, (self.staticsz - 2))
                if ssz > 0:
                    yield String(ssz, name="String")            
        if self.staticsz and len(self) < self.staticsz:
            yield Unused(self.staticsz - len(self), name="Overlay")

# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/504b6cfc-d57b-4296-92f4-ceefc0a2ca9b
class BiffString(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        ssz = yield UInt16(name="StringSize", comment="number of characters in string")
        flags = yield BitsField(
            Bit(name="Wide", comment="String is wide"),
            NullBits(7),
            name="Attributes", comment="StringAttribute")
        if flags["Wide"]:
            yield StringUtf16le(ssz, name="String")
        else:
            yield String(ssz, name="String")


class BiffBoundSheet(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        start = len(self)
        yield UInt32(name="StartOfSheet", comment="FilePointer as specified in [MS-OSHARED] section 2.2.1.5 that specifies the stream position of the start of the BOF record for the sheet")
        yield BitsField(
            Bit(name="Hidden", comment="sheet is hidden"),
            Bit(name="VeryHidden", comment="sheet is hidden and cannot be displayed using the user interface"),
            NullBits(6),
            name="State", comment="sheet state")
        yield UInt8(name="Type", comment="sheet type", values=[
            ("WorkOrDialog", 0),
            ("Macro", 1),
            ("Chart", 2),
            ("VbaModule", 6),
            ])
        yield ShortXLString(name="Name", comment="sheet name", staticsz=sz - (len(self) - start))



class BiffCell(Struct):

    def parse(self):
        yield UInt16(name="Row")
        yield UInt16(name="Column")
        yield UInt16(name="XFIndex", comment="unsigned integer that specifies a zero-based index of a cell XF record in the collection of XF records in the Globals Substream. Cell XF records are the subset of XF records with an fStyle field equal to 0. This value MUST be greater than or equal to 15, or equal to 0")


class RkNumber(Struct):

    def parse(self):
        yield UInt16(name="Ixfe", comment="specifies the format of the numeric value")
        yield UInt32(name="Number", comment="encoded number")

    @staticmethod
    def read(number):
        mul_by_100 = number & 1
        signed = number & 2
        number = number >>2
        if signed:
            # signed 30 bits integer
            if number & 30:
                number = - (~number + 1)
        else:
            number = number << 2 + 32
            number, = struct.unpack("<d", struct.pack("<Q", number))
        if mul_by_100:
            number = number * 100
        return number

class BiffRk(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        yield UInt16(name="Row")
        yield UInt16(name="Column")
        yield RkNumber(name="Data")



class BiffMulRk(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        start = len(self)
        yield UInt16(name="Row", comment="row containing the blank cells")
        yield UInt16(name="ColumnFirst", comment="first column in the sheet")
        number_of_entries = (sz - (2 + len(self) - start)) // 6
        yield Array(number_of_entries, RkNumber(), name="Data", comment="the numbers")
        yield UInt16(name="ColumnLast", comment="last column in the sheet")


class BiffFormulaValue(Struct):

    def parse(self):
        if self.look_ahead(8)[6:] == b"\xff\xff":
            typ = yield UInt8(name="ValueType", values=[
                ("String", 0),
                ("Bool", 1),
                ("Error", 2),
                ("Blank", 3),
                ])
            if typ == 1 or typ == 2:
                yield UInt8(name="Value")
                yield Unused(4, name="Padding")
            else:
                yield Unused(5, name="Padding")
            yield UInt16(name="Marker")
        else:
            # TODO: double
            yield UInt64(name="Value")

class BiffDimensions(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        yield UInt32(name="RowFirst", comment="first row in the sheet that contains a used cell")
        yield UInt32(name="RowLast", comment="last row in the sheet that contains a used cell")
        yield UInt16(name="ColumnFirst", comment="first column in the sheet that contains a used cell")
        yield UInt16(name="ColumnLast", comment="last column in the sheet that contains a used cell")
        yield Unused(2)

class BiffMulBlank(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        start = len(self)
        yield UInt32(name="Row", comment="row containing the blank cells")
        yield UInt16(name="ColumnFirst", comment="first column in the series of blank cells within the sheet")
        number_of_entries = (sz - (2 + len(self) - start)) // 2
        yield Array(number_of_entries, UInt16(), name="Cells", comment="element of this array contains an IXFCell structure corresponding to a blank cell in the series")
        yield UInt16(name="ColumnLast", comment="last column in the series of blank cells within the sheet")


class BiffRow(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        yield UInt16(name="Row")
        yield UInt16(name="ColumnFirst", comment="zero-based index of the first column that contains a cell populated with data or formatting in the current row. MUST be less than or equal to 255")
        yield UInt16(name="ColumnLast", comment="one-based index of the last column that contains a cell populated with data or formatting in the current row. MUST be less than or equal to 256. If ColumnLast is equal to ColumnFirst, this record specifies a row with no CELL records")
        yield UInt16(name="RowHeight", comment="row height in twips. If fDyZero is 1, the row is hidden and the value of miyRw specifies the original row height")
        yield Unused(4)
        yield BitsField(
            NullBits(8),
            Bit(name="GhostDirty", comment="row was formatted manually"),
            Bit(name="Unsynced", comment="row height was set manually"),
            Bit(name="Hidden", comment="row is hidden"),
            Bit(name="Collapsed", comment="specifies whether the rows that are one level of outlining deeper than the current row are included in the collapsed outline state"),
            NullBits(1),
            Bit(name="OutLevel3", comment="outline level of the row"),
            Bit(name="OutLevel2", comment="outline level of the row"),
            Bit(name="OutLevel1", comment="outline level of the row"),
            name="Flags", comment="row flags")
        yield UInt16(name="ExtraFlags")



# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/504b6cfc-d57b-4296-92f4-ceefc0a2ca9b
class BiffFormula(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        eof = len(self) + sz
        yield BiffCell(name="Cell")
        yield BiffFormulaValue(name="Value", comment="formula value")
        flags = yield BitsField(
            Bit(name="AlwaysCalc", comment="whether the formula needs to be calculated during the next recalculation"),
            NullBits(1),
            Bit(name="Fill", comment="whether the cell has a fill alignment or a center-across-selection alignment"),
            Bit(name="Shared", comment="whether the formula is part of a shared formula as defined in ShrFmla"),
            NullBits(1),
            Bit(name="ClearErrors", comment="whether the formula is excluded from formula error checking"),
            NullBits(10),
            name="Flags", comment="formula flags")
        yield UInt32(name="Cache", comment="field that specifies an application-specific cache of information. This cache exists for performance reasons only, and can be rebuilt based on information stored elsewhere in the file without affecting calculation results")
        formula_size, = struct.unpack("<H", self.look_ahead(2))
        if len(self) + formula_size + 2 > eof:
            # support buggy formulas
            yield Bytes(eof - len(self), name="FormulaRaw")
        else:
            flen = yield UInt16(name="FormulaLen")
            yield Bytes(flen, name="Formula")
            if len(self) < eof:
                yield Bytes(eof - len(self), name="Extra")

# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/d148e898-4504-4841-a793-ee85f3ea9eef
class BiffLbl(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        eof = len(self) + sz
        flags = yield BitsField(
            Bit(name="Hidden", comment="whether the defined name is not visible in the list of defined names"),
            Bit(name="Func", comment="whether the defined name represents an Excel macro (XLM)"),
            Bit(name="OB", comment="whether the defined name represents a Visual Basic for Applications (VBA) macro"),
            Bit(name="Proc", comment="whether the defined name represents a macro"),
            Bit(name="CalcExp", comment="contains a call to a function that can return an array"),
            Bit(name="Builtin", comment="defined name represents a built-in name"),
            *[Bit(name="Grp{}".format(i), comment="integer that specifies the function category for the defined name") for i in range(6)],
            NullBits(1),
            Bit(name="Published", comment="whether the defined name is published"),
            Bit(name="WorkbookParam", comment="whether the defined name is a workbook parameter"),
            name="Flags", comment="label flags")
        yield UInt8(name="Key", comment="unsigned integer value of the ASCII character that specifies the shortcut key for the macro represented by the defined name")
        ns = yield UInt8(name="NameSize", comment="number of characters in Name")
        formula_size = yield UInt16(name="FormulaSize", comment="number of bytes in Formula")
        yield Unused(2)
        yield UInt16(name="Sheet", comment="integer that specifies if the defined name is a local name, and if so, which sheet it is on. If itab is not 0, the defined name is a local name and the value MUST be a one-based index to the collection of BoundSheet8 records as they appear in the Globals Substream")
        yield Unused(4)
        is_wide_string = False
        if ns and self.look_ahead(1) == b"\x00":
            # sometimes the Wide byte is included, sometime not :/ luckily 0 is not a valid
            # Ptg, so if namesize is zero and formula starts with a zero, chances are
            # that this is the wide byte instead
            strflags = yield BitsField(
                Bit(name="Wide", comment="Name string is an UTF16 string"),
            name = "NameFlags")
            is_wide_string = strflags["Wide"]
        if ns:
            start_name = len(self)
            if flags["Builtin"]:
                yield UInt8(name="Name", values=[
                    ("Consolidate_Area", 0x00), 
                    ("Auto_Open", 0x01), 
                    ("Auto_Close", 0x02), 
                    ("Extract", 0x03), 
                    ("Database", 0x04), 
                    ("Criteria", 0x05), 
                    ("Print_Area", 0x06), 
                    ("Print_Titles", 0x07), 
                    ("Recorder", 0x08), 
                    ("Data_Form", 0x09), 
                    ("Auto_Activate", 0x0A), 
                    ("Auto_Deactivate", 0x0B), 
                    ("Sheet_Title", 0x0C), 
                    ("_FilterDatabase", 0x0D), 
                ])
            elif is_wide_string:
                yield StringUtf16le(ns, name="Name")
                ns = ns * 2
            else:
                yield String(ns, name="Name")
            if len(self) < start_name + ns:
                yield Unused(start_name + ns - len(self))
        if len(self) < eof and formula_size:
            yield Bytes(min(eof - len(self), formula_size), name="Formula")
        if len(self) < eof:
            yield Bytes(eof - len(self), name="Extra")

class BiffLabelSst(Struct):
    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        yield BiffCell(name="Cell")
        index = yield UInt32(name="Index", comment="index into SST")

# https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/b6231b92-d32e-4626-badd-c3310a672bab
class BiffSST(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        eof = len(self) + sz
        yield UInt32(name="References", comment="total number of references in the workbook to the strings in the shared string table")
        cnt = yield UInt32(name="UniqueCount", comment="integer that specifies the number of unique strings in the shared string table")
        for i in range(cnt):
            # SST can be interrupted by a continue keyword :(
            cur = len(self)
            if cur == eof:
                record, record_size = struct.unpack("<HH", self.look_ahead(4))
                if record != 0x3c:
                    raise FatalError("Not a continue record: {}".format(self.look_ahead(4)))
                yield Unused(4, name="ContinueRecord", comment="String interrupted by a continue record")
                eof += 4 + record_size
                continue
            total_size = 3
            str_size, str_flags = struct.unpack("<HB", self.look_ahead(3))
            p = 3
            if str_flags & 1:
                str_size = str_size * 2
            total_size += str_size
            if str_flags & 8:
                total_size += 2 + struct.unpack("<H", self.look_ahead(p+2)[p:])[0] * 4
                p += 2
            if str_flags & 4:
                total_size += 4 + struct.unpack("<I", self.look_ahead(p+4)[p:])[0]
            if cur + total_size > eof:
                # string would be interrupted by a continue record
                record, record_size = struct.unpack("<HH", self.look_ahead(4 + eof - cur)[eof - cur:])
                if record != 0x3c:
                    raise FatalError("Not a continue record: {} 0x{:x} + 0x{:x} > 0x{:x}".format(record, cur, total_size, eof))
                delta = 4   #  CONTINUE HEADER (2 + 2 bytes)
                if cur + 3 + str_size > eof:
                    delta += 1 # +1 because repeated Wide byte
                yield Unused(total_size + delta, name="InterruptedString", comment="String interrupted by a continue record, cannot parse :(")
                eof += 4 + record_size
            else:
                yield XLUnicodeRichExtendedString(name="String")
        if len(self) < eof:
            yield Bytes(eof - len(self), name="Overlay")            

class BiffExternSheet(Struct):

    def parse(self):
        yield UInt16(name="Opcode", values=EXCEL_OPCODE_ENUM)
        sz = yield UInt16(name="Size", comment="Opcode content size")
        number = yield UInt16(name="References")
        yield Array(number, ExternalSheetInfo(), name="Sheets")


class ExternalSheetInfo(Struct):

    def parse(self):
        yield UInt16(name="SupBook", comment="unsigned integer that specifies the zero-based index of a SupBook record in the collection of SupBook records in the Globals Substream ABNF")
        yield Int16(name="First", comment="signed integer that specifies the scope of the supporting link, and if a scope is specified, the first sheet in the scope of that supporting link. If the type of supporting link specified by the cch and virtPath fields of the SupBook record is same-sheet referencing, add-in referencing, DDE data source referencing, or OLE data source referencing, then no scope is specified and this value MUST be -2")
        yield Int16(name="Last", comment="signed integer that specifies the scope of the supporting link, and if a scope is specified, the last sheet in the scope of that supporting link. If the type of supporting link specified by the cch and virtPath fields of the SupBook record is same-sheet referencing, add-in referencing, DDE data source referencing, or OLE data source referencing, then no scope is specified and this value MUST be -2")


EXCEL_OPCODES = {}
EXCEL_OPCODE_ENUM = []
FUNC_DEF = {}

def delayed_globals():
    # faster startup time

    global EXCEL_OPCODES
    global EXCEL_OPCODE_ENUM
    global FUNC_DEFS

    EXCEL_OPCODES = {
        0x06: ("FORMULA", "Cell Formula", BiffFormula),
        0x0A: ("EOF", "End of File", BiffRecord),
        0x0C: ("CALCCOUNT", "Iteration Count", BiffRecord),
        0x0D: ("CALCMODE", "Calculation Mode", BiffRecord),
        0x0E: ("PRECISION", "Precision", BiffRecord),
        0x0F: ("REFMODE", "Reference Mode", BiffRecord),
        0x10: ("DELTA", "Iteration Increment", BiffRecord),
        0x11: ("ITERATION", "Iteration Mode", BiffRecord),
        0x12: ("PROTECT", "Protection Flag", BiffRecord),
        0x13: ("PASSWORD", "Protection Password", BiffRecord),
        0x14: ("HEADER", "Print Header on Each Page", BiffRecord),
        0x15: ("FOOTER", "Print Footer on Each Page", BiffRecord),
        0x16: ("EXTERNCOUNT", "Number of External References", BiffRecord),
        0x17: ("EXTERNSHEET", "External Reference", BiffExternSheet),
        0x18: ("LBL", "Cell Value String Constant", BiffLbl),
        0x19: ("WINDOWPROTECT", "Windows Are Protected", BiffRecord),
        0x1A: ("VERTICALPAGEBREAKS", "Explicit Column Page Breaks", BiffRecord),
        0x1B: ("HORIZONTALPAGEBREAKS", "Explicit Row Page Breaks", BiffRecord),
        0x1C: ("NOTE", "Comment Associated with a Cell", BiffRecord),
        0x1D: ("SELECTION", "Current Selection", BiffRecord),
        0x1E: ("FORMAT", "", BiffRecord),
        0x22: ("1904", "1904 Date System", BiffRecord),
        0x23: ("ExternName", "a User Defined Function (UDF) reference on a XLL or COM add-in, a DDE data item or an OLE data item", BiffRecord),
        0x26: ("LEFTMARGIN", "Left Margin Measurement", BiffRecord),
        0x27: ("RIGHTMARGIN", "Right Margin Measurement", BiffRecord),
        0x28: ("TOPMARGIN", "Top Margin Measurement", BiffRecord),
        0x29: ("BOTTOMMARGIN", "Bottom Margin Measurement", BiffRecord),
        0x2A: ("PRINTHEADERS", "Print Row/Column Labels", BiffRecord),
        0x2B: ("PRINTGRIDLINES", "Print Gridlines Flag", BiffRecord),
        0x2F: ("FILEPASS", "File Is Password-Protected", BiffRecord),
        0x31: ("FONT", "Font", BiffRecord),
        0x3C: ("CONTINUE", "Continues Long Records", BiffRecord),
        0x3D: ("WINDOW1", "Window Information", BiffRecord),
        0x40: ("BACKUP", "Save Backup Version of the File", BiffRecord),
        0x41: ("PANE", "Number of Panes and Their Position", BiffRecord),
        0x42: ("CODENAME", "VBE Object Name", BiffRecord),
        0x42: ("CODEPAGE", "Default Code Page", BiffRecord),
        0x4D: ("PLS", "Environment-Specific Print Record", BiffRecord),
        0x50: ("DCON", "Data Consolidation Information", BiffRecord),
        0x51: ("DCONREF", "Data Consolidation References", BiffRecord),
        0x52: ("DCONNAME", "Data Consolidation Named References", BiffRecord),
        0x55: ("DEFCOLWIDTH", "Default Width for Columns", BiffRecord),
        0x59: ("XCT", "CRN Record Count", BiffRecord),
        0x5A: ("CRN", "Nonresident Operands", BiffRecord),
        0x5B: ("FILESHARING", "File-Sharing Information", BiffRecord),
        0x5C: ("WRITEACCESS", "Write Access User Name", BiffRecord),
        0x5D: ("OBJ", "Describes a Graphic Object", BiffRecord),
        0x5E: ("UNCALCED", "Recalculation Status", BiffRecord),
        0x5F: ("SAVERECALC", "Recalculate Before Save", BiffRecord),
        0x60: ("TEMPLATE", "Workbook Is a Template", BiffRecord),
        0x61: ("INTL", "", BiffRecord),
        0x63: ("OBJPROTECT", "Objects Are Protected", BiffRecord),
        0x7D: ("COLINFO", "Column Formatting Information", BiffRecord),
        0x7E: ("RK", "Cell Value RK Number", BiffRk),
        0x7F: ("MDATA", "Image Data", BiffRecord),
        0x80: ("GUTS", "Size of Row and Column Gutters", BiffRecord),
        0x81: ("WSBOOL", "Additional Workspace Information", BiffRecord),
        0x82: ("GRIDSET", "State Change of Gridlines Option", BiffRecord),
        0x83: ("HCENTER", "Center Between Horizontal Margins", BiffRecord),
        0x84: ("VCENTER", "Center Between Vertical Margins", BiffRecord),
        0x85: ("BOUNDSHEET", "Sheet Information", BiffBoundSheet),
        0x86: ("WRITEPROT", "Workbook Is Write-Protected", BiffRecord),
        0x87: ("ADDIN", "Workbook Is an Add-in Macro", BiffRecord),
        0x88: ("EDG", "Edition Globals", BiffRecord),
        0x89: ("PUB", "Publisher", BiffRecord),
        0x8C: ("COUNTRY", "Default Country and WIN.INI Country", BiffRecord),
        0x8D: ("HIDEOBJ", "Object Display Options", BiffRecord),
        0x90: ("SORT", "Sorting Options", BiffRecord),
        0x91: ("SUB", "Subscriber", BiffRecord),
        0x92: ("PALETTE", "Color Palette Definition", BiffRecord),
        0x94: ("LHRECORD", ".WK? File Conversion Information", BiffRecord),
        0x95: ("LHNGRAPH", "Named Graph Information", BiffRecord),
        0x96: ("SOUND", "Sound Note", BiffRecord),
        0x98: ("LPR", "Sheet Was Printed Using LINE.PRINT(", BiffRecord),
        0x99: ("STANDARDWIDTH", "Standard Column Width", BiffRecord),
        0x9A: ("FNGROUPNAME", "Function Group Name", BiffRecord),
        0x9B: ("FILTERMODE", "Sheet Contains Filtered List", BiffRecord),
        0x9C: ("FNGROUPCOUNT", "Built-in Function Group Count", BiffRecord),
        0x9D: ("AUTOFILTERINFO", "Drop-Down Arrow Count", BiffRecord),
        0x9E: ("AUTOFILTER", "AutoFilter Data", BiffRecord),
        0xA0: ("SCL", "Window Zoom Magnification", BiffRecord),
        0xA1: ("SETUP", "Page Setup", BiffRecord),
        0xA9: ("COORDLIST", "Polygon Object Vertex Coordinates", BiffRecord),
        0xAB: ("GCW", "Global Column-Width Flags", BiffRecord),
        0xAE: ("SCENMAN", "Scenario Output Data", BiffRecord),
        0xAF: ("SCENARIO", "Scenario Data", BiffRecord),
        0xB0: ("SXVIEW", "View Definition", BiffRecord),
        0xB1: ("SXVD", "View Fields", BiffRecord),
        0xB2: ("SXVI", "View Item", BiffRecord),
        0xB4: ("SXIVD", "Row/Column Field IDs", BiffRecord),
        0xB5: ("SXLI", "Line Item Array", BiffRecord),
        0xB6: ("SXPI", "Page Item", BiffRecord),
        0xB8: ("DOCROUTE", "Routing Slip Information", BiffRecord),
        0xB9: ("RECIPNAME", "Recipient Name", BiffRecord),
        0xBC: ("SHRFMLA", "Shared Formula", BiffRecord),
        0xBD: ("MULRK", "Multiple  RK Cells", BiffMulRk),
        0xBE: ("MULBLANK", "Multiple Blank Cells", BiffMulBlank),
        0xC1: ("MMS", " ADDMENU / DELMENU Record Group Count", BiffRecord),
        0xC2: ("ADDMENU", "Menu Addition", BiffRecord),
        0xC3: ("DELMENU", "Menu Deletion", BiffRecord),
        0xC5: ("SXDI", "Data Item", BiffRecord),
        0xC6: ("SXDB", "PivotTable Cache Data", BiffRecord),
        0xCD: ("SXSTRING", "String", BiffRecord),
        0xD0: ("SXTBL", "Multiple Consolidation Source Info", BiffRecord),
        0xD1: ("SXTBRGIITM", "Page Item Name Count", BiffRecord),
        0xD2: ("SXTBPG", "Page Item Indexes", BiffRecord),
        0xD3: ("OBPROJ", "Visual Basic Project", BiffRecord),
        0xD5: ("SXIDSTM", "Stream ID", BiffRecord),
        0xD6: ("RSTRING", "Cell with Character Formatting", BiffRecord),
        0xD7: ("DBCELL", "Stream Offsets", BiffRecord),
        0xDA: ("BOOKBOOL", "Workbook Option Flag", BiffRecord),
        0xDC: ("PARAMQRY", "Query Parameters", BiffRecord),
        0xDC: ("SXEXT", "External Source Information", BiffRecord),
        0xDD: ("SCENPROTECT", "Scenario Protection", BiffRecord),
        0xDE: ("OLESIZE", "Size of OLE Object", BiffRecord),
        0xDF: ("UDDESC", "Description String for Chart Autoformat", BiffRecord),
        0xE0: ("XF", "Extended Format", BiffRecord),
        0xE1: ("INTERFACEHDR", "Beginning of User Interface Records", BiffRecord),
        0xE2: ("INTERFACEEND", "End of User Interface Records", BiffRecord),
        0xE3: ("SXVS", "View Source", BiffRecord),
        0xE5: ("MERGECELLS", "Merged Cells", BiffRecord),
        0xEA: ("TABIDCONF", "Sheet Tab ID of Conflict History", BiffRecord),
        0xEB: ("MSODRAWINGGROUP", "Microsoft Office Drawing Group", BiffRecord),
        0xEC: ("MSODRAWING", "Microsoft Office Drawing", BiffRecord),
        0xED: ("MSODRAWINGSELECTION", "Microsoft Office Drawing Selection", BiffRecord),
        0xF0: ("SXRULE", "PivotTable Rule Data", BiffRecord),
        0xF1: ("SXEX", "PivotTable View Extended Information", BiffRecord),
        0xF2: ("SXFILT", "PivotTable Rule Filter", BiffRecord),
        0xF4: ("SXDXF", "Pivot Table Formatting", BiffRecord),
        0xF5: ("SXITM", "Pivot Table Item Indexes", BiffRecord),
        0xF6: ("SXNAME", "PivotTable Name", BiffRecord),
        0xF7: ("SXSELECT", "PivotTable Selection Information", BiffRecord),
        0xF8: ("SXPAIR", "PivotTable Name Pair", BiffRecord),
        0xF9: ("SXFMLA", "Pivot Table Parsed Expression", BiffRecord),
        0xFB: ("SXFORMAT", "PivotTable Format Record", BiffRecord),
        0xFC: ("SST", "Shared String Table", BiffSST),
        0xFD: ("LABELSST", "Cell Value String Constant/ SST", BiffLabelSst),
        0xFF: ("EXTSST", "Extended Shared String Table", BiffRecord),
        0x100: ("SXVDEX", "Extended PivotTable View Fields", BiffRecord),
        0x103: ("SXFORMULA", "PivotTable Formula Record", BiffRecord),
        0x122: ("SXDBEX", "PivotTable Cache Data", BiffRecord),
        0x13D: ("TABID", "Sheet Tab Index Array", BiffRecord),
        0x160: ("USESELFS", "Natural Language Formulas Flag", BiffRecord),
        0x161: ("DSF", "Double Stream File", BiffRecord),
        0x162: ("XL5MODIFY", "Flag for  DSF", BiffRecord),
        0x1A5: ("FILESHARING2", "File-Sharing Information for Shared Lists", BiffRecord),
        0x1A9: ("USERBVIEW", "Workbook Custom View Settings", BiffRecord),
        0x1AA: ("USERSVIEWBEGIN", "Custom View Settings", BiffRecord),
        0x1AB: ("USERSVIEWEND", "End of Custom View Records", BiffRecord),
        0x1AD: ("QSI", "External Data Range", BiffRecord),
        0x1AE: ("SUPBOOK", "Supporting Workbook", BiffRecord),
        0x1AF: ("PROT4REV", "Shared Workbook Protection Flag", BiffRecord),
        0x1B0: ("CONDFMT", "Conditional Formatting Range Information", BiffRecord),
        0x1B1: ("CF", "Conditional Formatting Conditions", BiffRecord),
        0x1B2: ("DVAL", "Data Validation Information", BiffRecord),
        0x1B5: ("DCONBIN", "Data Consolidation Information", BiffRecord),
        0x1B6: ("TXO", "Text Object", BiffRecord),
        0x1B7: ("REFRESHALL", "Refresh Flag", BiffRecord),
        0x1B8: ("HLINK", "Hyperlink", BiffRecord),
        0x1BA: ("CODENAME", "Workbook name", BiffCodeName),
        0x1BB: ("SXFDBTYPE", "SQL Datatype Identifier", BiffRecord),
        0x1BC: ("PROT4REVPASS", "Shared Workbook Protection Password", BiffRecord),
        0x1BE: ("DV", "Data Validation Criteria", BiffRecord),
        0x1C0: ("EXCEL9FILE", "Excel 9 File", BiffRecord),
        0x1C1: ("RECALCID", "Recalc Information", BiffRecord),
        0x200: ("DIMENSIONS", "Cell Table Size", BiffDimensions),
        0x201: ("BLANK", "Cell Value Blank Cell", BiffRecord),
        0x203: ("NUMBER", "Cell Value Floating-Point Number", BiffRecord),
        0x204: ("LABEL", "label on the category axis for each series", BiffRecord),
        0x205: ("BOOLERR", "Cell Value Boolean or Error", BiffRecord),
        0x207: ("STRING", "String Value of a Formula", BiffString),
        0x208: ("ROW", "Describes a Row", BiffRow),
        0x20B: ("INDEX", "Index Record", BiffRecord),
        0x218: ("NAME", "Defined Name", BiffRecord),
        0x221: ("ARRAY", "Array-Entered Formula", BiffRecord),
        0x223: ("EXTERNNAME", "Externally Referenced Name", BiffRecord),
        0x225: ("DEFAULTROWHEIGHT", "Default Row Height", BiffRecord),
        0x231: ("FONT", "Font Description", BiffRecord),
        0x236: ("TABLE", "Data Table", BiffRecord),
        0x23E: ("WINDOW2", "Sheet Window Information", BiffRecord),
        0x27E: ("RK", "", BiffRk),
        0x293: ("STYLE", "Style Information", BiffRecord),
        0x406: ("FORMULA", "Cell Formula", BiffFormula),
        0x41E: ("FORMAT", "Number Format", BiffRecord),
        0x4BC: ("SHRFMLA", "Formula Shared information", BiffRecord),
        0x800: ("HLINKTOOLTIP", "Hyperlink Tooltip", BiffRecord),
        0x801: ("WEBPUB", "Web Publish Item", BiffRecord),
        0x802: ("QSISXTAG", "PivotTable and Query Table Extensions", BiffRecord),
        0x803: ("DBQUERYEXT", "Database Query Extensions", BiffRecord),
        0x804: ("EXTSTRING", " FRT String", BiffRecord),
        0x805: ("TXTQUERY", "Text Query Information", BiffRecord),
        0x806: ("QSIR", "Query Table Formatting", BiffRecord),
        0x807: ("QSIF", "Query Table Field Formatting", BiffRecord),
        0x809: ("BOF", "Beginning of File", BiffBeginOfFile),
        0x80A: ("OLEDBCONN", "OLE Database Connection", BiffRecord),
        0x80B: ("WOPT", "Web Options", BiffRecord),
        0x80C: ("SXVIEWEX", "Pivot Table OLAP Extensions", BiffRecord),
        0x80D: ("SXTH", "PivotTable OLAP Hierarchy", BiffRecord),
        0x80E: ("SXPIEX", "OLAP Page Item Extensions", BiffRecord),
        0x80F: ("SXVDTEX", "View Dimension OLAP Extensions", BiffRecord),
        0x810: ("SXVIEWEX9", "Pivot Table Extensions", BiffRecord),
        0x812: ("CONTINUEFRT", "Continued  FRT", BiffRecord),
        0x813: ("REALTIMEDATA", "Real-Time Data (RTD)", BiffRecord),
        0x862: ("SHEETEXT", "Extra Sheet Info", BiffRecord),
        0x863: ("BOOKEXT", "Extra Book Info", BiffRecord),
        0x864: ("SXADDL", "Pivot Table Additional Info", BiffRecord),
        0x865: ("CRASHRECERR", "Crash Recovery Error", BiffRecord),
        0x866: ("HFPicture", "Header / Footer Picture", BiffRecord),
        0x867: ("FEATHEADR", "Shared Feature Header", BiffRecord),
        0x868: ("FEAT", "Shared Feature Record", BiffRecord),
        0x86A: ("DATALABEXT", "Chart Data Label Extension", BiffRecord),
        0x86B: ("DATALABEXTCONTENTS", "Chart Data Label Extension Contents", BiffRecord),
        0x86C: ("CELLWATCH", "Cell Watch", BiffRecord),
        0x86d: ("FEATINFO", "Shared Feature Info Record", BiffRecord),
        0x871: ("FEATHEADR11", "Shared Feature Header 11", BiffRecord),
        0x872: ("FEAT11", "Shared Feature 11 Record", BiffRecord),
        0x873: ("FEATINFO11", "Shared Feature Info 11 Record", BiffRecord),
        0x874: ("DROPDOWNOBJIDS", "Drop Down Object", BiffRecord),
        0x875: ("CONTINUEFRT11", "Continue  FRT 11", BiffRecord),
        0x876: ("DCONN", "Data Connection", BiffRecord),
        0x877: ("LIST12", "Extra Table Data Introduced in Excel 2007", BiffRecord),
        0x878: ("FEAT12", "Shared Feature 12 Record", BiffRecord),
        0x879: ("CONDFMT12", "Conditional Formatting Range Information 12", BiffRecord),
        0x87A: ("CF12", "Conditional Formatting Condition 12", BiffRecord),
        0x87B: ("CFEX", "Conditional Formatting Extension", BiffRecord),
        0x87C: ("XFCRC", "XF Extensions Checksum", BiffRecord),
        0x87D: ("XFEXT", "XF Extension", BiffRecord),
        0x87E: ("EZFILTER12", "AutoFilter Data Introduced in Excel 2007", BiffRecord),
        0x87F: ("CONTINUEFRT12", "Continue FRT 12", BiffRecord),
        0x881: ("SXADDL12", "Additional Workbook Connections Information", BiffRecord),
        0x884: ("MDTINFO", "Information about a Metadata Type", BiffRecord),
        0x885: ("MDXSTR", "MDX Metadata String", BiffRecord),
        0x886: ("MDXTUPLE", "Tuple MDX Metadata", BiffRecord),
        0x887: ("MDXSET", "Set MDX Metadata", BiffRecord),
        0x888: ("MDXPROP", "Member Property MDX Metadata", BiffRecord),
        0x889: ("MDXKPI", "Key Performance Indicator MDX Metadata", BiffRecord),
        0x88A: ("MDTB", "Block of Metadata Records", BiffRecord),
        0x88B: ("PLV", "Page Layout View Settings in Excel 2007", BiffRecord),
        0x88C: ("COMPAT12", "Compatibility Checker 12", BiffRecord),
        0x88D: ("DXF", "Differential XF", BiffRecord),
        0x88E: ("TABLESTYLES", "Table Styles", BiffRecord),
        0x88F: ("TABLESTYLE", "Table Style", BiffRecord),
        0x890: ("TABLESTYLEELEMENT", "Table Style Element", BiffRecord),
        0x892: ("STYLEEXT", "Named Cell Style Extension", BiffRecord),
        0x893: ("NAMEPUBLISH", "Publish To Excel Server Data for Name", BiffRecord),
        0x894: ("NAMECMT", "Name Comment", BiffRecord),
        0x895: ("SORTDATA12", "Sort Data 12", BiffRecord),
        0x896: ("THEME", "Theme", BiffRecord),
        0x897: ("GUIDTYPELIB", "VB Project Typelib GUID", BiffRecord),
        0x898: ("FNGRP12", "Function Group", BiffRecord),
        0x899: ("NAMEFNGRP12", "Extra Function Group", BiffRecord),
        0x89A: ("MTRSETTINGS", "Multi-Threaded Calculation Settings", BiffRecord),
        0x89B: ("COMPRESSPICTURES", "Automatic Picture Compression Mode", BiffRecord),
        0x89C: ("HEADERFOOTER", "Header Footer", BiffRecord),
        0x8A3: ("FORCEFULLCALCULATION", "Force Full Calculation Settings", BiffRecord),
        0x8c1: ("LISTOBJ", "List Object", BiffRecord),
        0x8c2: ("LISTFIELD", "List Field", BiffRecord),
        0x8c3: ("LISTDV", "List Data Validation", BiffRecord),
        0x8c4: ("LISTCONDFMT", "List Conditional Formatting", BiffRecord),
        0x8c5: ("LISTCF", "List Cell Formatting", BiffRecord),
        0x8c6: ("FMQRY", "Filemaker queries", BiffRecord),
        0x8c7: ("FMSQRY", "File maker queries", BiffRecord),
        0x8c8: ("PLV", "Page Layout View in Mac Excel 11", BiffRecord),
        0x8c9: ("LNEXT", "Extension information for borders in Mac Office 11", BiffRecord),
        0x8ca: ("MKREXT", "Extension information for markers in Mac Office 11", BiffRecord),
    }
    EXCEL_OPCODE_ENUM = [(v[0], k) for k, v in EXCEL_OPCODES.items()]    

    FUNC_DEFS = {
        # index: (name, min#args, max#args, flags, #known_args, return_type, kargs)
        # https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/00b5dd7d-51ca-4938-b7b7-483fe0e5933b
        0  : ('COUNT',            0, 30, 0x04,  1, 'V', 'R'),
        1  : ('IF',               1,  3, 0x04,  3, 'V', 'VRR'),
        2  : ('ISNA',             1,  1, 0x02,  1, 'V', 'V'),
        3  : ('ISERROR',          1,  1, 0x02,  1, 'V', 'V'),
        4  : ('SUM',              0, 30, 0x04,  1, 'V', 'R'),
        5  : ('AVERAGE',          1, 30, 0x04,  1, 'V', 'R'),
        6  : ('MIN',              1, 30, 0x04,  1, 'V', 'R'),
        7  : ('MAX',              1, 30, 0x04,  1, 'V', 'R'),
        8  : ('ROW',              0,  1, 0x04,  1, 'V', 'R'),
        9  : ('COLUMN',           0,  1, 0x04,  1, 'V', 'R'),
        10 : ('NA',               0,  0, 0x02,  0, 'V', ''),
        11 : ('NPV',              2, 30, 0x04,  2, 'V', 'VR'),
        12 : ('STDEV',            1, 30, 0x04,  1, 'V', 'R'),
        13 : ('DOLLAR',           1,  2, 0x04,  1, 'V', 'V'),
        14 : ('FIXED',            2,  3, 0x04,  3, 'V', 'VVV'),
        15 : ('SIN',              1,  1, 0x02,  1, 'V', 'V'),
        16 : ('COS',              1,  1, 0x02,  1, 'V', 'V'),
        17 : ('TAN',              1,  1, 0x02,  1, 'V', 'V'),
        18 : ('ATAN',             1,  1, 0x02,  1, 'V', 'V'),
        19 : ('PI',               0,  0, 0x02,  0, 'V', ''),
        20 : ('SQRT',             1,  1, 0x02,  1, 'V', 'V'),
        21 : ('EXP',              1,  1, 0x02,  1, 'V', 'V'),
        22 : ('LN',               1,  1, 0x02,  1, 'V', 'V'),
        23 : ('LOG10',            1,  1, 0x02,  1, 'V', 'V'),
        24 : ('ABS',              1,  1, 0x02,  1, 'V', 'V'),
        25 : ('INT',              1,  1, 0x02,  1, 'V', 'V'),
        26 : ('SIGN',             1,  1, 0x02,  1, 'V', 'V'),
        27 : ('ROUND',            2,  2, 0x02,  2, 'V', 'VV'),
        28 : ('LOOKUP',           2,  3, 0x04,  2, 'V', 'VR'),
        29 : ('INDEX',            2,  4, 0x0c,  4, 'R', 'RVVV'),
        30 : ('REPT',             2,  2, 0x02,  2, 'V', 'VV'),
        31 : ('MID',              3,  3, 0x02,  3, 'V', 'VVV'),
        32 : ('LEN',              1,  1, 0x02,  1, 'V', 'V'),
        33 : ('VALUE',            1,  1, 0x02,  1, 'V', 'V'),
        34 : ('TRUE',             0,  0, 0x02,  0, 'V', ''),
        35 : ('FALSE',            0,  0, 0x02,  0, 'V', ''),
        36 : ('AND',              1, 30, 0x04,  1, 'V', 'R'),
        37 : ('OR',               1, 30, 0x04,  1, 'V', 'R'),
        38 : ('NOT',              1,  1, 0x02,  1, 'V', 'V'),
        39 : ('MOD',              2,  2, 0x02,  2, 'V', 'VV'),
        40 : ('DCOUNT',           3,  3, 0x02,  3, 'V', 'RRR'),
        41 : ('DSUM',             3,  3, 0x02,  3, 'V', 'RRR'),
        42 : ('DAVERAGE',         3,  3, 0x02,  3, 'V', 'RRR'),
        43 : ('DMIN',             3,  3, 0x02,  3, 'V', 'RRR'),
        44 : ('DMAX',             3,  3, 0x02,  3, 'V', 'RRR'),
        45 : ('DSTDEV',           3,  3, 0x02,  3, 'V', 'RRR'),
        46 : ('VAR',              1, 30, 0x04,  1, 'V', 'R'),
        47 : ('DVAR',             3,  3, 0x02,  3, 'V', 'RRR'),
        48 : ('TEXT',             2,  2, 0x02,  2, 'V', 'VV'),
        49 : ('LINEST',           1,  4, 0x04,  4, 'A', 'RRVV'),
        50 : ('TREND',            1,  4, 0x04,  4, 'A', 'RRRV'),
        51 : ('LOGEST',           1,  4, 0x04,  4, 'A', 'RRVV'),
        52 : ('GROWTH',           1,  4, 0x04,  4, 'A', 'RRRV'),
        0x0035: ('GOTO', 1,	1,	0x00,	1, 'V', 'R'),
        0x0036: ('HALT', 0, 1, 0x00, 1, 'V', 'V'),
        0x0037: ('RETURN', 0, 1, 0x00, 1, 'V', 'A'),
        56 : ('PV',               3,  5, 0x04,  5, 'V', 'VVVVV'),
        57 : ('FV',               3,  5, 0x04,  5, 'V', 'VVVVV'),
        58 : ('NPER',             3,  5, 0x04,  5, 'V', 'VVVVV'),
        59 : ('PMT',              3,  5, 0x04,  5, 'V', 'VVVVV'),
        60 : ('RATE',             3,  6, 0x04,  6, 'V', 'VVVVVV'),
        61 : ('MIRR',             3,  3, 0x02,  3, 'V', 'RVV'),
        62 : ('IRR',              1,  2, 0x04,  2, 'V', 'RV'),
        63 : ('RAND',             0,  0, 0x0a,  0, 'V', ''),
        64 : ('MATCH',            2,  3, 0x04,  3, 'V', 'VRR'),
        65 : ('DATE',             3,  3, 0x02,  3, 'V', 'VVV'),
        66 : ('TIME',             3,  3, 0x02,  3, 'V', 'VVV'),
        67 : ('DAY',              1,  1, 0x02,  1, 'V', 'V'),
        68 : ('MONTH',            1,  1, 0x02,  1, 'V', 'V'),
        69 : ('YEAR',             1,  1, 0x02,  1, 'V', 'V'),
        70 : ('WEEKDAY',          1,  2, 0x04,  2, 'V', 'VV'),
        71 : ('HOUR',             1,  1, 0x02,  1, 'V', 'V'),
        72 : ('MINUTE',           1,  1, 0x02,  1, 'V', 'V'),
        73 : ('SECOND',           1,  1, 0x02,  1, 'V', 'V'),
        74 : ('NOW',              0,  0, 0x0a,  0, 'V', ''),
        75 : ('AREAS',            1,  1, 0x02,  1, 'V', 'R'),
        76 : ('ROWS',             1,  1, 0x02,  1, 'V', 'R'),
        77 : ('COLUMNS',          1,  1, 0x02,  1, 'V', 'R'),
        78 : ('OFFSET',           3,  5, 0x04,  5, 'R', 'RVVVV'),
        0x004F: ('ABSREF', 2,	2,	0x00,	1, 'V', 'VR'),
        0x0050: ('RELREF', 2,	2,	0x00,	1, 'V', 'RR'),
        0x0051: ('ARGUMENT', 0,	3,	0x04,	3, 'V', 'VAR'),
        82 : ('SEARCH',           2,  3, 0x04,  3, 'V', 'VVV'),
        83 : ('TRANSPOSE',        1,  1, 0x02,  1, 'A', 'A'),
        0x0054: ('ERROR', 0,	2,	0x00,	2, 'V', 'VA'),
        0x0055: ('STEP', 0,	    0,	0x00,	0, 'V', ''),
        86 : ('TYPE',             1,  1, 0x02,  1, 'V', 'V'),
        0x0058: ('SET.NAME', 1, 2, 0x00, 1, 'V', 'VA'),
        0x0059: ('CALLER', 0,	0,	0x00,	0, 'V', ''),
        0x005A: ('DEREF', 1,	1,	0x00,	1, 'V', 'R'),
        0x005B: ('WINDOWS', 0,	2,	0x00,	2, 'V', 'VV'),
        92 : ('SERIESSUM',        4,  4, 0x02,  4, 'V', 'VVVA'),
        0x005D: ('DOCUMENTS', 0,	2,	0x04,	0, 'V', 'V'),
        0x005E: ('ACTIVE.CELL', 0,	0,	0x00,	0, 'V', ''),
        0x005F: ('SELECTION', 0,	0,	0x00,	0, 'V', ''),
        0x0060: ('RESULT', 0,	1,	0x00,	1, 'V', 'V'),
        97 : ('ATAN2',            2,  2, 0x02,  2, 'V', 'VV'),
        98 : ('ASIN',             1,  1, 0x02,  1, 'V', 'V'),
        99 : ('ACOS',             1,  1, 0x02,  1, 'V', 'V'),
        100: ('CHOOSE',           2, 30, 0x04,  2, 'V', 'VR'),
        101: ('HLOOKUP',          3,  4, 0x04,  4, 'V', 'VRRV'),
        102: ('VLOOKUP',          3,  4, 0x04,  4, 'V', 'VRRV'),
        0x0067: ('LINKS', 0,	2,	0x00,	2, 'V', 'VV'),
        0x0068: ('INPUT', 1,	7,	0x00,	6, 'V', 'VVVVVVV'),
        105: ('ISREF',            1,  1, 0x02,  1, 'V', 'R'),
        0x006A: ('GET.FORMULA', 1,	1,	0x00,	1, 'V', 'A'),
        0x006B: ('GET.NAME', 1,	2,	0x00,	1, 'V', 'VV'),
        0x006C: ('SET.VALUE', 2,	2,	0x00,	1, 'V', 'RV'),
        109: ('LOG',              1,  2, 0x04,  2, 'V', 'VV'),
        0x006E: ('EXEC', 1,	4,	0x00,	4, 'V', 'VVVV'),
        111: ('CHAR',             1,  1, 0x02,  1, 'V', 'V'),
        112: ('LOWER',            1,  1, 0x02,  1, 'V', 'V'),
        113: ('UPPER',            1,  1, 0x02,  1, 'V', 'V'),
        114: ('PROPER',           1,  1, 0x02,  1, 'V', 'V'),
        115: ('LEFT',             1,  2, 0x04,  2, 'V', 'VV'),
        116: ('RIGHT',            1,  2, 0x04,  2, 'V', 'VV'),
        117: ('EXACT',            2,  2, 0x02,  2, 'V', 'VV'),
        118: ('TRIM',             1,  1, 0x02,  1, 'V', 'V'),
        119: ('REPLACE',          4,  4, 0x02,  4, 'V', 'VVVV'),
        120: ('SUBSTITUTE',       3,  4, 0x04,  4, 'V', 'VVVV'),
        121: ('CODE',             1,  1, 0x02,  1, 'V', 'V'),
        0x007B: ('DIRECTORY', 0, 1, 0x00, 0, 'V', 'V'),
        124: ('FIND',             2,  3, 0x04,  3, 'V', 'VVV'),
        125: ('CELL',             1,  2, 0x0c,  2, 'V', 'VR'),
        126: ('ISERR',            1,  1, 0x02,  1, 'V', 'V'),
        127: ('ISTEXT',           1,  1, 0x02,  1, 'V', 'V'),
        128: ('ISNUMBER',         1,  1, 0x02,  1, 'V', 'V'),
        129: ('ISBLANK',          1,  1, 0x02,  1, 'V', 'V'),
        130: ('T',                1,  1, 0x02,  1, 'V', 'R'),
        131: ('N',                1,  1, 0x02,  1, 'V', 'R'),
        0x0084: ('FOPEN', 1,	2,	0x00,	2, 'V', 'VV'),
        0x0085: ('FCLOSE', 1,	1,	0x00,	1, 'V', 'V'),
        0x0086: ('FSIZE', 1,	1,	0x00,	1, 'V', 'V'),
        0x0087: ('FREADLN', 1,	1,	0x00,	1, 'V', 'V'),
        0x0088: ('FREAD', 1,	1,	0x00,	1, 'V', 'V'),
        0x0089: ('FWRITELN', 2,	2,	0x00,	1, 'V', 'VV'),
        0x008A: ('FWRITE', 2,	2,	0x00,	1, 'V', 'VV'),
        0x008B: ('FPOS', 1,	2,	0x00,	1, 'V', 'VV'),
        140: ('DATEVALUE',        1,  1, 0x02,  1, 'V', 'V'),
        141: ('TIMEVALUE',        1,  1, 0x02,  1, 'V', 'V'),
        142: ('SLN',              3,  3, 0x02,  3, 'V', 'VVV'),
        143: ('SYD',              4,  4, 0x02,  4, 'V', 'VVVV'),
        144: ('DDB',              4,  5, 0x04,  5, 'V', 'VVVVV'),
        0x0091: ('GET.DEF', 1,	3,	0x00,	2, 'V', 'VVV'),
        0x0092: ('REFTEXT', 1,	2,	0x00,	1, 'V', 'VR'),
        0x0093: ('TEXTREF', 1,	2,	0x00,	1, 'V', 'VV'),
        148: ('INDIRECT',         1,  2, 0x0c,  2, 'R', 'VV'),
        0x0095: ('REGISTER', 0,	29,	0x00,	29, 'V', 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVV'),
        0x0096: ('CALL', 1,	30,	0x00,	29, 'V', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA'),
        0x0097: ('ADD.BAR', 1,	30,	0x00,	1, 'V', 'VAAAAAAAAAAAAAAAAAAAAAAAAAA'),
        0x0098: ('ADD.MENU', 1,	4,	0x00,	1, 'V', 'VAAV'),
        0x0099: ('ADD.COMMAND', 3,	5,	0x00,	3, 'V', 'VAAAV'),
        0x009A: ('ENABLE.COMMAND', 4,	5,	0x00,	4, 'V', 'VVVVV'),
        0x009B: ('CHECK.COMMAND', 4,	5,	0x00,	4, 'V', 'VVVVV'),
        0x009C: ('RENAME.COMMAND', 4,	5,	0x00,	4, 'V', 'VVVVV'),
        0x009D: ('SHOW.BAR', 1,	1,	0x00,	1, 'V', 'V'),
        0x009E: ('DELETE.MENU', 2,	3,	0x00,	2, 'V', 'VVV'),
        0x009F: ('DELETE.COMMAND', 3,	4,	0x00,	1, 'V', 'VVVV'),
        0x00A0: ('GET.CHART.ITEM', 1,	3,	0x00,	2, 'V', 'VVV'),
        0x00A1: ('DIALOG.BOX', 1,	1,	0x00,	1, 'V', 'A'),
        162: ('CLEAN',            1,  1, 0x02,  1, 'V', 'V'),
        163: ('MDETERM',          1,  1, 0x02,  1, 'V', 'A'),
        164: ('MINVERSE',         1,  1, 0x02,  1, 'A', 'A'),
        165: ('MMULT',            2,  2, 0x02,  2, 'A', 'AA'),
        0x00A6: ('FILES', 0,	2,	0x00,	2, 'V', 'VV'),
        167: ('IPMT',             4,  6, 0x04,  6, 'V', 'VVVVVV'),
        168: ('PPMT',             4,  6, 0x04,  6, 'V', 'VVVVVV'),
        169: ('COUNTA',           0, 30, 0x04,  1, 'V', 'R'),
        0x00AA: ('CANCEL.KEY', 0,	2,	0x00,	2, 'V', 'VR'),
        0x00AB: ('FOR', 3,	4,	0x00,	4, 'V', 'VVVV'),
        0x00AC: ('WHILE', 1,	1,	0x00,	1, 'V', 'V'),
        0x00AD: ('BREAK', 0,	0,	0x00,	0, 'V', ''),
        0x00AE: ('NEXT', 0,	0,	0x00,	0, 'V', ''),
        0x00AF: ('INITIATE', 2,	2,	0x00,	1, 'V', 'VV'),
        0x00B0: ('REQUEST', 2,	2,	0x00,	1, 'V', 'VV'),
        0x00B1: ('POKE', 3,	3,	0x00,	1, 'V', 'VAA'),
        0x00B2: ('EXECUTE', 2,	2,	0x00,	1, 'V', 'VV'),
        0x00B3: ('TERMINATE', 1,	1,	0x00,	1, 'V', 'V'),
        0x00B4: ('RESTART', 1,	1,	0x00,	1, 'V', 'V'),
        0x00B5: ('HELP', 1,	1,	0x00,	1, 'V', 'V'),
        0x00B6: ('GET.BAR', 0,	4,	0x00,	4, 'V', 'VVVV'),
        183: ('PRODUCT',          0, 30, 0x04,  1, 'V', 'R'),
        184: ('FACT',             1,  1, 0x02,  1, 'V', 'V'),
        0x00B9: ('GET.CELL', 1,	2,	0x00,	1, 'V', 'VR'),
        0x00BA: ('GET.WORKSPACE', 1,	1,	0x00,	1, 'V', 'V'),
        0x00BB: ('GET.WINDOW', 1,	2,	0x00,	1, 'V', 'VV'),
        0x00BC: ('GET.DOCUMENT', 1,	2,	0x00,	1, 'V', 'VV'),
        189: ('DPRODUCT',         3,  3, 0x02,  3, 'V', 'RRR'),
        190: ('ISNONTEXT',        1,  1, 0x02,  1, 'V', 'V'),
        0x00BF: ('GET.NOTE', 0,	3,	0x00,	3, 'V', 'AVV'),
        0x00C0: ('NOTE', 0,	4,	0x00,	4, 'V', 'VAAA'),
        193: ('STDEVP',           1, 30, 0x04,  1, 'V', 'R'),
        194: ('VARP',             1, 30, 0x04,  1, 'V', 'R'),
        195: ('DSTDEVP',          3,  3, 0x02,  3, 'V', 'RRR'),
        196: ('DVARP',            3,  3, 0x02,  3, 'V', 'RRR'),
        197: ('TRUNC',            1,  2, 0x04,  2, 'V', 'VV'),
        198: ('ISLOGICAL',        1,  1, 0x02,  1, 'V', 'V'),
        199: ('DCOUNTA',          3,  3, 0x02,  3, 'V', 'RRR'),
        0x00C8: ('DELETE.BAR', 1,	1,	0x00,	1, 'V', 'V'),
        0x00C9: ('UNREGISTER', 1,	1,	0x00,	1, 'V', 'V'),
        204: ('USDOLLAR',         1,  2, 0x04,  2, 'V', 'VV'),
        205: ('FINDB',            2,  3, 0x04,  3, 'V', 'VVV'),
        206: ('SEARCHB',          2,  3, 0x04,  3, 'V', 'VVV'),
        207: ('REPLACEB',         4,  4, 0x02,  4, 'V', 'VVVV'),
        208: ('LEFTB',            1,  2, 0x04,  2, 'V', 'VV'),
        209: ('RIGHTB',           1,  2, 0x04,  2, 'V', 'VV'),
        210: ('MIDB',             3,  3, 0x02,  3, 'V', 'VVV'),
        211: ('LENB',             1,  1, 0x02,  1, 'V', 'V'),
        212: ('ROUNDUP',          2,  2, 0x02,  2, 'V', 'VV'),
        213: ('ROUNDDOWN',        2,  2, 0x02,  2, 'V', 'VV'),
        214: ('ASC',              1,  1, 0x02,  1, 'V', 'V'),
        215: ('DBCS',             1,  1, 0x02,  1, 'V', 'V'),
        216: ('RANK',             2,  3, 0x04,  3, 'V', 'VRV'),
        219: ('ADDRESS',          2,  5, 0x04,  5, 'V', 'VVVVV'),
        220: ('DAYS360',          2,  3, 0x04,  3, 'V', 'VVV'),
        221: ('TODAY',            0,  0, 0x0a,  0, 'V', ''),
        222: ('VDB',              5,  7, 0x04,  7, 'V', 'VVVVVVV'),
        0x00DF: ('ELSE', 0,	0,	0x00,	0, 'V', ''),
        0x00E0: ('ELSE.IF', 1,	1,	0x00,	1, 'V', 'V'),
        0x00E1: ('END.IF', 0,	0,	0x00,	0, 'V', ''),
        0x00E2: ('FOR.CELL', 1,	3,	0x00,	2, 'V', 'VAA'),
        227: ('MEDIAN',           1, 30, 0x04,  1, 'V', 'R'),
        228: ('SUMPRODUCT',       1, 30, 0x04,  1, 'V', 'A'),
        229: ('SINH',             1,  1, 0x02,  1, 'V', 'V'),
        230: ('COSH',             1,  1, 0x02,  1, 'V', 'V'),
        231: ('TANH',             1,  1, 0x02,  1, 'V', 'V'),
        232: ('ASINH',            1,  1, 0x02,  1, 'V', 'V'),
        233: ('ACOSH',            1,  1, 0x02,  1, 'V', 'V'),
        234: ('ATANH',            1,  1, 0x02,  1, 'V', 'V'),
        235: ('DGET',             3,  3, 0x02,  3, 'V', 'RRR'),
        0x00EC: ('CREATE.OBJECT', 2,	11,	0x00,	9, 'V', 'VAAAAAAAAAA'),
        0x00ED: ('VOLATILE', 1,	1,	0x00,	1, 'V', 'V'),
        0x00EE: ('LAST.ERROR', 0,	0,	0x00,	0, 'V', ''),
        0x00EF: ('CUSTOM.UNDO', 0,	2,	0x00,	2, 'V', 'VV'),
        0x00F0: ('CUSTOM.REPEAT', 0,	3,	0x00,	3, 'V', 'VVV'),
        0x00F1: ('FORMULA.CONVERT', 2,	5,	0x00,	3, 'V', 'VAAAA'),
        0x00F2: ('GET.LINK.INFO', 2,	4,	0x00,	2, 'V', 'VVVV'),
        0x00F3: ('TEXT.BOX', 1,	4,	0x00,	3, 'V', 'VVVV'),
        244: ('INFO',             1,  1, 0x02,  1, 'V', 'V'),
        0x00F5: ('GROUP', 0,	0,	0x00,	0, 'V', ''),
        0x00F6: ('GET.OBJECT', 1,	5,	0x00,	4, 'V', 'VVVVV'),
        247: ('DB',               4,  5, 0x04,  5, 'V', 'VVVVV'),
        0x00F8: ('PAUSE', 0,	1,	0x00,	1, 'V', 'V'),
        0x00FB: ('RESUME', 1,	1,	0x00,	1, 'V', 'V'),
        252: ('FREQUENCY',        2,  2, 0x02,  2, 'A', 'RR'),
        0x00FD: ('ADD.TOOLBAR', 0, 2, 0x00, 2, 'V', 'VV'),
        0x00FE: ('DELETE.TOOLBAR', 1, 1, 0x00, 1, 'V', 'V'),
        0x00FF: ('UserDefinedFunction', 1, 30, 0x00, 30, 'V', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'),
        0x0100: ('RESET.TOOLBAR', 1, 1, 0x00, 1, 'V', 'V'),
        0x0101: ('EVALUATE', 1, 1, 0x00, 1, 'V', 'V'),
        0x0102: ('GET.TOOLBAR', 2, 2, 0x00, 2, 'V', 'VV'),
        0x0103: ('GET.TOOL', 1, 3, 0x00, 3, 'V', 'VVV'),
        0x0104: ('SPELLING.CHECK', 1, 3, 0x00, 3, 'V', 'VVV'),
        261: ('ERROR.TYPE',       1,  1, 0x02,  1, 'V', 'V'),
        0x0106: ('APP.TITLE', 1, 1, 0x00, 1, 'V', 'V'),
        0x0107: ('WINDOW.TITLE', 1, 1, 0x00, 1, 'V', 'V'),
        0x0108: ('SAVE.TOOLBAR', 0, 2, 0x00, 2, 'V', 'VV'),
        0x0109: ('ENABLE.TOOL', 3, 3, 0x00, 3, 'V', 'VVV'),
        0x010A: ('PRESS.TOOL', 3, 3, 0x00, 3, 'V', 'VVV'),
        0x010B: ('REGISTER.ID', 3, 3, 0x00, 3, 'V', 'VVV'),
        0x010C: ('GET.WORKBOOK', 1, 2, 0x00, 2, 'V', 'VV'),
        269: ('AVEDEV',           1, 30, 0x04,  1, 'V', 'R'),
        270: ('BETADIST',         3,  5, 0x04,  1, 'V', 'V'),
        271: ('GAMMALN',          1,  1, 0x02,  1, 'V', 'V'),
        272: ('BETAINV',          3,  5, 0x04,  1, 'V', 'V'),
        273: ('BINOMDIST',        4,  4, 0x02,  4, 'V', 'VVVV'),
        274: ('CHIDIST',          2,  2, 0x02,  2, 'V', 'VV'),
        275: ('CHIINV',           2,  2, 0x02,  2, 'V', 'VV'),
        276: ('COMBIN',           2,  2, 0x02,  2, 'V', 'VV'),
        277: ('CONFIDENCE',       3,  3, 0x02,  3, 'V', 'VVV'),
        278: ('CRITBINOM',        3,  3, 0x02,  3, 'V', 'VVV'),
        279: ('EVEN',             1,  1, 0x02,  1, 'V', 'V'),
        280: ('EXPONDIST',        3,  3, 0x02,  3, 'V', 'VVV'),
        281: ('FDIST',            3,  3, 0x02,  3, 'V', 'VVV'),
        282: ('FINV',             3,  3, 0x02,  3, 'V', 'VVV'),
        283: ('FISHER',           1,  1, 0x02,  1, 'V', 'V'),
        284: ('FISHERINV',        1,  1, 0x02,  1, 'V', 'V'),
        285: ('FLOOR',            2,  2, 0x02,  2, 'V', 'VV'),
        286: ('GAMMADIST',        4,  4, 0x02,  4, 'V', 'VVVV'),
        287: ('GAMMAINV',         3,  3, 0x02,  3, 'V', 'VVV'),
        288: ('CEILING',          2,  2, 0x02,  2, 'V', 'VV'),
        289: ('HYPGEOMDIST',      4,  4, 0x02,  4, 'V', 'VVVV'),
        290: ('LOGNORMDIST',      3,  3, 0x02,  3, 'V', 'VVV'),
        291: ('LOGINV',           3,  3, 0x02,  3, 'V', 'VVV'),
        292: ('NEGBINOMDIST',     3,  3, 0x02,  3, 'V', 'VVV'),
        293: ('NORMDIST',         4,  4, 0x02,  4, 'V', 'VVVV'),
        294: ('NORMSDIST',        1,  1, 0x02,  1, 'V', 'V'),
        295: ('NORMINV',          3,  3, 0x02,  3, 'V', 'VVV'),
        296: ('NORMSINV',         1,  1, 0x02,  1, 'V', 'V'),
        297: ('STANDARDIZE',      3,  3, 0x02,  3, 'V', 'VVV'),
        298: ('ODD',              1,  1, 0x02,  1, 'V', 'V'),
        299: ('PERMUT',           2,  2, 0x02,  2, 'V', 'VV'),
        300: ('POISSON',          3,  3, 0x02,  3, 'V', 'VVV'),
        301: ('TDIST',            3,  3, 0x02,  3, 'V', 'VVV'),
        302: ('WEIBULL',          4,  4, 0x02,  4, 'V', 'VVVV'),
        303: ('SUMXMY2',          2,  2, 0x02,  2, 'V', 'AA'),
        304: ('SUMX2MY2',         2,  2, 0x02,  2, 'V', 'AA'),
        305: ('SUMX2PY2',         2,  2, 0x02,  2, 'V', 'AA'),
        306: ('CHITEST',          2,  2, 0x02,  2, 'V', 'AA'),
        307: ('CORREL',           2,  2, 0x02,  2, 'V', 'AA'),
        308: ('COVAR',            2,  2, 0x02,  2, 'V', 'AA'),
        309: ('FORECAST',         3,  3, 0x02,  3, 'V', 'VAA'),
        310: ('FTEST',            2,  2, 0x02,  2, 'V', 'AA'),
        311: ('INTERCEPT',        2,  2, 0x02,  2, 'V', 'AA'),
        312: ('PEARSON',          2,  2, 0x02,  2, 'V', 'AA'),
        313: ('RSQ',              2,  2, 0x02,  2, 'V', 'AA'),
        314: ('STEYX',            2,  2, 0x02,  2, 'V', 'AA'),
        315: ('SLOPE',            2,  2, 0x02,  2, 'V', 'AA'),
        316: ('TTEST',            4,  4, 0x02,  4, 'V', 'AAVV'),
        317: ('PROB',             3,  4, 0x04,  3, 'V', 'AAV'),
        318: ('DEVSQ',            1, 30, 0x04,  1, 'V', 'R'),
        319: ('GEOMEAN',          1, 30, 0x04,  1, 'V', 'R'),
        320: ('HARMEAN',          1, 30, 0x04,  1, 'V', 'R'),
        321: ('SUMSQ',            0, 30, 0x04,  1, 'V', 'R'),
        322: ('KURT',             1, 30, 0x04,  1, 'V', 'R'),
        323: ('SKEW',             1, 30, 0x04,  1, 'V', 'R'),
        324: ('ZTEST',            2,  3, 0x04,  2, 'V', 'RV'),
        325: ('LARGE',            2,  2, 0x02,  2, 'V', 'RV'),
        326: ('SMALL',            2,  2, 0x02,  2, 'V', 'RV'),
        327: ('QUARTILE',         2,  2, 0x02,  2, 'V', 'RV'),
        328: ('PERCENTILE',       2,  2, 0x02,  2, 'V', 'RV'),
        329: ('PERCENTRANK',      2,  3, 0x04,  2, 'V', 'RV'),
        330: ('MODE',             1, 30, 0x04,  1, 'V', 'A'),
        331: ('TRIMMEAN',         2,  2, 0x02,  2, 'V', 'RV'),
        332: ('TINV',             2,  2, 0x02,  2, 'V', 'VV'),
        336: ('CONCATENATE',      0, 30, 0x04,  1, 'V', 'V'),
        337: ('POWER',            2,  2, 0x02,  2, 'V', 'VV'),
        342: ('RADIANS',          1,  1, 0x02,  1, 'V', 'V'),
        343: ('DEGREES',          1,  1, 0x02,  1, 'V', 'V'),
        344: ('SUBTOTAL',         2, 30, 0x04,  2, 'V', 'VR'),
        345: ('SUMIF',            2,  3, 0x04,  3, 'V', 'RVR'),
        346: ('COUNTIF',          2,  2, 0x02,  2, 'V', 'RV'),
        347: ('COUNTBLANK',       1,  1, 0x02,  1, 'V', 'R'),
        350: ('ISPMT',            4,  4, 0x02,  4, 'V', 'VVVV'),
        351: ('DATEDIF',          3,  3, 0x02,  3, 'V', 'VVV'),
        352: ('DATESTRING',       1,  1, 0x02,  1, 'V', 'V'),
        353: ('NUMBERSTRING',     2,  2, 0x02,  2, 'V', 'VV'),
        354: ('ROMAN',            1,  2, 0x04,  2, 'V', 'VV'),
        358: ('GETPIVOTDATA',     2,  2, 0x02,  2, 'V', 'RV'),
        359: ('HYPERLINK',        1,  2, 0x04,  2, 'V', 'VV'),
        360: ('PHONETIC',         1,  1, 0x02,  1, 'V', 'V'),
        361: ('AVERAGEA',         1, 30, 0x04,  1, 'V', 'R'),
        362: ('MAXA',             1, 30, 0x04,  1, 'V', 'R'),
        363: ('MINA',             1, 30, 0x04,  1, 'V', 'R'),
        364: ('STDEVPA',          1, 30, 0x04,  1, 'V', 'R'),
        365: ('VARPA',            1, 30, 0x04,  1, 'V', 'R'),
        366: ('STDEVA',           1, 30, 0x04,  1, 'V', 'R'),
        367: ('VARA',             1, 30, 0x04,  1, 'V', 'R'),
        368: ('BAHTTEXT',         1,  1, 0x02,  1, 'V', 'V'),
        369: ('THAIDAYOFWEEK',    1,  1, 0x02,  1, 'V', 'V'),
        370: ('THAIDIGIT',        1,  1, 0x02,  1, 'V', 'V'),
        371: ('THAIMONTHOFYEAR',  1,  1, 0x02,  1, 'V', 'V'),
        372: ('THAINUMSOUND',     1,  1, 0x02,  1, 'V', 'V'),
        373: ('THAINUMSTRING',    1,  1, 0x02,  1, 'V', 'V'),
        374: ('THAISTRINGLENGTH', 1,  1, 0x02,  1, 'V', 'V'),
        375: ('ISTHAIDIGIT',      1,  1, 0x02,  1, 'V', 'V'),
        376: ('ROUNDBAHTDOWN',    1,  1, 0x02,  1, 'V', 'V'),
        377: ('ROUNDBAHTUP',      1,  1, 0x02,  1, 'V', 'V'),
        378: ('THAIYEAR',         1,  1, 0x02,  1, 'V', 'V'),
        379: ('RTD',              2,  5, 0x04,  1, 'V', 'V'),

        #generate based on https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/0b8acba5-86d2-4854-836e-0afaee743d44
        0x8000: ('BEEP', 0, 1, 0x04, 1, 'V', 'V'),
        0x8001: ('OPEN', 0, 17, 0x04, 17, 'V', 'VVVVVVVVVVVVVVVVV'),
        0x8002: ('OPEN.LINKS', 0, 15, 0x04, 15, 'V', 'VVVVVVVVVVVVVVV'),
        0x8003: ('CLOSE.ALL', 0, 0, 0x00, 0, 'V', ''),
        0x8004: ('SAVE', 0, 0, 0x00, 0, 'V', ''),
        0x8005: ('SAVE.AS', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x8006: ('FILE.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8007: ('PAGE.SETUP', 0, 30, 0x04, 30, 'V', 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV'),
        0x8008: ('PRINT', 0, 17, 0x04, 17, 'V', 'VVVVVVVVVVVVVVVVV'),
        0x8009: ('PRINTER.SETUP', 0, 1, 0x04, 1, 'V', 'V'),
        0x800A: ('QUIT', 0, 0, 0x00, 0, 'V', ''),
        0x800B: ('NEW.WINDOW', 0, 0, 0x00, 0, 'V', ''),
        0x800C: ('ARRANGE.ALL', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x800D: ('WINDOW.SIZE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x800E: ('WINDOW.MOVE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x800F: ('FULL', 0, 1, 0x04, 1, 'V', 'V'),
        0x8010: ('CLOSE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8011: ('RUN', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8016: ('SET.PRINT.AREA', 0, 1, 0x04, 1, 'V', 'A'),
        0x8017: ('SET.PRINT.TITLES', 0, 2, 0x04, 2, 'V', 'AA'),
        0x8018: ('SET.PAGE.BREAK', 0, 0, 0x00, 0, 'V', ''),
        0x8019: ('REMOVE.PAGE.BREAK', 0, 2, 0x04, 2, 'V', 'VV'),
        0x801A: ('FONT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x801B: ('DISPLAY', 0, 9, 0x04, 9, 'V', 'VVVVVVVVV'),
        0x801C: ('PROTECT.DOCUMENT', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x801D: ('PRECISION', 0, 1, 0x04, 1, 'V', 'V'),
        0x801E: ('A1.R1C1', 0, 1, 0x04, 1, 'V', 'V'),
        0x801F: ('CALCULATE.NOW', 0, 0, 0x00, 0, 'V', ''),
        0x8020: ('CALCULATION', 0, 11, 0x04, 11, 'V', 'VVVVVVVVVVV'),
        0x8022: ('DATA.FIND', 0, 1, 0x04, 1, 'V', 'V'),
        0x8023: ('EXTRACT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8024: ('DATA.DELETE', 0, 0, 0x00, 0, 'V', ''),
        0x8025: ('SET.DATABASE', 0, 0, 0x00, 0, 'V', ''),
        0x8026: ('SET.CRITERIA', 0, 0, 0x00, 0, 'V', ''),
        0x8027: ('SORT', 0, 17, 0x04, 17, 'V', 'VAAAAAAVVVVVVVVVV'),
        0x8028: ('DATA.SERIES', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x8029: ('TABLE', 0, 2, 0x04, 2, 'V', 'AA'),
        0x802A: ('FORMAT.NUMBER', 0, 1, 0x04, 1, 'V', 'V'),
        0x802B: ('ALIGNMENT', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x802C: ('STYLE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x802D: ('BORDER', 0, 27, 0x04, 27, 'V', 'VVVVVVVVVVVVVVVVVVVVVVVVVVV'),
        0x802E: ('CELL.PROTECTION', 0, 2, 0x04, 2, 'V', 'VV'),
        0x802F: ('COLUMN.WIDTH', 0, 5, 0x04, 5, 'V', 'VAAAA'),
        0x8030: ('UNDO', 0, 0, 0x00, 0, 'V', ''),
        0x8031: ('CUT', 0, 2, 0x04, 2, 'V', 'AA'),
        0x8032: ('COPY', 0, 2, 0x04, 2, 'V', 'AA'),
        0x8033: ('PASTE', 0, 1, 0x04, 1, 'V', 'A'),
        0x8034: ('CLEAR', 0, 1, 0x04, 1, 'V', 'V'),
        0x8035: ('PASTE.SPECIAL', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x8036: ('EDIT.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8037: ('INSERT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8038: ('FILL.RIGHT', 0, 0, 0x00, 0, 'V', ''),
        0x8039: ('FILL.DOWN', 0, 0, 0x00, 0, 'V', ''),
        0x803D: ('DEFINE.NAME', 0, 7, 0x04, 7, 'V', 'VAAAAAV'),
        0x803E: ('CREATE.NAMES', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x803F: ('FORMULA.GOTO', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8040: ('FORMULA.FIND', 0, 12, 0x04, 12, 'V', 'VVVVVVVVVVVV'),
        0x8041: ('SELECT.LAST.CELL', 0, 0, 0x00, 0, 'V', ''),
        0x8042: ('SHOW.ACTIVE.CELL', 0, 0, 0x00, 0, 'V', ''),
        0x8043: ('GALLERY.AREA', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8044: ('GALLERY.BAR', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8045: ('GALLERY.COLUMN', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8046: ('GALLERY.LINE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8047: ('GALLERY.PIE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8048: ('GALLERY.SCATTER', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8049: ('COMBINATION', 0, 1, 0x04, 1, 'V', 'V'),
        0x804A: ('PREFERRED', 0, 0, 0x00, 0, 'V', ''),
        0x804B: ('ADD.OVERLAY', 0, 0, 0x00, 0, 'V', ''),
        0x804C: ('GRIDLINES', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x804D: ('SET.PREFERRED', 0, 1, 0x04, 1, 'V', 'V'),
        0x804E: ('AXES', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x804F: ('LEGEND', 0, 1, 0x04, 1, 'V', 'V'),
        0x8050: ('ATTACH.TEXT', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8051: ('ADD.ARROW', 0, 0, 0x00, 0, 'V', ''),
        0x8052: ('SELECT.CHART', 0, 0, 0x00, 0, 'V', ''),
        0x8053: ('SELECT.PLOT.AREA', 0, 0, 0x00, 0, 'V', ''),
        0x8054: ('PATTERNS', 0, 13, 0x04, 13, 'V', 'VVVVVVVVVVVVV'),
        0x8055: ('MAIN.CHART', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x8056: ('OVERLAY', 0, 12, 0x04, 12, 'V', 'VVVVVVVVVVVV'),
        0x8057: ('SCALE', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x8058: ('FORMAT.LEGEND', 0, 1, 0x04, 1, 'V', 'V'),
        0x8059: ('FORMAT.TEXT', 0, 11, 0x04, 11, 'V', 'VVVVVVVVVVV'),
        0x805A: ('EDIT.REPEAT', 0, 0, 0x00, 0, 'V', ''),
        0x805B: ('PARSE', 0, 2, 0x04, 2, 'V', 'VA'),
        0x805C: ('JUSTIFY', 0, 0, 0x00, 0, 'V', ''),
        0x805D: ('HIDE', 0, 0, 0x00, 0, 'V', ''),
        0x805E: ('UNHIDE', 0, 1, 0x04, 1, 'V', 'V'),
        0x805F: ('WORKSPACE', 0, 16, 0x04, 16, 'V', 'VVVVVVVVVVVVVVVV'),
        0x8060: ('FORMULA', 0, 2, 0x04, 2, 'V', 'VA'),
        0x8061: ('FORMULA.FILL', 0, 2, 0x04, 2, 'V', 'VA'),
        0x8062: ('FORMULA.ARRAY', 0, 2, 0x04, 2, 'V', 'VA'),
        0x8063: ('DATA.FIND.NEXT', 0, 0, 0x00, 0, 'V', ''),
        0x8064: ('DATA.FIND.PREV', 0, 0, 0x00, 0, 'V', ''),
        0x8065: ('FORMULA.FIND.NEXT', 0, 0, 0x00, 0, 'V', ''),
        0x8066: ('FORMULA.FIND.PREV', 0, 0, 0x00, 0, 'V', ''),
        0x8067: ('ACTIVATE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8068: ('ACTIVATE.NEXT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8069: ('ACTIVATE.PREV', 0, 1, 0x04, 1, 'V', 'V'),
        0x806A: ('UNLOCKED.NEXT', 0, 0, 0x00, 0, 'V', ''),
        0x806B: ('UNLOCKED.PREV', 0, 0, 0x00, 0, 'V', ''),
        0x806C: ('COPY.PICTURE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x806D: ('SELECT', 0, 2, 0x04, 2, 'V', 'AA'),
        0x806E: ('DELETE.NAME', 0, 1, 0x04, 1, 'V', 'V'),
        0x806F: ('DELETE.FORMAT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8070: ('VLINE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8071: ('HLINE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8072: ('VPAGE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8073: ('HPAGE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8074: ('VSCROLL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8075: ('HSCROLL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8076: ('ALERT', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8077: ('NEW', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8078: ('CANCEL.COPY', 0, 1, 0x04, 1, 'V', 'V'),
        0x8079: ('SHOW.CLIPBOARD', 0, 0, 0x00, 0, 'V', ''),
        0x807A: ('MESSAGE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x807C: ('PASTE.LINK', 0, 0, 0x00, 0, 'V', ''),
        0x807D: ('APP.ACTIVATE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x807E: ('DELETE.ARROW', 0, 0, 0x00, 0, 'V', ''),
        0x807F: ('ROW.HEIGHT', 0, 4, 0x04, 4, 'V', 'VAAA'),
        0x8080: ('FORMAT.MOVE', 0, 3, 0x04, 3, 'V', 'VAA'),
        0x8081: ('FORMAT.SIZE', 0, 3, 0x04, 3, 'V', 'VAA'),
        0x8082: ('FORMULA.REPLACE', 0, 11, 0x04, 11, 'V', 'VVVVVVVVVVV'),
        0x8083: ('SEND.KEYS', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8084: ('SELECT.SPECIAL', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8085: ('APPLY.NAMES', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x8086: ('REPLACE.FONT', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x8087: ('FREEZE.PANES', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8088: ('SHOW.INFO', 0, 1, 0x04, 1, 'V', 'V'),
        0x8089: ('SPLIT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x808A: ('ON.WINDOW', 0, 2, 0x04, 2, 'V', 'VV'),
        0x808B: ('ON.DATA', 0, 2, 0x04, 2, 'V', 'VV'),
        0x808C: ('DISABLE.INPUT', 0, 1, 0x04, 1, 'V', 'V'),
        0x808E: ('OUTLINE', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x808F: ('LIST.NAMES', 0, 0, 0x00, 0, 'V', ''),
        0x8090: ('FILE.CLOSE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8091: ('SAVE.WORKBOOK', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x8092: ('DATA.FORM', 0, 0, 0x00, 0, 'V', ''),
        0x8093: ('COPY.CHART', 0, 1, 0x04, 1, 'V', 'V'),
        0x8094: ('ON.TIME', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x8095: ('WAIT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8096: ('FORMAT.FONT', 0, 15, 0x04, 15, 'V', 'VVVVVVVVVVVVVVV'),
        0x8097: ('FILL.UP', 0, 0, 0x00, 0, 'V', ''),
        0x8098: ('FILL.LEFT', 0, 0, 0x00, 0, 'V', ''),
        0x8099: ('DELETE.OVERLAY', 0, 0, 0x00, 0, 'V', ''),
        0x809B: ('SHORT.MENUS', 0, 1, 0x04, 1, 'V', 'V'),
        0x809F: ('SET.UPDATE.STATUS', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x80A1: ('COLOR.PALETTE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80A2: ('DELETE.STYLE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80A3: ('WINDOW.RESTORE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80A4: ('WINDOW.MAXIMIZE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80A6: ('CHANGE.LINK', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x80A7: ('CALCULATE.DOCUMENT', 0, 0, 0x00, 0, 'V', ''),
        0x80A8: ('ON.KEY', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80A9: ('APP.RESTORE', 0, 0, 0x00, 0, 'V', ''),
        0x80AA: ('APP.MOVE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80AB: ('APP.SIZE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80AC: ('APP.MINIMIZE', 0, 0, 0x00, 0, 'V', ''),
        0x80AD: ('APP.MAXIMIZE', 0, 0, 0x00, 0, 'V', ''),
        0x80AE: ('BRING.TO.FRONT', 0, 0, 0x00, 0, 'V', ''),
        0x80AF: ('SEND.TO.BACK', 0, 0, 0x00, 0, 'V', ''),
        0x80B9: ('MAIN.CHART.TYPE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80BA: ('OVERLAY.CHART.TYPE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80BB: ('SELECT.END', 0, 1, 0x04, 1, 'V', 'V'),
        0x80BC: ('OPEN.MAIL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80BD: ('SEND.MAIL', 0, 3, 0x04, 3, 'V', 'AVV'),
        0x80BE: ('STANDARD.FONT', 0, 9, 0x04, 9, 'V', 'VVVVVVVVV'),
        0x80BF: ('CONSOLIDATE', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x80C0: ('SORT.SPECIAL', 0, 14, 0x04, 14, 'V', 'VVAAAAAAVVVVVV'),
        0x80C1: ('GALLERY.3D.AREA', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C2: ('GALLERY.3D.COLUMN', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C3: ('GALLERY.3D.LINE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C4: ('GALLERY.3D.PIE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C5: ('VIEW.3D', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x80C6: ('GOAL.SEEK', 0, 3, 0x04, 3, 'V', 'AAA'),
        0x80C7: ('WORKGROUP', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C8: ('FILL.GROUP', 0, 1, 0x04, 1, 'V', 'V'),
        0x80C9: ('UPDATE.LINK', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80CA: ('PROMOTE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80CB: ('DEMOTE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80CC: ('SHOW.DETAIL', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x80CE: ('UNGROUP', 0, 0, 0x00, 0, 'V', ''),
        0x80CF: ('OBJECT.PROPERTIES', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80D0: ('SAVE.NEW.OBJECT', 0, 1, 0x04, 1, 'V', 'V'),
        0x80D1: ('SHARE', 0, 0, 0x00, 0, 'V', ''),
        0x80D2: ('SHARE.NAME', 0, 1, 0x04, 1, 'V', 'V'),
        0x80D3: ('DUPLICATE', 0, 0, 0x00, 0, 'V', ''),
        0x80D4: ('APPLY.STYLE', 0, 1, 0x04, 1, 'V', 'V'),
        0x80D5: ('ASSIGN.TO.OBJECT', 0, 1, 0x04, 1, 'V', 'A'),
        0x80D6: ('OBJECT.PROTECTION', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80D7: ('HIDE.OBJECT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80D8: ('SET.EXTRACT', 0, 0, 0x00, 0, 'V', ''),
        0x80D9: ('CREATE.PUBLISHER', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x80DA: ('SUBSCRIBE.TO', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80DB: ('ATTRIBUTES', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80DC: ('SHOW.TOOLBAR', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x80DE: ('PRINT.PREVIEW', 0, 1, 0x04, 1, 'V', 'V'),
        0x80DF: ('EDIT.COLOR', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x80E0: ('SHOW.LEVELS', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80E1: ('FORMAT.MAIN', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x80E2: ('FORMAT.OVERLAY', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x80E3: ('ON.RECALC', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80E4: ('EDIT.SERIES', 0, 7, 0x04, 7, 'V', 'VAAAAAA'),
        0x80E5: ('DEFINE.STYLE', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x80F0: ('LINE.PRINT', 0, 11, 0x04, 11, 'V', 'VVVVVVVVVVV'),
        0x80F3: ('ENTER.DATA', 0, 1, 0x04, 1, 'V', 'A'),
        0x80F9: ('GALLERY.RADAR', 0, 2, 0x04, 2, 'V', 'VV'),
        0x80FA: ('MERGE.STYLES', 0, 1, 0x04, 1, 'V', 'V'),
        0x80FB: ('EDITION.OPTIONS', 0, 7, 0x04, 7, 'V', 'VAAAAAA'),
        0x80FC: ('PASTE.PICTURE', 0, 0, 0x00, 0, 'V', ''),
        0x80FD: ('PASTE.PICTURE.LINK', 0, 0, 0x00, 0, 'V', ''),
        0x80FE: ('SPELLING', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x8100: ('ZOOM', 0, 1, 0x04, 1, 'V', 'V'),
        0x8103: ('INSERT.OBJECT', 0, 13, 0x04, 13, 'V', 'VVVVVVVAVVAVV'),
        0x8104: ('WINDOW.MINIMIZE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8109: ('SOUND.NOTE', 0, 3, 0x04, 3, 'V', 'AVV'),
        0x810A: ('SOUND.PLAY', 0, 3, 0x04, 3, 'V', 'AVV'),
        0x810B: ('FORMAT.SHAPE', 0, 5, 0x04, 5, 'V', 'VVAVV'),
        0x810C: ('EXTEND.POLYGON', 0, 1, 0x04, 1, 'V', 'V'),
        0x810D: ('FORMAT.AUTO', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x8110: ('GALLERY.3D.BAR', 0, 1, 0x04, 1, 'V', 'V'),
        0x8111: ('GALLERY.3D.SURFACE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8112: ('FILL.AUTO', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8114: ('CUSTOMIZE.TOOLBAR', 0, 1, 0x04, 1, 'V', 'V'),
        0x8115: ('ADD.TOOL', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8116: ('EDIT.OBJECT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8117: ('ON.DOUBLECLICK', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8118: ('ON.ENTRY', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8119: ('WORKBOOK.ADD', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x811A: ('WORKBOOK.MOVE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x811B: ('WORKBOOK.COPY', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x811C: ('WORKBOOK.OPTIONS', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x811D: ('SAVE.WORKSPACE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8120: ('CHART.WIZARD', 0, 14, 0x04, 14, 'V', 'VAVVVVVVVVVVVV'),
        0x8121: ('DELETE.TOOL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8122: ('MOVE.TOOL', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x8123: ('WORKBOOK.SELECT', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8124: ('WORKBOOK.ACTIVATE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8125: ('ASSIGN.TO.TOOL', 0, 3, 0x04, 3, 'V', 'VVA'),
        0x8127: ('COPY.TOOL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8128: ('RESET.TOOL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8129: ('CONSTRAIN.NUMERIC', 0, 1, 0x04, 1, 'V', 'V'),
        0x812A: ('PASTE.TOOL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x812E: ('WORKBOOK.NEW', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8131: ('SCENARIO.CELLS', 0, 1, 0x04, 1, 'V', 'A'),
        0x8132: ('SCENARIO.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8133: ('SCENARIO.ADD', 0, 6, 0x04, 6, 'V', 'VVAVVV'),
        0x8134: ('SCENARIO.EDIT', 0, 7, 0x04, 7, 'V', 'VVVAVVV'),
        0x8135: ('SCENARIO.SHOW', 0, 1, 0x04, 1, 'V', 'V'),
        0x8136: ('SCENARIO.SHOW.NEXT', 0, 0, 0x00, 0, 'V', ''),
        0x8137: ('SCENARIO.SUMMARY', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8138: ('PIVOT.TABLE.WIZARD', 0, 16, 0x04, 16, 'V', 'VAAVVVVVVVVVVVVV'),
        0x8139: ('PIVOT.FIELD.PROPERTIES', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x813A: ('PIVOT.FIELD', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x813B: ('PIVOT.ITEM', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x813C: ('PIVOT.ADD.FIELDS', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x813E: ('OPTIONS.CALCULATION', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x813F: ('OPTIONS.EDIT', 0, 11, 0x04, 11, 'V', 'VVVVVVVVVVV'),
        0x8140: ('OPTIONS.VIEW', 0, 18, 0x04, 18, 'V', 'VVVVVVVVVVVVVVVVVV'),
        0x8141: ('ADDIN.MANAGER', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8142: ('MENU.EDITOR', 0, 0, 0x00, 0, 'V', ''),
        0x8143: ('ATTACH.TOOLBARS', 0, 0, 0x00, 0, 'V', ''),
        0x8144: ('VBAActivate', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8145: ('OPTIONS.CHART', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x8148: ('VBA.INSERT.FILE', 0, 1, 0x04, 1, 'V', 'V'),
        0x814A: ('VBA.PROCEDURE.DEFINITION', 0, 0, 0x00, 0, 'V', ''),
        0x8150: ('ROUTING.SLIP', 0, 6, 0x04, 6, 'V', 'AVVVVV'),
        0x8152: ('ROUTE.DOCUMENT', 0, 0, 0x00, 0, 'V', ''),
        0x8153: ('MAIL.LOGON', 0, 3, 0x04, 3, 'V', 'AAV'),
        0x8156: ('INSERT.PICTURE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8157: ('EDIT.TOOL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8158: ('GALLERY.DOUGHNUT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x815E: ('CHART.TREND', 0, 8, 0x04, 8, 'V', 'VVVVVVVV'),
        0x8160: ('PIVOT.ITEM.PROPERTIES', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x8162: ('WORKBOOK.INSERT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8163: ('OPTIONS.TRANSITION', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x8164: ('OPTIONS.GENERAL', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x8172: ('FILTER.ADVANCED', 0, 5, 0x04, 5, 'V', 'VAAAV'),
        0x8175: ('MAIL.ADD.MAILER', 0, 0, 0x00, 0, 'V', ''),
        0x8176: ('MAIL.DELETE.MAILER', 0, 0, 0x00, 0, 'V', ''),
        0x8177: ('MAIL.REPLY', 0, 0, 0x00, 0, 'V', ''),
        0x8178: ('MAIL.REPLY.ALL', 0, 0, 0x00, 0, 'V', ''),
        0x8179: ('MAIL.FORWARD', 0, 0, 0x00, 0, 'V', ''),
        0x817A: ('MAIL.NEXT.LETTER', 0, 0, 0x00, 0, 'V', ''),
        0x817B: ('DATA.LABEL', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x817C: ('INSERT.TITLE', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x817D: ('FONT.PROPERTIES', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x817E: ('MACRO.OPTIONS', 0, 10, 0x04, 10, 'V', 'VVVVVVVVVV'),
        0x817F: ('WORKBOOK.HIDE', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8180: ('WORKBOOK.UNHIDE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8181: ('WORKBOOK.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x8182: ('WORKBOOK.NAME', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8184: ('GALLERY.CUSTOM', 0, 1, 0x04, 1, 'V', 'V'),
        0x8186: ('ADD.CHART.AUTOFORMAT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x8187: ('DELETE.CHART.AUTOFORMAT', 0, 1, 0x04, 1, 'V', 'V'),
        0x8188: ('CHART.ADD.DATA', 0, 6, 0x04, 6, 'V', 'VAVVVV'),
        0x8189: ('AUTO.OUTLINE', 0, 0, 0x00, 0, 'V', ''),
        0x818A: ('TAB.ORDER', 0, 0, 0x00, 0, 'V', ''),
        0x818B: ('SHOW.DIALOG', 0, 1, 0x04, 1, 'V', 'V'),
        0x818C: ('SELECT.ALL', 0, 0, 0x00, 0, 'V', ''),
        0x818D: ('UNGROUP.SHEETS', 0, 0, 0x00, 0, 'V', ''),
        0x818E: ('SUBTOTAL.CREATE', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x818F: ('SUBTOTAL.REMOVE', 0, 0, 0x00, 0, 'V', ''),
        0x8190: ('RENAME.OBJECT', 0, 1, 0x04, 1, 'V', 'V'),
        0x819C: ('WORKBOOK.SCROLL', 0, 2, 0x04, 2, 'V', 'VV'),
        0x819D: ('WORKBOOK.NEXT', 0, 0, 0x00, 0, 'V', ''),
        0x819E: ('WORKBOOK.PREV', 0, 0, 0x00, 0, 'V', ''),
        0x819F: ('WORKBOOK.TAB.SPLIT', 0, 1, 0x04, 1, 'V', 'V'),
        0x81A0: ('FULL.SCREEN', 0, 1, 0x04, 1, 'V', 'V'),
        0x81A1: ('WORKBOOK.PROTECT', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81A4: ('SCROLLBAR.PROPERTIES', 0, 7, 0x04, 7, 'V', 'VVVVVVV'),
        0x81A5: ('PIVOT.SHOW.PAGES', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81A6: ('TEXT.TO.COLUMNS', 0, 14, 0x04, 14, 'V', 'VAVVVVVVVVVVVV'),
        0x81A7: ('FORMAT.CHARTTYPE', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x81A8: ('LINK.FORMAT', 0, 0, 0x00, 0, 'V', ''),
        0x81A9: ('TRACER.DISPLAY', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81AE: ('TRACER.NAVIGATE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81AF: ('TRACER.CLEAR', 0, 0, 0x00, 0, 'V', ''),
        0x81B0: ('TRACER.ERROR', 0, 0, 0x00, 0, 'V', ''),
        0x81B1: ('PIVOT.FIELD.GROUP', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x81B2: ('PIVOT.FIELD.UNGROUP', 0, 0, 0x00, 0, 'V', ''),
        0x81B3: ('CHECKBOX.PROPERTIES', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x81B4: ('LABEL.PROPERTIES', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81B5: ('LISTBOX.PROPERTIES', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x81B6: ('EDITBOX.PROPERTIES', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x81B7: ('PIVOT.REFRESH', 0, 1, 0x04, 1, 'V', 'V'),
        0x81B8: ('LINK.COMBO', 0, 1, 0x04, 1, 'V', 'V'),
        0x81B9: ('OPEN.TEXT', 0, 17, 0x04, 17, 'V', 'VVVVVVVVVVVVVVVVV'),
        0x81BA: ('HIDE.DIALOG', 0, 1, 0x04, 1, 'V', 'V'),
        0x81BB: ('SET.DIALOG.FOCUS', 0, 1, 0x04, 1, 'V', 'V'),
        0x81BC: ('ENABLE.OBJECT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81BD: ('PUSHBUTTON.PROPERTIES', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x81BE: ('SET.DIALOG.DEFAULT', 0, 1, 0x04, 1, 'V', 'V'),
        0x81BF: ('FILTER', 0, 6, 0x04, 6, 'V', 'VVVVVV'),
        0x81C0: ('FILTER.SHOW.ALL', 0, 0, 0x00, 0, 'V', ''),
        0x81C1: ('CLEAR.OUTLINE', 0, 0, 0x00, 0, 'V', ''),
        0x81C2: ('FUNCTION.WIZARD', 0, 1, 0x04, 1, 'V', 'V'),
        0x81C3: ('ADD.LIST.ITEM', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81C4: ('SET.LIST.ITEM', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81C5: ('REMOVE.LIST.ITEM', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81C6: ('SELECT.LIST.ITEM', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81C7: ('SET.CONTROL.VALUE', 0, 1, 0x04, 1, 'V', 'V'),
        0x81C8: ('SAVE.COPY.AS', 0, 1, 0x04, 1, 'V', 'V'),
        0x81CA: ('OPTIONS.LISTS.ADD', 0, 2, 0x04, 2, 'V', 'VA'),
        0x81CB: ('OPTIONS.LISTS.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x81CC: ('SERIES.AXES', 0, 1, 0x04, 1, 'V', 'V'),
        0x81CD: ('SERIES.X', 0, 1, 0x04, 1, 'V', 'A'),
        0x81CE: ('SERIES.Y', 0, 2, 0x04, 2, 'V', 'AA'),
        0x81CF: ('ERRORBAR.X', 0, 4, 0x04, 4, 'V', 'VVVA'),
        0x81D0: ('ERRORBAR.Y', 0, 4, 0x04, 4, 'V', 'VVVA'),
        0x81D1: ('FORMAT.CHART', 0, 18, 0x04, 18, 'V', 'AVVVVVVVVVVVVVVVVV'),
        0x81D2: ('SERIES.ORDER', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81D3: ('MAIL.LOGOFF', 0, 0, 0x00, 0, 'V', ''),
        0x81D4: ('CLEAR.ROUTING.SLIP', 0, 1, 0x04, 1, 'V', 'V'),
        0x81D5: ('APP.ACTIVATE.MICROSOFT', 0, 1, 0x04, 1, 'V', 'V'),
        0x81D6: ('MAIL.EDIT.MAILER', 0, 6, 0x04, 6, 'V', 'VAAAVA'),
        0x81D7: ('ON.SHEET', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81D8: ('STANDARD.WIDTH', 0, 1, 0x04, 1, 'V', 'V'),
        0x81D9: ('SCENARIO.MERGE', 0, 1, 0x04, 1, 'V', 'V'),
        0x81DA: ('SUMMARY.INFO', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x81DB: ('FIND.FILE', 0, 0, 0x00, 0, 'V', ''),
        0x81DC: ('ACTIVE.CELL.FONT', 0, 14, 0x04, 14, 'V', 'VVVVVVVVVVVVVV'),
        0x81DD: ('ENABLE.TIPWIZARD', 0, 1, 0x04, 1, 'V', 'V'),
        0x81DE: ('VBA.MAKE.ADDIN', 0, 1, 0x04, 1, 'V', 'V'),
        0x81E0: ('INSERTDATATABLE', 0, 1, 0x04, 1, 'V', 'V'),
        0x81E1: ('WORKGROUP.OPTIONS', 0, 0, 0x00, 0, 'V', ''),
        0x81E2: ('MAIL.SEND.MAILER', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81E5: ('AUTOCORRECT', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81E9: ('POST.DOCUMENT', 0, 1, 0x04, 1, 'V', 'V'),
        0x81EB: ('PICKLIST', 0, 0, 0x00, 0, 'V', ''),
        0x81ED: ('VIEW.SHOW', 0, 1, 0x04, 1, 'V', 'V'),
        0x81EE: ('VIEW.DEFINE', 0, 3, 0x04, 3, 'V', 'VVV'),
        0x81EF: ('VIEW.DELETE', 0, 1, 0x04, 1, 'V', 'V'),
        0x81FD: ('SHEET.BACKGROUND', 0, 2, 0x04, 2, 'V', 'VV'),
        0x81FE: ('INSERT.MAP.OBJECT', 0, 0, 0x00, 0, 'V', ''),
        0x81FF: ('OPTIONS.MENONO', 0, 5, 0x04, 5, 'V', 'VVVVV'),
        0x8205: ('MSOCHECKS', 0, 0, 0x00, 0, 'V', ''),
        0x8206: ('NORMAL', 0, 0, 0x00, 0, 'V', ''),
        0x8207: ('LAYOUT', 0, 0, 0x00, 0, 'V', ''),
        0x8208: ('RM.PRINT.AREA', 0, 1, 0x04, 1, 'V', 'A'),
        0x8209: ('CLEAR.PRINT.AREA', 0, 0, 0x00, 0, 'V', ''),
        0x820A: ('ADD.PRINT.AREA', 0, 0, 0x00, 0, 'V', ''),
        0x820B: ('MOVE.BRK', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x8221: ('HIDECURR.NOTE', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8222: ('HIDEALL.NOTES', 0, 1, 0x04, 1, 'V', 'V'),
        0x8223: ('DELETE.NOTE', 0, 1, 0x04, 1, 'V', 'A'),
        0x8224: ('TRAVERSE.NOTES', 0, 2, 0x04, 2, 'V', 'AV'),
        0x8225: ('ACTIVATE.NOTES', 0, 2, 0x04, 2, 'V', 'AV'),
        0x826C: ('PROTECT.REVISIONS', 0, 0, 0x00, 0, 'V', ''),
        0x826D: ('UNPROTECT.REVISIONS', 0, 0, 0x00, 0, 'V', ''),
        0x8287: ('OPTIONS.ME', 0, 9, 0x04, 9, 'V', 'AVVVVVVVV'),
        0x828D: ('WEB.PUBLISH', 0, 9, 0x04, 9, 'V', 'VVVVVVVVV'),
        0x829B: ('NEWWEBQUERY', 0, 1, 0x04, 1, 'V', 'V'),
        0x82A1: ('PIVOT.TABLE.CHART', 0, 16, 0x04, 16, 'V', 'VAAVVVVVVVVVVVVV'),
        0x82F1: ('OPTIONS.SAVE', 0, 4, 0x04, 4, 'V', 'VVVV'),
        0x82F3: ('OPTIONS.SPELL', 0, 12, 0x04, 12, 'V', 'VVVVVVVVVVVV'),
        0x8328: ('HIDEALL.INKANNOTS', 0, 1, 0x04, 1, 'V', 'V'),
    }



class ExcelSheet:

    def __init__(self, name):
        self.name = name
        self.workbook = None
        self.boundsheet = None
        self.dimensions = None
        self.labels = []
        self.records = 0
        self.formulas = {}
        self.values = {}


class Workbook(Struct):

    def __init__(self, document, *args, **kwargs):
        Struct.__init__(self, *args, **kwargs)
        self.document = document

    def parse(self):
        last_string_formula_cell = None
        label_index = 0
        seen = 0
        while self.remaining() >= 4:
            opcode, sz = struct.unpack("<HH", self.look_ahead(4))
            if self.remaining() < 4 + sz:
                print("Invalid opcode at #{:x} of size {}".format(self.offset, sz))
                break
            name, comment, class_ = EXCEL_OPCODES.get(opcode, ["???", "", BiffRecord])
            if name == "BOF" and seen > 0:
                # rare cases of embedded doc
                typ, = struct.unpack("<H", self.look_ahead(8)[6:])
                NAMES = { 5: "Global", 6: "VB Module", 0x10: "XLS", 0x20:"XLC", 0x40: "XLM", 0x100: "GlobalW"}
                if typ not in NAMES:
                    raise FatalError("Unrecognized document type: 0x{:x}".format(typ))
                sheet = ExcelSheet(NAMES[typ])
                self.document.sheets.append(sheet)
                boundsheet = self.document.boundsheets.get(self.offset + len(self))
                if boundsheet:
                    sheet.name = boundsheet["Name"]["String"]
                    sheet.boundsheet = boundsheet
                wb = yield Workbook(self.document, name=NAMES[typ], category=Type.DATA)
                sheet.workbook = wb
                continue
            obj = yield class_(name=name, comment=comment)
            seen += 1
            if name == "EOF":
                break
            last_sheet = self.document.sheets[-1]
            if name != "CONTINUE":
                last_sheet.records += 1
            if name == "BOUNDSHEET":
                self.document.boundsheets[obj["StartOfSheet"]] = obj
            elif name == "LBL":
                last_sheet.labels.append(obj)
                if "Name" in obj:
                    if obj.Name.has_enum:
                        lbl = obj.Name.enum
                    else:
                        lbl = obj.Name.value
                else:
                    lbl = "anonymous_label_{}".format(label_index)
                    label_index += 1
                if "Formula" in obj:
                    last_sheet.formulas[lbl] = obj["Formula"]
            elif name == "SST":
                for i in range(4, 4 + obj["UniqueCount"]):
                    if type(obj[i]) == bytes:
                        # broken by CONTINUE
                        s = ""
                    else:
                        s = obj[i]["String"]
                    self.document.sst.append(s)
            elif name == "DIMENSIONS":
                last_sheet.dimensions = obj
            elif name == "EXTERNSHEET":
                for sheet in obj["Sheets"]:
                    self.document.externals.append((sheet["SupBook"], sheet["First"], sheet["Last"]))
            elif name == "SUPBOOK":
                self.document.supbooks.append(obj)
            elif name == "RK":
                cell = cellnameabs(obj["Row"], obj["Column"])
                value = RkNumber.read(obj["Data"]["Number"])
                last_sheet.values[cell] = value
            elif name == "MULRK":
                row = obj["Row"]
                col = obj["ColumnFirst"]
                for rk in obj["Data"]:
                    cell = cellnameabs(row, col + 1)
                    value = RkNumber.read(rk["Number"])
                    last_sheet.values[cell] = value
            elif name == "FORMULA" and "Formula" in obj:
                self.document.formulas.append(obj)
                cell = cellnameabs(obj["Cell"]["Row"], obj["Cell"]["Column"])
                last_sheet.formulas[cell] = obj["Formula"]
                if "Value" in obj["Value"]:
                    last_sheet.values[cell] = obj["Value"]["Value"]
                else:
                    last_string_formula_cell = cell
            elif name == "LABELSST":
                cell = cellnameabs(obj["Cell"]["Row"], obj["Cell"]["Column"])
                if obj["Index"] < len(self.document.sst):
                    last_sheet.values[cell] = self.document.sst[obj["Index"]]
            if last_string_formula_cell:
                if name == "STRING":
                    last_sheet.values[last_string_formula_cell] = obj["String"]
                last_string_formula_cell = None

        

################################### FORMULA DECOMPILATION ###################################
# most code taken from xlrd2 project

FMLA_TYPE_CELL = 1
FMLA_TYPE_SHARED = 2
FMLA_TYPE_ARRAY = 4
FMLA_TYPE_COND_FMT = 8
FMLA_TYPE_DATA_VAL = 16
FMLA_TYPE_NAME = 32
ALL_FMLA_TYPES = 63


FMLA_TYPEDESCR_MAP = {
    1 : 'CELL',
    2 : 'SHARED',
    4 : 'ARRAY',
    8 : 'COND-FMT',
    16: 'DATA-VAL',
    32: 'NAME',
}

_TOKEN_NOT_ALLOWED = {
    0x01:   ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tExp
    0x02:   ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tTbl
    0x0F:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tIsect
    0x10:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tUnion/List
    0x11:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRange
    0x20:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArray
    0x23:   FMLA_TYPE_SHARED, # tName
    0x39:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tNameX
    0x3A:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRef3d
    0x3B:   FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArea3d
    0x2C:   FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tRefN
    0x2D:   FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tAreaN
    # plus weird stuff like tMem*
}.get

oBOOL = 3
oERR =  4
oMSNG = 5 # tMissArg
oNUM =  2
oREF = -1
oREL = -2
oSTRG = 1
oUNK =  0
oARR = 6

okind_dict = {
    -2: "oREL",
    -1: "oREF",
    0 : "oUNK",
    1 : "oSTRG",
    2 : "oNUM",
    3 : "oBOOL",
    4 : "oERR",
    5 : "oMSNG",
    6 : "oARR"
}

listsep = ',' #### probably should depend on locale


# sztabN[opcode] -> the number of bytes to consume.
# -1 means variable
# -2 means this opcode not implemented in this version.
# Which N to use? Depends on biff_version; see szdict.
sztab0 = [-2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 8, 4, 2, 2, 3, 9, 8, 2, 3, 8, 4, 7, 5, 5, 5, 2, 4, 7, 4, 7, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2]
sztab1 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 8, 2, 3, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2]
sztab2 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 8, 3, 4, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2]
sztab3 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, -2, -2, 2, 2, 3, 9, 8, 3, 4, 15, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 25, 18, 21, 18, 21, -2, -2]
sztab4 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -2, -2, 2, 2, 3, 9, 8, 3, 4, 5, 5, 9, 7, 7, 7, 3, 5, 9, 5, 9, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 7, 7, 11, 7, 11, -2, -2]

szdict = {
    20 : sztab0,
    21 : sztab0,
    30 : sztab1,
    40 : sztab2,
    45 : sztab2,
    50 : sztab3,
    70 : sztab3,
    80 : sztab4,
}

# For debugging purposes ... the name for each opcode
# (without the prefix "t" used on OOo docs)
onames = ['Unk00', 'Exp', 'Tbl', 'Add', 'Sub', 'Mul', 'Div', 'Power', 'Concat', 'LT', 'LE', 'EQ', 'GE', 'GT', 'NE', 'Isect', 'List', 'Range', 'Uplus', 'Uminus', 'Percent', 'Paren', 'MissArg', 'Str', 'Extended', 'Attr', 'Sheet', 'EndSheet', 'Err', 'Bool', 'Int', 'Num', 'Array', 'Func', 'FuncVar', 'Name', 'Ref', 'Area', 'MemArea', 'MemErr', 'MemNoMem', 'MemFunc', 'RefErr', 'AreaErr', 'RefN', 'AreaN', 'MemAreaN', 'MemNoMemN', '', '', '', '', '', '', '', '', 'FuncCE', 'NameX', 'Ref3d', 'Area3d', 'RefErr3d', 'AreaErr3d', '', '']

tAttrNames = {
    0x00: "Skip??", # seen in SAMPLES.XLS which shipped with Excel 5.0
    0x01: "Volatile",
    0x02: "If",
    0x04: "Choose",
    0x08: "Skip",
    0x10: "Sum",
    0x20: "Assign",
    0x40: "Space",
    0x41: "SpaceVolatile",
}

error_opcodes = set([0x07, 0x08, 0x0A, 0x0B, 0x1C, 0x1D, 0x2F])

tRangeFuncs = (min, max, min, max, min, max)
tIsectFuncs = (max, min, max, min, max, min)

tAdd = 0x03
tSub = 0x04
tMul = 0x05
tDiv = 0x06
tPower = 0x07
tConcat = 0x08
tLT, tLE, tEQ, tGE, tGT, tNE = range(0x09, 0x0F)


def nop(x):
    return x

def _opr_pow(x, y): return x ** y

def _opr_lt(x, y): return x <  y
def _opr_le(x, y): return x <= y
def _opr_eq(x, y): return x == y
def _opr_ge(x, y): return x >= y
def _opr_gt(x, y): return x >  y
def _opr_ne(x, y): return x != y

def num2strg(num):
    """
    Attempt to emulate Excel's default conversion from number to string.
    """
    s = str(num)
    if s.endswith(".0"):
        s = s[:-2]
    return s

_arith_argdict = {oNUM: nop,     oSTRG: float}
_cmp_argdict =   {oNUM: nop,     oSTRG: nop}
# Seems no conversions done on relops; in Excel, "1" > 9 produces TRUE.
_strg_argdict =  {oNUM:num2strg, oSTRG:nop}
binop_rules = {
    tAdd:   (_arith_argdict, oNUM, opr.add,  30, '+'),
    tSub:   (_arith_argdict, oNUM, opr.sub,  30, '-'),
    tMul:   (_arith_argdict, oNUM, opr.mul,  40, '*'),
    tDiv:   (_arith_argdict, oNUM, opr.truediv,  40, '/'),
    tPower: (_arith_argdict, oNUM, _opr_pow, 50, '^',),
    tConcat:(_strg_argdict, oSTRG, opr.add,  20, '&'),
    tLT:    (_cmp_argdict, oBOOL, _opr_lt,   10, '<'),
    tLE:    (_cmp_argdict, oBOOL, _opr_le,   10, '<='),
    tEQ:    (_cmp_argdict, oBOOL, _opr_eq,   10, '='),
    tGE:    (_cmp_argdict, oBOOL, _opr_ge,   10, '>='),
    tGT:    (_cmp_argdict, oBOOL, _opr_gt,   10, '>'),
    tNE:    (_cmp_argdict, oBOOL, _opr_ne,   10, '<>'),
}

unop_rules = {
    0x13: (lambda x: -x,        70, '-', ''), # unary minus
    0x12: (lambda x: x,         70, '+', ''), # unary plus
    0x14: (lambda x: x / 100.0, 60, '',  '%'),# percent
}

LEAF_RANK = 90
FUNC_RANK = 90

STACK_ALARM_LEVEL = 5
STACK_PANIC_LEVEL = 10


class Operand(object):
    value = None
    kind = oUNK
    text = '?'

    def __init__(self, akind=None, avalue=None, arank=0, atext='?'):
        if akind is not None:
            self.kind = akind
        if avalue is not None:
            self.value = avalue
        self.rank = arank
        self.text = atext

    def __repr__(self):
        kind_text = okind_dict.get(self.kind, "?Unknown kind?")
        return "Operand(kind={}, value={}, text={})".format(kind_text, self.value, self.text)


class Ref3D(tuple):
    def __init__(self, atuple):
        self.coords = atuple[0:6]
        self.relflags = atuple[6:12]
        if not self.relflags:
            self.relflags = (0, 0, 0, 0, 0, 0)
        (self.shtxlo, self.shtxhi,
        self.rowxlo, self.rowxhi,
        self.colxlo, self.colxhi) = self.coords

    def __repr__(self):
        if not self.relflags or self.relflags == (0, 0, 0, 0, 0, 0):
            return "Ref3D(coords=%r)" % (self.coords, )
        else:
            return "Ref3D(coords=%r, relflags=%r)" \
                % (self.coords, self.relflags)


def do_box_funcs(box_funcs, boxa, boxb):
    return tuple(
        func(numa, numb)
        for func, numa, numb in zip(box_funcs, boxa.coords, boxb.coords)
    )

def adjust_cell_addr_biff8(rowval, colval, reldelta, browx=None, bcolx=None):
    row_rel = (colval >> 15) & 1
    col_rel = (colval >> 14) & 1
    rowx = rowval
    colx = colval & 0xff
    if reldelta:
        if row_rel and rowx >= 32768:
            rowx -= 65536
        if col_rel and colx >= 128:
            colx -= 256
    else:
        if row_rel:
            rowx -= browx
        if col_rel:
            colx -= bcolx
    return rowx, colx, row_rel, col_rel

def adjust_cell_addr_biff_le7(
        rowval, colval, reldelta, browx=None, bcolx=None):
    row_rel = (rowval >> 15) & 1
    col_rel = (rowval >> 14) & 1
    rowx = rowval & 0x3fff
    colx = colval
    if reldelta:
        if row_rel and rowx >= 8192:
            rowx -= 16384
        if col_rel and colx >= 128:
            colx -= 256
    else:
        if row_rel:
            rowx -= browx
        if col_rel:
            colx -= bcolx
    return rowx, colx, row_rel, col_rel

def get_cell_addr(data, pos, bv, reldelta, browx=None, bcolx=None):
    if bv >= 80:
        rowval, colval = unpack("<HH", data[pos:pos+4])
        # print "    rv=%04xh cv=%04xh" % (rowval, colval)
        return adjust_cell_addr_biff8(rowval, colval, reldelta, browx, bcolx)
    else:
        rowval, colval = unpack("<HB", data[pos:pos+3])
        # print "    rv=%04xh cv=%04xh" % (rowval, colval)
        return adjust_cell_addr_biff_le7(
                    rowval, colval, reldelta, browx, bcolx)

def get_cell_range_addr(data, pos, bv, reldelta, browx=None, bcolx=None):
    if bv >= 80:
        row1val, row2val, col1val, col2val = unpack("<HHHH", data[pos:pos+8])
        # print "    rv=%04xh cv=%04xh" % (row1val, col1val)
        # print "    rv=%04xh cv=%04xh" % (row2val, col2val)
        res1 = adjust_cell_addr_biff8(row1val, col1val, reldelta, browx, bcolx)
        res2 = adjust_cell_addr_biff8(row2val, col2val, reldelta, browx, bcolx)
        return res1, res2
    else:
        row1val, row2val, col1val, col2val = unpack("<HHBB", data[pos:pos+6])
        # print "    rv=%04xh cv=%04xh" % (row1val, col1val)
        # print "    rv=%04xh cv=%04xh" % (row2val, col2val)
        res1 = adjust_cell_addr_biff_le7(
                    row1val, col1val, reldelta, browx, bcolx)
        res2 = adjust_cell_addr_biff_le7(
                    row2val, col2val, reldelta, browx, bcolx)
        return res1, res2

def get_externsheet_local_range(bk, refx, blah=0):
    try:
        info = bk.externals[refx]
    except IndexError:
        return (-101, -101)
    ref_recordx, ref_first_sheetx, ref_last_sheetx = info
    if ref_recordx >= len(bk.supbooks):
        supbook = None
    else:
        supbook = bk.supbooks[ref_recordx]
    if False:
        if ref_recordx == bk._supbook_addins_inx:
            if blah:
                print("/// get_externsheet_local_range(refx=%d) -> addins %r" % (refx, info), file=bk.logfile)
            assert ref_first_sheetx == 0xFFFE == ref_last_sheetx
            return (-5, -5)
        if ref_recordx != bk._supbook_locals_inx:
            if blah:
                print("/// get_externsheet_local_range(refx=%d) -> external %r" % (refx, info), file=bk.logfile)
            return (-4, -4) # external reference
        if ref_first_sheetx == 0xFFFE == ref_last_sheetx:
            if blah:
                print("/// get_externsheet_local_range(refx=%d) -> unspecified sheet %r" % (refx, info), file=bk.logfile)
            return (-1, -1) # internal reference, any sheet
        if ref_first_sheetx == 0xFFFF == ref_last_sheetx:
            if blah:
                print("/// get_externsheet_local_range(refx=%d) -> deleted sheet(s)" % (refx, ), file=bk.logfile)
            return (-2, -2) # internal reference, deleted sheet(s)

    first = bk.adjust_refsheet_index(ref_first_sheetx)
    last = bk.adjust_refsheet_index(ref_last_sheetx)
    if first is None or last is None or not(0 <= first <= last < len(bk.sheets)):
        return (-102, -102) # stuffed up somewhere :-(
    if not(0 <= first <= last):
        return (-3, -3) # internal reference, but to a macro sheet
    return first, last


def rownamerel(rowx, rowxrel, browx=None, r1c1=0):
    # if no base rowx is provided, we have to return r1c1
    if browx is None:
        r1c1 = True
    if not rowxrel:
        if r1c1:
            return "R%d" % (rowx+1)
        return "$%d" % (rowx+1)
    if r1c1:
        if rowx:
            return "R[%d]" % rowx
        return "R"
    return "%d" % ((browx + rowx) % 65536 + 1)

def colnamerel(colx, colxrel, bcolx=None, r1c1=0):
    # if no base colx is provided, we have to return r1c1
    if bcolx is None:
        r1c1 = True
    if not colxrel:
        if r1c1:
            return "C%d" % (colx + 1)
        return "$" + colname(colx)
    if r1c1:
        if colx:
            return "C[%d]" % colx
        return "C"
    return colname((bcolx + colx) % 256)

def cellname(rowx, colx):
    """Utility function: ``(5, 7)`` => ``'H6'``"""
    return "%s%d" % (colname(colx), rowx+1)

def cellnameabs(rowx, colx, r1c1=0):
    """Utility function: ``(5, 7)`` => ``'$H$6'``"""
    if r1c1:
        return "R%dC%d" % (rowx+1, colx+1)
    return "$%s$%d" % (colname(colx), rowx+1)

def cellnamerel(rowx, colx, rowxrel, colxrel, browx=None, bcolx=None, r1c1=0):
    if not rowxrel and not colxrel:
        return cellnameabs(rowx, colx, r1c1)
    if (rowxrel and browx is None) or (colxrel and bcolx is None):
        # must flip the whole cell into R1C1 mode
        r1c1 = True
    c = colnamerel(colx, colxrel, bcolx, r1c1)
    r = rownamerel(rowx, rowxrel, browx, r1c1)
    if r1c1:
        return r + c
    return c + r

def colname(colx):
    """Utility function: ``7`` => ``'H'``, ``27`` => ``'AB'``"""
    res = ""
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    xdiv26, xmod26 = divmod(colx, 26)
    res = alphabet[xmod26]
    while xdiv26:
        xdiv26, xmod26 = divmod(xdiv26, 26)
        res = alphabet[xmod26] + res
    return res

def rangename2d(rlo, rhi, clo, chi, r1c1=0):
    """ ``(5, 20, 7, 10)`` => ``'$H$6:$J$20'`` """
    if r1c1:
        return
    if rhi == rlo+1 and chi == clo+1:
        return cellnameabs(rlo, clo, r1c1)
    return "%s:%s" % (cellnameabs(rlo, clo, r1c1), cellnameabs(rhi-1, chi-1, r1c1))

def rangename2drel(rlo_rhi_clo_chi, rlorel_rhirel_clorel_chirel, browx=None, bcolx=None, r1c1=0):
    rlo, rhi, clo, chi = rlo_rhi_clo_chi
    rlorel, rhirel, clorel, chirel = rlorel_rhirel_clorel_chirel
    if (rlorel or rhirel) and browx is None:
        r1c1 = True
    if (clorel or chirel) and bcolx is None:
        r1c1 = True
    start_cell = cellnamerel(rlo, clo, rlorel, clorel, browx, bcolx, r1c1)
    end_cell = cellnamerel(rhi - 1, chi - 1, rhirel, chirel, browx, bcolx, r1c1)
    if start_cell == end_cell:
        return str(start_cell)
    else:
        return "%s:%s" % (
            cellnamerel(rlo, clo, rlorel, clorel, browx, bcolx, r1c1),
            cellnamerel(rhi - 1, chi - 1, rhirel, chirel, browx, bcolx, r1c1),
        )


def rangename3d(book, ref3d):
    """
    Utility function:
    ``Ref3D(1, 4, 5, 20, 7, 10)`` =>
    ``'Sheet2:Sheet3!$H$6:$J$20'``
    (assuming Excel's default sheetnames)
    """
    coords = ref3d.coords
    return "%s!%s" % (
        sheetrange(book, *coords[:2]),
        rangename2d(*coords[2:6]))

def rangename3drel(book, ref3d, browx=None, bcolx=None, r1c1=0):
    """
    Utility function:
    ``Ref3D(coords=(0, 1, -32, -22, -13, 13), relflags=(0, 0, 1, 1, 1, 1))``

    In R1C1 mode => ``'Sheet1!R[-32]C[-13]:R[-23]C[12]'``

    In A1 mode => depends on base cell ``(browx, bcolx)``
    """
    coords = ref3d.coords
    relflags = ref3d.relflags
    shdesc = sheetrangerel(book, coords[:2], relflags[:2])
    rngdesc = rangename2drel(coords[2:6], relflags[2:6], browx, bcolx, r1c1)
    if not shdesc:
        return rngdesc
    return "%s!%s" % (shdesc, rngdesc)

def quotedsheetname(shnames, shx):
    if shx >= 0:
        shname = shnames[shx]
    else:
        shname = {
            -1: "?internal; any sheet?",
            -2: "internal; deleted sheet",
            -3: "internal; macro sheet",
            -4: "<<external>>",
        }.get(shx, "?error %d?" % shx)
    # if "'" in shname:
    #     return "'" + shname.replace("'", "''") + "'"
    # if " " in shname:
    #     return "'" + shname + "'"
    return "'" + shname + "'"

def sheetrange(book, slo, shi):
    shnames = [x.name for x in book.sheets]
    shdesc = quotedsheetname(shnames, slo)
    if slo != shi-1:
        shdesc += ":" + quotedsheetname(shnames, shi-1)
    return shdesc

def sheetrangerel(book, srange, srangerel):
    slo, shi = srange
    slorel, shirel = srangerel
    if not slorel and not shirel:
        return sheetrange(book, slo, shi)
    assert (slo == 0 == shi-1) and slorel and shirel
    return ""

def unpack_unicode_update_pos(data, pos, lenlen=2, known_len=None):
    if known_len is not None:
        # On a NAME record, the length byte is detached from the front of the string.
        nchars = known_len
    else:
        nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0]
        pos += lenlen
    if not nchars and not data[pos:]:
        # Zero-length string with no options byte
        return ("", pos)
    options = data[pos]
    pos += 1
    phonetic = options & 0x04
    richtext = options & 0x08
    if richtext:
        rt = unpack('<H', data[pos:pos+2])[0]
        pos += 2
    if phonetic:
        sz = unpack('<i', data[pos:pos+4])[0]
        pos += 4
    if options & 0x01:
        # Uncompressed UTF-16-LE
        strg = data[pos:pos+2*nchars].decode('utf_16_le')
        pos += 2*nchars
    else:
        # Note: this is COMPRESSED (not ASCII!) encoding!!!
        strg = data[pos:pos+nchars].decode("latin_1")
        pos += nchars
    if richtext:
        pos += 4 * rt
    if phonetic:
        pos += sz
    return (strg, pos)


#########################################################################################################


class WorkbookAnalyzer8(FileTypeAnalyzer):
    category = malcat.FileType.DOCUMENT
    name = "Office.Workbook8"
    regexp = r"\x09\x08\x10\x00..\x05\x00" 


    def adjust_refsheet_index(self, idx):
        i = 0
        for j, sheet in enumerate(self.sheets):
            if sheet.boundsheet is None or sheet.boundsheet["Type"] not in (0, 1):
                continue
            if i == idx:
                return j
            i += 1

    def get_label_by_index(self, idx):
        i = 0
        for sheet in self.sheets:
            for label in sheet.labels:
                if i == idx:
                    if label.Name.has_enum:
                        return sheet, label.Name.enum
                    return sheet, label.Name.value
                i += 1
        return None, None

    def decompile_formula(self, data, browx=None, bcolx=None, recursion=0):
        if self.version < 80:
            raise ValueError("BIFF version not supported: {}".format(self.version))
        sztab = szdict[self.version]
        res = ""
        pos = 0
        stack = []
        spush = stack.append
        spop = stack.pop
        unk_opnd = Operand(oUNK, None)
        error_opnd = Operand(oERR, None)
        bv = self.version
        reldelta = True
        bk = self
        any_rel = 0
        any_err = 0
        r1c1 = False

        def do_binop(opcd, stk):
            if len(stk) < 2:
                raise ValueError("not enough arguments for {}: {}".format(opcd, stk))
            bop = stk.pop()
            aop = stk.pop()
            argdict, result_kind, func, rank, sym = binop_rules[opcd]
            otext = ''.join([
                '('[:aop.rank <= rank],
                aop.text,
                ')'[:aop.rank <= rank],
                sym,
                '('[:bop.rank <= rank],
                bop.text,
                ')'[:bop.rank <= rank],
            ])
            resop = Operand(result_kind, None, rank, otext)
            stk.append(resop)

        def do_unaryop(opcode, result_kind, stk):
            if len(stk) < 1:
                raise ValueError("not enough argumentsfor {}: {}".format(opcd, stk))
            aop = stk.pop()
            func, rank, sym1, sym2 = unop_rules[opcode]
            otext = ''.join([
                sym1,
                '('[:aop.rank < rank],
                aop.text,
                ')'[:aop.rank < rank],
                sym2,
            ])
            stk.append(Operand(result_kind, None, rank, otext))

        while pos < len(data):
            opcode = data[pos] & 0x1f
            optype = (data[pos] & 0x60) >> 5
            if optype:
                opx = opcode + 32
            else:
                opx = opcode
            oname = onames[opx]
            sz = sztab[opx]
            if sz == -2:
                raise ValueError("Unexpected token 0x{:02x} ({}); biff_version={:d}".format(data[pos], oname, self.version))
            if not optype:
                if opcode <= 0x01: # tExp
                    if bv >= 30:
                        fmt = '<x2H'
                    else:
                        fmt = '<xHB'
                    assert pos == 0 and not stack
                    rowx, colx = unpack(fmt, data)
                    text = "SHARED FMLA at rowx=%d colx=%d" % (rowx, colx)
                    spush(Operand(oUNK, None, LEAF_RANK, text))
                elif 0x03 <= opcode <= 0x0E:
                    # Add, Sub, Mul, Div, Power
                    # tConcat
                    # tLT, ..., tNE
                    do_binop(opcode, stack)
                elif opcode == 0x0F: # tIsect
                    assert len(stack) >= 2
                    bop = stack.pop()
                    aop = stack.pop()
                    sym = ' '
                    rank = 80 ########## check #######
                    otext = ''.join([
                        '('[:aop.rank < rank],
                        aop.text,
                        ')'[:aop.rank < rank],
                        sym,
                        '('[:bop.rank < rank],
                        bop.text,
                        ')'[:bop.rank < rank],
                    ])
                    res = Operand(oREF)
                    res.text = otext
                    if bop.kind == oERR or aop.kind == oERR:
                        res.kind = oERR
                    elif bop.kind == oUNK or aop.kind == oUNK:
                        # This can happen with undefined
                        # (go search in the current sheet) labels.
                        # For example =Bob Sales
                        # Each label gets a NAME record with an empty formula (!)
                        # Evaluation of the tName token classifies it as oUNK
                        # res.kind = oREF
                        pass
                    elif bop.kind == oREF == aop.kind:
                        pass
                    elif bop.kind == oREL == aop.kind:
                        res.kind = oREL
                    else:
                        pass
                    spush(res)
                elif opcode == 0x10: # tList
                    assert len(stack) >= 2
                    bop = stack.pop()
                    aop = stack.pop()
                    sym = ','
                    rank = 80 ########## check #######
                    otext = ''.join([
                        '('[:aop.rank < rank],
                        aop.text,
                        ')'[:aop.rank < rank],
                        sym,
                        '('[:bop.rank < rank],
                        bop.text,
                        ')'[:bop.rank < rank],
                    ])
                    res = Operand(oREF, None, rank, otext)
                    if bop.kind == oERR or aop.kind == oERR:
                        res.kind = oERR
                    elif bop.kind in (oREF, oREL) and aop.kind in (oREF, oREL):
                        res.kind = oREF
                        if aop.kind == oREL or bop.kind == oREL:
                            res.kind = oREL
                    else:
                        pass
                    spush(res)
                elif opcode == 0x11: # tRange
                    assert len(stack) >= 2
                    bop = stack.pop()
                    aop = stack.pop()
                    sym = ':'
                    rank = 80 ########## check #######
                    otext = ''.join([
                        '('[:aop.rank < rank],
                        aop.text,
                        ')'[:aop.rank < rank],
                        sym,
                        '('[:bop.rank < rank],
                        bop.text,
                        ')'[:bop.rank < rank],
                    ])
                    res = Operand(oREF, None, rank, otext)
                    if bop.kind == oERR or aop.kind == oERR:
                        res = oERR
                    elif bop.kind == oREF == aop.kind:
                        pass
                    else:
                        pass
                    spush(res)
                elif 0x12 <= opcode <= 0x14: # tUplus, tUminus, tPercent
                    do_unaryop(opcode, oNUM, stack)
                elif opcode == 0x15: # tParen
                    # source cosmetics
                    pass
                elif opcode == 0x16: # tMissArg
                    spush(Operand(oMSNG, None, LEAF_RANK, ''))
                elif opcode == 0x17: # tStr
                    if bv <= 70:
                        strg, newpos = unpack_string_update_pos(
                                            data, pos+1, bk.encoding, lenlen=1)
                    else:
                        strg, newpos = unpack_unicode_update_pos(
                                            data, pos+1, lenlen=1)
                    sz = newpos - pos
                    text = '"' + strg.replace('"', '""') + '"'
                    spush(Operand(oSTRG, None, LEAF_RANK, text))
                elif opcode == 0x18: # tExtended
                    # new with BIFF 8
                    assert bv >= 80
                    sz = 6
                    subname = "Elf"
                    # not in OOo docs, don't even know how to determine its length
                    raise NotImplemented("tExtended token not implemented")
                elif opcode == 0x19: # tAttr
                    subop, nc = unpack("<BH", data[pos+1:pos+4])
                    subname = tAttrNames.get(subop, "??Unknown??")
                    if subop == 0x04: # Choose
                        sz = nc * 2 + 6
                    elif subop == 0x10: # Sum (single arg)
                        sz = 4
                        assert len(stack) >= 1
                        aop = stack[-1]
                        otext = 'SUM(%s)' % aop.text
                        stack[-1] = Operand(oNUM, None, FUNC_RANK, otext)
                    else:
                        sz = 4
                elif 0x1A <= opcode <= 0x1B: # tSheet, tEndSheet
                    assert bv < 50
                    raise NotImplemented("tSheet & tEndsheet tokens not implemented")
                elif 0x1C <= opcode <= 0x1F: # tErr, tBool, tInt, tNum
                    inx = opcode - 0x1C
                    nb = [1, 1, 2, 8][inx]
                    kind = [oERR, oBOOL, oNUM, oNUM][inx]
                    value, = unpack("<" + "BBHd"[inx], data[pos+1:pos+1+nb])
                    if inx == 2: # tInt
                        value = float(value)
                        text = str(value)
                    elif inx == 3: # tNum
                        text = str(value)
                    elif inx == 1: # tBool
                        text = ('FALSE', 'TRUE')[value]
                    else:
                        text = '"' + {0: '#NULL!', 7: '#DIV/0!', 15: '#VALUE!', 23: '#REF!', 29: '#NAME?', 36: '#NUM!', 42: '#N/A'}.get(value, "#?") + '"'
                    spush(Operand(kind, None, LEAF_RANK, text))
                else:
                    raise NotImplemented("Unhandled opcode: 0x{:02x}".format(opcode))
            else:
                if opcode == 0x00: # tArray
                    spush(unk_opnd)
                elif opcode == 0x01: # tFunc
                    nb = 1 + int(bv >= 40)
                    funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0]
                    func_attrs = FUNC_DEFS.get(funcx, None)
                    if not func_attrs:
                        spush(unk_opnd)
                    else:
                        func_name, nargs = func_attrs[:2]
                        assert len(stack) >= nargs
                        if nargs:
                            if nargs>0:
                                argtext = listsep.join(arg.text for arg in stack[-nargs:])
                            else:
                                argtext = ""
                            otext = "%s(%s)" % (func_name, argtext)
                            if nargs > 0:
                                del stack[-nargs:]
                        else:
                            otext = func_name + "()"
                        res = Operand(oUNK, None, FUNC_RANK, otext)
                        spush(res)
                elif opcode == 0x02: #tFuncVar
                    nb = 1 + int(bv >= 40)
                    nargs, funcx_val = unpack("<B" + " BH"[nb], data[pos+1:pos+2+nb])
                    prompt, nargs = divmod(nargs, 128)
                    macro, funcx = divmod(funcx_val, 32768)
                    #### TODO #### if funcx == 255: # call add-in function
                    if funcx == 255:
                        if len(stack) > 0:
                            nargs -= 1
                            if '(' in stack[0].text:
                                text = stack[0].text[:stack[0].text.index('(')]
                            else:
                                text = stack[0].text
                            func_name_index = len(stack) - nargs - 1
                            func_attrs = (stack[func_name_index].text, nargs, nargs)
                            del stack[func_name_index]
                        else:
                            func_attrs = ("CALL_ADDIN", 1, 30)
                    else:
                        func_attrs = FUNC_DEFS.get(funcx_val, None)
                    if not func_attrs:
                        spush(unk_opnd)
                    else:
                        func_name, minargs, maxargs = func_attrs[:3]
                        assert minargs <= nargs <= maxargs
                        assert len(stack) >= nargs
                        assert len(stack) >= nargs
                        if nargs>0:
                            argtext = listsep.join(arg.text for arg in stack[-nargs:])
                        else:
                            argtext = ""
                        otext = "%s(%s)" % (func_name, argtext)
                        res = Operand(oUNK, None, FUNC_RANK, otext)
                        if nargs > 0:
                            del stack[-nargs:]
                        spush(res)
                elif opcode == 0x03: #tName
                    tgtnamex = unpack("<H", data[pos+1:pos+3])[0] - 1
                    # Only change with BIFF version is number of trailing UNUSED bytes!
                    sheet, name = bk.get_label_by_index(tgtnamex)
                    otext = f"{sheet.name}!{name}"
                    res = Operand(oUNK, None, LEAF_RANK, otext)
                    spush(res)
                elif opcode == 0x04: # tRef
                    res = get_cell_addr(data, pos+1, bv, reldelta, browx, bcolx)
                    rowx, colx, row_rel, col_rel = res
                    is_rel = row_rel or col_rel
                    if is_rel:
                        okind = oREL
                    else:
                        okind = oREF
                    otext = cellnamerel(rowx, colx, row_rel, col_rel, browx, bcolx, r1c1)
                    res = Operand(okind, None, LEAF_RANK, otext)
                    spush(res)
                elif opcode == 0x05: # tArea
                    res1, res2 = get_cell_range_addr(data, pos+1, bv, reldelta, browx, bcolx)
                    rowx1, colx1, row_rel1, col_rel1 = res1
                    rowx2, colx2, row_rel2, col_rel2 = res2
                    coords = (rowx1, rowx2+1, colx1, colx2+1)
                    relflags = (row_rel1, row_rel2, col_rel1, col_rel2)
                    if sum(relflags):  # relative
                        okind = oREL
                    else:
                        okind = oREF
                    otext = rangename2drel(coords, relflags, browx, bcolx, r1c1)
                    res = Operand(okind, None, LEAF_RANK, otext)
                    spush(res)
                elif opcode == 0x06: # tMemArea
                    pass
                    # not_in_name_formula(op, oname)
                elif opcode == 0x09: # tMemFunc
                    nb = unpack("<H", data[pos+1:pos+3])[0]
                    # no effect on stack
                elif opcode == 0x0C: #tRefN
                    res = get_cell_addr(data, pos+1, bv, reldelta, browx, bcolx)
                    # note *ALL* tRefN usage has signed offset for relative addresses
                    any_rel = 1
                    rowx, colx, row_rel, col_rel = res
                    is_rel = row_rel or col_rel
                    if is_rel:
                        okind = oREL
                    else:
                        okind = oREF
                    otext = cellnamerel(rowx, colx, row_rel, col_rel, browx, bcolx, r1c1)
                    res = Operand(okind, None, LEAF_RANK, otext)
                    spush(res)
                elif opcode == 0x0D: #tAreaN
                    # res = get_cell_range_addr(data, pos+1, bv, reldelta, browx, bcolx)
                    # # note *ALL* tAreaN usage has signed offset for relative addresses
                    # any_rel = 1
                    # if blah: print >> bk.logfile, "   ", res
                    res1, res2 = get_cell_range_addr(
                                    data, pos+1, bv, reldelta, browx, bcolx)
                    rowx1, colx1, row_rel1, col_rel1 = res1
                    rowx2, colx2, row_rel2, col_rel2 = res2
                    coords = (rowx1, rowx2+1, colx1, colx2+1)
                    relflags = (row_rel1, row_rel2, col_rel1, col_rel2)
                    if sum(relflags):  # relative
                        okind = oREL
                    else:
                        okind = oREF
                    otext = rangename2drel(coords, relflags, browx, bcolx, r1c1)
                    res = Operand(okind, None, LEAF_RANK, otext)
                    spush(res)
                elif opcode == 0x1A: # tRef3d
                    if bv >= 80:
                        res = get_cell_addr(data, pos+3, bv, reldelta, browx, bcolx)
                        refx = unpack("<H", data[pos+1:pos+3])[0]
                        refx = 0
                        shx1, shx2 = get_externsheet_local_range(bk, refx)
                    else:
                        res = get_cell_addr(data, pos+15, bv, reldelta, browx, bcolx)
                        raw_extshtx, raw_shx1, raw_shx2 = unpack("<hxxxxxxxxhh", data[pos+1:pos+15])
                        shx1, shx2 = get_externsheet_local_range_b57(
                                        bk, raw_extshtx, raw_shx1, raw_shx2)
                    rowx, colx, row_rel, col_rel = res
                    is_rel = row_rel or col_rel
                    any_rel = any_rel or is_rel
                    coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1)
                    any_err |= shx1 < -1
                    res = Operand(oUNK, None)
                    if is_rel:
                        relflags = (0, 0, row_rel, row_rel, col_rel, col_rel)
                        ref3d = Ref3D(coords + relflags)
                        res.kind = oREL
                        res.text = rangename3drel(bk, ref3d, browx, bcolx, r1c1)
                    else:
                        ref3d = Ref3D(coords)
                        res.kind = oREF
                        res.text = rangename3d(bk, ref3d)
                    res.rank = LEAF_RANK
                    res.value = None
                    spush(res)
                elif opcode == 0x1B: # tArea3d
                    if bv >= 80:
                        res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta)
                        refx = unpack("<H", data[pos+1:pos+3])[0]
                        shx1, shx2 = get_externsheet_local_range(bk, refx)
                    else:
                        res1, res2 = get_cell_range_addr(data, pos+15, bv, reldelta)
                        raw_extshtx, raw_shx1, raw_shx2 = unpack("<hxxxxxxxxhh", data[pos+1:pos+15])
                        shx1, shx2 = get_externsheet_local_range_b57(
                                        bk, raw_extshtx, raw_shx1, raw_shx2)
                    any_err |= shx1 < -1
                    rowx1, colx1, row_rel1, col_rel1 = res1
                    rowx2, colx2, row_rel2, col_rel2 = res2
                    is_rel = row_rel1 or col_rel1 or row_rel2 or col_rel2
                    any_rel = any_rel or is_rel
                    coords = (shx1, shx2+1, rowx1, rowx2+1, colx1, colx2+1)
                    res = Operand(oUNK, None)
                    if is_rel:
                        relflags = (0, 0, row_rel1, row_rel2, col_rel1, col_rel2)
                        ref3d = Ref3D(coords + relflags)
                        res.kind = oREL
                        res.text = rangename3drel(bk, ref3d, browx, bcolx, r1c1)
                    else:
                        ref3d = Ref3D(coords)
                        res.kind = oREF
                        res.text = rangename3d(bk, ref3d)
                    res.rank = LEAF_RANK
                    spush(res)
                elif opcode == 0x19: # tNameX
                    dodgy = 0
                    res = Operand(oUNK, None)
                    if bv >= 80:
                        refx, tgtnamex = unpack("<HH", data[pos+1:pos+5])
                        tgtnamex -= 1
                        origrefx = refx
                    else:
                        refx, tgtnamex = unpack("<hxxxxxxxxH", data[pos+1:pos+13])
                        tgtnamex -= 1
                        origrefx = refx
                        if refx > 0:
                            refx -= 1
                        elif refx < 0:
                            refx = -refx - 1
                        else:
                            dodgy = 1
                    if not dodgy:
                        if bv >= 80:
                            shx1, shx2 = get_externsheet_local_range(bk, refx)
                        elif origrefx > 0:
                            shx1, shx2 = (-4, -4) # external ref
                        else:
                            exty = bk._externsheet_type_b57[refx]
                            if exty == 4: # non-specific sheet in own doc't
                                shx1, shx2 = (-1, -1) # internal, any sheet
                            else:
                                shx1, shx2 = (-666, -666)
                    okind = oUNK
                    ovalue = None
                    if shx1 == -5: # addin func name
                        okind = oSTRG
                        ovalue = bk.addin_func_names[tgtnamex]
                        otext = '"' + ovalue.replace('"', '""') + '"'
                    elif dodgy or shx1 < -1:
                        otext = "<<Name #{:d} in external(?) file #{:d}>>".format(tgtnamex, origrefx)
                    else:
                        sheet, name = bk.get_label_by_index(tgtnamex)
                        otext = f"{sheet.name}!{name}"
                    res = Operand(okind, ovalue, LEAF_RANK, otext)
                    spush(res)
                elif opcode in error_opcodes:
                    any_err = 1
                    spush(error_opnd)
                else:
                    raise ValueError("FORMULA: /// Not handled yet: t" + oname)
            if sz <= 0:
                raise ValueError("Fatal: token size is not positive")
            pos += sz

        if len(stack) != 1:
            return str(stack)
        else:
            return stack[0].text



    def decompile(self):
        res = ""
        for sheet in self.sheets:
            res += f"################################### {sheet.name:^40.40s} ###################################\n"
            res += "#### CELL VALUES\n"
            for cell, val in sheet.values.items():
                res += f"● {cell}: {repr(val)}\n"
            res += "\n#### CELL FORMULAS\n"
            for cell, f in sheet.formulas.items():
                try:
                    res += f"● {cell}: {self.decompile_formula(f)}"
                except BaseException as e:
                    import traceback
                    res += f"● {cell}: {traceback.format_exc()}"
                res += "\n"
            res += "\n\n"
        return res



    def parse(self, hint):
        delayed_globals()
        self.sheets = []
        self.externals = []
        self.supbooks = []
        self.sst = []
        self.formulas = []
        self.boundsheets = {}
        self.version = 0
        self.set_architecture(malcat.Architecture.BIFF)
        while self.remaining() >= 4:
            opcode, sz = struct.unpack("<HH", self.read(self.tell(), 4))
            if self.remaining() < 4 + sz:
                raise FatalError("Invalid opcode at #{:x} of size {}".format(self.tell(), sz))
            name, comment, class_ = EXCEL_OPCODES.get(opcode, ["???", "", BiffRecord])
            if name != "BOF":
                break
            typ, = struct.unpack("<H", self.read(self.tell() + 6, 2))
            NAMES = { 5: "Global", 6: "VB Module", 0x10: "XLS", 0x20:"XLC", 0x40: "XLM", 0x100: "GlobalW"}
            if typ not in NAMES:
                raise FatalError("Unrecognized document type: 0x{:x}".format(typ))
            sheet = ExcelSheet(NAMES[typ])
            self.sheets.append(sheet)
            boundsheet = self.boundsheets.get(self.tell())
            if boundsheet:
                sheet.name = boundsheet["Name"]["String"]
                sheet.boundsheet = boundsheet
            wb = yield Workbook(self, name=NAMES[typ], category=Type.DATA)
            sheet.workbook = wb
            self.confirm()
        del self.boundsheets

        # version
        global_bof = None
        for s in self.sheets:
            if s.name in ("Global", "GlobalW"):
                global_bof = s.workbook.at(0)
                break
        else:
            raise FatalError("Could not locate global sheet")
        # taken from xlrd2
        version1 = global_bof["Opcode"] // 256
        version2 = global_bof["BiffVersion"]
        if version1 == 0x08:
            build = global_bof["BuildIdentifier"]
            year = global_bof["BuildYear"]
            if version2 == 0x0600:
                self.version = 80
            elif version2 == 0x0500:
                if year < 1994 or build in (2412, 3218, 3321):
                    self.version = 50
                else:
                    self.version = 70
            else:
                # dodgy one, created by a 3rd-party tool
                self.version = {
                    0x0000: 21,
                    0x0007: 21,
                    0x0200: 21,
                    0x0300: 30,
                    0x0400: 40,
                }.get(version2, 0)
        elif version1 in (0x04, 0x02, 0x00):
            self.version = {0x04: 40, 0x02: 30, 0x00: 21}[version1]
        if self.version == 40 and global_bof["DocumentType"] == 0x100:
            self.version = 45 # i.e. 4W

        # sections
        for sheet in self.sheets:
            if sheet.boundsheet is None:
                continue
            self.add_section(sheet.boundsheet["Name"]["String"], sheet.workbook.offset, sheet.workbook.size, r = True, w = sheet.boundsheet["Type"] == 0, x = sheet.boundsheet["Type"] in (1,6))

