diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 61d57f2..0156440 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -13,17 +13,17 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} architecture: x64 - name: Install run: | - pip install -e . + pip install -ve ./src/ccaterpillar pip install -r requirements.txt pip install -r test/requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a80ac3..f179498 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,33 +8,51 @@ find_package( add_compile_definitions(_CPMODULE) +# TODO: document this change +set(CP_C_CONFIGURED DEFINED ENV{CP_ENABLE_NATIVE} OR DEFINED CP_ENABLE_NATIVE) -if (DEFINED ENV{CP_ENABLE_NATIVE}) +if (CP_C_CONFIGURED) python_add_library( _C MODULE - src/arch.c - src/atomobj.c - src/context.c - src/field.c - src/option.c - src/module.c - src/context.c - src/state.c - src/struct.c - - src/parsing_typeof.c - src/parsing_sizeof.c - src/parsing_pack.c - src/parsing_unpack.c - - src/atomimpl/intatomobj.c - src/atomimpl/floatatomobj.c - src/atomimpl/boolatomobj.c - src/atomimpl/charatomobj.c - src/atomimpl/padatomobj.c - src/atomimpl/stringatomobj.c + src/ccaterpillar/arch.c + src/ccaterpillar/atomobj.c + src/ccaterpillar/context.c + src/ccaterpillar/option.c + src/ccaterpillar/module.c + src/ccaterpillar/context.c + src/ccaterpillar/state.c + src/ccaterpillar/struct.c + src/ccaterpillar/layer.c + src/ccaterpillar/default.c + + src/ccaterpillar/parsing_typeof.c + src/ccaterpillar/parsing_sizeof.c + src/ccaterpillar/parsing_pack.c + src/ccaterpillar/parsing_unpack.c + + src/ccaterpillar/atomimpl/int.c + src/ccaterpillar/atomimpl/float.c + src/ccaterpillar/atomimpl/bool.c + src/ccaterpillar/atomimpl/char.c + src/ccaterpillar/atomimpl/pad.c + src/ccaterpillar/atomimpl/string.c + src/ccaterpillar/atomimpl/const.c + src/ccaterpillar/atomimpl/bytes.c + src/ccaterpillar/atomimpl/pstring.c + src/ccaterpillar/atomimpl/enum.c + src/ccaterpillar/atomimpl/varint.c + src/ccaterpillar/atomimpl/computed.c + src/ccaterpillar/atomimpl/lazy.c + src/ccaterpillar/atomimpl/cstring.c + + src/ccaterpillar/atomimpl/builtins/builtin.c + src/ccaterpillar/atomimpl/builtins/repeated.c + src/ccaterpillar/atomimpl/builtins/condition.c + src/ccaterpillar/atomimpl/builtins/switch.c + src/ccaterpillar/atomimpl/builtins/atoffset.c + src/ccaterpillar/atomimpl/builtins/primitive.c WITH_SOABI ) @@ -44,10 +62,18 @@ target_include_directories(_C PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpilla message(STATUS "CMake ${CMAKE_VERSION}") message(STATUS "Python ${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}") +set (CP_GENAPI_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/src/code_gen/genapi.py) +set (CP_CAPI_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillar/include/caterpillar/caterpillarapi.h.in) +set (CP_CAPI_CSRC ${CMAKE_CURRENT_SOURCE_DIR}/src/ccaterpillar/caterpillarapi.c.in) +set (CP_CAPI_HEADER_OUT ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillar/include/caterpillar/caterpillarapi.h) +set (CP_CAPI_CSRC_OUT ${CMAKE_CURRENT_SOURCE_DIR}/src/ccaterpillar/caterpillarapi.c) + add_custom_target( genapi ALL - COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/code_gen/genapi.py ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillar/include/caterpillar/caterpillarapi.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillarapi.c.in - BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillar/include/caterpillar/caterpillarapi.h ${CMAKE_CURRENT_SOURCE_DIR}/src/caterpillarapi.c + # execute python ./src/code_gen/genapi.py ./src/caterpillar/include/caterpillar/caterpillarapi.h.in ./src/ccaterpillar/caterpillarapi.c.in + # in the root directory + COMMAND ${PYTHON_EXECUTABLE} ${CP_GENAPI_SCRIPT} ${CP_CAPI_HEADER} ${CP_CAPI_CSRC} + BYPRODUCTS ${CP_CAPI_HEADER_OUT} ${CP_CAPI_CSRC_OUT} COMMENT "Generating Public Caterpillar API" ) diff --git a/README.md b/README.md index dc1eb65..8d45d45 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ options will be added in the future. Documentation is [here >](https://matrixedi * it helps you to create cleaner and more compact code. * You can even extend Caterpillar and write your parsing logic in C or C++!! -## What does it look like? +## Give me some code! ```python -from caterpillar._Py import * +from caterpillar.py import * @struct(order=LittleEndian) class Format: @@ -61,24 +61,19 @@ on its powerful features, explore the official [documentation](https://matrixedi > As of Caterpillar v2.1.2 it is possible to install the library without the need of > compiling the C extension. -Simply use pip to install the package with all tools, all extra packages and the C extension: +### Python-only installation + ```bash -pip install "caterpillar[all]@git+https://github.com/MatrixEditor/caterpillar.git" +pip install "caterpillar[all]@git+https://github.com/MatrixEditor/caterpillar" ``` -If you want to use the native C extension you can specify the following environment variables: - -* `CP_ENABLE_NATIVE` - Enables installation of the native C extension -* `CP_ENABLE_TOOLS` - Enables installation of extra tools (proposed) +### C-extension installation -For instance, the following command will install the `caterpillar` package with -a native interface and with extra tools: ```bash -CP_ENABLE_NATIVE=1 \ -CP_ENABLE_TOOLS=1 \ -pip install git+https://github.com/MatrixEditor/caterpillar +pip install "caterpillar[all]@git+https://github.com/MatrixEditor/caterpillar/#subdirectory=src/ccaterpillar" ``` + ## Starting Point Please visit the [Documentation](https://matrixeditor.github.io/caterpillar/), it contains a complete tutorial on how to use this library. diff --git a/docs/requirements.txt b/docs/requirements.txt index c1500b1..ad16558 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,5 @@ sphinx pydata-sphinx-theme sphinx-design -breathe \ No newline at end of file +breathe +sphinx-copybutton \ No newline at end of file diff --git a/docs/sphinx/source/conf.py b/docs/sphinx/source/conf.py index aa9a9ce..efcc88a 100644 --- a/docs/sphinx/source/conf.py +++ b/docs/sphinx/source/conf.py @@ -27,7 +27,8 @@ "sphinx.ext.viewcode", "sphinx_design", "breathe", - "c_annotations" + "c_annotations", + "sphinx_copybutton" ] templates_path = ["_templates"] @@ -61,7 +62,7 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False -refcount_file = 'extensions/refcounts.dat' +refcount_file = '../../../src/capi.dat' # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/docs/sphinx/source/development/index.rst b/docs/sphinx/source/development/index.rst index 5e0c89d..8a5a340 100644 --- a/docs/sphinx/source/development/index.rst +++ b/docs/sphinx/source/development/index.rst @@ -10,5 +10,6 @@ Development :numbered: :maxdepth: 2 + roadmap.rst contribution.rst changelog.rst \ No newline at end of file diff --git a/docs/sphinx/source/development/roadmap.rst b/docs/sphinx/source/development/roadmap.rst new file mode 100644 index 0000000..a2ad41a --- /dev/null +++ b/docs/sphinx/source/development/roadmap.rst @@ -0,0 +1,111 @@ +.. _dev-roadmap: + +******** +Roadmap +******** + +.. |check_| raw:: html + + + +.. |uncheck_| raw:: html + + + +.. role:: text-danger + +.. role:: text-warning + +Python API +---------- + +- |check_| Implementation of parsing process (unpack, pack) +- |check_| Struct class (:class:`Struct`) with wrapper function (:code:`@struct`) +- |uncheck_| Python docs and examples +- |uncheck_| Python tests + +C API +----- + +- |check_| Implementation of parsing process (unpack, pack) +- |uncheck_| Struct class (:c:type:`CpStructObject`) +- |uncheck_| Struct wrapper function +- |uncheck_| Python docs + +Struct Objects: +^^^^^^^^^^^^^^^ + +- |check_| Struct (C type: :c:type:`CpStructObject`, Py type: :class:`Struct`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |uncheck_| Bitfield + +Atom Objects: +^^^^^^^^^^^^^ + +- |check_| Integer (uint8-128 and int8-128) (C type: :c:type:`CpIntAtomObject`, Py type: :class:`int_t`) + [:text-danger:`missing docs`] + +- |check_| Float, Double (C type: :c:type:`CpFloatAtomObject`, Py type: :class:`float_t`) + [:text-danger:`missing docs`] + +- |check_| Boolean (C type: :c:type:`CpBoolAtomObject`, Py type: :class:`bool_t`) + Global instance: :code:`boolean`, + [:text-danger:`missing docs`] + +- |check_| Char (C type: :c:type:`CpCharAtomObject`, Py type: :class:`char_t`) + Global instance: :code:`char`, + [:text-danger:`missing docs`] + +- |check_| Padding (C type: :c:type:`CpPaddingAtomObject`, Py type: :class:`padding_t`) + Global instance: :code:`padding`, + [:text-danger:`missing docs`] + +- |check_| String (C type: :c:type:`CpStringAtomObject`, Py type: :class:`string`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |check_| Const (C type: :c:type:`CpConstAtomObject`, Py type: :class:`const_t`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |uncheck_| CString (C type: :c:type:`CpCStringAtomObject`, Py type: :class:`cstring`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |check_| Bytes (C type: :c:type:`CpBytesAtomObject`, Py type: :class:`octetstring`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |check_| Enum (C type: :c:type:`CpEnumAtomObject`, Py type: :class:`enumeration`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |check_| Computed (C type: :c:type:`CpComputedAtomObject`, Py type: :class:`computed`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |check_| PString (C type: :c:type:`CpPStringAtomObject`, Py type: :class:`pstring`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |uncheck_| Prefixed + .. seealso:: *link issue here* + +- |check_| Lazy + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |uncheck_| uuid + .. seealso:: *link issue here* + +- |uncheck_| Conditional: If, Else, ElseIf + .. seealso:: *link issue here* + +- |check_| VarInt (C type: :c:type:`CpVarIntAtomObject`, Py type: :class:`varint_t`) + [:text-danger:`missing docs`], + [:text-warning:`missing perftest`] + +- |uncheck_| While + .. seealso:: *link issue here* \ No newline at end of file diff --git a/docs/sphinx/source/extensions/c_annotations.py b/docs/sphinx/source/extensions/c_annotations.py index 4d01d2c..9ef9c98 100644 --- a/docs/sphinx/source/extensions/c_annotations.py +++ b/docs/sphinx/source/extensions/c_annotations.py @@ -6,9 +6,10 @@ from os import path import docutils from docutils import nodes +from sphinx.addnodes import pending_xref from sphinx.locale import _ as sphinx_gettext -from sphinx import addnodes +from sphinx import addnodes, application class RCEntry: @@ -21,56 +22,90 @@ def __init__(self, name): class Annotations: def __init__(self, refcount_filename): self.refcount_data = {} + self.typedef_data = {} with open(refcount_filename, "r", encoding="utf-8") as fp: for line in fp: line = line.strip() if line[:1] in ("", "#"): # blank lines and comments continue - parts = line.split(":", 2) - if len(parts) != 3: - raise ValueError("Wrong field count in %r" % line) - function, type, refcount = parts - # Get the entry, creating it if needed: - try: - entry = self.refcount_data[function] - except KeyError: - entry = self.refcount_data[function] = RCEntry(function) - if not refcount or refcount == "null": - refcount = None - else: - refcount = int(refcount) - entry.result_type = type - entry.result_refs = refcount + def_type, *parts = line.split(":") + match def_type: + case "func": + index, name, rtype, refcount = parts + # Get the entry, creating it if needed: + entry = self.refcount_data.get(name) + if not entry: + entry = self.refcount_data[name] = RCEntry(name) + if not refcount or refcount == "null": + refcount = None + else: + refcount = int(refcount) + entry.result_type = rtype + entry.result_refs = refcount + + case "type": + index, struct_name, typedef_name, py_type = parts + if py_type != "-": + self.typedef_data[py_type] = typedef_name + + def add_py_annotations( + self, app: application.Sphinx, node: docutils.nodes.Element, par + ): + + if not par[0].has_key("ids") or not par[0]["ids"]: + return + + name = par[0]["ids"][0] + if not name.startswith("caterpillar.c."): + return + + if par["objtype"] != "class": + return + + name = name[14:] + if name in self.typedef_data: + typedef_name = self.typedef_data[name] + + rc = sphinx_gettext(f"C API Type: {typedef_name}") + node.insert(0, nodes.emphasis(rc, rc, classes=["refcount", "text-info"])) + node.insert(1, nodes.line()) + + def add_c_annotation(self, app, node, par): + if not par[0].has_key("ids") or not par[0]["ids"]: + return + + name = par[0]["ids"][0] + if name.startswith("c."): + name = name[2:] + + objtype = par["objtype"] + if objtype != "function": + return + entry = self.refcount_data.get(name) + if not entry: + return + + if not entry.result_type.endswith("Object*"): + return + + if entry.result_refs is None: + rc = sphinx_gettext("Return value: Always NULL.") + elif entry.result_refs: + rc = sphinx_gettext("Return value: New reference.") + else: + rc = sphinx_gettext("Return value: Borrowed reference.") + node.insert(0, nodes.emphasis(rc, rc, classes=["refcount", "text-info"])) def add_annotations(self, app, doctree): for node in doctree.findall(addnodes.desc_content): par = node.parent - if par["domain"] != "c": - continue - if not par[0].has_key("ids") or not par[0]["ids"]: - continue - name = par[0]["ids"][0] - if name.startswith("c."): - name = name[2:] - - objtype = par["objtype"] - if objtype != "function": - continue - entry = self.refcount_data.get(name) - if not entry: - continue - - if not entry.result_type.endswith("Object*"): - continue - - if entry.result_refs is None: - rc = sphinx_gettext("Return value: Always NULL.") - elif entry.result_refs: - rc = sphinx_gettext("Return value: New reference.") - else: - rc = sphinx_gettext("Return value: Borrowed reference.") - node.insert(0, nodes.emphasis(rc, rc, classes=["refcount", "text-info"])) + match par["domain"]: + case "c": + self.add_c_annotation(app, node, par) + + case "py": + self.add_py_annotations(app, node, par) def init_annotations(app): diff --git a/docs/sphinx/source/extensions/refcounts.dat b/docs/sphinx/source/extensions/refcounts.dat index 36aa46f..ec563fe 100644 --- a/docs/sphinx/source/extensions/refcounts.dat +++ b/docs/sphinx/source/extensions/refcounts.dat @@ -10,9 +10,6 @@ CpStruct_GetAnnotations:PyObject*:+1 CpStructFieldInfo_New:CpStructFieldInfoObject*:+1 CpCharAtom_Unpack:PyObject*:+1 - -CpContext_GetAttr:PyObject*:+1 -CpContext_GetAttrString:PyObject*:+1 CpContext_New:CpContextObject*:+1 CpUnaryExpr_New:CpUnaryExprObject*:+1 @@ -39,4 +36,11 @@ CpState_Read:PyObject*:+1 CpState_ReadFully:PyObject*:+1 CpState_Write:PyObject*:+1 -CpLayer_New:CpLayerObject*:+1 \ No newline at end of file +CpLayer_New:CpLayerObject*:+1 + +CpIntAtom_Unpack:PyObject*:+1 + +CpFloatAtom_Unpack:PyObject*:+1 + +CpRepeatedAtom_GetLength:PyObject*:+1 +CpRepeatedAtom_Unpack:PyObject*:+1 \ No newline at end of file diff --git a/docs/sphinx/source/index.rst b/docs/sphinx/source/index.rst index f9a0d10..97b0893 100644 --- a/docs/sphinx/source/index.rst +++ b/docs/sphinx/source/index.rst @@ -16,15 +16,34 @@ efficient. structs that adjust their size based on the current context. This framework enables you to write complex structures in a compact and readable manner. -.. code-block:: - :caption: Simple example of a custom struct +.. tab-set:: - @struct - class Format: - magic: b"Foo" # constant values - name: CString(...) # C-String without a fixed length - value: le + uint16 # little endian encoding - entries: be + CString[uint32::] # arrays with big-endian prefixed length + .. tab-item:: Python + + .. code-block:: + :caption: Simple example of a custom struct + + @struct + class Format: + magic: b"Foo" # constant values + name: CString(...) # C-String without a fixed length + value: le + uint16 # little endian encoding + entries: be + CString[uint32::] # arrays with big-endian prefixed length + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Simple example of a custom struct + + from caterpillar.c import BIG_ENDIAN as be + + @struct + class Format: + magic: b"Foo" # constant values + name: cstring(...) # C-String without a fixed length + value: u16 # little endian encoding + entries: cstring(...)[be + u32::] # arrays with big-endian prefixed length + value2: bool # python type mapping configurable .. admonition:: Hold up, wait a minute! @@ -34,27 +53,39 @@ to write complex structures in a compact and readable manner. Working with defined classes is as straightforward as working with normal classes. *All constant values are created automatically!* + >>> obj = Format(name="Hello, World!", value=10, entries=["Bar", "Baz"]) >>> print(obj) Format(magic=b'Foo', name='Hello, World!', value=10, entries=['Bar', 'Baz']) Packing and unpacking have never been easier: ->>> pack(obj) -b'FooHello, World!\x00\n\x00\x00\x00\x00\x02Bar\x00Baz\x00' ->>> unpack(Format, _) -Format(magic=b'Foo', name='Hello, World!', value=10, entries=['Bar', 'Baz']) -- What about documentation? +.. tab-set:: + + .. tab-item:: Python + + >>> pack(obj) + b'FooHello, World!\x00\n\x00\x00\x00\x00\x02Bar\x00Baz\x00' + >>> unpack(Format, _) + Format(magic=b'Foo', name='Hello, World!', value=10, entries=['Bar', 'Baz']) - There are specialized options created only for documentation purposes, so you don't - have to worry about documenting fields. Just apply the documentation as usual. + .. tab-item:: Caterpillar C + + >>> pack(obj, Format) + b'FooHello, World!\x00\n\x00\x00\x00\x00\x02Bar\x00Baz\x00\x01' + >>> unpack(_, Format) + + +- What about documentation? + There are specialized options created only for documentation purposes, so you don't + have to worry about documenting fields. Just apply the documentation as usual. - You want to optimize memory space? + No problem! It is possible to shrink the memory space occupied by unpacked objects up + to 4 times. More information are provided when discussing available configuration + :ref:`options`. - No problem! It is possible to shrink the memory space occupied by unpacked objects up - to 4 times. More information are provided when discussing available configuration - :ref:`options`. Where to start? --------------- diff --git a/docs/sphinx/source/installing/index.rst b/docs/sphinx/source/installing/index.rst index b93a1a1..3930ee8 100644 --- a/docs/sphinx/source/installing/index.rst +++ b/docs/sphinx/source/installing/index.rst @@ -9,9 +9,8 @@ to install it by providing the Git link. This library has no fixed dependencies, out of the box. There are several installation options you can use to install a desired part of the library. -*caterpillar* ships with a native C extension and additional tools, that are using native -extensions too. As not everyone wants to use C extensions, it's default installation candidate -does not include the native extension. +*caterpillar* ships with a native C extension and additional tools. As not everyone wants to +use C extensions, it's default installation candidate does not include the native extension. .. code-block:: bash @@ -28,6 +27,11 @@ If you want to use the native C extension you can specify the following environm * `CP_ENABLE_NATIVE` - Enables installation of the native C extension * `CP_ENABLE_TOOLS` - Enables installation of extra tools (proposed) +**or** you can use the following command to install it directly: + +.. code-block:: bash + + pip install git+https://github.com/MatrixEditor/caterpillar/#subdirectory=src/ccaterpillar This project comes with packaging extras. Therefore, if you want to enable specific structs, you have to install the corresponding extra. It can be done using pip again: diff --git a/docs/sphinx/source/library/ctypes/int.rst b/docs/sphinx/source/library/ctypes/int.rst new file mode 100644 index 0000000..ebbd881 --- /dev/null +++ b/docs/sphinx/source/library/ctypes/int.rst @@ -0,0 +1,192 @@ +.. _api-ctypes_int: + +************* +Integer Types +************* + +This document provides detailed API documentation for integer types in the :code:`caterpillar.c` module. These +integer types are low-level representations of integers with customizable bit-length, signedness, and byte +order. They are particularly useful when interacting with binary data or when a fixed-width integer +representation is required. + +The integer types offered here are optimized for performance, making them more efficient than their Python +equivalents (:class:`FormatField`) in almost every context. The global predefined integer types (e.g., +:data:`u8`, :data:`i32`) are convenient for most common cases, but you can also define custom integer types +with specific bit-widths, signedness, and endianness using the :class:`Int` class. + +.. py:class:: caterpillar.c.Int(bits, signed=True, little_endian=True) + + This class implements an integer type with a variable number of bits, + supporting signed and unsigned integers, and little-endian or big-endian + byte orders. + + Unlike its Python counterpart (:class:`FormatField`), this C-based class + focuses solely on integer operations, optimizing performance through direct + use of the `int.from_bytes` and `int.to_bytes` methods. Although you can + instantiate the class directly, it is recommended to use the global predefined + instances for common integer types (see below for more details). + + .. note:: + For most use cases, utilize the pre-defined global instances like :data:`u8`, + :data:`i16`, etc., for simplicity and efficiency. + + .. csv-table:: Supported Operations + :header: "Action", "Operator", "Result" + :widths: 15, 30, 30 + + "Byteorder", ":code:` + `", "new :class:`Int` object with corresponding byteorder" + "Sequence", ":code:`[]`", ":class:`repeated` object" + "Offset", ":code:` @ `", ":class:`atoffset` object" + "Condition", ":code:` // `", ":class:`condition` object" + "Switch", ":code:` >> `", ":class:`switch` object" + + .. versionadded:: 2.2.0 + + + .. py:method:: __init__(self, bits, signed=True, little_endian=True) + + Initializes the integer type with the given number of bits, sign specification, + and byte order. + + :param int bits: The number of bits for the integer. Must be a multiple of 8. + :param bool signed: Optional; whether the integer is signed. Defaults to True. + :param bool little_endian: Optional; whether the integer should be stored in little-endian byte order. Defaults to True. + + .. code-block:: python + :caption: Examples of using :class:`Int` + + from caterpillar.c import * + + # Unsigned 8-bit integer + assert unpack(b"\x01", u8) == 1 + + # Signed 8-bit integer + assert pack(1, i8) == b"\x01" + + # Unsigned 16-bit little-endian integer + assert unpack(b"\x01\x00", u16) == 1 + + # Signed 16-bit big-endian integer + assert pack(-1, Int(16, signed=True, little_endian=False)) == b"\xff\xff" + + .. py:method:: __repr__(self) -> str + + Returns a string representation of the integer type. The format of the returned string + is as follows: + + .. code-block:: + + := '<' ('le' | 'be') ['u'] 'int' '>' + + >>> assert repr(u32) == "" + + :return: A string representation of the integer type, indicating the byte order, sign, and number of bits. + + .. py:method:: __type__(self) -> type + + Returns the Python type for this class. This is typically :code:`int`. + + :return: The Python type for this class. + + .. py:method:: __size__(self, ctx) -> int + + Returns the size in bytes of the integer type. + + >>> assert sizeof(u64) == 8 + + :param ctx: The context object. (must not be null) + :return: The size in bytes of the integer type. + + .. py:attribute:: nbytes + :type: int + + The number of bytes in the integer, calculated from :attr:`nbits`. + + .. py:attribute:: nbits + :type: int + + The number of bits in the integer. + + >>> assert u32.nbits == 32 + + .. py:attribute:: signed + :type: bool + + Indicates whether the integer is signed (True) or unsigned (False). + + >>> assert i8.signed is True + + .. py:attribute:: little_endian + :type: bool + + Indicates whether the integer is stored in little-endian byte order (True) or big-endian byte order (False). + + >>> assert Int(32, little_endian=False).little_endian is False + >>> assert (BIG_ENDIAN + u32).little_endian is False + + +Pre-Defined Integer Types (global) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. raw:: html + +
+ +The following pre-defined integer types are globally available, optimized for +common use cases. By default, they use little-endian byte order. To switch to +big-endian, use the byteorder operation (e.g., :code:`BIG_ENDIAN + u16`). + +Unsigned Integer Types: +~~~~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: caterpillar.c.u8 + + Unsigned 8-bit integer type. + +.. py:data:: caterpillar.c.u16 + + Unsigned 16-bit integer type. + +.. py:data:: caterpillar.c.u24 + + Unsigned 24-bit integer type. + +.. py:data:: caterpillar.c.u32 + + Unsigned 32-bit integer type. + +.. py:data:: caterpillar.c.u64 + + Unsigned 64-bit integer type. + +.. py:data:: caterpillar.c.u128 + + Unsigned 128-bit integer type. + + +Signed Integer Types: +~~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: caterpillar.c.i8 + + Signed 8-bit integer type. + +.. py:data:: caterpillar.c.i16 + + Signed 16-bit integer type. + +.. py:data:: caterpillar.c.i24 + + Signed 24-bit integer type. + +.. py:data:: caterpillar.c.i32 + + Signed 32-bit integer type. + +.. py:data:: caterpillar.c.i64 + + Signed 64-bit integer type. + +.. py:data:: caterpillar.c.i128 + + Signed 128-bit integer type. diff --git a/docs/sphinx/source/library/fields/common.rst b/docs/sphinx/source/library/fields/common.rst index 6a4c45b..7642d40 100644 --- a/docs/sphinx/source/library/fields/common.rst +++ b/docs/sphinx/source/library/fields/common.rst @@ -7,93 +7,93 @@ Common Structs Numeric Structs --------------- -.. autoclass:: caterpillar.fields.FormatField +.. autoclass:: caterpillar.py.FormatField :members: -.. autoattribute:: caterpillar.fields.uint8 +.. autoattribute:: caterpillar.py.uint8 -.. autoattribute:: caterpillar.fields.int8 +.. autoattribute:: caterpillar.py.int8 -.. autoattribute:: caterpillar.fields.uint16 +.. autoattribute:: caterpillar.py.uint16 -.. autoattribute:: caterpillar.fields.int16 +.. autoattribute:: caterpillar.py.int16 -.. autoattribute:: caterpillar.fields.uint32 +.. autoattribute:: caterpillar.py.uint32 -.. autoattribute:: caterpillar.fields.int32 +.. autoattribute:: caterpillar.py.int32 -.. autoattribute:: caterpillar.fields.uint64 +.. autoattribute:: caterpillar.py.uint64 -.. autoattribute:: caterpillar.fields.int64 +.. autoattribute:: caterpillar.py.int64 -.. autoattribute:: caterpillar.fields.size_t +.. autoattribute:: caterpillar.py.size_t -.. autoattribute:: caterpillar.fields.ssize_t +.. autoattribute:: caterpillar.py.ssize_t -.. autoattribute:: caterpillar.fields.float16 +.. autoattribute:: caterpillar.py.float16 -.. autoattribute:: caterpillar.fields.float32 +.. autoattribute:: caterpillar.py.float32 -.. autoattribute:: caterpillar.fields.float64 +.. autoattribute:: caterpillar.py.float64 -.. autoattribute:: caterpillar.fields.void_ptr +.. autoattribute:: caterpillar.py.void_ptr -.. autoattribute:: caterpillar.fields.char +.. autoattribute:: caterpillar.py.char -.. autoattribute:: caterpillar.fields.boolean +.. autoattribute:: caterpillar.py.boolean -.. autoattribute:: caterpillar.fields.padding +.. autoattribute:: caterpillar.py.padding -.. autoclass:: caterpillar.fields.Int +.. autoclass:: caterpillar.py.Int :members: -.. autoclass:: caterpillar.fields.UInt +.. autoclass:: caterpillar.py.UInt :members: -.. autoattribute:: caterpillar.fields.vint +.. autoattribute:: caterpillar.py.vint -.. autoclass:: caterpillar.fields.VarInt +.. autoclass:: caterpillar.py.VarInt :members: Bytes, Strings -------------- -.. autoclass:: caterpillar.fields.Memory +.. autoclass:: caterpillar.py.Memory :members: -.. autoclass:: caterpillar.fields.Bytes +.. autoclass:: caterpillar.py.Bytes :members: -.. autoclass:: caterpillar.fields.String +.. autoclass:: caterpillar.py.String :members: -.. autoclass:: caterpillar.fields.Prefixed +.. autoclass:: caterpillar.py.Prefixed :members: -.. autoclass:: caterpillar.fields.CString +.. autoclass:: caterpillar.py.CString :members: -.. autoclass:: caterpillar.fields.ConstString +.. autoclass:: caterpillar.py.ConstString :members: -.. autoclass:: caterpillar.fields.ConstBytes +.. autoclass:: caterpillar.py.ConstBytes :members: Special Structs --------------- -.. autoattribute:: caterpillar.fields.Pass +.. autoattribute:: caterpillar.py.Pass -.. autoclass:: caterpillar.fields.Computed +.. autoclass:: caterpillar.py.Computed :members: -.. autoclass:: caterpillar.fields.Transformer +.. autoclass:: caterpillar.py.Transformer :members: -.. autoclass:: caterpillar.fields.Enum +.. autoclass:: caterpillar.py.Enum :members: -.. autoclass:: caterpillar.fields.Const +.. autoclass:: caterpillar.py.Const :members: \ No newline at end of file diff --git a/docs/sphinx/source/library/fields/compression.rst b/docs/sphinx/source/library/fields/compression.rst index 6fbbaf4..cf7296a 100644 --- a/docs/sphinx/source/library/fields/compression.rst +++ b/docs/sphinx/source/library/fields/compression.rst @@ -7,16 +7,16 @@ Compression Structs Basic structs ------------- -.. autoclass:: caterpillar.fields.Compressed +.. autoclass:: caterpillar.py.Compressed :members: Supported compression types --------------------------- -.. autofunction:: caterpillar.fields.ZLibCompressed +.. autofunction:: caterpillar.py.ZLibCompressed -.. autofunction:: caterpillar.fields.Bz2Compressed +.. autofunction:: caterpillar.py.Bz2Compressed -.. autofunction:: caterpillar.fields.LZMACompressed +.. autofunction:: caterpillar.py.LZMACompressed -.. autofunction:: caterpillar.fields.LZOCompressed +.. autofunction:: caterpillar.py.LZOCompressed diff --git a/docs/sphinx/source/library/fields/field_model.rst b/docs/sphinx/source/library/fields/field_model.rst index f0b3c68..e737cc0 100644 --- a/docs/sphinx/source/library/fields/field_model.rst +++ b/docs/sphinx/source/library/fields/field_model.rst @@ -4,24 +4,24 @@ Field Model *********** -.. autoclass:: caterpillar.fields.Field() +.. autoclass:: caterpillar.py.Field() :members: :private-members: :no-undoc-members: -.. autoclass:: caterpillar.fields.FieldMixin +.. autoclass:: caterpillar.py.FieldMixin :special-members: -.. autoclass:: caterpillar.fields.FieldStruct +.. autoclass:: caterpillar.py.FieldStruct :members: :special-members: -.. autoclass:: caterpillar.fields.Chain +.. autoclass:: caterpillar.py.Chain :members: :special-members: -.. autoclass:: caterpillar.fields.If +.. autoclass:: caterpillar.py.If :members: -.. autoclass:: caterpillar.fields.ElseIf +.. autoclass:: caterpillar.py.ElseIf :members: \ No newline at end of file diff --git a/docs/sphinx/source/library/index.rst b/docs/sphinx/source/library/index.rst index 7e1c14c..cada89b 100644 --- a/docs/sphinx/source/library/index.rst +++ b/docs/sphinx/source/library/index.rst @@ -8,25 +8,19 @@ Library .. toctree:: :maxdepth: 2 - :caption: Basic Core classes + :caption: Python API byteorder.rst options.rst abc.rst - - -.. toctree:: - :maxdepth: 2 - :caption: Context and Exceptions - context.rst exceptions.rst + model.rst + fields/index.rst .. toctree:: :maxdepth: 2 - :caption: Struct model - - model.rst - fields/index.rst + :caption: C API Python Types + ctypes/int.rst diff --git a/docs/sphinx/source/reference/capi/objects/atoms/boolatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/bool.rst similarity index 100% rename from docs/sphinx/source/reference/capi/objects/atoms/boolatom.rst rename to docs/sphinx/source/reference/capi/objects/atoms/bool.rst diff --git a/docs/sphinx/source/reference/capi/objects/atoms/charatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/char.rst similarity index 100% rename from docs/sphinx/source/reference/capi/objects/atoms/charatom.rst rename to docs/sphinx/source/reference/capi/objects/atoms/char.rst diff --git a/docs/sphinx/source/reference/capi/objects/atoms/fieldatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/fieldatom.rst deleted file mode 100644 index 1f1fdfe..0000000 --- a/docs/sphinx/source/reference/capi/objects/atoms/fieldatom.rst +++ /dev/null @@ -1,108 +0,0 @@ -.. _reference-capi_fieldatom: - -****************** -Field Atom Objects -****************** - -Python Field Atoms ------------------- - -.. c:var:: PyTypeObject CpFieldAtom_Type - - The type object for the :c:type:`CpFieldAtomObject` class. - - -.. c:type:: CpFieldAtomObject - - C implementation of the Python equivalent (:class:`~caterpillar.fields.FieldMixin`). - - A simple mixin to support operators used to create `Field` instances. - - -.. c:function:: int CpFieldAtom_CheckExact(PyObject *op) - - Checks if the given object is an :c:type:`CpFieldAtomObject`. - - -.. c:function:: int CpFieldAtom_Check(PyObject *op) - - Checks if the given object is instance of an :c:type:`CpFieldAtomObject` - - -.. c:macro:: CpFieldAtom_HEAD - - Inspired by :code:`PyObject_HEAD`, places the :c:type:`CpFieldAtomObject` at the - beginning of the object's memory block. For instance the following code: - - .. code-block:: c - - struct FooAtom { - CpFieldAtom_HEAD - - // other fields - }; - - extern PyTypeObject FooAtom_Type; - - The defined class is now a subclass of :c:type:`CpFieldAtomObject` and inherits - all methods. Make sure you don't forget to initialize the type correctly in your - module code: - - .. code-block:: c - - // ... - FooAtom_Type.tp_base = &CpFieldAtom_Type; - CpModule_SetupType(&FooAtom_Type); - // ... - - -C Field Atoms -------------- - -.. c:var:: PyTypeObject CpFieldCAtom_Type - - The type object for the :c:type:`CpFieldCAtomObject` class. - - -.. c:type:: CpFieldCAtomObject - - Field atom for C fields. Inherits from :c:type:`CpCAtomObject`. - - -.. c:function:: int CpFieldCAtom_CheckExact(PyObject *op) - - Checks if the given object is an :c:type:`CpFieldCAtomObject`. - - -.. c:function:: int CpFieldCAtom_Check(PyObject *op) - - Checks if the given object is instance of an :c:type:`CpFieldCAtomObject` - - -.. c:macro:: CpFieldCAtom_HEAD - - Inspired by :code:`PyObject_HEAD`, places the :c:type:`CpFieldCAtomObject` at the - beginning of the object's memory block. - - -.. c:macro:: CpFieldCAtom_CATOM(self) - - Returns the :c:type:`CpCAtomObject` from the given object which must be a - :c:type:`CpFieldCAtomObject`. This macro can be used to set the packing and - unpacking implementations: - - .. code-block:: c - - static PyObject* - fooatom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) - { - FooAtom *self = (FooAtom *)type->tp_alloc(type, 0); - if (self != NULL) { - // initialize the fields - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)fooatom_pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)fooatom_unpack; - // ... - } - return (PyObject*)self; - } - diff --git a/docs/sphinx/source/reference/capi/objects/atoms/floatatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/float.rst similarity index 85% rename from docs/sphinx/source/reference/capi/objects/atoms/floatatom.rst rename to docs/sphinx/source/reference/capi/objects/atoms/float.rst index 02cb855..31bcfaa 100644 --- a/docs/sphinx/source/reference/capi/objects/atoms/floatatom.rst +++ b/docs/sphinx/source/reference/capi/objects/atoms/float.rst @@ -8,7 +8,7 @@ Float Atoms .. c:var:: PyTypeObject CpFloatAtom_Type - The type object for the :c:type:`floatatom` class. + The type object for the :c:type:`CpFloatAtomObject` class. .. c:type:: CpFloatAtomObject @@ -20,6 +20,11 @@ Float Atoms .. c:function:: PyObject* CpFloatAtom_Unpack(CpFloatAtomObject* self, CpLayerObject* layer) +.. c:function:: int CpFloatAtom_Check(PyObject *op) + + +.. c:function:: int CpFloatAtom_CheckExact(PyObject *op) + Runtime Performance ------------------- diff --git a/docs/sphinx/source/reference/capi/objects/atoms/index.rst b/docs/sphinx/source/reference/capi/objects/atoms/index.rst index d2d1d36..38fe60b 100644 --- a/docs/sphinx/source/reference/capi/objects/atoms/index.rst +++ b/docs/sphinx/source/reference/capi/objects/atoms/index.rst @@ -7,9 +7,8 @@ Atom Objects .. toctree:: :maxdepth: 1 - fieldatom.rst - intatom.rst - charatom.rst - floatatom.rst - boolatom.rst - paddingatom.rst + int + char + float + bool + padding diff --git a/docs/sphinx/source/reference/capi/objects/atoms/int.rst b/docs/sphinx/source/reference/capi/objects/atoms/int.rst new file mode 100644 index 0000000..94423d0 --- /dev/null +++ b/docs/sphinx/source/reference/capi/objects/atoms/int.rst @@ -0,0 +1,108 @@ +.. _reference-capi_intatom: + +************* +Integer Atoms +************* + +The :c:type:`CpIntAtomObject` is a specialized implementation for handling integers, +providing efficient mechanisms to pack and unpack sized integers. Unlike its Python +counterpart (:class:`FormatField`), this C-based class focuses solely on integer operations. + + +.. c:var:: PyTypeObject CpIntAtom_Type + + The type object for the :class:`~caterpillar.c.Int` class. + +This implementation utilizes :code:`int.from_bytes` and :code:`int.to_bytes`. Direct C +calls are optimized, reducing runtime overhead compared to Python. + +.. c:type:: CpIntAtomObject + + A fundamental class for managing fixed-size integers. Further recommendations for optimal + use are detailed below. + + .. c:var:: PyObject * m_byte_count + + Represents the number of bytes in the integer. This can be accessed in Python using the attribute :code:`nbytes`. + + .. c:var:: int _m_signed + + Indicates whether the integer is signed. A value of ``1`` means signed, and ``0`` means unsigned. + + .. c:var:: int _m_little_endian + + Indicates the endianness of the integer. A value of ``1`` signifies little-endian, while ``0`` signifies big-endian. + + +.. c:function:: int CpIntAtom_Pack(CpIntAtomObject* self, PyObject* value, CpLayerObject* layer) + + Packs the given value into the given layer. Returns ``-1`` if an error occurs. + + +.. c:function:: PyObject* CpIntAtom_Unpack(CpIntAtomObject* self, CpLayerObject* layer) + + Unpacks the value from the given layer. Returns *NULL* if an error occurs. + + +.. c:function:: int CpIntAtom_Check(PyObject *op) + + Checks if the given object is an :c:type:`CpIntAtomObject` + + +.. c:function:: int CpIntAtom_CheckExact(PyObject *op) + + Checks if the given object is an instance of an :c:type:`CpIntAtomObject` + + +.. rubric:: Runtime Performance + +Measurements represent the accumulated runtime of one million calls to +:code:`unpack` or :code:`pack` using the corresponding implementation +in seconds. + +.. list-table:: + :header-rows: 1 + :widths: 20 20 20 20 20 + + * - Function + - Caterpillar [0]_ + - Caterpillar G [1]_ + - Caterpillar C [2]_ + - Construct [3]_ + * - :code:`unpack` + - 4.002179 + - 2.782663 + - 0.815902 + - 1.581962 + * - :code:`pack` + - 3.866999 + - 2.707753 + - 0.926041 + - 1.587046 + + +.. [0] local field instance +.. [1] global field instance +.. [2] C implementation +.. [3] Construct :code:`Int32sn` + +The *benchmark* has been performed using the following code snippets: + +.. code-block:: python + :linenos: + + from caterpillar import _C, model, fields + from construct import Int32sn + + # Caterpillar + model.unpack(fields.Field(fields.int32), b"\x00\xFF\x00\xFF") + + # Caterpillar (Global) + I32_G = fields.Field(fields.int32) + model.unpack(I32_G, b"\x00\xFF\x00\xFF") + + # Caterpillar (C) + _C.unpack(b"\x00\xFF\x00\xFF", _C.i32) + + # Construct + Int32sn.parse(b"\x00\xFF\x00\xFF") diff --git a/docs/sphinx/source/reference/capi/objects/atoms/intatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/intatom.rst deleted file mode 100644 index 5398106..0000000 --- a/docs/sphinx/source/reference/capi/objects/atoms/intatom.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. _reference-capi_intatom: - -************* -Integer Atoms -************* - -The :c:type:`CpIntAtomObject` is a specialized implementation for handling integers, -providing efficient mechanisms to pack and unpack sized integers. Unlike its Python -counterpart (:class:`FormatField`), this C-based class focuses solely on integer operations. - - -.. c:var:: PyTypeObject CpIntAtom_Type - - The type object for the :c:type:`intatom` class. - -This implementation utilizes :code:`int.from_bytes` and :code:`int.to_bytes`. Direct C -calls are optimized, reducing runtime overhead compared to Python. - -.. c:type:: CpIntAtomObject - - A fundamental class for managing fixed-size integers. Further recommendations for optimal - use are detailed below. - - .. c:var:: PyObject * m_byte_count - - Represents the number of bytes in the integer. This can be accessed in Python using the attribute :code:`nbytes`. - - .. c:var:: int _m_signed - - Indicates whether the integer is signed. A value of ``1`` means signed, and ``0`` means unsigned. - - .. c:var:: int _m_little_endian - - Indicates the endianness of the integer. A value of ``1`` signifies little-endian, while ``0`` signifies big-endian. - - -Recommendations ---------------- - -The following examples illustrate how to effectively utilize the :c:type:`intatom` class -and the associated methods: - -.. code-block:: python - :linenos: - - from caterpillar._C import LITTLE_ENDIAN as le - from caterpillar._C import intatom, unpack, i16 - - # Define a global 16-bit little-endian signed integer atom - I16_LE = le + i16 - _I16_LE = intatom(16, signed=True, little_endian=True) - - unpack(b"\x01\x02", _I16_LE) - unpack(b"\x01\x02", intatom(16, signed=True, little_endian=True)) - unpack(b"\x01\x02", I16_LE) - unpack(b"\x01\x02", le + i16) - -*TODO: describe the impact on runtime overhead of these four methods* - diff --git a/docs/sphinx/source/reference/capi/objects/atoms/paddingatom.rst b/docs/sphinx/source/reference/capi/objects/atoms/padding.rst similarity index 100% rename from docs/sphinx/source/reference/capi/objects/atoms/paddingatom.rst rename to docs/sphinx/source/reference/capi/objects/atoms/padding.rst diff --git a/docs/sphinx/source/reference/capi/objects/contextobj.rst b/docs/sphinx/source/reference/capi/objects/contextobj.rst index 20cc060..897b7d7 100644 --- a/docs/sphinx/source/reference/capi/objects/contextobj.rst +++ b/docs/sphinx/source/reference/capi/objects/contextobj.rst @@ -26,17 +26,6 @@ Context Objects Checks if the given object is instance of an :c:type:`CpContextObject`. -.. c:function:: PyObject* CpContext_GetAttr(PyObject* context, PyObject* key, _coremodulestate* state) - - Get an attribute from a context by using :code:`__context_getattr__`. Returns - *NULL* if the attribute is not found. - - -.. c:function:: PyObject *CpContext_GetAttrString(PyObject* context, const char* key) - - Get an attribute from a context by using :code:`__context_getattr__`. Returns - *NULL* if the attribute is not found. - .. c:function:: CpContextObject *CpContext_New(void) Creates a new context and returns it. Returns *NULL* if an error occurs. diff --git a/docs/sphinx/source/reference/capi/objects/fieldobj.rst b/docs/sphinx/source/reference/capi/objects/fieldobj.rst deleted file mode 100644 index a4b8df9..0000000 --- a/docs/sphinx/source/reference/capi/objects/fieldobj.rst +++ /dev/null @@ -1,59 +0,0 @@ -.. _reference-capi_field: - -************* -Field Objects -************* - -.. c:var:: PyTypeObject CpField_Type - - The type object for the :c:type:`CpFieldObject` class. - - -.. c:type:: CpFieldObject - - C implementation of the Python equivalent. Represents a field in a data structure. - - -.. c:function:: int CpField_CheckExact(PyObject *op) - - Checks if the given object is an :c:type:`CpFieldObject`. - - -.. c:function:: int CpField_Check(PyObject *op) - - Checks if the given object is instance of an :c:type:`CpFieldObject` - - -.. c:function:: CpFieldObject *CpField_New(PyObject *atom) - - Creates a new field and returns it. Returns *NULL* if an error occurs. - - -.. c:function:: int CpField_HasCondition(CpFieldObject *field) - - Returns whether the field has a condition. This function always succeeds. - - -.. c:function:: int CpField_IsEnabled(CpFieldObject *field, PyObject *context) - - Returns whether the field is enabled. The context object must conform to - the context protocol.Returns :code:`1` if the field is enabled, :code:`0` - otherwise and returns :code:`-1` if an error occurs. - - -.. c:function:: Py_ssize_t CpField_GetOffset(CpFieldObject *field, PyObject *context) - - Returns the offset of the field. The context object must conform to the - context protocol. Returns :code:`-1` if an error occurs. - - -.. c:function:: PyObject *CpField_EvalSwitch(CpFieldObject *field, PyObject *value, PyObject *context) - - Evaluates a switch field (if configured to do so). The context object must conform to the - context protocol. Returns *NULL* if an error occurs. - - -.. c:function:: PyObject *CpField_GetLength(CpFieldObject *field, PyObject *context) - - Returns the length of the field. The context object must conform to the - context protocol. Returns *NULL* if an error occurs. \ No newline at end of file diff --git a/docs/sphinx/source/reference/capi/objects/objimpl.rst b/docs/sphinx/source/reference/capi/objects/objimpl.rst index f9edabf..45d1de0 100644 --- a/docs/sphinx/source/reference/capi/objects/objimpl.rst +++ b/docs/sphinx/source/reference/capi/objects/objimpl.rst @@ -14,6 +14,5 @@ Concrete Objects Layer contextpath state layer - fieldobj struct atoms/index.rst \ No newline at end of file diff --git a/docs/sphinx/source/reference/capi/objects/state.rst b/docs/sphinx/source/reference/capi/objects/state.rst index 755978d..e8220b0 100644 --- a/docs/sphinx/source/reference/capi/objects/state.rst +++ b/docs/sphinx/source/reference/capi/objects/state.rst @@ -41,7 +41,7 @@ State Objects Returns the current position of the state. Returns *NULL* if an error occurs. -.. c:function:: PyObject *CpState_Seek(CpStateObject *state, Py_ssize_t pos, int whence) +.. c:function:: PyObject *CpState_Seek(CpStateObject *state, Py_ssize_t pos, PyObject whence) Seeks to the given position. Returns :code:`0` if successful. Returns :code:`-1` if an error occurs. diff --git a/docs/sphinx/source/reference/capi/parsing.rst b/docs/sphinx/source/reference/capi/parsing.rst index 131666a..04a4271 100644 --- a/docs/sphinx/source/reference/capi/parsing.rst +++ b/docs/sphinx/source/reference/capi/parsing.rst @@ -11,64 +11,20 @@ Packing and Unpacking Returns the type of the given atom object. + .. c:function:: PyObject *CpSizeOf(PyObject *atom, PyObject *globals) - PyObject *CpSizeOf_Field(CpFieldObject *, CpLayerObject *) - PyObject *CpSizeOf_Struct(CpStructObject *, CpLayerObject *) - PyObject *CpSizeOf_Common(PyObject *, CpLayerObject *) Returns the size of the given atom object. + .. c:function:: int CpPack(PyObject *op, PyObject *atom, PyObject *io, PyObject *globals) Packs the given value using the given atom object and returns :code::`0` if successful. Returns :code:`-1` if an error occurs. The *globals* parameter may be *NULL*. - Serializing objects follows specific rules when it comes to CpLayerObject - creation. The first layer (root layer) will be assigned with the path - ''. By default, cp_pack will automatically create a new layer object - when called. Therefore, it is recommended to utilize cp_pack_internal for - C calls to avoid unnecessary layer creation. - - Currently, the following conditions allow the creation of a new layer: - - - Start packing an object - - Serializing a sequence object that requires to iterate over all elements individually. Note that atoms that support :code:`__unpack_many__` won't be called with the newly created sequence layer. - - Struct object or named sequences of fields in general will always introduce a new layer object. - - There are some built-in types that are designed to fasten calls by providing - a native C implementation. It is recommended to implement new classes in C - to leverage its speed rather than define new Python atom classes. - - A full workflow of cp_pack with all layers displayed would be the following: - .. code-block:: text - - 1. cp_pack: create new state and root layer | l: - 2. cp_pack_internal: measure atom type and call | <--+-+-+ - according C method | l: | | | - 3. One of the following methods will be called: | | | - a. cp_pack_catom: calls native C method | l: | | | - b. cp_pack_atom: call python pack method | l: | | | - c. cp_pack_field: inserts the field instance into | | | - the current layer and calls cp_pack_internal | | | - again. | l: -+ | | - d. cp_pack_struct: creates a new object layer | | - that addresses only the current struct's | | - values. Calls cp_pack_internal on each field | l: ---+ | - e. cp_pack_common: createsa a new layer if | - multiple objects should be packed AND | - __[un]pack_many__ is not implemented. | l: -+ - - The following functions are defined and called within the packing process: - - .. c:function:: int CpPack_Field(PyObject *, CpFieldObject *, CpLayerObject *) - int CpPack_Struct(PyObject *, CpStructObject *, CpLayerObject *) - int CpPack_CAtom(PyObject *, CpCAtomObject *, CpLayerObject *) - int CpPack_Common(PyObject *, PyObject *, CpLayerObject *) - int _Cp_Pack(PyObject*, PyObject*, CpLayerObject* ) - - .. c:function:: int _CpPack_EvalLength(CpLayerObject* layer, PyObject* length, Py_ssize_t size, bool* seq_greedy, Py_ssize_t* seq_length) +.. c:function:: int _CpPack_EvalLength(CpLayerObject* layer, PyObject* length, Py_ssize_t size, bool* seq_greedy, Py_ssize_t* seq_length) Evaluates the length of the sequence to pack. @@ -83,16 +39,8 @@ Packing and Unpacking :param seq_length: destination pointer to store the length of the sequence :return: :code:`0` if successful, :code:`-1` if an error occurs -.. c:function:: PyObject *CpUnpack(PyObject *atom, PyObject *io, PyObject *globals) - Unlike serializing objects, unpacking returns fully qualified objects as a - result. In addition, the internal 'obj' within a struct layer will be a - :c:type:`CpContextObject` instance. +.. c:function:: PyObject *CpUnpack(PyObject *atom, PyObject *io, PyObject *globals) - The following functions are defined and called within the unpacking process: + *TODO* - .. c:function:: PyObject *_Cp_Unpack(PyObject*, CpLayerObject*) - PyObject *CpUnpack_Field(CpFieldObject*, CpLayerObject*) - PyObject *CpUnpack_Struct(CpStructObject*, CpLayerObject*) - PyObject *CpUnpack_CAtom(CpCAtomObject*, CpLayerObject*) - PyObject *CpUnpack_Common(PyObject *, CpLayerObject*) diff --git a/docs/sphinx/source/reference/index.rst b/docs/sphinx/source/reference/index.rst index 0b9875a..5fdf64a 100644 --- a/docs/sphinx/source/reference/index.rst +++ b/docs/sphinx/source/reference/index.rst @@ -23,14 +23,15 @@ documentation. If you want to switch to the C API instead, please refer to the sections described in the CAPI reference. .. toctree:: - :maxdepth: 3 + :maxdepth: 2 :numbered: :caption: C API Reference + capi/extension capi/abstract capi/objects/objimpl capi/parsing capi/macros capi/module - capi/extension + diff --git a/docs/sphinx/source/tutorial/basics.rst b/docs/sphinx/source/tutorial/basics.rst index 82b1511..d7b5cd6 100644 --- a/docs/sphinx/source/tutorial/basics.rst +++ b/docs/sphinx/source/tutorial/basics.rst @@ -27,24 +27,52 @@ When dealing with binary data, numbers play a crucial role. Besides the default (e.g., uint8, uint16, etc.), *caterpillar* introduces some special integer formats. The default types include: -* Unsigned (:code:`u...`) and signed: - :attr:`~caterpillar.fields.int8`, :attr:`~caterpillar.fields.uint8`, - :attr:`~caterpillar.fields.int16`, :attr:`~caterpillar.fields.uint16`, - :attr:`~caterpillar.fields.int24`, :attr:`~caterpillar.fields.uint24`, - :attr:`~caterpillar.fields.int32`, :attr:`~caterpillar.fields.uint32`, - :attr:`~caterpillar.fields.int64`, :attr:`~caterpillar.fields.uint64`, - :attr:`~caterpillar.fields.ssize_t`, :attr:`~caterpillar.fields.size_t`, +.. tab-set:: -* Floating point: + .. tab-item:: Python - :attr:`~caterpillar.fields.float16`, :attr:`~caterpillar.fields.float32`, - :attr:`~caterpillar.fields.float64` + * Unsigned (:code:`u...`) and signed: + + :attr:`~caterpillar.fields.int8`, :attr:`~caterpillar.fields.uint8`, + :attr:`~caterpillar.fields.int16`, :attr:`~caterpillar.fields.uint16`, + :attr:`~caterpillar.fields.int24`, :attr:`~caterpillar.fields.uint24`, + :attr:`~caterpillar.fields.int32`, :attr:`~caterpillar.fields.uint32`, + :attr:`~caterpillar.fields.int64`, :attr:`~caterpillar.fields.uint64`, + :attr:`~caterpillar.fields.ssize_t`, :attr:`~caterpillar.fields.size_t`, + + * Floating point: + + :attr:`~caterpillar.fields.float16`, :attr:`~caterpillar.fields.float32`, + :attr:`~caterpillar.fields.float64` + + * Special primitives: + + :attr:`~caterpillar.fields.boolean`, :attr:`~caterpillar.fields.char`, + :attr:`~caterpillar.fields.void_ptr` + + + .. tab-item:: Caterpillar C + + * Unsigned (:code:`u...`) and signed: + + :attr:`~caterpillar.c.i8`, :attr:`~caterpillar.c.u8`, + :attr:`~caterpillar.c.i16`, :attr:`~caterpillar.c.u16`, + :attr:`~caterpillar.c.i24`, :attr:`~caterpillar.c.u24`, + :attr:`~caterpillar.c.i32`, :attr:`~caterpillar.c.u32`, + :attr:`~caterpillar.c.i64`, :attr:`~caterpillar.c.u64`, + :attr:`~caterpillar.c.i128`, :attr:`~caterpillar.c.u128`, + + * Floating point: + + :attr:`~caterpillar.c.f16`, :attr:`~caterpillar.c.f32`, + :attr:`~caterpillar.c.f64` + + * Special primitives: + + :attr:`~caterpillar.c.boolean`, :attr:`~caterpillar.c.char` -* Special primitives: - :attr:`~caterpillar.fields.boolean`, :attr:`~caterpillar.fields.char`, - :attr:`~caterpillar.fields.void_ptr` Custom-sized integer ~~~~~~~~~~~~~~~~~~~~ @@ -53,18 +81,38 @@ It's also possible to use integers with a custom size (in bits). However, it's i that you have to define the struct with the bit count, and internally, only the occupied bytes will be used. For example: ->>> field = F(Int(24)) # three-byte signed integer ->>> field = F(UInt(40)) # five-byte unsigned integer +.. tab-set:: + + .. tab-item:: Python + + >>> field = F(Int(24)) # three-byte signed integer + >>> field = F(UInt(40)) # five-byte unsigned integer + + + .. tab-item:: Caterpillar C + + >>> i48 = Int(48) # six-byte signed integer + >>> u40 = Int(40, signed=False) # five-byte unsigned integer Variable-sized integer ~~~~~~~~~~~~~~~~~~~~~~ -The built-in struct :class:`~caterpillar.fields.VarInt` supports parsing and building integers with variable length. Its +The built-in struct :class:`.py.VarInt`/:class:`.c.VarInt` supports parsing and building integers with variable length. Its documentation provides a detailed explanation of all different configurations. ->>> field = F(vint) # or F(VarInt()) +.. tab-set:: + + .. tab-item:: Python + + >>> field = F(vint) # or F(VarInt()) + + .. tab-item:: Caterpillar C + + >>> # use 'varint' directly or use VarInt() + >>> be_varint = BIG_ENDIAN + varint + >>> le_varint = VarInt(little_endian=True) Enumerations @@ -76,21 +124,43 @@ standard Python enumerations - classes extending :code:`enum.Enum` - with ease. Let's revisit `pHYS `_ chunk to add an enum to the last field. -.. code-block:: python - :caption: Simple enumeration in a struct definition +.. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :caption: Simple enumeration in a struct definition - import enum + import enum - class PHYSUnit(enum.IntEnum): # <-- the enum value doesn't have to be int - __struct__ = uint8 # <-- to make the code even more compact, use this - UNKNOWN = 0 - METRE = 1 + class PHYSUnit(enum.IntEnum): # <-- the enum value doesn't have to be int + __struct__ = uint8 # <-- to make the code even more compact, use this + UNKNOWN = 0 + METRE = 1 - @struct(order=BigEndian) # <-- same as before - class PHYSChunk: - pixels_per_unit_x: uint32 - pixels_per_unit_y: uint32 - unit: PHYSUnit # <-- now we have an auto-enumeration + @struct(order=BigEndian) # <-- same as before + class PHYSChunk: + pixels_per_unit_x: uint32 + pixels_per_unit_y: uint32 + unit: PHYSUnit # <-- now we have an auto-enumeration + + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Simple enumeration in a struct definition + + import enum + + class PHYSUnit(enum.IntEnum): # <-- the enum value doesn't have to be int + UNKNOWN = 0 + METRE = 1 + + @struct(endian=BIG_ENDIAN) # <-- same as before + class PHYSChunk: + pixels_per_unit_x: u32 + pixels_per_unit_y: u32 + unit: enumeration(u8, PHYSUnit) # <-- atom is required here .. important:: It's worth noting that a default value can be specified for the field as a fallback. If @@ -107,7 +177,19 @@ simplifies this with item access for defining arrays of static or dynamic size. We started with the `PLTE `_ chunk, which stores three-byte sequences. We can define an array of RGB objects as follows: ->>> PLTEChunk = RGB[this.length / 3] +.. tab-set:: + + .. tab-item:: Python + + >>> PLTEChunk = RGB[this.length / 3] + + .. tab-item:: Caterpillar C + + >>> PLTEChunk = RGB.__struct__[ContextPath("obj.length") / 3] + + .. versionadded:: 2.2.0 + The syntax will be changed once `__class_getitem__` is implemented by + any :class:`.c.Struct` instance. Since this chunk has only one field, the array specifier is used to make it a list type. The length is calculated based on the chunk's length field divided by three because the RGB class @@ -123,18 +205,50 @@ CString The CString in this library extends beyond a mere reference to C strings. It provides additional functionality, as demonstrated in the structure of the next chunk. -.. code-block:: python - :caption: The `tEXt `_ chunk structure - - @struct - class TEXTChunk: - # dynamic sized string that ends with a null-byte - keyword: CString(encoding="ISO-8859-1") - # static sized string based on the current context. some notes: - # - parent.length is the current chunkt's length - # - lenof(...) is the runtime length of the context variable - # - 1 because of the extra null-byte that is stripped from keyword - text: CString(encoding="ISO-8859-1", length=parent.length - lenof(this.keword) - 1) +.. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :caption: The `tEXt `_ chunk structure + + from caterpillar.py import * + from caterpillar.shortcuts import lenof + + @struct + class TEXTChunk: + # dynamic sized string that ends with a null-byte + keyword: CString(encoding="ISO-8859-1") + # static sized string based on the current context. some notes: + # - parent.length is the current chunkt's length + # - lenof(...) is the runtime length of the context variable + # - 1 because of the extra null-byte that is stripped from keyword + text: CString(encoding="ISO-8859-1", length=parent.length - lenof(this.keword) - 1) + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: The `tEXt` chunk structure + + from caterpillar.c import * # <-- main difference + from caterpillar.shortcuts import lenof + # NOTE: lenof works here, because Caterpillar C's Context implements + # the 'Context Protocol'. + + parent = ContextPath("parent.obj") + this = ContextPath("obj") + + @struct + class TEXTChunk: + # dynamic sized string that ends with a null-byte + keyword: cstring(encoding="ISO-8859-1") + # static sized string based on the current context. some notes: + # - parent.length is the current chunkt's length + # - lenof(...) is the runtime length of the context variable + # - 1 because of the extra null-byte that is stripped from keyword + text: cstring(encoding="ISO-8859-1", length=parent.length - lenof(this.keword) - 1) + + .. admonition:: Challenge @@ -145,25 +259,62 @@ additional functionality, as demonstrated in the structure of the next chunk. This solution serves as an example and isn't the only way to approach it! - .. code-block:: python - - @struct - class ITXTChunk: - keyword: CString(encoding="utf-8") - compression_flag: uint8 - # we actually don't need an Enum here - compression_method: uint8 - language_tag: CString(encoding="ASCII") - translated_keyword: CString(encoding="utf-8") - # length is calculated with parent.length - len(keyword)+len(b"\x00") - ... - text: CString( - encoding="utf-8", - length=parent.length - lenof(this.translated_keyword) - lenof(this.keyword) - 5, - ) + .. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :linenos: + + @struct + class ITXTChunk: + keyword: CString(encoding="utf-8") + compression_flag: uint8 + # we actually don't need an Enum here + compression_method: uint8 + language_tag: CString(encoding="ASCII") + translated_keyword: CString(encoding="utf-8") + # length is calculated with parent.length - len(keyword)+len(b"\x00") - ... + text: CString( + encoding="utf-8", + length=parent.length - lenof(this.translated_keyword) - lenof(this.keyword) - 5, + ) + + .. tab-item:: Caterpillar C + + .. code-block:: python + :linenos: + + from caterpillar.c import * # <-- main difference + from caterpillar.shortcuts import lenof + + parent = ContextPath("parent.obj") + this = ContextPath("obj") + + @struct + class ITXTChunk: + keyword: cstring() # default encoding is "utf-8" + compression_flag: u8 + # we actually don't need an Enum here + compression_method: u8 + language_tag: cstring(encoding="ASCII") + translated_keyword: cstring(...) # explicit greedy parsing + # length is calculated with parent.length - len(keyword)+len(b"\x00") - ... + text: cstring( + parent.length - lenof(this.translated_keyword) - lenof(this.keyword) - 5, + ) You can also apply your own termination character, for example: ->>> struct = CString(pad="\x0A") +.. tab-set:: + + .. tab-item:: Python + + >>> struct = CString(pad="\x0A") + + .. tab-item:: Caterpillar C + + >>> s = cstring(sep="\x0A") This struct will use a space as the termination character and strip all trailing padding bytes. @@ -174,7 +325,16 @@ String Besides special the special *c strings* there's a default :class:`~caterpillar.fields.String` class that implements the basic behaviour of a string. It's crucial to specify the length for this struct. ->>> struct = String(100 or this.length) # static integer or context lambda +.. tab-set:: + + .. tab-item:: Python + + >>> struct = String(100 or this.length) # static integer or context lambda + + .. tab-item:: Caterpillar C + + >>> # takes static length, context lambda, another atom or ... for greedy parsing + >>> s = cstring(100) Prefixed @@ -185,11 +345,23 @@ encoding is specified, the returned value will be of type :code:`bytes`. This cl using the given struct and then retrieves the corresponding number of bytes from the stream returned by that struct. ->>> field = F(Prefixed(uint8, encoding="utf-8")) ->>> pack("Hello, World!", field) -b'\rHello, World!' ->>> unpack(field, _) -'Hello, World!' +.. tab-set:: + + .. tab-item:: Python + + >>> field = F(Prefixed(uint8, encoding="utf-8")) + >>> pack("Hello, World!", field) + b'\rHello, World!' + >>> unpack(field, _) + 'Hello, World!' + + .. tab-item:: Caterpillar C + + >>> s = pstring(u8) + >>> pack("Hello, World!", s) + b'\rHello, World!' + >>> unpack(_, s) + 'Hello, World!' Byte Sequences @@ -201,11 +373,19 @@ Memory When dealing with data that can be stored in memory and you intend to print out your unpacked object, the :class:`~caterpillar.fields.Memory` struct is recommended. ->>> m = F(Memory(5)) # static size; dynamic size is allowed too ->>> pack(bytes([i for i in range(5)], m)) -b'\x00\x01\x02\x03\x04' ->>> unpack(m, _) - +.. tab-set:: + + .. tab-item:: Python + + >>> m = F(Memory(5)) # static size; dynamic size is allowed too + >>> pack(bytes([i for i in range(5)], m)) + b'\x00\x01\x02\x03\x04' + >>> unpack(m, _) + + + .. tab-item:: Caterpillar C + + *Not supported yet.* Bytes ~~~~~ @@ -214,20 +394,48 @@ If direct access to the bytes is what you need, the :class:`~caterpillar.fields. converts the :code:`memoryview` to :code:`bytes`. Additionally, as mentioned earlier, you can use the :class:`~caterpillar.fields.Prefixed` class to unpack bytes of a prefixed size. ->>> field = F(Bytes(5)) # static, dynamic and greedy size allowed + +.. tab-set:: + + .. tab-item:: Python + + >>> field = F(Bytes(5)) # static, dynamic and greedy size allowed + + + .. tab-item:: Caterpillar C + + >>> b = octetstring(5) # static, dynamic size allowed With the gained knowledge, let's implement the struct for the `fDAT `_ chunk of our PNG format. It should look like this: -.. code-block:: python - :caption: Implementation for the frame data chunk - @struct(order=BigEndian) # <-- endianess as usual - class FDATChunk: - sequence_number: uint32 - # We rather use a memory instance here instead of Bytes() - frame_data: Memory(parent.length - 4) +.. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :caption: Implementation for the frame data chunk + + @struct(order=BigEndian) # <-- endianess as usual + class FDATChunk: + sequence_number: uint32 + # We rather use a memory instance here instead of Bytes() + frame_data: Memory(parent.length - 4) + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Implementation for the frame data chunk + + parent = ContextPath("parent.obj") + + @struct(endian=BIG_ENDIAN) + class FDATChunk: + sequence_number: u32 + frame_data: octetstring(parent.length - 4) + .. admonition:: Challenge @@ -236,6 +444,8 @@ chunk of our PNG format. It should look like this: .. dropdown:: Solution + Python API only: + .. code-block:: python :caption: Sample implementation of the *zTXt* chunk @@ -272,14 +482,29 @@ reference in detail. The current object that is being packed or parsed can be referenced with a shortcut :attr:`~caterpillar.context.this`. Additionally, the parent object (if any) can be referenced by using :attr:`~caterpillar.context.parent`. +.. tab-set:: -.. code-block:: python - :caption: Understanding the *context* + .. tab-item:: Python + + .. code-block:: python + :caption: Understanding the *context* + + @struct + class Format: + length: uint8 + foo: CString(this.length) # <-- just reference the length field + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Understanding the *context* + + this = ContextPath("obj") - @struct - class Format: - length: uint8 - foo: CString(this.length) # <-- just reference the length field + @struct + class Format: + length: u8 + foo: cstring(this.length) .. note:: You can apply any operation on context paths. However, be aware that conditional branches must diff --git a/docs/sphinx/source/tutorial/first_steps.rst b/docs/sphinx/source/tutorial/first_steps.rst index dc071ca..12af577 100644 --- a/docs/sphinx/source/tutorial/first_steps.rst +++ b/docs/sphinx/source/tutorial/first_steps.rst @@ -31,19 +31,35 @@ Given the important role of structs in this library, let's start by understandin starting point is the `PLTE `_ chunk, which uses three-byte entries for its data. -.. code-block:: python - :caption: RGB struct for the PLTE chunk +.. tab-set:: - from caterpillar.model import struct # <-- these imports are important - from caterpillar.fields import * + .. tab-item:: Python - @struct # <-- just decorate the class with the struct() function - class RGB: - r: uint8 # <-- a field can be defined just like this - g: uint8 - b: uint8 + .. code-block:: python + :caption: RGB struct for the PLTE chunk + + from caterpillar.py import * # <-- just import everything + + @struct # <-- just decorate the class with the struct() function + class RGB: + r: uint8 # <-- a field can be defined just like this + g: uint8 + b: uint8 + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: RGB struct for the PLTE chunk (using Caterpillar C) + + from caterpillar.c import * # <-- just import everything + + @struct # <-- just decorate the class with the struct() function + class RGB: + r: u8 # <-- a field can be defined just like this + g: u8 + b: u8 + With this simple annotation, the struct class becomes universally applicable. You can integrate it into other struct definitions or instantiate objects of the class. @@ -51,7 +67,8 @@ integrate it into other struct definitions or instantiate objects of the class. .. note:: To optimize memory space and get faster attribute access times, you have to explicitly - enable the :attr:`~caterpillar.options.S_SLOTS` option. More information can be taken from :ref:`options`. + enable the :attr:`~caterpillar.options.S_SLOTS` option. More information can be taken from + :ref:`options`. Wow, thats it? That was less than expected? Let's move directly to working with the defined class. @@ -71,9 +88,20 @@ module. When packing data, a struct and an input object are needed. Thanks to the RGB class encapsulating its struct instance, explicitly stating the struct to use becomes unnecessary. ->>> obj = RGB(r=1, g=2, b=3) ->>> pack(obj) # equivalent to pack(obj, RGB) -b'\x01\x02\x03' +.. tab-set:: + + .. tab-item:: Python + + >>> obj = RGB(r=1, g=2, b=3) + >>> pack(obj) # equivalent to pack(obj, RGB) + b'\x01\x02\x03' + + .. tab-item:: Caterpillar C + + >>> obj = RGB(r=1, g=2, b=3) + >>> pack(obj, RGB) # required as of version 2.2.0 + b'\x01\x02\x03' + Unpacking data ^^^^^^^^^^^^^^ @@ -81,8 +109,17 @@ Unpacking data Recreating data from binary streams is as easy as serializing objects. Here, providing the struct directly or our struct class is necessary. ->>> unpack(RGB, b"\x01\x02\x03") -RGB(r=1, g=2, b=3) +.. tab-set:: + + .. tab-item:: Python + + >>> unpack(RGB, b"\x01\x02\x03") + RGB(r=1, g=2, b=3) + + .. tab-item:: Caterpillar C + + >>> unpack(b"\x01\x02\x03", RGB) + RGB(r=1, g=2, b=3) And no, we're not done yet - we've just wrapped up the warm-up! @@ -100,14 +137,36 @@ configure the struct to correctly decode these integer fields. `StackOverflow `_ or `Wikipedia `_ -.. code-block:: python - :caption: Configuring a struct-wide endianess - @struct(order=BigEndian) # <-- extra argument to apply the order to all fields. - class PHYSChunk: - pixels_per_unit_x: uint32 # <-- same definition as above - pixels_per_unit_y: uint32 - unit: uint8 # <-- endianess meaningless, only one byte +.. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :caption: Configuring a struct-wide endianess + + @struct(order=BigEndian) # <-- extra argument to apply the order to all fields. + class PHYSChunk: + pixels_per_unit_x: uint32 # <-- same definition as above + pixels_per_unit_y: uint32 + unit: uint8 # <-- endianess meaningless, only one byte + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Configuring a struct-wide endianess + + @struct(endian=BIG_ENDIAN) # <-- extra argument to apply the order to all fields. + class PHYSChunk: + pixels_per_unit_x: u32 # <-- same definition as above + pixels_per_unit_y: u32 + unit: u8 # <-- endianess meaningless, only one byte + + .. note:: + Even though, there will be :code:`` visible in the annotations + of the class, the created struct stores the modified big endian integer + atom. + If your structs depend on the architecture associated with the binary, you can also specify a struct-wide :class:`~caterpillar.byteorder.Arch`. @@ -120,17 +179,37 @@ struct-wide :class:`~caterpillar.byteorder.Arch`. .. dropdown:: Solution :icon: check - .. code-block:: python - :caption: Example implementation + Example implementation + + .. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :linenos: + + @struct(order=BigEndian) + class TIMEChunk: + year: uint16 # <-- we could also use: BigEndian + uint16 + month: uint8 + day: uint8 + hour: uint8 + minute: uint8 + second: uint8 + + .. tab-item:: Caterpillar C - @struct(order=BigEndian) - class TIMEChunk: - year: uint16 # <-- we could also use: BigEndian + uint16 - month: uint8 - day: uint8 - hour: uint8 - minute: uint8 - second: uint8 + .. code-block:: python + :linenos: + + @struct(endian=BIG_ENDIAN) + class TIMEChunk: + year: u16 # <-- we could also use: BIG_ENDIAN + u16 + month: u8 + day: u8 + hour: u8 + minute: u8 + second: u8 Note that we can integrate this struct later on. @@ -142,12 +221,26 @@ To minimize changes to your codebase or require as little adaptation as possible this library, there's a documentation feature. By utilizing the ability to globally apply options, you just need the following code: -.. code-block:: python - :caption: Enable documentation feature +.. tab-set:: + + .. tab-item:: Python + + .. code-block:: python + :caption: Enable documentation feature + + from caterpillar.shortcuts import opt + + opt.set_struct_flags(opt.S_REPLACE_TYPES) + + + .. tab-item:: Caterpillar C + + .. code-block:: python + :caption: Enable documentation feature - from caterpillar.shortcuts import opt + from caterpillar.c import * - opt.set_struct_flags(opt.S_REPLACE_TYPES) + STRUCT_OPTIONS.add(S_REPLACE_TYPES) .. tip:: If you are working with `Sphinx `_, you might need diff --git a/docs/sphinx/source/tutorial/index.rst b/docs/sphinx/source/tutorial/index.rst index 430552b..8d2a40d 100644 --- a/docs/sphinx/source/tutorial/index.rst +++ b/docs/sphinx/source/tutorial/index.rst @@ -22,10 +22,11 @@ For those ready to dig deeper or contribute to the framework, the :ref:`library- is an important resource to start from. .. toctree:: - :caption: Roadmap + :caption: Python :numbered: :maxdepth: 4 first_steps.rst basics.rst advanced.rst + diff --git a/examples/bitfield_example.py b/examples/bitfield_example.py index d9fc6b7..039feb0 100644 --- a/examples/bitfield_example.py +++ b/examples/bitfield_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import bitfield, char, int8, unpack, pack +from caterpillar.py import bitfield, char, int8, unpack, pack try: from rich import print diff --git a/examples/greedy_example.py b/examples/greedy_example.py index e194406..4b381cb 100644 --- a/examples/greedy_example.py +++ b/examples/greedy_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import struct, BigEndian, this, unpack, uint8 +from caterpillar.py import struct, BigEndian, this, unpack, uint8 try: from rich import print diff --git a/examples/inheritance_example.py b/examples/inheritance_example.py index 2715b59..0b6f2b2 100644 --- a/examples/inheritance_example.py +++ b/examples/inheritance_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import struct, pack, unpack, this, uint8, int32, Bytes +from caterpillar.py import struct, pack, unpack, this, uint8, int32, Bytes try: from rich import print diff --git a/examples/inner_struct.py b/examples/inner_struct.py index 9bd2993..270fe3d 100644 --- a/examples/inner_struct.py +++ b/examples/inner_struct.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import struct, pack, unpack, uint8 +from caterpillar.py import struct, pack, unpack, uint8 try: from rich import print diff --git a/examples/operator_example.py b/examples/operator_example.py index a031452..c999d8d 100644 --- a/examples/operator_example.py +++ b/examples/operator_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import struct, uint16, Operator, S_REPLACE_TYPES +from caterpillar.py import struct, uint16, Operator, S_REPLACE_TYPES # Here, we define a custom operator named 'M' that will multiply # the second argument by 2. diff --git a/examples/pointer_example.py b/examples/pointer_example.py index eef81df..f9d3237 100644 --- a/examples/pointer_example.py +++ b/examples/pointer_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import ( +from caterpillar.py import ( set_struct_flags, uintptr, CString, diff --git a/examples/template_example.py b/examples/template_example.py index 52e0c17..9fb0b49 100644 --- a/examples/template_example.py +++ b/examples/template_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import ( +from caterpillar.py import ( struct, set_struct_flags, S_REPLACE_TYPES, diff --git a/examples/union_example.py b/examples/union_example.py index 12ad58d..aa56c55 100644 --- a/examples/union_example.py +++ b/examples/union_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import uint16, uint32, boolean, union +from caterpillar.py import uint16, uint32, boolean, union try: from rich import print diff --git a/examples/varint_example.py b/examples/varint_example.py index 78b6796..d8ad9e8 100644 --- a/examples/varint_example.py +++ b/examples/varint_example.py @@ -1,5 +1,5 @@ # type: ignore -from caterpillar._Py import ( +from caterpillar.py import ( vint, VARINT_LSB, pack, diff --git a/pyproject.toml b/pyproject.toml index 97cde92..e024a7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,11 @@ build-backend = "scikit_build_core.build" build-dir = "build/{wheel_tag}" sdist.include = ["*.pyi", "*.h"] wheel.py-api = "cp312" +cmake.source-dir = "." [project] name = "caterpillar" -version = "2.1.5" +version = "2.2.0" description="Library to pack and unpack structurized binary data." authors = [ diff --git a/src/atomimpl/intatomobj.c b/src/atomimpl/intatomobj.c deleted file mode 100644 index 323ef49..0000000 --- a/src/atomimpl/intatomobj.c +++ /dev/null @@ -1,176 +0,0 @@ -/* intatom C implementation */ -#include - -#include "caterpillar/caterpillar.h" -#include "caterpillar/atoms/int.h" -#include "caterpillar/arch.h" -#include "caterpillar/state.h" -#include "caterpillar/module.h" -#include - -static PyObject* -cp_intatom__type__(CpIntAtomObject* self) -{ - return Py_XNewRef(&PyLong_Type); -} - -static PyObject * -cp_intatom__size__(CpIntAtomObject* self, PyObject *ctx) -{ - return Py_XNewRef(self->m_byte_count); -} - -static PyObject* -cp_intatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - CpIntAtomObject* self = (CpIntAtomObject*)type->tp_alloc(type, 0); - if (self != NULL) { - self->m_byte_count = NULL; - self->_m_bits = 0; - self->_m_signed = true; - self->_m_little_endian = true; - - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpIntAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpIntAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = NULL; - CpFieldCAtom_CATOM(self).ob_unpack_many = NULL; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_intatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_intatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; - } - return (PyObject*)self; -} - -static void -cp_intatom_dealloc(CpIntAtomObject* self) -{ - Py_XDECREF(self->m_byte_count); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -static int -cp_intatom_init(CpIntAtomObject* self, PyObject* args, PyObject* kwds) -{ - static char* kwlist[] = { "nbits", "signed", "little_endian", NULL }; - int _signed = true, little_endian = true; - size_t bits = 0; - if (!PyArg_ParseTupleAndKeywords( - args, kwds, "I|pp", kwlist, &bits, &_signed, &little_endian)) { - return -1; - } - if (bits == 0) { - PyErr_SetString(PyExc_ValueError, "bytes cannot be zero"); - return -1; - } - if (bits % 8) { - PyErr_SetString(PyExc_ValueError, "nbits must be a multiple of 8"); - return -1; - } - - self->_m_bits = bits; - self->_m_byte_count = bits / 8; - self->_m_signed = _signed; - self->_m_little_endian = little_endian; - self->m_byte_count = PyLong_FromUnsignedLong(self->_m_byte_count); - if (!self->m_byte_count) { - return -1; - } - return 0; -} - - - -/* Public API */ -int -CpIntAtom_Pack(CpIntAtomObject* self, PyObject* op, CpLayerObject* layer) -{ - PyObject* bytes = CpObject_CreateOneArg(&PyBytes_Type, self->m_byte_count); - if (!bytes) { - return -1; - } - - if (!PyLong_Check(op)) { - PyErr_Format(PyExc_TypeError, "op must be an int, got %R", op); - Py_DECREF(bytes); - } - - int little_endian = self->_m_little_endian; - if (layer->m_field) { - _modulestate *mod = layer->m_state->mod; - PyObject *endian = ((CpFieldObject *)layer->m_field)->m_endian; - - if (CpEndian_Check(endian)) - /* If the field has an endian specified, use that. */ - little_endian = CpEndian_IsLittleEndian((CpEndianObject *)endian, mod); - } - - int res = _PyLong_AsByteArray((PyLongObject*)op, - (unsigned char*)PyBytes_AS_STRING(op), - self->_m_byte_count, - little_endian, - self->_m_signed); - if (res == -1) { - return -1; - } - - PyObject* result = CpState_Write(layer->m_state, bytes); - if (!result) { - return -1; - } - Py_DECREF(result); - return 0; -} - -PyObject* -CpIntAtom_Unpack(CpIntAtomObject* self, CpLayerObject* layer) -{ - PyObject* bytes = CpState_Read(layer->m_state, self->_m_byte_count); - if (!bytes) { - return NULL; - } - - int little_endian = self->_m_little_endian; - if (layer->m_field) { - _modulestate *mod = layer->m_state->mod; - PyObject *endian = ((CpFieldObject *)layer->m_field)->m_endian; - - if (CpEndian_Check(endian)) - /* If the field has an endian specified, use that. */ - little_endian = CpEndian_IsLittleEndian((CpEndianObject *)endian, mod); - } - - PyObject* obj = - _PyLong_FromByteArray((unsigned char*)PyBytes_AS_STRING(bytes), - self->_m_byte_count, - little_endian, - self->_m_signed); - Py_DECREF(bytes); - return obj; -} - -/* docs */ - -/* type */ -static PyMemberDef CpIntAtom_Members[] = { - { "nbytes", - T_PYSSIZET, - offsetof(CpIntAtomObject, _m_byte_count), - READONLY, - NULL }, - { "nbits", T_PYSSIZET, offsetof(CpIntAtomObject, _m_bits), READONLY, NULL }, - { "signed", T_BOOL, offsetof(CpIntAtomObject, _m_signed), READONLY, NULL }, - { "little_endian", - T_BOOL, - offsetof(CpIntAtomObject, _m_little_endian)}, - { NULL } /* Sentinel */ -}; - -PyTypeObject CpIntAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(intatom), - .tp_basicsize = sizeof(CpIntAtomObject), - .tp_dealloc = (destructor)cp_intatom_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = NULL, - .tp_members = CpIntAtom_Members, - .tp_new = (newfunc)cp_intatom_new, -}; \ No newline at end of file diff --git a/src/atomimpl/padatomobj.c b/src/atomimpl/padatomobj.c deleted file mode 100644 index c411c85..0000000 --- a/src/atomimpl/padatomobj.c +++ /dev/null @@ -1,205 +0,0 @@ -/* padding atom implementation */ - -#include "caterpillar/caterpillar.h" -#include "caterpillar/atoms/primitive.h" -#include "caterpillar/parsing.h" -#include "caterpillar/module.h" -#include - -static PyObject* -cp_paddingatom__type__(PyObject* self) -{ - return Py_XNewRef(&_PyNone_Type); -} - -static PyObject* -cp_paddingatom__size__(PyObject* self, PyObject *ctx) -{ - /* NOTE: - We are using the size of one byte here to allow padding atoms to be - statically sized. It helps size calculation as well as defines the - default size for padding atoms. - */ - return PyLong_FromSize_t(1); -} - -static PyObject* -cp_paddingatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) -{ - CpPaddingAtomObject* self = (CpPaddingAtomObject*)type->tp_alloc(type, 0); - if (self != NULL) { - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpPaddingAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpPaddingAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = - (packmanyfunc)CpPaddingAtom_PackMany; - CpFieldCAtom_CATOM(self).ob_unpack_many = - (unpackmanyfunc)CpPaddingAtom_UnpackMany; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_paddingatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_paddingatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; - } - return (PyObject*)self; -} - -static void -cp_paddingatom_dealloc(CpPaddingAtomObject* self) -{ - self->padding = 0; - Py_TYPE(self)->tp_free((PyObject*)self); -} - -static int -cp_paddingatom_init(CpPaddingAtomObject* self, PyObject* args, PyObject* kwds) -{ - static char* kwlist[] = { "pad", NULL }; - char value = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|b", kwlist, &value)) - return -1; - - self->padding = value; - return 0; -} - -/* Public API */ - -int -CpPaddingAtom_Pack(CpPaddingAtomObject* self, - PyObject* value, - CpLayerObject* layer) -{ - /* value will be ignored here */ - PyObject* res; - if (!self->padding) { - res = CpState_Write(layer->m_state, layer->m_state->mod->cp_bytes__false); - } else { - PyObject* bytes = PyBytes_FromStringAndSize((char*)&self->padding, 1); - if (!bytes) { - return -1; - } - res = CpState_Write(layer->m_state, bytes); - Py_DECREF(bytes); - } - - if (!res) { - return -1; - } - Py_XDECREF(res); - return 0; -} - -int -CpPaddingAtom_PackMany(CpPaddingAtomObject* self, - PyObject* value, - CpLayerObject* layer) -{ - /* value will be ignored here */ - PyObject *res = NULL, *bytes = NULL, *objSize = NULL, *objLengh = NULL; - bool greedy = false; - Py_ssize_t length = 0; - - if (!layer->m_field) { - PyErr_SetString(PyExc_TypeError, "layer must have a field"); - return -1; - } - - /* The following call will fail if the length is a prefixed statement - * (slice)*/ - objLengh = - CpField_GetLength((CpFieldObject*)layer->m_field, (PyObject*)layer); - if (!objLengh) { - return -1; - } - if (_CpPack_EvalLength(layer, objLengh, -1, &greedy, &length) < 0) { - Py_XDECREF(objLengh); - return -1; - } - Py_XDECREF(objLengh); - - if (length == 0) { - return 0; - } else if (length < 0) { - PyErr_SetString(PyExc_ValueError, "length must be >= 0"); - return -1; - } else if (greedy) { - PyErr_SetString(PyExc_ValueError, "length must be >= 0 and not greedy!"); - return -1; - } - - objSize = PyLong_FromSsize_t(length); - if (!objSize) { - return -1; - } - bytes = CpObject_CreateOneArg(&PyBytes_Type, objSize); - if (!bytes) { - Py_DECREF(objSize); - return -1; - } - /* unsafe { */ - memset(PyBytes_AS_STRING(bytes), self->padding, length); - /* } */ - res = CpState_Write(layer->m_state, bytes); - Py_DECREF(bytes); - Py_DECREF(objSize); - if (!res) { - return -1; - } - Py_XDECREF(res); - return 0; -} - -PyObject* -CpPaddingAtom_Unpack(CpPaddingAtomObject* self, CpLayerObject* layer) -{ - PyObject* res = CpState_Read(layer->m_state, 1); - if (!res) { - return NULL; - } - Py_XDECREF(res); - Py_RETURN_NONE; -} - -PyObject* -CpPaddingAtom_UnpackMany(CpPaddingAtomObject* self, CpLayerObject* layer) -{ - PyObject *res = NULL, *bytes = NULL, *objLengh = NULL; - bool greedy = false; - Py_ssize_t length = 0, parsedLength = 0; - - objLengh = - CpField_GetLength((CpFieldObject*)layer->m_field, (PyObject*)layer); - if (!objLengh) { - return NULL; - } - if (_CpPack_EvalLength(layer, objLengh, -1, &greedy, &length) < 0) { - Py_DECREF(objLengh); - return NULL; - } - Py_DECREF(objLengh); - - res = CpState_Read(layer->m_state, length); - if (!res) { - return NULL; - } - - Py_XDECREF(res); - Py_RETURN_NONE; -} - -PyObject* -cp_paddingatom__repr__(CpPaddingAtomObject* self) -{ - return PyUnicode_FromFormat("padding(0x%02x)", self->padding); -} - -/* type setup */ -PyTypeObject CpPaddingAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(paddingatom), - .tp_basicsize = sizeof(CpPaddingAtomObject), - .tp_dealloc = (destructor)cp_paddingatom_dealloc, - .tp_repr = (reprfunc)cp_paddingatom__repr__, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = NULL, - .tp_init = (initproc)cp_paddingatom_init, - .tp_new = (newfunc)cp_paddingatom_new, -}; \ No newline at end of file diff --git a/src/capi.dat b/src/capi.dat new file mode 100644 index 0000000..6e213c9 --- /dev/null +++ b/src/capi.dat @@ -0,0 +1,233 @@ +# Caterpillar C API +# +# All types and API functions are described in this file. They are used to +# generate the Caterpillar C API headers and to build the documentation. +# +# modified format from sphinx documentation +# see https://github.com/python/cpython/blob/main/Doc/data/refcounts.dat +# +# There are currently four different types of statements: +# +# type:INDEX:STRUCT_NAME:TYPEDEF_NAME:CAPI_TYPE_NAME +# Defines a C API type for a C structure. The index is optional and +# the CAPI_TYPE_NAME will add an extra macro to the header file. +# +# obj:INDEX:NAME:TYPE +# Defines a C API object. +# +# func:INDEX:NAME:RETURN_TYPE:REFCOUNT +# Defines a C API function. The function must be present within the +# source set of this file. +# +# src:FILE +# Defines the source file (relative to this file) that contains the +# function definitions. + +src:ccaterpillar/context.c +src:ccaterpillar/arch.c +src:ccaterpillar/atomobj.c +src:ccaterpillar/option.c +src:ccaterpillar/struct.c +src:ccaterpillar/state.c +src:ccaterpillar/layer.c +src:ccaterpillar/parsing_pack.c +src:ccaterpillar/parsing_unpack.c +src:ccaterpillar/parsing_typeof.c +src:ccaterpillar/parsing_sizeof.c +src:ccaterpillar/atomimpl/bool.c +src:ccaterpillar/atomimpl/float.c +src:ccaterpillar/atomimpl/char.c +src:ccaterpillar/atomimpl/int.c +src:ccaterpillar/atomimpl/pad.c +src:ccaterpillar/atomimpl/string.c +src:ccaterpillar/atomimpl/const.c +src:ccaterpillar/atomimpl/bytes.c +src:ccaterpillar/atomimpl/pstring.c +src:ccaterpillar/atomimpl/enum.c +src:ccaterpillar/atomimpl/varint.c +src:ccaterpillar/atomimpl/computed.c +src:ccaterpillar/atomimpl/lazy.c +src:ccaterpillar/atomimpl/cstring.c +src:ccaterpillar/atomimpl/builtins/builtin.c +src:ccaterpillar/atomimpl/builtins/repeated.c +src:ccaterpillar/atomimpl/builtins/condition.c +src:ccaterpillar/atomimpl/builtins/switch.c +src:ccaterpillar/atomimpl/builtins/atoffset.c +src:ccaterpillar/atomimpl/builtins/primitive.c + +# First index is reserved for the global module reference +obj:0:CpModule:PyModuleDef + +type:-:_modulestate:_modulestate:- + +type:1:_atomobj:CpAtomObject:atom +type:2:_catomobj:CpCAtomObject:catom +type:3:_archobj:CpArchObject:Arch +type:4:_endianobj:CpEndianObject:Endian +type:5:_contextobj:CpContextObject:Context +type:6:_unaryexpr:CpUnaryExprObject:unaryexpr +type:7:_binaryexpr:CpBinaryExprObject:binaryexpr +type:8:_contextpath:CpContextPathObject:ContextPath +# type:9:_fieldobj:CpFieldObject:Field +# type:10:_fieldatomobj:CpFieldAtomObject:fieldatom +# type:11:_fieldcatomobj:CpFieldCAtomObject:fieldcatom + +obj:12:CpInvalidDefault_Type:- +obj:13:CpDefaultOption_Type:- +obj:14:_CpInvalidDefault_Object:PyObject +obj:15:_CpDefaultOption_Object:PyObject + +type:16:_option:CpOptionObject:Option +type:17:_stateobj:CpStateObject:State +type:18:_layerobj:CpLayerObject:layer + +# REVISIT: maybe rename to _structfieldinfo +type:19:_fieldinfoobj:CpStructFieldInfoObject:fieldinfo +type:20:_structobj:CpStructObject:Struct +type:21:_floatatomobj:CpFloatAtomObject:Float +type:22:_intatomobj:CpIntAtomObject:Int +type:23:_boolatomobj:CpBoolAtomObject:Bool +type:24:_charatomobj:CpCharAtomObject:Char +type:25:_paddingatomobj:CpPaddingAtomObject:Padding +type:26:_stringatomobj:CpStringAtomObject:string +type:27:_constatomobj:CpConstAtomObject:const +type:28:_builtinatomobj:CpBuiltinAtomObject:builtinatom +type:29:_repeatedatomobj:CpRepeatedAtomObject:repeated +type:30:_seqlayerobj:CpSeqLayerObject:seqlayer +type:31:_objlayerobj:CpObjLayerObject:objlayer +type:32:_conditionatomobj:CpConditionAtomObject:condition +type:33:_switchatomobj:CpSwitchAtomObject:switch +type:34:_offsetatomobj:CpOffsetAtomObject:atoffset +type:35:_primitiveatomobj:CpPrimitiveAtomObject:patom +type:36:_lengthinfoobj:CpLengthInfoObject:lengthinfo +type:37:_bytesatomobj:CpBytesAtomObject:octetstring +type:38:_pstringatomobj:CpPStringAtomObject:pstring +type:39:_enumatomobj:CpEnumAtomObject:enumeration +type:40:_varintatomobj:CpVarIntAtomObject:VarInt +type:41:_computedatomobj:CpComputedAtomObject:computed +type:42:_lazyatomobj:CpLazyAtomObject:lazy +type:43:_cstringatomobj:CpCStringAtomObject:cstring + +func:50:CpEndian_IsLittleEndian:int:null +func:53:CpContext_New:CpContextObject*:+1 +func:54:CpUnaryExpr_New:CpUnaryExprObject*:+1 +func:55:CpBinaryExpr_New:CpBinaryExprObject*:+1 +func:56:CpContextPath_New:CpContextPathObject*:+1 +func:57:CpContextPath_FromString:CpContextPathObject*:+1 +func:58:CpTypeMap_Lookup:PyObject*:+1 +func:59:CpTypeMap_Register:int:null +# func:58:CpField_New:CpFieldObject*:null +# func:59:CpField_HasCondition:int:null +# func:60:CpField_IsEnabled:int:null +# func:61:CpField_GetOffset:Py_ssize_t:null +# func:62:CpField_EvalSwitch:PyObject*:+1 +# func:63:CpField_GetLength:PyObject*:+1 +func:64:CpTypeOf:PyObject*:+1 +# func:65:CpTypeOf_Field:PyObject*:+1 +func:66:CpTypeOf_Common:PyObject*:+1 +func:67:CpPack:int:null +# func:68:CpPack_Field:int:null +# func:69:CpPack_Common:int:null +# func:70:CpPack_Struct:int:null +func:72:_CpPack_EvalLength:int:null +func:73:CpSizeOf:PyObject*:+1 +# func:74:CpSizeOf_Field:PyObject*:+1 +func:75:CpSizeOf_Struct:PyObject*:+1 +func:76:CpSizeOf_Common:PyObject*:+1 +func:77:_Cp_SizeOf:PyObject*:+1 +func:78:CpUnpack:PyObject*:+1 +# func:79:CpUnpack_Field:PyObject*:+1 +# func:80:CpUnpack_Common:PyObject*:+1 +# func:81:CpUnpack_Struct:PyObject*:+1 +func:83:_CpUnpack_EvalLength:int:null +func:84:CpUnpack_CAtom:PyObject*:+1 +func:85:CpPack_CAtom:int:null +func:86:CpSizeOf_CAtom:PyObject*:+1 +func:87:CpTypeOf_CAtom:PyObject*:+1 +func:88:CpState_New:CpStateObject*:null +func:89:CpState_Tell:PyObject*:+1 +func:90:CpState_Seek:PyObject*:+1 +func:91:CpState_Read:PyObject*:+1 +func:92:CpState_ReadSsize_t:PyObject*:+1 +func:93:CpState_ReadFully:PyObject*:+1 +func:94:CpState_Write:PyObject*:+1 +func:95:CpState_SetGlobals:int:null +func:96:CpLayer_New:CpLayerObject*:+1 +func:98:CpLayer_Invalidate:int:null +func:99:CpStructFieldInfo_New:CpStructFieldInfoObject*:+1 +func:100:CpStruct_AddFieldInfo:int:null +# func:101:CpStruct_AddField:int:null +func:102:CpStruct_New:CpStructObject*:+1 +func:103:CpStruct_GetAnnotations:PyObject*:+1 +func:104:CpStruct_ReplaceType:int:null +func:105:CpStruct_HasOption:int:null +func:106:CpStruct_Pack:int:null +func:107:CpStruct_Unpack:PyObject*:+1 +func:108:CpStruct_SizeOf:PyObject*:+1 +func:109:CpStructModel_Check:int:null +func:110:CpStructModel_GetStruct:PyObject*:+1 +func:111:CpSeqLayer_New:CpSeqLayerObject*:+1 +func:112:CpSeqLayer_SetSequence:int:null +func:113:CpObjLayer_New:CpObjLayerObject*:+1 + +# atom api +func:120:CpIntAtom_Pack:int:null +func:121:CpIntAtom_Unpack:PyObject*:+1 +func:122:CpFloatAtom_Pack:int:null +func:123:CpFloatAtom_Unpack:PyObject*:+1 +func:124:CpBoolAtom_Pack:int:null +func:125:CpBoolAtom_Unpack:PyObject*:+1 +func:126:CpCharAtom_Pack:int:null +func:127:CpCharAtom_Unpack:PyObject*:+1 +func:128:CpPaddingAtom_Pack:int:null +func:129:CpPaddingAtom_PackMany:int:null +func:130:CpPaddingAtom_Unpack:PyObject*:+1 +func:131:CpPaddingAtom_UnpackMany:PyObject*:+1 +func:132:CpStringAtom_Pack:int:null +func:133:CpStringAtom_Unpack:PyObject*:+1 +func:134:CpConstAtom_Pack:int:null +func:135:CpConstAtom_Unpack:PyObject*:+1 +func:-:CpRepeatedAtom_New:CpRepeatedAtomObject*:+1 +func:136:CpRepeatedAtom_Pack:int:null +func:137:CpRepeatedAtom_Unpack:PyObject*:+1 +func:138:CpRepeatedAtom_GetLength:PyObject*:+1 +func:-:CpConditionAtom_New:CpConditionAtomObject*:+1 +func:139:CpConditionAtom_Pack:int:null +func:140:CpConditionAtom_Unpack:PyObject*:+1 +func:141:CpConditionAtom_IsEnabled:int:null +func:-:CpSwitchAtom_New:CpSwitchAtomObject*:+1 +func:142:CpSwitchAtom_GetNext:PyObject*:+1 +func:143:CpSwitchAtom_Pack:int:null +func:144:CpSwitchAtom_Unpack:PyObject*:+1 +func:-:CpOffsetAtom_FromSsize_t:CpOffsetAtomObject*:+1 +func:-:CpOffsetAtom_New:CpOffsetAtomObject*:+1 +func:145:CpOffsetAtom_Pack:int:null +func:146:CpOffsetAtom_Unpack:PyObject*:+1 +func:147:CpOffsetAtom_GetOffset:PyObject*:+1 +func:-:CpBytesAtom_New:CpBytesAtomObject*:+1 +func:148:CpBytesAtom_GetLength:PyObject*:+1 +func:149:CpBytesAtom_Pack:int:null +func:150:CpBytesAtom_Unpack:PyObject*:+1 +func:-:CpPStringAtom_New:CpPStringAtomObject*:+1 +func:152:CpPStringAtom_Pack:int:null +func:153:CpPStringAtom_Unpack:PyObject*:+1 +func:-:CpEnumAtom_New:CpEnumAtomObject*:+1 +func:154:CpEnumAtom_Pack:int:null +func:155:CpEnumAtom_Unpack:PyObject*:+1 +func:-:CpVarIntAtom_New:CpVarIntAtomObject*:+1 +func:156:CpVarIntAtom_Pack:int:null +func:157:CpVarIntAtom_Unpack:PyObject*:+1 +func:158:CpVarIntAtom_BSwap:PyObject*:+1 +func:159:CpVarIntAtom_BSwapUnsignedLongLong:ulonglong:null +func:160:CpVarIntAtom_BSwapLongLong:longlong:null +func:161:CpVarIntAtom_BSwapSsize_t:Py_ssize_t:null +func:-:CpComputedAtom_New:CpComputedAtomObject*:+1 +func:162:CpComputedAtom_Pack:int:null +func:163:CpComputedAtom_Unpack:PyObject*:+1 +func:-:CpComputedAtom_Value:PyObject*:+1 +func:-:CpLazyAtom_New:CpLazyAtomObject*:+1 +func:164:CpLazyAtom_Pack:int:null +func:165:CpLazyAtom_Unpack:PyObject*:+1 +func:-:CpLazyAtom_Atom:PyObject*:+1 +func:166:CpCStringAtom_Pack:int:null +func:167:CpCStringAtom_Unpack:PyObject*:+1 \ No newline at end of file diff --git a/src/caterpillar/_C.pyi b/src/caterpillar/_C.pyi index 0244f9e..813ee8c 100644 --- a/src/caterpillar/_C.pyi +++ b/src/caterpillar/_C.pyi @@ -1,69 +1,95 @@ from __future__ import annotations -from typing import Any, Optional, Collection, Union, Callable, IO +from typing import Any, Optional, Collection, Union, Callable, IO, TypeVar +from enum import EnumType _Length = Union[int, ContextLambda, slice, Ellipsis] +ContextLambda = Callable[[Context], Any] +_ConstType = Union[Any, ContextLambda] -DefaultOption: DefaultOptionType = ... -FIELD_OPTIONS: set = ... -F_DYNAMIC: Option = ... -F_SEQUENTIAL: Option = ... -HOST_ARCH: Arch = ... -InvalidDefault: InvalidDefaultType = ... -NATIVE_ENDIAN: Endian = ... -LITTLE_ENDIAN: Endian = ... -BIG_ENDIAN: Endian = ... -STRUCT_OPTIONS: set = ... -S_DISCARD_CONST: Option = ... -S_DISCARD_UNNAMED: Option = ... -S_EVAL_ANNOTATIONS: Option = ... -S_REPLACE_TYPES: Option = ... -S_SLOTS: Option = ... -S_UNION: Option = ... +BIG_ENDIAN: Endian +DefaultOption: DefaultOptionType +FIELD_OPTIONS: set +F_DYNAMIC: Option +F_SEQUENTIAL: Option +HOST_ARCH: Arch +InvalidDefault: InvalidDefaultType +LITTLE_ENDIAN: Endian +NATIVE_ENDIAN: Endian +STRUCT_OPTIONS: set +S_DISCARD_CONST: Option +S_DISCARD_UNNAMED: Option +S_EVAL_ANNOTATIONS: Option +S_REPLACE_TYPES: Option +S_SLOTS: Option +S_UNION: Option +boolean: Bool +char: Char +f16: Float +f32: Float +f64: Float +i128: Int +i16: Int +i24: Int +i32: Int +i64: Int +i8: Int +lsbvarint: VarInt +padding: Padding +u128: Int +u16: Int +u24: Int +u32: Int +u64: Int +u8: Int +varint: VarInt class Context(dict): - def __init__(self, *args, **kwargs) -> None: ... + def __init__(self, /, **kwargs) -> None: ... def __context_getattr__(self, *args, **kwargs): ... -ContextLambda = Callable[[Context], Any] - class Arch: name: str ptr_size: int def __init__(self, name: str, ptr_size: int) -> None: ... def __hash__(self) -> int: ... -class BinaryExpr: +class binaryexpr: expr: int lhs: Union[ContextLambda, Any] rhs: Union[ContextLambda, Any] - def __init__(self, *args, **kwargs) -> None: ... - def __call__(self, *args, **kwargs): ... + def __init__( + self, + expr: int, + left: Union[ContextLambda, Any], + right: Union[ContextLambda, Any], + ) -> None: ... + def __call__(self, *args, **kwargs) -> Any: ... class ContextPath: path: str def __init__(self, path: str) -> None: ... - def __add__(self, other) -> BinaryExpr: ... - def __and__(self, other) -> BinaryExpr: ... + def __add__(self, other) -> binaryexpr: ... + def __and__(self, other) -> binaryexpr: ... def __call__(self, context: Context) -> Any: ... - def __eq__(self, other: object) -> BinaryExpr: ... - def __floordiv__(self, other) -> BinaryExpr: ... - def __ge__(self, other: object) -> BinaryExpr: ... - def __gt__(self, other: object) -> BinaryExpr: ... + def __eq__(self, other: object) -> binaryexpr: ... + def __floordiv__(self, other) -> binaryexpr: ... + def __ge__(self, other: object) -> binaryexpr: ... + def __gt__(self, other: object) -> binaryexpr: ... def __hash__(self) -> int: ... - def __invert__(self) -> UnaryExpr: ... - def __le__(self, other: object) -> BinaryExpr: ... - def __lshift__(self, other) -> BinaryExpr: ... - def __lt__(self, other: object) -> BinaryExpr: ... - def __mod__(self, other) -> BinaryExpr: ... - def __mul__(self, other) -> BinaryExpr: ... - def __ne__(self, other: object) -> BinaryExpr: ... - def __neg__(self) -> UnaryExpr: ... - def __or__(self, other) -> BinaryExpr: ... - def __sub__(self, other) -> UnaryExpr: ... - def __truediv__(self, other) -> BinaryExpr: ... + def __invert__(self) -> unaryexpr: ... + def __le__(self, other: object) -> binaryexpr: ... + def __lshift__(self, other) -> binaryexpr: ... + def __lt__(self, other: object) -> binaryexpr: ... + def __mod__(self, other) -> binaryexpr: ... + def __mul__(self, other) -> binaryexpr: ... + def __ne__(self, other: object) -> binaryexpr: ... + def __neg__(self) -> unaryexpr: ... + def __or__(self, other) -> binaryexpr: ... + def __sub__(self, other) -> unaryexpr: ... + def __truediv__(self, other) -> binaryexpr: ... def __type__(self) -> type: ... - def __xor__(self, other) -> BinaryExpr: ... + def __xor__(self, other) -> binaryexpr: ... class DefaultOptionType: def __init__(self) -> None: ... @@ -82,85 +108,48 @@ class Endian: class atom: def __init__(self) -> None: ... - def __pack__(self, obj: Any, context: Context) -> None: ... - def __size__(self, context: Context) -> Any: ... + def __pack__(self, obj: Any, context: layer) -> None: ... + def __size__(self, context: layer) -> Any: ... def __type__(self) -> Any: ... - def __unpack__(self, context: Context) -> Any: ... + def __unpack__(self, context: layer) -> Any: ... class catom(atom): def __init__(self) -> None: ... - def __pack__(self, obj: Any, context: Context) -> None: ... - def __pack_many__(self, obj: Collection[Any], context: Context) -> None: ... - def __size__(self, context: Context) -> Any: ... + def __pack__(self, obj: Any, context: layer) -> None: ... + def __pack_many__(self, obj: Collection[Any], layer: layer) -> None: ... + def __size__(self, context: layer) -> Any: ... def __type__(self) -> Any: ... - def __unpack__(self, context: Context) -> Any: ... - def __unpack_many__(self, context: Context) -> Collection[Any]: ... + def __unpack__(self, context: layer) -> Any: ... + def __unpack_many__(self, context: layer, lengthinfo) -> Collection[Any]: ... -class fieldatom(atom): - def __init__(self) -> None: ... - def __add__(self, endian: Endian) -> Field: ... - def __floordiv__(self, condition: Union[bool, ContextLambda]) -> Field: ... - def __getitem__( - self, length: _Length - ) -> Field: ... - def __matmul__(self, offset: Union[ContextLambda, int]) -> Field: ... - def __or__(self, option: Option) -> Field: ... - def __radd__(self, endian: Endian) -> Field: ... - def __rfloordiv__(self, condition: Union[bool, ContextLambda]) -> Field: ... - def __rmatmul__(self, offset: Union[ContextLambda, int]) -> Field: ... - def __ror__(self, option: Option) -> Field: ... - def __rrshift__(self, switch: Union[dict, ContextLambda]) -> Field: ... - def __rshift__(self, switch: Union[dict, ContextLambda]) -> Field: ... - def __rxor__(self, option: Option) -> Field: ... - def __xor__(self, option: Option) -> Field: ... +class builtinatom(catom): + def __init__(self, *args, **kwargs) -> None: ... + def __floordiv__(self, other) -> condition: ... + def __getitem__(self, index) -> repeated: ... + def __matmul__(self, *args, **kwargs) -> atoffset: ... + def __rfloordiv__(self, other) -> condition: ... + def __rmatmul__(self, *args, **kwargs) -> atoffset: ... + def __rrshift__(self, other) -> switch: ... + def __rshift__(self, other) -> switch: ... -class fieldcatom(catom): - def __init__(self) -> None: ... - def __add__(self, endian: Endian) -> Field: ... - def __floordiv__(self, condition: Union[bool, ContextLambda]) -> Field: ... - def __getitem__( - self, length: _Length - ) -> Field: ... - def __matmul__(self, offset: Union[ContextLambda, int]) -> Field: ... - def __or__(self, option: Option) -> Field: ... - def __radd__(self, endian: Endian) -> Field: ... - def __rfloordiv__(self, condition: Union[bool, ContextLambda]) -> Field: ... - def __rmatmul__(self, offset: Union[ContextLambda, int]) -> Field: ... - def __ror__(self, option: Option) -> Field: ... - def __rrshift__(self, switch: Union[dict, ContextLambda]) -> Field: ... - def __rshift__(self, switch: Union[dict, ContextLambda]) -> Field: ... - def __rxor__(self, option: Option) -> Field: ... - def __xor__(self, option: Option) -> Field: ... +class Char(builtinatom): + def __init__(self, *args, **kwargs) -> None: ... -class Field: - arch: Arch - atom: atom +class condition(builtinatom): + atom: Any condition: Union[bool, ContextLambda] - default: Union[Any, InvalidDefaultType] - endian: Endian - length: _Length - name: str - offset: Union[ContextLambda, int] - options: set[Option] - switch: Union[dict, ContextLambda] - def __init__( - self, - atom: atom, - name: str = ..., - endian: Endian = ..., - offset: Union[ContextLambda, int] = ..., - arch: Arch = ..., - length: _Length = ..., - default: Union[Any, InvalidDefaultType] = ..., - switch: Union[dict, ContextLambda] = ..., - options: set[Option] = ..., - condition: Union[bool, ContextLambda] = ..., - ) -> None: ... + def __init__(self, condition: Union[bool, ContextLambda], atom: Any) -> None: ... + def is_enabled(self, context: layer) -> bool: ... + def __set_byteorder__(self, byteorder: Endian) -> condition: ... + +class const(builtinatom): + def __init__(self, *args, **kwargs) -> None: ... + +class builtinatom(catom): + def __init__(self) -> None: ... def __add__(self, endian: Endian) -> Field: ... def __floordiv__(self, condition: Union[bool, ContextLambda]) -> Field: ... - def __getitem__( - self, length: _Length - ) -> Field: ... + def __getitem__(self, length: _Length) -> Field: ... def __matmul__(self, offset: Union[ContextLambda, int]) -> Field: ... def __or__(self, option: Option) -> Field: ... def __radd__(self, endian: Endian) -> Field: ... @@ -199,10 +188,17 @@ class State: class fieldinfo: excluded: bool - field: Field - def __init__(self, field: Field, excluded: bool = ...) -> None: ... + default: Any + field: atom + name: str + def __init__(self, field: atom, excluded: bool = ...) -> None: ... + +class lengthinfo: + length: int + greedy: bool + def __init__(self, length: int = ..., greedy: bool = ...) -> None: ... -class Struct(fieldatom): +class Struct(builtinatom): members: dict[str, fieldinfo] model: type options: set[Option] @@ -215,7 +211,7 @@ class Struct(fieldatom): alter_model: bool = ..., ) -> None: ... -class UnaryExpr: +class unaryexpr: expr: int value: Union[ContextLambda, Any] def __init__(self, expr: int, value: Union[ContextLambda, Any]) -> Any: ... @@ -252,41 +248,130 @@ def pack(__obj: Any, __struct: atom, **globals) -> bytes: ... def sizeof(obj: atom, globals: Optional[dict | Context] = ...): ... def unpack(__io: Any, __struct: atom, **globals) -> Any: ... -class intatom(fieldcatom): +class Int(builtinatom): little_endian: bool nbits: int nbytes: int signed: bool - def __init__(self, nbits: int, signed: bool = ..., little_endian: bool = ...) -> None: ... - -i16: intatom -i24: intatom -i32: intatom -i64: intatom -i8: intatom -u16: intatom -u24: intatom -u32: intatom -u64: intatom -u8: intatom - -class floatatom(fieldcatom): + def __init__( + self, nbits: int, signed: bool = ..., little_endian: bool = ... + ) -> None: ... + +class Float(builtinatom): little_endian: bool nbits: int nbytes: int def __init__(self, nbits: int, little_endian: bool = ...) -> None: ... -f16: floatatom -f32: floatatom -f64: floatatom - -class paddingatom(fieldcatom): +class Padding(builtinatom): def __init__(self, pad: int) -> None: ... -padding: paddingatom - -class stringatom(fieldcatom): +class string(builtinatom): encoding: str errors: str length: _Length def __init__(self, length: _Length, encoding: str, errors: str = ...) -> None: ... + +class atoffset(builtinatom): + offset: Union[int, ContextLambda] + whence: int + def __init__( + self, offset: Union[int, ContextLambda], atom: atom, whence: int = ... + ) -> None: ... + def get_offset(self, layer: layer) -> int: ... + def __set_byteorder__(self, byteorder: Endian) -> atoffset: ... + + +class repeated(builtinatom): + atom: Any + length: _Length + def __init__(self, atom: atom, length: _Length) -> None: ... + def __set_byteorder__(self, byteorder: Endian) -> repeated: ... + +class seqlayer(layer): + index: int + length: int + sequence: Collection + def __init__(self, *args, **kwargs) -> None: ... + +class switch(builtinatom): + atom: Any + cases: Union[dict[Any, Any], ContextLambda] + def __init__( + self, atom: atom, cases: Union[dict[Any, Any], ContextLambda] + ) -> None: ... + def get_next(self, obj: Any, context: layer) -> Any: ... + def __set_byteorder__(self, byteorder: Endian) -> switch: ... + +class cstring(builtinatom): + length: Union[_Length, atom] + encoding: str + errors: str + terminator: str + keep_terminator: bool + + def __init__( + self, + length: Union[_Length, atom], + encoding: str = ..., + errors: str = ..., + sep: str = ..., + keep_terminator: bool = ..., + ) -> None: ... + +class octetstring(builtinatom): + length: _Length + def __init__(self, length: _Length) -> None: ... + +class enumeration(builtinatom): + atom: Any + enum_type: EnumType + members: dict[Any, Any] + default: Any + def __init__( + self, + atom: atom, + enum_type: EnumType, + default: Any = ..., + ) -> None: ... + def __set_byteorder__(self, byteorder: Endian) -> enumeration: ... + +class pstring(builtinatom): + atom: Any + encoding: str + errors: str + def __init__(self, atom: atom, encoding: str = ..., errors: str = ...) -> None: ... + def __set_byteorder__(self, byteorder: Endian) -> pstring: ... + +class VarInt(builtinatom): + little_endian: bool + lsb: bool + def __init__(self, little_endian: bool = ..., lsb: bool = ...) -> None: ... + def __set_byteorder__(self, byteorder: Endian) -> VarInt: ... + +class computed(builtinatom): + value: _ConstType + def __init__(self, value: _ConstType) -> None: ... + +class objlayer(layer): + obj: Context + def __init__( + self, state: State, path: str = ..., parent: layer = ..., obj: Context = ... + ) -> None: ... + def __context_getattr__(self, path: str) -> Any: ... + +class lazy(builtinatom): + always_lazy: bool + fn: Callable[[], atom] + def __init__(self, fn: Callable[[], atom], always_lazy: bool = ...) -> None: ... + def __set_byteorder__(self, byteorder: Endian) -> lazy: ... + +class patom(atom): + def __init__(self) -> None: ... + def __floordiv__(self, other): ... + def __getitem__(self, index): ... + def __matmul__(self, *args, **kwargs): ... + def __rfloordiv__(self, other): ... + def __rmatmul__(self, *args, **kwargs): ... + def __rrshift__(self, other): ... + def __rshift__(self, other): ... diff --git a/src/caterpillar/__init__.py b/src/caterpillar/__init__.py index df1f0fe..9ee086e 100644 --- a/src/caterpillar/__init__.py +++ b/src/caterpillar/__init__.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = "2.1.5" +__version__ = "2.2.0" __release__ = None __author__ = "MatrixEditor" diff --git a/src/caterpillar/c.py b/src/caterpillar/c.py new file mode 100644 index 0000000..8d79d74 --- /dev/null +++ b/src/caterpillar/c.py @@ -0,0 +1,23 @@ +from caterpillar._C import * # noqa + + +def struct(cls: type = None, /, **kwargs) -> type: + """Decorator for new struct classes using the C API. + + >>> @struct + ... class MyStruct: + ... pass + + :param cls: The target class used as the base model. + :param options: Additional options specifying what to include in the final class. + :param endian: Optional configuration value for the byte order of a field. + :param arch: Global architecture definition (will be inferred on all fields). + :param field_options: Additional field options. + """ + def wrap(cls) -> type: + return Struct(cls, alter_model=True, **kwargs).model + + if cls is not None: + return Struct(cls, alter_model=True, **kwargs).model + + return wrap diff --git a/src/caterpillar/include/caterpillar/arch.h b/src/caterpillar/include/caterpillar/arch.h index 09fa1bb..a79b6ca 100644 --- a/src/caterpillar/include/caterpillar/arch.h +++ b/src/caterpillar/include/caterpillar/arch.h @@ -88,4 +88,56 @@ struct _endianobj */ #define CpEndian_Check(op) PyObject_TypeCheck((PyObject*)(op), &CpEndian_Type) +/** + * @brief Set the byteorder of the given object + * + * @param op the object to set + * @param endian the new byteorder + * @return 0 on success, -1 on error + */ +static inline PyObject* +CpEndian_SetEndian(PyObject* op, CpEndianObject* endian) +{ + PyObject* attr = PyObject_GetAttrString(op, "__set_byteorder__"); + if (!attr) { + return NULL; + } + PyObject* ret = PyObject_CallOneArg(attr, (PyObject*)endian); + if (!ret) { + return NULL; + } + Py_DECREF(attr); + return ret; +} + +#define _CpEndian_ImplSetByteorder_MethDef(name, docs) \ + { \ + "__set_byteorder__", (PyCFunction)cp_##name##_set_byteorder, \ + METH_VARARGS | METH_KEYWORDS, (docs) \ + } + +#define _CpEndian_KwArgsGetByteorder(ret) \ + static char* kwlist[] = { "byteorder", NULL }; \ + PyObject* byteorder = NULL; \ + if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &byteorder)) { \ + return ret; \ + } \ + if (!CpEndian_Check(byteorder)) { \ + PyErr_SetString(PyExc_TypeError, "byteorder must be an Endian object"); \ + return ret; \ + } + +#define _CpEndian_ImplSetByteorder(typename, name, field) \ + static PyObject* cp_##name##_set_byteorder( \ + typename* self, PyObject* args, PyObject* kw) \ + { \ + _CpEndian_KwArgsGetByteorder(NULL); \ + PyObject* ret = CpEndian_SetEndian(field, (CpEndianObject*)byteorder); \ + if (!ret) { \ + return NULL; \ + } \ + _Cp_SetObj(field, ret); \ + return Py_NewRef((PyObject*)self); \ + } + #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atomobj.h b/src/caterpillar/include/caterpillar/atomobj.h index dfafa09..b002961 100644 --- a/src/caterpillar/include/caterpillar/atomobj.h +++ b/src/caterpillar/include/caterpillar/atomobj.h @@ -110,6 +110,17 @@ CpAtom_Pack(PyObject* atom, PyObject* attrname, PyObject* op, PyObject* ctx) return PyObject_CallMethodObjArgs(atom, attrname, op, ctx, NULL); } +// --------------------------------------------------------------------------- +// Length info + +struct _lengthinfoobj +{ + PyObject_HEAD + + Py_ssize_t m_length; + int m_greedy; +}; + // --------------------------------------------------------------------------- // CAtom @@ -176,8 +187,20 @@ typedef int (*packfunc)(PyObject*, PyObject*, PyObject*); * This is another specialized version of the `packfunc` function, which * is used to pack multiple objects to the underlying stream. See `packfunc` * for more information. + * + * The signature of this function is as follows: + * @code {.cpp} + * int packmanyfunc(PyObject* self, PyObject* op, PyObject* ctx, + * CpLengthInfo* info); + * @endcode + * + * @param self the atom object (self) + * @param op the object to pack + * @param ctx the context object + * @param info the length information + * @return the result of the `__pack_many__` method */ -typedef int (*packmanyfunc)(PyObject*, PyObject*, PyObject*); +typedef int (*packmanyfunc)(PyObject*, PyObject*, PyObject*, PyObject*); /** * @brief Unpack-Protocol @@ -198,8 +221,19 @@ typedef PyObject* (*unpackfunc)(PyObject*, PyObject*); * This is another specialized version of the `unpackfunc` function, which * is used to unpack multiple objects from the underlying stream. See * `unpackfunc` for more information. + * + * The signature of this function is as follows: + * @code {.cpp} + * PyObject* unpackmanyfunc(PyObject* self, PyObject* ctx, + * CpLengthInfo* info); + * @endcode + * + * @param self the atom object (self) + * @param ctx the context object + * @param info the length information + * @return the result of the `__unpack_many__` method */ -typedef PyObject* (*unpackmanyfunc)(PyObject*, PyObject*); +typedef PyObject* (*unpackmanyfunc)(PyObject*, PyObject*, PyObject*); /// Experimental API /** diff --git a/src/caterpillar/include/caterpillar/atoms/builtins.h b/src/caterpillar/include/caterpillar/atoms/builtins.h new file mode 100644 index 0000000..60288ca --- /dev/null +++ b/src/caterpillar/include/caterpillar/atoms/builtins.h @@ -0,0 +1,151 @@ +/** + * Copyright (C) MatrixEditor 2024 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef BUILTINS_H +#define BUILTINS_H + +#include "caterpillar/atomobj.h" +#include "caterpillar/caterpillarapi.h" + +// PROPOSAL: Builtin atoms are similar to the Python class FieldMixin +// but implement all parts of a field separately. For instance, the +// builtin atom for a sequence only implements the sequence part. +struct _builtinatomobj +{ + CpCAtom_HEAD +}; + +#define CpBuiltinAtom_CheckExact(op) Py_IS_TYPE((op), &CpBuiltinAtom_Type) +#define CpBuiltinAtom_Check(op) PyObject_TypeCheck((op), &CpBuiltinAtom_Type) +#define CpBuiltinAtom_HEAD CpBuiltinAtomObject ob_base; +#define CpBuiltinAtom_CATOM(self) (self)->ob_base.ob_base + +//------------------------------------------------------------------------------ +// Repeated +struct _repeatedatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores a reference to the actual parsing struct that will be used + /// to parse or build our data. This attribute is never null. + PyObject* m_atom; + + /// A constant or dynamic value to represent the amount of structs. Zero + /// indicates there are no sequence types associated with this field. + PyObject* m_length; +}; + +#define CpRepeatedAtom_CheckExact(op) Py_IS_TYPE((op), &CpRepeatedAtom_Type) +#define CpRepeatedAtom_Check(op) PyObject_TypeCheck((op), &CpRepeatedAtom_Type) + +inline CpRepeatedAtomObject* +CpRepeatedAtom_New(PyObject* atom, PyObject* length) +{ + return (CpRepeatedAtomObject*)CpObject_Create( + &CpRepeatedAtom_Type, "OO", atom, length); +} + +//------------------------------------------------------------------------------ +// Conditional +struct _conditionatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores a reference to the actual parsing struct that will be used + /// to parse or build our data. This attribute is never null. + PyObject* m_atom; + + /// A constant or dynamic value to represent the condition. + PyObject* m_condition; +}; + +#define CpConditionAtom_CheckExact(op) Py_IS_TYPE((op), &CpConditionAtom_Type) +#define CpConditionAtom_Check(op) PyObject_TypeCheck((op), &CpConditionAtom_Type) + +static inline CpConditionAtomObject* +CpConditionAtom_New(PyObject* atom, PyObject* condition) +{ + return (CpConditionAtomObject*)CpObject_Create( + &CpConditionAtom_Type, "OO", condition, atom); +} + +//------------------------------------------------------------------------------ +// Switch +struct _switchatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores a reference to the actual parsing struct that will be used + /// to parse or build our data. This attribute is never null. + PyObject* m_atom; + + // A dictionary or dynamic value to represent the cases. + PyObject* m_cases; + + // -- internal --- + int s_callable; +}; + +#define CpSwitchAtom_CheckExact(op) Py_IS_TYPE((op), &CpSwitchAtom_Type) +#define CpSwitchAtom_Check(op) PyObject_TypeCheck((op), &CpSwitchAtom_Type) + +static inline CpSwitchAtomObject* +CpSwitchAtom_New(PyObject* atom, PyObject* cases) +{ + return (CpSwitchAtomObject*)CpObject_Create( + &CpSwitchAtom_Type, "OO", atom, cases); +} + +//------------------------------------------------------------------------------ +// Offset +struct _offsetatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores a reference to the actual parsing struct that will be used + /// to parse or build our data. This attribute is never null. + PyObject* m_atom; + + PyObject* m_offset; + PyObject* m_whence; + + // -- internal --- + int s_is_number; +}; + +#define CpOffsetAtom_CheckExact(op) Py_IS_TYPE((op), &CpOffsetAtom_Type) +#define CpOffsetAtom_Check(op) PyObject_TypeCheck((op), &CpOffsetAtom_Type) + +static inline CpOffsetAtomObject* +CpOffsetAtom_FromSsize_t(PyObject* atom, Py_ssize_t offset) +{ + PyObject* o = PyLong_FromSsize_t(offset); + if (o == NULL) + return NULL; + CpOffsetAtomObject* a = + (CpOffsetAtomObject*)CpObject_Create(&CpOffsetAtom_Type, "OO", atom, o); + Py_DECREF(o); + return a; +} + +static inline CpOffsetAtomObject* +CpOffsetAtom_New(PyObject* atom, PyObject* offset) +{ + return (CpOffsetAtomObject*)CpObject_Create( + &CpOffsetAtom_Type, "OO", atom, offset); +} + +#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/const.h b/src/caterpillar/include/caterpillar/atoms/const.h new file mode 100644 index 0000000..f7afc70 --- /dev/null +++ b/src/caterpillar/include/caterpillar/atoms/const.h @@ -0,0 +1,40 @@ +/** + * Copyright (C) MatrixEditor 2024 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CONSTATOMOBJ_H +#define CONSTATOMOBJ_H + +#include "caterpillar/atoms/builtins.h" + +struct _constatomobj +{ + CpBuiltinAtom_HEAD + + PyObject* m_value; + PyObject* m_atom; +}; + +#define CpConstAtom_CheckExact(op) Py_IS_TYPE((op), &CpConstAtom_Type) +#define CpConstAtom_Check(op) PyObject_TypeCheck((op), &CpConstAtom_Type) + +static inline CpConstAtomObject* +CpConstAtom_New(PyObject* value, PyObject* atom) +{ + return (CpConstAtomObject*)CpObject_Create( + &CpConstAtom_Type, "OO", atom, value); +} + +#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/enum.h b/src/caterpillar/include/caterpillar/atoms/enum.h new file mode 100644 index 0000000..d1ea921 --- /dev/null +++ b/src/caterpillar/include/caterpillar/atoms/enum.h @@ -0,0 +1,50 @@ +/** + * Copyright (C) MatrixEditor 2024 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef ENUMATOMOBJ_H +#define ENUMATOMOBJ_H + +#include "caterpillar/atoms/builtins.h" + +struct _enumatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores the mapping of the underlying enum type. + PyObject* m_members; + + PyObject* m_value2member_map; + PyObject* m_default; + + /// Reference to the atom that is responsible for + /// parsing the enum value + PyObject* m_atom; + + /// Reference to the enum type + PyObject* m_enum_type; +}; + +#define CpEnumAtom_CheckExact(op) Py_IS_TYPE((op), &CpEnumAtom_Type) +#define CpEnumAtom_Check(op) (PyObject_TypeCheck((op), &CpEnumAtom_Type)) + +static inline CpEnumAtomObject* +CpEnumAtom_New(PyObject* members, PyObject* atom) +{ + return (CpEnumAtomObject*)CpObject_Create( + &CpEnumAtom_Type, "OO", members, atom); +} + +#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/float.h b/src/caterpillar/include/caterpillar/atoms/float.h index 5aac58a..11bf6f7 100644 --- a/src/caterpillar/include/caterpillar/atoms/float.h +++ b/src/caterpillar/include/caterpillar/atoms/float.h @@ -17,12 +17,11 @@ #ifndef FLOATATOMOBJ_H #define FLOATATOMOBJ_H -#include "caterpillar/caterpillarapi.h" -#include "caterpillar/field.h" +#include "caterpillar/atoms/builtins.h" struct _floatatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD /// Stores the amount of bytes this float atom /// has in total @@ -35,18 +34,7 @@ struct _floatatomobj int _m_little_endian; }; -// PyAPI_DATA(PyTypeObject) CpFloatAtom_Type; - -/** @brief Checks if the given object is an integer atom object */ #define CpFloatAtom_CheckExact(op) Py_IS_TYPE((op), &CpFloatAtom_Type) -/** @brief Checks if the given object is an integer atom object */ -#define CpFloatAtom_Check(op) (PyObject_IsInstance((op), &CpFloatAtom_Type)) - -PyAPI_FUNC(int) CpFloatAtom_Pack(CpFloatAtomObject* self, - PyObject* value, - CpLayerObject* layer); - -PyAPI_FUNC(PyObject*) - CpFloatAtom_Unpack(CpFloatAtomObject* self, CpLayerObject* layer); +#define CpFloatAtom_Check(op) (PyObject_TypeCheck((op), &CpFloatAtom_Type)) #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/int.h b/src/caterpillar/include/caterpillar/atoms/int.h index b5a79f9..5776fde 100644 --- a/src/caterpillar/include/caterpillar/atoms/int.h +++ b/src/caterpillar/include/caterpillar/atoms/int.h @@ -17,12 +17,11 @@ #ifndef INTATOMOBJ_H #define INTATOMOBJ_H -#include "caterpillar/caterpillarapi.h" -#include "caterpillar/field.h" +#include "caterpillar/atoms/builtins.h" struct _intatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD /// Stores the amount of bits this integer atom /// has in total @@ -40,17 +39,36 @@ struct _intatomobj int _m_little_endian; }; -/// Integer atom object type -// PyAPI_DATA(PyTypeObject) CpIntAtom_Type; - /** @brief Checks if the given object is an integer atom object */ #define CpIntAtom_CheckExact(op) Py_IS_TYPE((op), &CpIntAtom_Type) /** @brief Checks if the given object is an integer atom object */ -#define CpIntAtom_Check(op) (PyObject_IsInstance((op), &CpIntAtom_Type)) +#define CpIntAtom_Check(op) (PyObject_TypeCheck((op), &CpIntAtom_Type)) + +//------------------------------------------------------------------------------ +// varint atom + +struct _varintatomobj +{ + CpBuiltinAtom_HEAD + + /// Stores whether or not the integer is little endian + int _m_little_endian; + + /// Specifies that the last significant byte will use a ``1`` to identify + /// the end of the varint. Otherwise, zero will be used (which is the + /// default setting). + int _m_lsb; +}; + +#define CpVarIntAtom_CheckExact(op) Py_IS_TYPE((op), &CpVarIntAtom_Type) +#define CpVarIntAtom_Check(op) (PyObject_TypeCheck((op), &CpVarIntAtom_Type)) + +static inline CpVarIntAtomObject* +CpVarIntAtom_New(bool little_endian, bool lsb) +{ + return (CpVarIntAtomObject*)CpObject_Create( + &CpVarIntAtom_Type, "II", little_endian, lsb); +} -PyAPI_FUNC(int) - CpIntAtom_Pack(CpIntAtomObject* self, PyObject* value, CpLayerObject* layer); -PyAPI_FUNC(PyObject*) - CpIntAtom_Unpack(CpIntAtomObject* self, CpLayerObject* layer); #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/primitive.h b/src/caterpillar/include/caterpillar/atoms/primitive.h index f5c5668..d030fc1 100644 --- a/src/caterpillar/include/caterpillar/atoms/primitive.h +++ b/src/caterpillar/include/caterpillar/atoms/primitive.h @@ -17,8 +17,22 @@ #ifndef PRIMITIVEATOMOBJ_H #define PRIMITIVEATOMOBJ_H -#include "caterpillar/caterpillarapi.h" -#include "caterpillar/field.h" +#include "caterpillar/atoms/builtins.h" + +//------------------------------------------------------------------------------ +// primitive atom +struct _primitiveatomobj +{ + CpAtom_HEAD +}; + +#define CpPrimitiveAtom_NAME "patom" +#define CpPrimitiveAtom_CheckExact(op) Py_IS_TYPE((op), &CpPrimitiveAtom_Type) +#define CpPrimitiveAtom_Check(op) \ + (PyObject_TypeCheck((op), &CpPrimitiveAtom_Type)) + +//------------------------------------------------------------------------------ +// Bool /// @struct _boolatomobj /// @brief Struct representing a bool atom object. @@ -27,7 +41,7 @@ /// family. struct _boolatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD }; /// @brief Bool atom object type @@ -58,81 +72,103 @@ struct _boolatomobj * @return True if the object is of type CpBoolAtom_Type or a subtype, * false otherwise. */ -#define CpBoolAtom_Check(op) (PyObject_IsInstance((op), &CpBoolAtom_Type)) - -/** - * @brief Packs a value into the underlying stream. - * - * @param self The bool atom object - * @param value The boolean value to be packed. - * @param layer The layer object providing the context for packing. - * @return Integer status code (typically 0 for success, non-zero for error). - */ -PyAPI_FUNC(int) CpBoolAtom_Pack(CpBoolAtomObject* self, - PyObject* value, - CpLayerObject* layer); - -/** - * @brief Unpacks a value from the underlying stream. - * - * @param self The bool atom object instance. - * @param layer The layer object providing the context for unpacking. - * @return The unpacked boolean value as a PyObject. - */ -PyAPI_FUNC(PyObject*) - CpBoolAtom_Unpack(CpBoolAtomObject* self, CpLayerObject* layer); +#define CpBoolAtom_Check(op) (PyObject_TypeCheck((op), &CpBoolAtom_Type)) //------------------------------------------------------------------------------ // Char Atom struct _charatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD }; -/// Char atom object type -// PyAPI_DATA(PyTypeObject) CpCharAtom_Type; - /** @brief Checks if the given object is a char atom object */ #define CpCharAtom_CheckExact(op) Py_IS_TYPE((op), &CpCharAtom_Type) /** @brief Checks if the given object is a char atom object */ -#define CpCharAtom_Check(op) (PyObject_IsInstance((op), &CpCharAtom_Type)) - -PyAPI_FUNC(int) CpCharAtom_Pack(CpCharAtomObject* self, - PyObject* value, - CpLayerObject* layer); - -PyAPI_FUNC(PyObject*) - CpCharAtom_Unpack(CpCharAtomObject* self, CpLayerObject* layer); +#define CpCharAtom_Check(op) (PyObject_TypeCheck((op), &CpCharAtom_Type)) //------------------------------------------------------------------------------ // Padding struct _paddingatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD - char padding; + char _m_padding; }; -/// Padding atom object type -// PyAPI_DATA(PyTypeObject) CpPaddingAtom_Type; - -/** @brief Checks if the given object is a padding atom object */ #define CpPaddingAtom_CheckExact(op) Py_IS_TYPE((op), &CpPaddingAtom_Type) -/** @brief Checks if the given object is a padding atom object */ -#define CpPaddingAtom_Check(op) (PyObject_IsInstance((op), &CpPaddingAtom_Type)) +#define CpPaddingAtom_Check(op) (PyObject_TypeCheck((op), &CpPaddingAtom_Type)) + +//------------------------------------------------------------------------------ +// Computed + +struct _computedatomobj +{ + CpBuiltinAtom_HEAD + + PyObject* m_value; + int s_callable; +}; + +#define CpComputedAtom_CheckExact(op) Py_IS_TYPE((op), &CpComputedAtom_Type) +#define CpComputedAtom_Check(op) \ + (PyObject_TypeCheck((op), &CpComputedAtom_Type)) + +#define CpComputedAtom_VALUE(op) (((CpComputedAtomObject*)(op))->m_value) + +/*CpAPI*/ +static inline PyObject* +CpComputedAtom_Value(CpComputedAtomObject* self, CpLayerObject* layer) +{ + return self->s_callable ? PyObject_CallOneArg(self->m_value, (PyObject*)layer) + : Py_NewRef(self->m_value); +} + +/*CpAPI*/ +static inline CpComputedAtomObject* +CpComputedAtom_New(PyObject* value) +{ + return (CpComputedAtomObject*)CpObject_Create( + &CpComputedAtom_Type, "O", value); +} + +//------------------------------------------------------------------------------ +// Lazy + +struct _lazyatomobj +{ + CpBuiltinAtom_HEAD -PyAPI_FUNC(int) CpPaddingAtom_Pack(CpPaddingAtomObject* self, - PyObject* _, - CpLayerObject* layer); + PyObject* m_fn; + PyObject* m_atom; + int s_always_lazy; +}; + +#define CpLazyAtom_CheckExact(op) Py_IS_TYPE((op), &CpLazyAtom_Type) +#define CpLazyAtom_Check(op) (PyObject_TypeCheck((op), &CpLazyAtom_Type)) +#define CpLazyAtom_ATOM(op) (((CpLazyAtomObject*)(op))->m_atom) -PyAPI_FUNC(int) CpPaddingAtom_PackMany(CpPaddingAtomObject* self, - PyObject* _, - CpLayerObject* layer); +/*CpAPI*/ +static inline PyObject* +CpLazyAtom_Atom(CpLazyAtomObject* self) +{ + if (self->s_always_lazy) + return PyObject_CallNoArgs(self->m_fn); -PyAPI_FUNC(PyObject*) - CpPaddingAtom_Unpack(CpPaddingAtomObject* self, CpLayerObject* layer); + if (!self->m_atom) { + Py_XSETREF(self->m_atom, PyObject_CallNoArgs(self->m_fn)); + if (!self->m_atom) + return NULL; + } -PyAPI_FUNC(PyObject*) - CpPaddingAtom_UnpackMany(CpPaddingAtomObject* self, CpLayerObject* layer); + return Py_NewRef(self->m_atom); +} + +/*CpAPI*/ +static inline CpLazyAtomObject* +CpLazyAtom_New(PyObject* fn, int always_lazy) +{ + return (CpLazyAtomObject*)CpObject_Create( + &CpLazyAtom_Type, "Oi", fn, always_lazy); +} #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/atoms/string.h b/src/caterpillar/include/caterpillar/atoms/string.h index 6b9ea98..9fd5b30 100644 --- a/src/caterpillar/include/caterpillar/atoms/string.h +++ b/src/caterpillar/include/caterpillar/atoms/string.h @@ -17,28 +17,125 @@ #ifndef STRINGATOMOBJ_H #define STRINGATOMOBJ_H -#include "caterpillar/caterpillarapi.h" -#include "caterpillar/field.h" +#include "caterpillar/atoms/builtins.h" +#include "caterpillar/parsing.h" +// --------------------------------------------------------------------------- +// Default String struct _stringatomobj { - CpFieldCAtom_HEAD + CpBuiltinAtom_HEAD + /// The length of this string. It can be a constant integer + /// object or a ContextLambda. PyObject* m_length; + + /// Whether this string should ignore errors when parsing PyObject* m_errors; + + /// The encoding of this string PyObject* m_encoding; }; -// PyAPI_DATA(PyTypeObject) CpStringAtom_Type; - #define CpStringAtom_CheckExact(op) Py_IS_TYPE((op), &CpStringAtom_Type) #define CpStringAtom_Check(op) PyObject_TypeCheck((op), &CpStringAtom_Type) -PyAPI_FUNC(int) CpStringAtom_Pack(CpStringAtomObject* self, - PyObject* value, - CpLayerObject* layer); +// TODO: CString, PString + +// --------------------------------------------------------------------------- +// Bytes +struct _bytesatomobj +{ + CpBuiltinAtom_HEAD + + /// The length of this string. It can be a constant integer + /// object or a ContextLambda. + PyObject* m_length; + + // -- internal --- + int s_callable; +}; + +// REVISIT: The name of this atom should be something similar to 'bytes' +#define CpBytesAtom_CheckExact(op) Py_IS_TYPE((op), &CpBytesAtom_Type) +#define CpBytesAtom_Check(op) PyObject_TypeCheck((op), &CpBytesAtom_Type) + +static inline CpBytesAtomObject* +CpBytesAtom_New(PyObject* length) +{ + return (CpBytesAtomObject*)CpObject_CreateOneArg(&CpBytesAtom_Type, length); +} + +// --------------------------------------------------------------------------- +// PString + +struct _pstringatomobj +{ + CpBuiltinAtom_HEAD + + /// The atom that parses the length of this string + PyObject* m_atom; + + /// Whether this string should ignore errors when parsing + PyObject* m_errors; + + /// The encoding of this string + PyObject* m_encoding; +}; + +#define CpPStringAtom_CheckExact(op) Py_IS_TYPE((op), &CpPStringAtom_Type) +#define CpPStringAtom_Check(op) PyObject_TypeCheck((op), &CpPStringAtom_Type) -PyAPI_FUNC(PyObject*) - CpStringAtom_Unpack(CpStringAtomObject* self, CpLayerObject* layer); +static inline CpPStringAtomObject* +CpPStringAtom_New(PyObject* atom, PyObject* encoding) +{ + return (CpPStringAtomObject*)CpObject_Create( + &CpPStringAtom_Type, "OO", atom, encoding); +} + +// --------------------------------------------------------------------------- +// CString + +struct _cstringatomobj +{ + CpBuiltinAtom_HEAD + + PyObject *m_length; + PyObject *m_encoding; + PyObject *m_errors; + PyObject *m_terminator; + PyObject *_m_terminator_bytes; + + int s_callable; + int s_number; + int s_keep_terminator; + int s_greedy; +}; + +#define CpCStringAtom_CheckExact(op) Py_IS_TYPE((op), &CpCStringAtom_Type) +#define CpCStringAtom_Check(op) PyObject_TypeCheck((op), &CpCStringAtom_Type) + +static inline CpCStringAtomObject* +CpCStringAtom_New(PyObject* length, PyObject* encoding, PyObject* errors, + PyObject* terminator) +{ + return (CpCStringAtomObject*)CpObject_Create( + &CpCStringAtom_Type, "OOOO", length, encoding, errors, terminator); +} + +static inline PyObject* +CpCStringAtom_Length(CpCStringAtomObject* self, CpLayerObject *layer) +{ + if (self->s_number) { + return Py_NewRef(self->m_length); + } + if (self->s_greedy) { + Py_RETURN_NONE; + } + if (self->s_callable) { + return PyObject_CallOneArg(self->m_length, (PyObject *)layer); + } + return _Cp_Unpack(self->m_length, layer); +} #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/caterpillar.h b/src/caterpillar/include/caterpillar/caterpillar.h index c8923c2..1124a9b 100644 --- a/src/caterpillar/include/caterpillar/caterpillar.h +++ b/src/caterpillar/include/caterpillar/caterpillar.h @@ -11,13 +11,17 @@ #include "caterpillar/struct.h" #include "caterpillar/context.h" #include "caterpillar/atomobj.h" -#include "caterpillar/field.h" #include "caterpillar/state.h" +#include "caterpillar/parsing.h" +#include "caterpillar/default.h" // Atom Objects +#include "caterpillar/atoms/builtins.h" #include "caterpillar/atoms/int.h" #include "caterpillar/atoms/float.h" #include "caterpillar/atoms/primitive.h" #include "caterpillar/atoms/string.h" +#include "caterpillar/atoms/const.h" +#include "caterpillar/atoms/enum.h" #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/caterpillarapi.h b/src/caterpillar/include/caterpillar/caterpillarapi.h index 06d440d..916e0e0 100644 --- a/src/caterpillar/include/caterpillar/caterpillarapi.h +++ b/src/caterpillar/include/caterpillar/caterpillarapi.h @@ -28,14 +28,14 @@ /* struct and typedefs */ struct _modulestate; typedef struct _modulestate _modulestate; -struct _endianobj; -typedef struct _endianobj CpEndianObject; -struct _archobj; -typedef struct _archobj CpArchObject; struct _atomobj; typedef struct _atomobj CpAtomObject; struct _catomobj; typedef struct _catomobj CpCAtomObject; +struct _archobj; +typedef struct _archobj CpArchObject; +struct _endianobj; +typedef struct _endianobj CpEndianObject; struct _contextobj; typedef struct _contextobj CpContextObject; struct _unaryexpr; @@ -44,20 +44,14 @@ struct _binaryexpr; typedef struct _binaryexpr CpBinaryExprObject; struct _contextpath; typedef struct _contextpath CpContextPathObject; -struct _fieldobj; -typedef struct _fieldobj CpFieldObject; -struct _fieldatomobj; -typedef struct _fieldatomobj CpFieldAtomObject; -struct _fieldcatomobj; -typedef struct _fieldcatomobj CpFieldCAtomObject; struct _option; typedef struct _option CpOptionObject; struct _stateobj; typedef struct _stateobj CpStateObject; struct _layerobj; typedef struct _layerobj CpLayerObject; -struct CpStructFieldInfo; -typedef struct CpStructFieldInfo CpStructFieldInfoObject; +struct _fieldinfoobj; +typedef struct _fieldinfoobj CpStructFieldInfoObject; struct _structobj; typedef struct _structobj CpStructObject; struct _floatatomobj; @@ -72,6 +66,40 @@ struct _paddingatomobj; typedef struct _paddingatomobj CpPaddingAtomObject; struct _stringatomobj; typedef struct _stringatomobj CpStringAtomObject; +struct _constatomobj; +typedef struct _constatomobj CpConstAtomObject; +struct _builtinatomobj; +typedef struct _builtinatomobj CpBuiltinAtomObject; +struct _repeatedatomobj; +typedef struct _repeatedatomobj CpRepeatedAtomObject; +struct _seqlayerobj; +typedef struct _seqlayerobj CpSeqLayerObject; +struct _objlayerobj; +typedef struct _objlayerobj CpObjLayerObject; +struct _conditionatomobj; +typedef struct _conditionatomobj CpConditionAtomObject; +struct _switchatomobj; +typedef struct _switchatomobj CpSwitchAtomObject; +struct _offsetatomobj; +typedef struct _offsetatomobj CpOffsetAtomObject; +struct _primitiveatomobj; +typedef struct _primitiveatomobj CpPrimitiveAtomObject; +struct _lengthinfoobj; +typedef struct _lengthinfoobj CpLengthInfoObject; +struct _bytesatomobj; +typedef struct _bytesatomobj CpBytesAtomObject; +struct _pstringatomobj; +typedef struct _pstringatomobj CpPStringAtomObject; +struct _enumatomobj; +typedef struct _enumatomobj CpEnumAtomObject; +struct _varintatomobj; +typedef struct _varintatomobj CpVarIntAtomObject; +struct _computedatomobj; +typedef struct _computedatomobj CpComputedAtomObject; +struct _lazyatomobj; +typedef struct _lazyatomobj CpLazyAtomObject; +struct _cstringatomobj; +typedef struct _cstringatomobj CpCStringAtomObject; #ifdef _CPMODULE @@ -81,63 +109,99 @@ are then used and implemented in the internal API. */ extern PyModuleDef CpModule; +extern PyTypeObject CpAtom_Type; +#define CpAtom_NAME "atom" extern PyTypeObject CpCAtom_Type; +#define CpCAtom_NAME "catom" extern PyTypeObject CpArch_Type; +#define CpArch_NAME "Arch" extern PyTypeObject CpEndian_Type; +#define CpEndian_NAME "Endian" extern PyTypeObject CpContext_Type; +#define CpContext_NAME "Context" extern PyTypeObject CpUnaryExpr_Type; +#define CpUnaryExpr_NAME "unaryexpr" extern PyTypeObject CpBinaryExpr_Type; +#define CpBinaryExpr_NAME "binaryexpr" extern PyTypeObject CpContextPath_Type; -extern PyTypeObject CpField_Type; -extern PyTypeObject CpFieldAtom_Type; -extern PyTypeObject CpFieldCAtom_Type; +#define CpContextPath_NAME "ContextPath" extern PyTypeObject CpInvalidDefault_Type; extern PyTypeObject CpDefaultOption_Type; extern PyObject _CpInvalidDefault_Object; extern PyObject _CpDefaultOption_Object; -extern PyTypeObject CpAtom_Type; extern PyTypeObject CpOption_Type; +#define CpOption_NAME "Option" extern PyTypeObject CpState_Type; +#define CpState_NAME "State" extern PyTypeObject CpLayer_Type; +#define CpLayer_NAME "layer" extern PyTypeObject CpStructFieldInfo_Type; +#define CpStructFieldInfo_NAME "fieldinfo" extern PyTypeObject CpStruct_Type; +#define CpStruct_NAME "Struct" extern PyTypeObject CpFloatAtom_Type; +#define CpFloatAtom_NAME "Float" extern PyTypeObject CpIntAtom_Type; +#define CpIntAtom_NAME "Int" extern PyTypeObject CpBoolAtom_Type; +#define CpBoolAtom_NAME "Bool" extern PyTypeObject CpCharAtom_Type; +#define CpCharAtom_NAME "Char" extern PyTypeObject CpPaddingAtom_Type; +#define CpPaddingAtom_NAME "Padding" extern PyTypeObject CpStringAtom_Type; +#define CpStringAtom_NAME "string" +extern PyTypeObject CpConstAtom_Type; +#define CpConstAtom_NAME "const" +extern PyTypeObject CpBuiltinAtom_Type; +#define CpBuiltinAtom_NAME "builtinatom" +extern PyTypeObject CpRepeatedAtom_Type; +#define CpRepeatedAtom_NAME "repeated" +extern PyTypeObject CpSeqLayer_Type; +#define CpSeqLayer_NAME "seqlayer" +extern PyTypeObject CpObjLayer_Type; +#define CpObjLayer_NAME "objlayer" +extern PyTypeObject CpConditionAtom_Type; +#define CpConditionAtom_NAME "condition" +extern PyTypeObject CpSwitchAtom_Type; +#define CpSwitchAtom_NAME "switch" +extern PyTypeObject CpOffsetAtom_Type; +#define CpOffsetAtom_NAME "atoffset" +extern PyTypeObject CpPrimitiveAtom_Type; +#define CpPrimitiveAtom_NAME "patom" +extern PyTypeObject CpLengthInfo_Type; +#define CpLengthInfo_NAME "lengthinfo" +extern PyTypeObject CpBytesAtom_Type; +#define CpBytesAtom_NAME "octetstring" +extern PyTypeObject CpPStringAtom_Type; +#define CpPStringAtom_NAME "pstring" +extern PyTypeObject CpEnumAtom_Type; +#define CpEnumAtom_NAME "enumeration" +extern PyTypeObject CpVarIntAtom_Type; +#define CpVarIntAtom_NAME "VarInt" +extern PyTypeObject CpComputedAtom_Type; +#define CpComputedAtom_NAME "computed" +extern PyTypeObject CpLazyAtom_Type; +#define CpLazyAtom_NAME "lazy" +extern PyTypeObject CpCStringAtom_Type; +#define CpCStringAtom_NAME "cstring" int CpEndian_IsLittleEndian(CpEndianObject* endian, _modulestate* mod); CpContextObject* CpContext_New(void); CpUnaryExprObject* CpUnaryExpr_New(int op, PyObject* value); CpBinaryExprObject* CpBinaryExpr_New(int op, PyObject* left, PyObject* right); CpContextPathObject* CpContextPath_New(PyObject* path); CpContextPathObject* CpContextPath_FromString(const char* path); -CpFieldObject* CpField_New(PyObject* atom); -int CpField_HasCondition(CpFieldObject* self); -int CpField_IsEnabled(CpFieldObject* self, PyObject* context); -Py_ssize_t CpField_GetOffset(CpFieldObject* self, PyObject* context); -PyObject* CpField_EvalSwitch(CpFieldObject* self, PyObject* op, PyObject* context); -PyObject* CpField_GetLength(CpFieldObject* self, PyObject* context); +PyObject* CpTypeMap_Lookup(PyObject* annotation, _modulestate* mod); +int CpTypeMap_Register(PyObject* annotation, PyObject* handler, _modulestate* mod); PyObject* CpTypeOf(PyObject* op); -PyObject* CpTypeOf_Field(CpFieldObject* op); PyObject* CpTypeOf_Common(PyObject* op); int CpPack(PyObject* op, PyObject* atom, PyObject* io, PyObject* globals); -int CpPack_Field(PyObject* op, CpFieldObject* field, CpLayerObject* layer); -int CpPack_Common(PyObject* op, PyObject* atom, CpLayerObject* layer); -int CpPack_Struct(PyObject* op, CpStructObject* struct_, CpLayerObject* layer); -int _Cp_Pack(PyObject* op, PyObject* atom, CpLayerObject* layer); int _CpPack_EvalLength(CpLayerObject* layer,PyObject* length,Py_ssize_t size,bool* greedy,Py_ssize_t* dstLength); PyObject* CpSizeOf(PyObject* op, PyObject* globals); -PyObject* CpSizeOf_Field(CpFieldObject* field, CpLayerObject* layer); PyObject* CpSizeOf_Struct(CpStructObject* struct_, CpLayerObject* layer); PyObject* CpSizeOf_Common(PyObject* op, CpLayerObject* layer); PyObject* _Cp_SizeOf(PyObject* op, CpLayerObject* layer); PyObject* CpUnpack(PyObject* atom, PyObject* io, PyObject* globals); -PyObject* CpUnpack_Field(CpFieldObject* field, CpLayerObject* layer); -PyObject* CpUnpack_Common(PyObject* op, CpLayerObject* layer); -PyObject* CpUnpack_Struct(CpStructObject* struct_, CpLayerObject* layer); -PyObject* _Cp_Unpack(PyObject* atom, CpLayerObject* layer); int _CpUnpack_EvalLength(CpLayerObject* layer,PyObject* length,bool* seq_greedy,Py_ssize_t* seq_length); PyObject* CpUnpack_CAtom(CpCAtomObject* catom, CpLayerObject* layer); int CpPack_CAtom(PyObject* op, CpCAtomObject* catom, CpLayerObject* layer); @@ -145,23 +209,75 @@ PyObject* CpSizeOf_CAtom(CpCAtomObject* catom, CpLayerObject* layer); PyObject * CpTypeOf_CAtom(CpCAtomObject* op); CpStateObject* CpState_New(PyObject* io); PyObject* CpState_Tell(CpStateObject* self); -PyObject* CpState_Seek(CpStateObject* self, PyObject* offset, int whence); -PyObject* CpState_Read(CpStateObject* self, Py_ssize_t size); +PyObject* CpState_Seek(CpStateObject* self, PyObject* offset, PyObject* whence); +PyObject* CpState_Read(CpStateObject* self, PyObject* sizeobj); +PyObject* CpState_ReadSsize_t(CpStateObject* self, Py_ssize_t size); PyObject* CpState_ReadFully(CpStateObject* self); PyObject* CpState_Write(CpStateObject* self, PyObject* value); int CpState_SetGlobals(CpStateObject* self, PyObject* globals); CpLayerObject* CpLayer_New(CpStateObject* state, CpLayerObject* parent); int CpLayer_Invalidate(CpLayerObject* self); -int CpLayer_SetSequence(CpLayerObject* self,PyObject* sequence,Py_ssize_t length,int8_t greedy); -CpStructFieldInfoObject* CpStructFieldInfo_New(CpFieldObject* field); +CpStructFieldInfoObject* CpStructFieldInfo_New(PyObject* name, PyObject* field); int CpStruct_AddFieldInfo(CpStructObject* o, CpStructFieldInfoObject* info); -int CpStruct_AddField(CpStructObject* o, CpFieldObject* field, int exclude); CpStructObject* CpStruct_New(PyObject* model); PyObject* CpStruct_GetAnnotations(CpStructObject* o, int eval); int CpStruct_ReplaceType(CpStructObject* o, PyObject* name, PyObject* type); int CpStruct_HasOption(CpStructObject* o, PyObject* option); +int CpStruct_Pack(CpStructObject* self, PyObject* obj, CpLayerObject* layer); +PyObject* CpStruct_Unpack(CpStructObject* self, CpLayerObject* layer); +PyObject* CpStruct_SizeOf(CpStructObject* self, CpLayerObject* layer); int CpStructModel_Check(PyObject* model, _modulestate* state); PyObject* CpStructModel_GetStruct(PyObject* model, _modulestate* state); +CpSeqLayerObject* CpSeqLayer_New(CpStateObject* state, CpLayerObject* parent); +int CpSeqLayer_SetSequence(CpSeqLayerObject* self,PyObject* sequence,Py_ssize_t length,int8_t greedy); +CpObjLayerObject* CpObjLayer_New(CpStateObject* state, CpLayerObject* parent); +int CpIntAtom_Pack(CpIntAtomObject* self, PyObject* op, CpLayerObject* layer); +PyObject* CpIntAtom_Unpack(CpIntAtomObject* self, CpLayerObject* layer); +int CpFloatAtom_Pack(CpFloatAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpFloatAtom_Unpack(CpFloatAtomObject* self, CpLayerObject* layer); +int CpBoolAtom_Pack(CpBoolAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpBoolAtom_Unpack(CpBoolAtomObject* self, CpLayerObject* layer); +int CpCharAtom_Pack(CpCharAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpCharAtom_Unpack(CpCharAtomObject* self, CpLayerObject* layer); +int CpPaddingAtom_Pack(CpPaddingAtomObject* self,PyObject* value,CpLayerObject* layer); +int CpPaddingAtom_PackMany(CpPaddingAtomObject* self,PyObject* value,CpLayerObject* layer,CpLengthInfoObject* lengthinfo); +PyObject* CpPaddingAtom_Unpack(CpPaddingAtomObject* self, CpLayerObject* layer); +PyObject* CpPaddingAtom_UnpackMany(CpPaddingAtomObject* self,CpLayerObject* layer,CpLengthInfoObject* lengthinfo); +int CpStringAtom_Pack(CpStringAtomObject* self,PyObject* value,CpLayerObject* layer); +PyObject* CpStringAtom_Unpack(CpStringAtomObject* self, CpLayerObject* layer); +int CpConstAtom_Pack(CpConstAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpConstAtom_Unpack(CpConstAtomObject* self, CpLayerObject* layer); +int CpRepeatedAtom_Pack(CpRepeatedAtomObject* self,PyObject* op,CpLayerObject* layer); +PyObject* CpRepeatedAtom_Unpack(CpRepeatedAtomObject* self, CpLayerObject* layer); +PyObject* CpRepeatedAtom_GetLength(CpRepeatedAtomObject* self, PyObject* context); +int CpConditionAtom_Pack(CpConditionAtomObject* self, PyObject* op, PyObject* layer); +PyObject* CpConditionAtom_Unpack(CpConditionAtomObject* self, CpLayerObject* layer); +int CpConditionAtom_IsEnabled(CpConditionAtomObject* self, PyObject* context); +PyObject* CpSwitchAtom_GetNext(CpSwitchAtomObject* self, PyObject* op, PyObject* context); +int CpSwitchAtom_Pack(CpSwitchAtomObject* self, PyObject* obj, CpLayerObject* layer); +PyObject* CpSwitchAtom_Unpack(CpSwitchAtomObject* self, CpLayerObject* layer); +int CpOffsetAtom_Pack(CpOffsetAtomObject* self, PyObject* obj, CpLayerObject* layer); +PyObject* CpOffsetAtom_Unpack(CpOffsetAtomObject* self, CpLayerObject* layer); +PyObject* CpOffsetAtom_GetOffset(CpOffsetAtomObject* self, PyObject* layer); +PyObject* CpBytesAtom_GetLength(CpBytesAtomObject* self, CpLayerObject* layer); +int CpBytesAtom_Pack(CpBytesAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpBytesAtom_Unpack(CpBytesAtomObject* self, CpLayerObject* layer); +int CpPStringAtom_Pack(CpPStringAtomObject* self,PyObject* value,CpLayerObject* layer); +PyObject* CpPStringAtom_Unpack(CpPStringAtomObject* self, CpLayerObject* layer); +int CpEnumAtom_Pack(CpEnumAtomObject* self, PyObject* value, CpLayerObject* layer); +PyObject* CpEnumAtom_Unpack(CpEnumAtomObject* self, CpLayerObject* layer); +int CpVarIntAtom_Pack(CpVarIntAtomObject* self,PyObject* value,CpLayerObject* layer); +PyObject* CpVarIntAtom_Unpack(CpVarIntAtomObject* self, CpLayerObject* layer); +PyObject* CpVarIntAtom_BSwap(PyObject* number, bool little_endian); +unsigned long long CpVarIntAtom_BSwapUnsignedLongLong(unsigned long long number,bool little_endian); +long long CpVarIntAtom_BSwapLongLong(long long number, bool little_endian); +Py_ssize_t CpVarIntAtom_BSwapSsize_t(Py_ssize_t number, bool little_endian); +int CpComputedAtom_Pack(CpComputedAtomObject* self,PyObject* obj,CpLayerObject* layer); +PyObject* CpComputedAtom_Unpack(CpComputedAtomObject* self, CpLayerObject* layer); +int CpLazyAtom_Pack(CpLazyAtomObject* self, PyObject* obj, CpLayerObject* layer); +PyObject* CpLazyAtom_Unpack(CpLazyAtomObject* self, CpLayerObject* layer); +int CpCStringAtom_Pack(CpCStringAtomObject* self,PyObject* value,CpLayerObject* layer); +PyObject* CpCStringAtom_Unpack(CpCStringAtomObject* self, CpLayerObject* layer); #else @@ -172,63 +288,63 @@ internal API functions and types. Their indices are static and defined in caterpillar_api.py */ #define CpModule (*(PyModuleDef *)Cp_API[0]) -#define CpCAtom_Type (*(PyTypeObject *)Cp_API[1]) -#define CpArch_Type (*(PyTypeObject *)Cp_API[2]) -#define CpEndian_Type (*(PyTypeObject *)Cp_API[3]) -#define CpContext_Type (*(PyTypeObject *)Cp_API[4]) -#define CpUnaryExpr_Type (*(PyTypeObject *)Cp_API[5]) -#define CpBinaryExpr_Type (*(PyTypeObject *)Cp_API[6]) -#define CpContextPath_Type (*(PyTypeObject *)Cp_API[7]) -#define CpField_Type (*(PyTypeObject *)Cp_API[8]) -#define CpFieldAtom_Type (*(PyTypeObject *)Cp_API[9]) -#define CpFieldCAtom_Type (*(PyTypeObject *)Cp_API[10]) -#define CpInvalidDefault_Type (*(PyTypeObject *)Cp_API[11]) -#define CpDefaultOption_Type (*(PyTypeObject *)Cp_API[12]) -#define _CpInvalidDefault_Object (*(PyObject *)Cp_API[13]) -#define _CpDefaultOption_Object (*(PyObject *)Cp_API[14]) -#define CpAtom_Type (*(PyTypeObject *)Cp_API[15]) -#define CpOption_Type (*(PyTypeObject *)Cp_API[16]) -#define CpState_Type (*(PyTypeObject *)Cp_API[17]) -#define CpLayer_Type (*(PyTypeObject *)Cp_API[18]) -#define CpStructFieldInfo_Type (*(PyTypeObject *)Cp_API[19]) -#define CpStruct_Type (*(PyTypeObject *)Cp_API[20]) -#define CpFloatAtom_Type (*(PyTypeObject *)Cp_API[21]) -#define CpIntAtom_Type (*(PyTypeObject *)Cp_API[22]) -#define CpBoolAtom_Type (*(PyTypeObject *)Cp_API[23]) -#define CpCharAtom_Type (*(PyTypeObject *)Cp_API[24]) -#define CpPaddingAtom_Type (*(PyTypeObject *)Cp_API[25]) -#define CpStringAtom_Type (*(PyTypeObject *)Cp_API[26]) +#define CpAtom_Type (*(atom *)Cp_API[1]) +#define CpCAtom_Type (*(catom *)Cp_API[2]) +#define CpArch_Type (*(Arch *)Cp_API[3]) +#define CpEndian_Type (*(Endian *)Cp_API[4]) +#define CpContext_Type (*(Context *)Cp_API[5]) +#define CpUnaryExpr_Type (*(unaryexpr *)Cp_API[6]) +#define CpBinaryExpr_Type (*(binaryexpr *)Cp_API[7]) +#define CpContextPath_Type (*(ContextPath *)Cp_API[8]) +#define CpInvalidDefault_Type (*(PyTypeObject *)Cp_API[12]) +#define CpDefaultOption_Type (*(PyTypeObject *)Cp_API[13]) +#define _CpInvalidDefault_Object (*(PyObject *)Cp_API[14]) +#define _CpDefaultOption_Object (*(PyObject *)Cp_API[15]) +#define CpOption_Type (*(Option *)Cp_API[16]) +#define CpState_Type (*(State *)Cp_API[17]) +#define CpLayer_Type (*(layer *)Cp_API[18]) +#define CpStructFieldInfo_Type (*(fieldinfo *)Cp_API[19]) +#define CpStruct_Type (*(Struct *)Cp_API[20]) +#define CpFloatAtom_Type (*(Float *)Cp_API[21]) +#define CpIntAtom_Type (*(Int *)Cp_API[22]) +#define CpBoolAtom_Type (*(Bool *)Cp_API[23]) +#define CpCharAtom_Type (*(Char *)Cp_API[24]) +#define CpPaddingAtom_Type (*(Padding *)Cp_API[25]) +#define CpStringAtom_Type (*(string *)Cp_API[26]) +#define CpConstAtom_Type (*(const *)Cp_API[27]) +#define CpBuiltinAtom_Type (*(builtinatom *)Cp_API[28]) +#define CpRepeatedAtom_Type (*(repeated *)Cp_API[29]) +#define CpSeqLayer_Type (*(seqlayer *)Cp_API[30]) +#define CpObjLayer_Type (*(objlayer *)Cp_API[31]) +#define CpConditionAtom_Type (*(condition *)Cp_API[32]) +#define CpSwitchAtom_Type (*(switch *)Cp_API[33]) +#define CpOffsetAtom_Type (*(atoffset *)Cp_API[34]) +#define CpPrimitiveAtom_Type (*(patom *)Cp_API[35]) +#define CpLengthInfo_Type (*(lengthinfo *)Cp_API[36]) +#define CpBytesAtom_Type (*(octetstring *)Cp_API[37]) +#define CpPStringAtom_Type (*(pstring *)Cp_API[38]) +#define CpEnumAtom_Type (*(enumeration *)Cp_API[39]) +#define CpVarIntAtom_Type (*(VarInt *)Cp_API[40]) +#define CpComputedAtom_Type (*(computed *)Cp_API[41]) +#define CpLazyAtom_Type (*(lazy *)Cp_API[42]) +#define CpCStringAtom_Type (*(cstring *)Cp_API[43]) #define CpEndian_IsLittleEndian (*((int (*)(CpEndianObject* endian, _modulestate* mod)))Cp_API[50]) #define CpContext_New (*((CpContextObject* (*)(void)))Cp_API[53]) #define CpUnaryExpr_New (*((CpUnaryExprObject* (*)(int op, PyObject* value)))Cp_API[54]) #define CpBinaryExpr_New (*((CpBinaryExprObject* (*)(int op, PyObject* left, PyObject* right)))Cp_API[55]) #define CpContextPath_New (*((CpContextPathObject* (*)(PyObject* path)))Cp_API[56]) #define CpContextPath_FromString (*((CpContextPathObject* (*)(const char* path)))Cp_API[57]) -#define CpField_New (*((CpFieldObject* (*)(PyObject* atom)))Cp_API[58]) -#define CpField_HasCondition (*((int (*)(CpFieldObject* self)))Cp_API[59]) -#define CpField_IsEnabled (*((int (*)(CpFieldObject* self, PyObject* context)))Cp_API[60]) -#define CpField_GetOffset (*((Py_ssize_t (*)(CpFieldObject* self, PyObject* context)))Cp_API[61]) -#define CpField_EvalSwitch (*((PyObject* (*)(CpFieldObject* self, PyObject* op, PyObject* context)))Cp_API[62]) -#define CpField_GetLength (*((PyObject* (*)(CpFieldObject* self, PyObject* context)))Cp_API[63]) +#define CpTypeMap_Lookup (*((PyObject* (*)(PyObject* annotation, _modulestate* mod)))Cp_API[58]) +#define CpTypeMap_Register (*((int (*)(PyObject* annotation, PyObject* handler, _modulestate* mod)))Cp_API[59]) #define CpTypeOf (*((PyObject* (*)(PyObject* op)))Cp_API[64]) -#define CpTypeOf_Field (*((PyObject* (*)(CpFieldObject* op)))Cp_API[65]) #define CpTypeOf_Common (*((PyObject* (*)(PyObject* op)))Cp_API[66]) #define CpPack (*((int (*)(PyObject* op, PyObject* atom, PyObject* io, PyObject* globals)))Cp_API[67]) -#define CpPack_Field (*((int (*)(PyObject* op, CpFieldObject* field, CpLayerObject* layer)))Cp_API[68]) -#define CpPack_Common (*((int (*)(PyObject* op, PyObject* atom, CpLayerObject* layer)))Cp_API[69]) -#define CpPack_Struct (*((int (*)(PyObject* op, CpStructObject* struct_, CpLayerObject* layer)))Cp_API[70]) -#define _Cp_Pack (*((int (*)(PyObject* op, PyObject* atom, CpLayerObject* layer)))Cp_API[71]) #define _CpPack_EvalLength (*((int (*)(CpLayerObject* layer,PyObject* length,Py_ssize_t size,bool* greedy,Py_ssize_t* dstLength)))Cp_API[72]) #define CpSizeOf (*((PyObject* (*)(PyObject* op, PyObject* globals)))Cp_API[73]) -#define CpSizeOf_Field (*((PyObject* (*)(CpFieldObject* field, CpLayerObject* layer)))Cp_API[74]) #define CpSizeOf_Struct (*((PyObject* (*)(CpStructObject* struct_, CpLayerObject* layer)))Cp_API[75]) #define CpSizeOf_Common (*((PyObject* (*)(PyObject* op, CpLayerObject* layer)))Cp_API[76]) #define _Cp_SizeOf (*((PyObject* (*)(PyObject* op, CpLayerObject* layer)))Cp_API[77]) #define CpUnpack (*((PyObject* (*)(PyObject* atom, PyObject* io, PyObject* globals)))Cp_API[78]) -#define CpUnpack_Field (*((PyObject* (*)(CpFieldObject* field, CpLayerObject* layer)))Cp_API[79]) -#define CpUnpack_Common (*((PyObject* (*)(PyObject* op, CpLayerObject* layer)))Cp_API[80]) -#define CpUnpack_Struct (*((PyObject* (*)(CpStructObject* struct_, CpLayerObject* layer)))Cp_API[81]) -#define _Cp_Unpack (*((PyObject* (*)(PyObject* atom, CpLayerObject* layer)))Cp_API[82]) #define _CpUnpack_EvalLength (*((int (*)(CpLayerObject* layer,PyObject* length,bool* seq_greedy,Py_ssize_t* seq_length)))Cp_API[83]) #define CpUnpack_CAtom (*((PyObject* (*)(CpCAtomObject* catom, CpLayerObject* layer)))Cp_API[84]) #define CpPack_CAtom (*((int (*)(PyObject* op, CpCAtomObject* catom, CpLayerObject* layer)))Cp_API[85]) @@ -236,23 +352,75 @@ caterpillar_api.py #define CpTypeOf_CAtom (*((PyObject * (*)(CpCAtomObject* op)))Cp_API[87]) #define CpState_New (*((CpStateObject* (*)(PyObject* io)))Cp_API[88]) #define CpState_Tell (*((PyObject* (*)(CpStateObject* self)))Cp_API[89]) -#define CpState_Seek (*((PyObject* (*)(CpStateObject* self, PyObject* offset, int whence)))Cp_API[90]) -#define CpState_Read (*((PyObject* (*)(CpStateObject* self, Py_ssize_t size)))Cp_API[91]) -#define CpState_ReadFully (*((PyObject* (*)(CpStateObject* self)))Cp_API[92]) -#define CpState_Write (*((PyObject* (*)(CpStateObject* self, PyObject* value)))Cp_API[93]) -#define CpState_SetGlobals (*((int (*)(CpStateObject* self, PyObject* globals)))Cp_API[94]) -#define CpLayer_New (*((CpLayerObject* (*)(CpStateObject* state, CpLayerObject* parent)))Cp_API[95]) -#define CpLayer_Invalidate (*((int (*)(CpLayerObject* self)))Cp_API[96]) -#define CpLayer_SetSequence (*((int (*)(CpLayerObject* self,PyObject* sequence,Py_ssize_t length,int8_t greedy)))Cp_API[97]) -#define CpStructFieldInfo_New (*((CpStructFieldInfoObject* (*)(CpFieldObject* field)))Cp_API[98]) -#define CpStruct_AddFieldInfo (*((int (*)(CpStructObject* o, CpStructFieldInfoObject* info)))Cp_API[99]) -#define CpStruct_AddField (*((int (*)(CpStructObject* o, CpFieldObject* field, int exclude)))Cp_API[100]) -#define CpStruct_New (*((CpStructObject* (*)(PyObject* model)))Cp_API[101]) -#define CpStruct_GetAnnotations (*((PyObject* (*)(CpStructObject* o, int eval)))Cp_API[102]) -#define CpStruct_ReplaceType (*((int (*)(CpStructObject* o, PyObject* name, PyObject* type)))Cp_API[103]) -#define CpStruct_HasOption (*((int (*)(CpStructObject* o, PyObject* option)))Cp_API[104]) -#define CpStructModel_Check (*((int (*)(PyObject* model, _modulestate* state)))Cp_API[105]) -#define CpStructModel_GetStruct (*((PyObject* (*)(PyObject* model, _modulestate* state)))Cp_API[106]) +#define CpState_Seek (*((PyObject* (*)(CpStateObject* self, PyObject* offset, PyObject* whence)))Cp_API[90]) +#define CpState_Read (*((PyObject* (*)(CpStateObject* self, PyObject* sizeobj)))Cp_API[91]) +#define CpState_ReadSsize_t (*((PyObject* (*)(CpStateObject* self, Py_ssize_t size)))Cp_API[92]) +#define CpState_ReadFully (*((PyObject* (*)(CpStateObject* self)))Cp_API[93]) +#define CpState_Write (*((PyObject* (*)(CpStateObject* self, PyObject* value)))Cp_API[94]) +#define CpState_SetGlobals (*((int (*)(CpStateObject* self, PyObject* globals)))Cp_API[95]) +#define CpLayer_New (*((CpLayerObject* (*)(CpStateObject* state, CpLayerObject* parent)))Cp_API[96]) +#define CpLayer_Invalidate (*((int (*)(CpLayerObject* self)))Cp_API[98]) +#define CpStructFieldInfo_New (*((CpStructFieldInfoObject* (*)(PyObject* name, PyObject* field)))Cp_API[99]) +#define CpStruct_AddFieldInfo (*((int (*)(CpStructObject* o, CpStructFieldInfoObject* info)))Cp_API[100]) +#define CpStruct_New (*((CpStructObject* (*)(PyObject* model)))Cp_API[102]) +#define CpStruct_GetAnnotations (*((PyObject* (*)(CpStructObject* o, int eval)))Cp_API[103]) +#define CpStruct_ReplaceType (*((int (*)(CpStructObject* o, PyObject* name, PyObject* type)))Cp_API[104]) +#define CpStruct_HasOption (*((int (*)(CpStructObject* o, PyObject* option)))Cp_API[105]) +#define CpStruct_Pack (*((int (*)(CpStructObject* self, PyObject* obj, CpLayerObject* layer)))Cp_API[106]) +#define CpStruct_Unpack (*((PyObject* (*)(CpStructObject* self, CpLayerObject* layer)))Cp_API[107]) +#define CpStruct_SizeOf (*((PyObject* (*)(CpStructObject* self, CpLayerObject* layer)))Cp_API[108]) +#define CpStructModel_Check (*((int (*)(PyObject* model, _modulestate* state)))Cp_API[109]) +#define CpStructModel_GetStruct (*((PyObject* (*)(PyObject* model, _modulestate* state)))Cp_API[110]) +#define CpSeqLayer_New (*((CpSeqLayerObject* (*)(CpStateObject* state, CpLayerObject* parent)))Cp_API[111]) +#define CpSeqLayer_SetSequence (*((int (*)(CpSeqLayerObject* self,PyObject* sequence,Py_ssize_t length,int8_t greedy)))Cp_API[112]) +#define CpObjLayer_New (*((CpObjLayerObject* (*)(CpStateObject* state, CpLayerObject* parent)))Cp_API[113]) +#define CpIntAtom_Pack (*((int (*)(CpIntAtomObject* self, PyObject* op, CpLayerObject* layer)))Cp_API[120]) +#define CpIntAtom_Unpack (*((PyObject* (*)(CpIntAtomObject* self, CpLayerObject* layer)))Cp_API[121]) +#define CpFloatAtom_Pack (*((int (*)(CpFloatAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[122]) +#define CpFloatAtom_Unpack (*((PyObject* (*)(CpFloatAtomObject* self, CpLayerObject* layer)))Cp_API[123]) +#define CpBoolAtom_Pack (*((int (*)(CpBoolAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[124]) +#define CpBoolAtom_Unpack (*((PyObject* (*)(CpBoolAtomObject* self, CpLayerObject* layer)))Cp_API[125]) +#define CpCharAtom_Pack (*((int (*)(CpCharAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[126]) +#define CpCharAtom_Unpack (*((PyObject* (*)(CpCharAtomObject* self, CpLayerObject* layer)))Cp_API[127]) +#define CpPaddingAtom_Pack (*((int (*)(CpPaddingAtomObject* self,PyObject* value,CpLayerObject* layer)))Cp_API[128]) +#define CpPaddingAtom_PackMany (*((int (*)(CpPaddingAtomObject* self,PyObject* value,CpLayerObject* layer,CpLengthInfoObject* lengthinfo)))Cp_API[129]) +#define CpPaddingAtom_Unpack (*((PyObject* (*)(CpPaddingAtomObject* self, CpLayerObject* layer)))Cp_API[130]) +#define CpPaddingAtom_UnpackMany (*((PyObject* (*)(CpPaddingAtomObject* self,CpLayerObject* layer,CpLengthInfoObject* lengthinfo)))Cp_API[131]) +#define CpStringAtom_Pack (*((int (*)(CpStringAtomObject* self,PyObject* value,CpLayerObject* layer)))Cp_API[132]) +#define CpStringAtom_Unpack (*((PyObject* (*)(CpStringAtomObject* self, CpLayerObject* layer)))Cp_API[133]) +#define CpConstAtom_Pack (*((int (*)(CpConstAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[134]) +#define CpConstAtom_Unpack (*((PyObject* (*)(CpConstAtomObject* self, CpLayerObject* layer)))Cp_API[135]) +#define CpRepeatedAtom_Pack (*((int (*)(CpRepeatedAtomObject* self,PyObject* op,CpLayerObject* layer)))Cp_API[136]) +#define CpRepeatedAtom_Unpack (*((PyObject* (*)(CpRepeatedAtomObject* self, CpLayerObject* layer)))Cp_API[137]) +#define CpRepeatedAtom_GetLength (*((PyObject* (*)(CpRepeatedAtomObject* self, PyObject* context)))Cp_API[138]) +#define CpConditionAtom_Pack (*((int (*)(CpConditionAtomObject* self, PyObject* op, PyObject* layer)))Cp_API[139]) +#define CpConditionAtom_Unpack (*((PyObject* (*)(CpConditionAtomObject* self, CpLayerObject* layer)))Cp_API[140]) +#define CpConditionAtom_IsEnabled (*((int (*)(CpConditionAtomObject* self, PyObject* context)))Cp_API[141]) +#define CpSwitchAtom_GetNext (*((PyObject* (*)(CpSwitchAtomObject* self, PyObject* op, PyObject* context)))Cp_API[142]) +#define CpSwitchAtom_Pack (*((int (*)(CpSwitchAtomObject* self, PyObject* obj, CpLayerObject* layer)))Cp_API[143]) +#define CpSwitchAtom_Unpack (*((PyObject* (*)(CpSwitchAtomObject* self, CpLayerObject* layer)))Cp_API[144]) +#define CpOffsetAtom_Pack (*((int (*)(CpOffsetAtomObject* self, PyObject* obj, CpLayerObject* layer)))Cp_API[145]) +#define CpOffsetAtom_Unpack (*((PyObject* (*)(CpOffsetAtomObject* self, CpLayerObject* layer)))Cp_API[146]) +#define CpOffsetAtom_GetOffset (*((PyObject* (*)(CpOffsetAtomObject* self, PyObject* layer)))Cp_API[147]) +#define CpBytesAtom_GetLength (*((PyObject* (*)(CpBytesAtomObject* self, CpLayerObject* layer)))Cp_API[148]) +#define CpBytesAtom_Pack (*((int (*)(CpBytesAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[149]) +#define CpBytesAtom_Unpack (*((PyObject* (*)(CpBytesAtomObject* self, CpLayerObject* layer)))Cp_API[150]) +#define CpPStringAtom_Pack (*((int (*)(CpPStringAtomObject* self,PyObject* value,CpLayerObject* layer)))Cp_API[152]) +#define CpPStringAtom_Unpack (*((PyObject* (*)(CpPStringAtomObject* self, CpLayerObject* layer)))Cp_API[153]) +#define CpEnumAtom_Pack (*((int (*)(CpEnumAtomObject* self, PyObject* value, CpLayerObject* layer)))Cp_API[154]) +#define CpEnumAtom_Unpack (*((PyObject* (*)(CpEnumAtomObject* self, CpLayerObject* layer)))Cp_API[155]) +#define CpVarIntAtom_Pack (*((int (*)(CpVarIntAtomObject* self,PyObject* value,CpLayerObject* layer)))Cp_API[156]) +#define CpVarIntAtom_Unpack (*((PyObject* (*)(CpVarIntAtomObject* self, CpLayerObject* layer)))Cp_API[157]) +#define CpVarIntAtom_BSwap (*((PyObject* (*)(PyObject* number, bool little_endian)))Cp_API[158]) +#define CpVarIntAtom_BSwapUnsignedLongLong (*((unsigned long long (*)(unsigned long long number,bool little_endian)))Cp_API[159]) +#define CpVarIntAtom_BSwapLongLong (*((long long (*)(long long number, bool little_endian)))Cp_API[160]) +#define CpVarIntAtom_BSwapSsize_t (*((Py_ssize_t (*)(Py_ssize_t number, bool little_endian)))Cp_API[161]) +#define CpComputedAtom_Pack (*((int (*)(CpComputedAtomObject* self,PyObject* obj,CpLayerObject* layer)))Cp_API[162]) +#define CpComputedAtom_Unpack (*((PyObject* (*)(CpComputedAtomObject* self, CpLayerObject* layer)))Cp_API[163]) +#define CpLazyAtom_Pack (*((int (*)(CpLazyAtomObject* self, PyObject* obj, CpLayerObject* layer)))Cp_API[164]) +#define CpLazyAtom_Unpack (*((PyObject* (*)(CpLazyAtomObject* self, CpLayerObject* layer)))Cp_API[165]) +#define CpCStringAtom_Pack (*((int (*)(CpCStringAtomObject* self,PyObject* value,CpLayerObject* layer)))Cp_API[166]) +#define CpCStringAtom_Unpack (*((PyObject* (*)(CpCStringAtomObject* self, CpLayerObject* layer)))Cp_API[167]) /** * @brief Public C API for extension modules as reference table diff --git a/src/caterpillar/include/caterpillar/context.h b/src/caterpillar/include/caterpillar/context.h index 905ece0..e43019b 100644 --- a/src/caterpillar/include/caterpillar/context.h +++ b/src/caterpillar/include/caterpillar/context.h @@ -59,21 +59,6 @@ struct _contextobj */ #define CpContext_Check(op) PyObject_TypeCheck((op), &CpContext_Type) -/** - * @brief Get an attribute from a context by using __context_getattr__. - * - * @return the attribute, or NULL on failure - */ -PyAPI_FUNC(PyObject*) - CpContext_GetAttr(PyObject* ctx, PyObject* key, _modulestate* m); - -/** - * @brief Get an attribute from a context by using __context_getattr__. - * - * @return the attribute, or NULL on failure - */ -PyAPI_FUNC(PyObject*) CpContext_GetAttrString(PyObject* ctx, const char* key); - // ----------------------------------------------------------------------------- // unary expression enum diff --git a/src/caterpillar/include/caterpillar/default.h b/src/caterpillar/include/caterpillar/default.h new file mode 100644 index 0000000..eb47cd0 --- /dev/null +++ b/src/caterpillar/include/caterpillar/default.h @@ -0,0 +1,30 @@ +/** + * Copyright (C) MatrixEditor 2024 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CP_DEFAULT_H +#define CP_DEFAULT_H + +#include "caterpillarapi.h" + +/* immortal objects */ + +#define CpInvalidDefault (&_CpInvalidDefault_Object) +#define Cp_IsInvalidDefault(o) ((o) == CpInvalidDefault) + +#define CpDefaultOption (&_CpDefaultOption_Object) +#define Cp_IsDefaultOption(o) ((o) == CpDefaultOption) + +#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/field.h b/src/caterpillar/include/caterpillar/field.h deleted file mode 100644 index 3ac4fd3..0000000 --- a/src/caterpillar/include/caterpillar/field.h +++ /dev/null @@ -1,180 +0,0 @@ -/** - * Copyright (C) MatrixEditor 2024 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#ifndef CP_FIELD_H -#define CP_FIELD_H - -#include "atomobj.h" - -/* Caterpillar Field definition */ - -/** - * @brief C implementation of the Python equivalent. - * - * Represents a field in a data structure. - */ -struct _fieldobj -{ - PyObject_HEAD - - /// The name of this field - PyObject* m_name; - - /// Stores a reference to the actual parsing struct that will be used - /// to parse or build our data. This attribute is never null. - PyObject* m_atom; - - /// An automatically inferred or explicitly specified byte order. Note that - /// this attribute may have no impact on the underlying struct. The default - /// byte order is `NATIVE_ENDIAN`. - PyObject* m_endian; - - /// Using the `@` operator an offset can be assigned to a field. If set, the - /// stream will be reset and set to the original position. - /// - /// The minus one indicates that no offset has been associated with this - /// field. - /// - /// @see CpField_GetOffset - PyObject* m_offset; - - /// Additional options that can be enabled using the logical OR operator - /// `|`. - /// - /// Note that there are default options that will be set automatically: - /// * `field:keep_position`: - /// Persists the streams position after parsing data using the - /// underlying struct. In relation to `offset`, this option will reset - /// the stream to its original position if deactivated. - /// * `field:dynamic`: - /// Specifies that this field does not store a constant size. - /// * `field:sequential`: - /// An automatic flag that indicates this field stores a sequential - /// struct. - PyObject* m_options; - - /// A constant or dynamic value to represent the amount of structs. Zero - /// indicates there are no sequence types associated with this field. - PyObject* m_length; - - /// An extra attribute that stores additional options that can be translates - /// as a switch statement. - PyObject* m_switch; - - /// Given optional execution this attribute should be used to return a boolean - /// value that decides whether the value of this field should be set. Using - /// `//` the condition can be set during class declaration. - PyObject* m_condition; - - /// The field's architecture (inferred or explicitly specified). - PyObject* m_arch; - - /// The configured default value. - PyObject* m_default; - - // internal state variables - int8_t s_size; - int8_t s_type; - int8_t s_sequential; - int8_t s_keep_pos; -}; - -/// Field type -// PyAPI_DATA(PyTypeObject) CpField_Type; - -/** - * @brief Check whether the given object is afield - * - * @param v the object to check - * @return true if the object is a field - * @return false if the object is not a field - */ -#define CpField_CheckExact(v) Py_IS_TYPE((v), &CpField_Type) - -/** - * @brief Check whether the given object is a field - * - * @param v the object to check - * @return true if the object is a field - * @return false if the object is not a field - */ -#define CpField_Check(v) PyObject_TypeCheck((v), &CpField_Type) - -// ----------------------------------------------------------------------------- -// field atom - -/** - * @brief C implementation of the Python equivalent (FieldMixin). - * - * A simple mixin to support operators used to create `Field` instances. - */ -struct _fieldatomobj -{ - CpAtom_HEAD -}; - -/// Field atom type -// PyAPI_DATA(PyTypeObject) CpFieldAtom_Type; - -/** - * @brief Check whether the given object is a field atom - * - * @param v the object to check - * @return true if the object is a field atom - * @return false if the object is not a field atom - */ -#define CpFieldAtom_CheckExact(v) Py_IS_TYPE((v), &CpFieldAtom_Type) - -/** - * @brief Check whether the given object is a field atom - * - * @param v the object to check - * @return true if the object is a field atom - * @return false if the object is not a field atom - */ -#define CpFieldAtom_Check(v) PyObject_TypeCheck((v), &CpFieldAtom_Type) - -#define CpFieldAtom_HEAD CpFieldAtomObject ob_base; - -// ----------------------------------------------------------------------------- -// field C atom -struct _fieldcatomobj -{ - CpCAtom_HEAD -}; - -/** - * @brief Check whether the given object is a field C atom - * - * @param v the object to check - * @return true if the object is a field C atom - * @return false if the object is not a field C atom - */ -#define CpFieldCAtom_CheckExact(v) Py_IS_TYPE((v), &CpFieldCAtom_Type) - -/** - * @brief Check whether the given object is a field C atom - * - * @param v the object to check - * @return true if the object is a field C atom - * @return false if the object is not a field C atom - */ -#define CpFieldCAtom_Check(v) PyObject_TypeCheck((v), &CpFieldCAtom_Type) - -#define CpFieldCAtom_HEAD CpFieldCAtomObject ob_base; -#define CpFieldCAtom_CATOM(x) (x)->ob_base.ob_base - -#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/macros.h b/src/caterpillar/include/caterpillar/macros.h index 11cb4ca..c3fb6bd 100644 --- a/src/caterpillar/include/caterpillar/macros.h +++ b/src/caterpillar/include/caterpillar/macros.h @@ -23,6 +23,8 @@ #include +#define _Cp_NameStr(x) ("caterpillar._C." x) + /** * @brief Appends the name of the C module to the given name. */ diff --git a/src/caterpillar/include/caterpillar/module.h b/src/caterpillar/include/caterpillar/module.h index 2d83c00..70bb7b9 100644 --- a/src/caterpillar/include/caterpillar/module.h +++ b/src/caterpillar/include/caterpillar/module.h @@ -52,8 +52,9 @@ struct _modulestate // typing constants PyObject* Any_Type; PyObject* List_Type; + PyObject* Optional_Type; PyObject* Union_Type; - PyObject *BytesIO_Type; + PyObject* BytesIO_Type; // string constants // strings @@ -70,6 +71,7 @@ struct _modulestate PyObject* str___unpack_many__; PyObject* str___size__; PyObject* str___type__; + PyObject* str___bits__; PyObject* str___annotations__; PyObject* str___mro__; PyObject* str___struct__; @@ -78,6 +80,8 @@ struct _modulestate PyObject* str___weakref__; PyObject* str___dict__; PyObject* str___qualname__; + PyObject* str__member_map_; + PyObject* str__value2member_map_; PyObject* str_start; PyObject* str_ctx__root; @@ -85,16 +89,20 @@ struct _modulestate PyObject* str_bytesio_getvalue; PyObject* str_builder_process; PyObject* str_pattern_match; + PyObject* str_cstring_default_pad; + PyObject* str_utf8; // compiled regex for unnamed fields PyObject* cp_regex__unnamed; PyObject* inspect_getannotations; // cached objects - PyObject *cp_bytes__true; - PyObject *cp_bytes__false; -}; + PyObject* cp_bytes__true; + PyObject* cp_bytes__false; + // type handler map + PyObject* cp_typehandler_map; +}; /** * @brief Get the module state object @@ -121,18 +129,6 @@ get_global_module_state(void) return get_module_state(PyState_FindModule(&CpModule)); } -/* immortal objects */ -// PyAPI_DATA(PyTypeObject) CpInvalidDefault_Type; -// PyAPI_DATA(PyTypeObject) CpDefaultOption_Type; - -// PyAPI_DATA(PyObject) _CpInvalidDefault_Object; -#define CpInvalidDefault (&_CpInvalidDefault_Object) -#define Cp_IsInvalidDefault(o) ((o) == CpInvalidDefault) - -// PyAPI_DATA(PyObject) _CpDefaultOption_Object; -#define CpDefaultOption (&_CpDefaultOption_Object) -#define Cp_IsDefaultOption(o) ((o) == CpDefaultOption) - /* utility macros */ #define CpModule_SetupType(op) \ if (PyType_Ready(op) < 0) \ diff --git a/src/caterpillar/include/caterpillar/parsing.h b/src/caterpillar/include/caterpillar/parsing.h new file mode 100644 index 0000000..3a05b0c --- /dev/null +++ b/src/caterpillar/include/caterpillar/parsing.h @@ -0,0 +1,50 @@ +/** + * Copyright (C) MatrixEditor 2024 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef CP_PARSING_H +#define CP_PARSING_H + +#include "caterpillar/atomobj.h" +#include "caterpillar/module.h" +#include "caterpillar/state.h" + +static inline int +_Cp_Pack(PyObject* op, PyObject* atom, CpLayerObject* layer) +{ + if (CpCAtom_Check(atom)) { + return CpPack_CAtom(op, (CpCAtomObject*)atom, layer); + } + + _modulestate* mod = layer->m_state->mod; + PyObject* result = CpAtom_Pack(atom, mod->str___pack__, op, (PyObject*)layer); + if (result == NULL) { + return -1; + } + Py_DECREF(result); + return 0; +} + +static inline PyObject* +_Cp_Unpack(PyObject* op, CpLayerObject* layer) +{ + if (CpCAtom_Check(op)) { + return CpUnpack_CAtom((CpCAtomObject*)op, layer); + } + return PyObject_CallMethodOneArg( + op, layer->m_state->mod->str___unpack__, (PyObject*)layer); +} + +#endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/state.h b/src/caterpillar/include/caterpillar/state.h index 5133148..68411fe 100644 --- a/src/caterpillar/include/caterpillar/state.h +++ b/src/caterpillar/include/caterpillar/state.h @@ -74,37 +74,26 @@ struct _stateobj //----------------------------------------------------------------------------- // layer +enum { + CpLayerClass_Default = 0, + CpLayerClass_Sequence = 1, + CpLayerClass_Object = 2, +}; /** - * @brief TODO + * @brief Top level layer class with a reference to its parent and the global + * parsing state. */ struct _layerobj { PyObject_HEAD - /// The parent layer - struct _layerobj* m_parent; + /// The parent layer + struct _layerobj* m_parent; /// the global parsing state CpStateObject* m_state; - // --- Context sensitive variables --- - - /// In case a struct is linked to a field, the `Field` instance will always - /// set this layer to be accessible from within the underlying struct. - PyObject* m_field; - - /// When packing or unpacking objects, the current object attributes are - /// stored within an object context. This is a special context that allows - /// access to previously parsed fields or attributes of the input object. To - /// minimize the number of calls using this attribute, a shortcut named `this` - /// was defined, which automatically inserts a path to the object context. - PyObject* m_obj; - - /// In case a switch-case statement is activated in a field, the layer will - /// store the parsed value in this variable temporarily. - PyObject* m_value; - /// Although it is optional to provide the current parsing or building path, /// it is recommended. All nesting structures implement a behavior that /// automatically adds a sub-path while packing or unpacking. Special names @@ -112,24 +101,46 @@ struct _layerobj /// elements PyObject* m_path; - /// Same as `m_obj` but for the sequential elements. - PyObject* m_sequence; + // REVISIT: do we need them? + /// Lazily loaded context sensitive variables (this field remains NULL until + /// the first item gets added to it) + // PyObject *m_locals; - /// The length of the current collection. - Py_ssize_t m_length; + /// The type of the layer + int8_t m_class; - /// When packing or unpacking collections of elements, the current working - /// index is given under this layer variable. It is set only in this specific - /// situation. - Py_ssize_t m_index; + // --- Context sensitive variables --- - // --- Internal state variables --- - int8_t s_greedy; - int8_t s_sequential; -}; + // /// In case a struct is linked to a field, the `Field` instance will always + // /// set this layer to be accessible from within the underlying struct. + // PyObject* m_field; + + // /// When packing or unpacking objects, the current object attributes are + // /// stored within an object context. This is a special context that allows + // /// access to previously parsed fields or attributes of the input object. To + // /// minimize the number of calls using this attribute, a shortcut named `this` + // /// was defined, which automatically inserts a path to the object context. + // PyObject* m_obj; + + // /// In case a switch-case statement is activated in a field, the layer will + // /// store the parsed value in this variable temporarily. + // PyObject* m_value; + + // /// Same as `m_obj` but for the sequential elements. + // PyObject* m_sequence; -/// Layer type -// PyAPI_DATA(PyTypeObject) CpLayer_Type; + // /// The length of the current collection. + // Py_ssize_t m_length; + + // /// When packing or unpacking collections of elements, the current working + // /// index is given under this layer variable. It is set only in this specific + // /// situation. + // Py_ssize_t m_index; + + // // --- Internal state variables --- + // int8_t s_greedy; + // int8_t s_sequential; +}; /** * @brief Check whether the given object is a layer @@ -148,7 +159,7 @@ struct _layerobj * @return false if the object is not a layer */ #define CpLayer_Check(v) PyObject_TypeCheck((v), &CpLayer_Type) - +#define CpLayer_HEAD CpLayerObject ob_base; #define CpLayer_AppendPath(layer, newpath) \ Py_XSETREF( \ @@ -159,4 +170,58 @@ struct _layerobj : (layer)->m_path), \ _PyUnicode_AsString((newpath)))); +//----------------------------------------------------------------------------- +// seq layer + +struct _seqlayerobj +{ + CpLayer_HEAD + + /// Same as `m_obj` but for the sequential elements. + PyObject* m_sequence; + + /// The length of the current collection. + Py_ssize_t m_length; + + /// When packing or unpacking collections of elements, the current working + /// index is given under this layer variable. It is set only in this specific + /// situation. + Py_ssize_t m_index; + + int8_t s_greedy; +}; + +#define CpSeqLayer_CheckExact(v) Py_IS_TYPE((v), &CpSeqLayer_Type) +#define CpSeqLayer_Check(v) PyObject_TypeCheck((v), &CpSeqLayer_Type) + +//----------------------------------------------------------------------------- +// obj layer + +struct _objlayerobj +{ + CpLayer_HEAD + + /// When packing or unpacking objects, the current object attributes are + /// stored within an object context. This is a special context that allows + /// access to previously parsed fields or attributes of the input object. To + /// minimize the number of calls using this attribute, a shortcut named `this` + /// was defined, which automatically inserts a path to the object context. + PyObject* m_obj; +}; + +#define CpObjLayer_CheckExact(v) Py_IS_TYPE((v), &CpObjLayer_Type) +#define CpObjLayer_Check(v) PyObject_TypeCheck((v), &CpObjLayer_Type) + +//----------------------------------------------------------------------------- +// field layer + +struct _fieldlayerobj +{ + CpLayer_HEAD + + /// In case a struct is linked to a field, the `Field` instance will always + /// set this layer to be accessible from within the underlying struct. + PyObject* m_field; +}; + #endif \ No newline at end of file diff --git a/src/caterpillar/include/caterpillar/struct.h b/src/caterpillar/include/caterpillar/struct.h index 4d3f0b5..3fe351b 100644 --- a/src/caterpillar/include/caterpillar/struct.h +++ b/src/caterpillar/include/caterpillar/struct.h @@ -18,19 +18,25 @@ #define CP_STRUCT_H #include "caterpillar/caterpillarapi.h" -#include "caterpillar/field.h" +#include "caterpillar/atoms/builtins.h" /* Caterpillar Struct C implementation */ /** * @brief Internal class used to store information about a field */ -struct CpStructFieldInfo +struct _fieldinfoobj { PyObject_HEAD - /// the referenced field object - CpFieldObject* m_field; + /// the referenced field object + PyObject *m_field; + + /// the name of the field + PyObject *m_name; + + /// The configured default value. + PyObject* m_default; // Excluded: True if the field is included in the struct int8_t s_excluded; @@ -70,7 +76,8 @@ struct CpStructFieldInfo */ struct _structobj { - CpFieldAtom_HEAD PyTypeObject* m_model; // underlying class + CpBuiltinAtom_HEAD + PyTypeObject* m_model; // underlying class PyObject* m_members; // Dict[str, FieldInfo] PyObject* m_options; // set[CpOption] @@ -89,9 +96,6 @@ struct _structobj _modulestate* s_mod; }; -/// Struct object type -// PyAPI_DATA(PyTypeObject) CpStruct_Type; - /** * @brief Checks if the given object is a struct object * diff --git a/src/caterpillar/_Py.py b/src/caterpillar/py.py similarity index 100% rename from src/caterpillar/_Py.py rename to src/caterpillar/py.py diff --git a/src/caterpillarapi.c b/src/caterpillarapi.c deleted file mode 100644 index 5af36dd..0000000 --- a/src/caterpillarapi.c +++ /dev/null @@ -1,114 +0,0 @@ -/* These pointers will be stored in the C-object for use in other - extension modules -*/ - -void *Cp_API[] = { - (void *) &CpModule, - (void *) &CpCAtom_Type, - (void *) &CpArch_Type, - (void *) &CpEndian_Type, - (void *) &CpContext_Type, - (void *) &CpUnaryExpr_Type, - (void *) &CpBinaryExpr_Type, - (void *) &CpContextPath_Type, - (void *) &CpField_Type, - (void *) &CpFieldAtom_Type, - (void *) &CpFieldCAtom_Type, - (void *) &CpInvalidDefault_Type, - (void *) &CpDefaultOption_Type, - (void *) &_CpInvalidDefault_Object, - (void *) &_CpDefaultOption_Object, - (void *) &CpAtom_Type, - (void *) &CpOption_Type, - (void *) &CpState_Type, - (void *) &CpLayer_Type, - (void *) &CpStructFieldInfo_Type, - (void *) &CpStruct_Type, - (void *) &CpFloatAtom_Type, - (void *) &CpIntAtom_Type, - (void *) &CpBoolAtom_Type, - (void *) &CpCharAtom_Type, - (void *) &CpPaddingAtom_Type, - (void *) &CpStringAtom_Type, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - (void *) &CpEndian_IsLittleEndian, - NULL, - NULL, - (void *) &CpContext_New, - (void *) &CpUnaryExpr_New, - (void *) &CpBinaryExpr_New, - (void *) &CpContextPath_New, - (void *) &CpContextPath_FromString, - (void *) &CpField_New, - (void *) &CpField_HasCondition, - (void *) &CpField_IsEnabled, - (void *) &CpField_GetOffset, - (void *) &CpField_EvalSwitch, - (void *) &CpField_GetLength, - (void *) &CpTypeOf, - (void *) &CpTypeOf_Field, - (void *) &CpTypeOf_Common, - (void *) &CpPack, - (void *) &CpPack_Field, - (void *) &CpPack_Common, - (void *) &CpPack_Struct, - (void *) &_Cp_Pack, - (void *) &_CpPack_EvalLength, - (void *) &CpSizeOf, - (void *) &CpSizeOf_Field, - (void *) &CpSizeOf_Struct, - (void *) &CpSizeOf_Common, - (void *) &_Cp_SizeOf, - (void *) &CpUnpack, - (void *) &CpUnpack_Field, - (void *) &CpUnpack_Common, - (void *) &CpUnpack_Struct, - (void *) &_Cp_Unpack, - (void *) &_CpUnpack_EvalLength, - (void *) &CpUnpack_CAtom, - (void *) &CpPack_CAtom, - (void *) &CpSizeOf_CAtom, - (void *) &CpTypeOf_CAtom, - (void *) &CpState_New, - (void *) &CpState_Tell, - (void *) &CpState_Seek, - (void *) &CpState_Read, - (void *) &CpState_ReadFully, - (void *) &CpState_Write, - (void *) &CpState_SetGlobals, - (void *) &CpLayer_New, - (void *) &CpLayer_Invalidate, - (void *) &CpLayer_SetSequence, - (void *) &CpStructFieldInfo_New, - (void *) &CpStruct_AddFieldInfo, - (void *) &CpStruct_AddField, - (void *) &CpStruct_New, - (void *) &CpStruct_GetAnnotations, - (void *) &CpStruct_ReplaceType, - (void *) &CpStruct_HasOption, - (void *) &CpStructModel_Check, - (void *) &CpStructModel_GetStruct -}; - diff --git a/src/arch.c b/src/ccaterpillar/arch.c similarity index 90% rename from src/arch.c rename to src/ccaterpillar/arch.c index f832e47..2ca84b8 100644 --- a/src/arch.c +++ b/src/ccaterpillar/arch.c @@ -1,8 +1,7 @@ /* CpArch and CpEndian */ #include "caterpillar/caterpillar.h" -#include "caterpillar/arch.h" -#include "caterpillar/field.h" -#include "structmember.h" + +#include /* CpArch */ static PyObject* @@ -54,8 +53,7 @@ cp_arch_init(CpArchObject* self, PyObject* args, PyObject* kw) static PyObject* cp_arch_repr(CpArchObject* self) { - return PyUnicode_FromFormat( - "CpArch(name=%R, ptr_size=%i)", self->name, self->pointer_size); + return PyUnicode_FromFormat("", self->pointer_size, self->name); } static PyObject* @@ -101,7 +99,7 @@ PyTypeObject CpArch_Type = { .tp_dealloc = (destructor)cp_arch_dealloc, .tp_repr = (reprfunc)cp_arch_repr, .tp_hash = (hashfunc)cp_arch_hash, - .tp_richcompare =(richcmpfunc)cp_arch_richcmp, + .tp_richcompare = (richcmpfunc)cp_arch_richcmp, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = cp_arch_doc, .tp_members = CpArch_Members, @@ -174,8 +172,16 @@ cp_endian_init(CpEndianObject* self, PyObject* args, PyObject* kw) static PyObject* cp_endian_repr(CpEndianObject* self) { - return PyUnicode_FromFormat( - "CpEndian(name=%R, ch='%c')", self->name, self->id); + switch (self->id) { + case '=': + return PyUnicode_FromFormat(""); + case '<': + return PyUnicode_FromFormat(""); + case '>': + return PyUnicode_FromFormat(""); + default: + return PyUnicode_FromFormat("", self->id, self->name); + } } static PyObject* @@ -197,12 +203,12 @@ cp_endian_hash(CpEndianObject* self) static PyObject* cp_endian_as_number_add(CpEndianObject* self, PyObject* atom) { - CpFieldObject* field = (CpFieldObject*)CpField_New(atom); - if (!field) { + if (!atom) { + PyErr_SetString(PyExc_ValueError, "atom must be non-null"); return NULL; } - _Cp_SetObj(field->m_endian, self); - return (PyObject*)field; + + return CpEndian_SetEndian(atom, self); } /* Doc strings */ diff --git a/src/atomimpl/boolatomobj.c b/src/ccaterpillar/atomimpl/bool.c similarity index 72% rename from src/atomimpl/boolatomobj.c rename to src/ccaterpillar/atomimpl/bool.c index bc5a058..9428473 100644 --- a/src/atomimpl/boolatomobj.c +++ b/src/ccaterpillar/atomimpl/bool.c @@ -1,7 +1,6 @@ /* boolatom C implementation */ -#include "caterpillar/atoms/primitive.h" -#include "caterpillar/module.h" -#include "caterpillar/state.h" +#include "caterpillar/caterpillar.h" + #include static PyObject* @@ -21,13 +20,13 @@ cp_boolatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { CpBoolAtomObject* self = (CpBoolAtomObject*)type->tp_alloc(type, 0); if (self != NULL) { - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpBoolAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpBoolAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = NULL; - CpFieldCAtom_CATOM(self).ob_unpack_many = NULL; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_boolatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_boolatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpBoolAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpBoolAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_boolatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_boolatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; } return (PyObject*)self; } @@ -45,7 +44,7 @@ cp_boolatom_init(CpBoolAtomObject* self, PyObject* args, PyObject* kwds) } /* Public API */ - +/*CpAPI*/ int CpBoolAtom_Pack(CpBoolAtomObject* self, PyObject* value, CpLayerObject* layer) { @@ -63,11 +62,12 @@ CpBoolAtom_Pack(CpBoolAtomObject* self, PyObject* value, CpLayerObject* layer) return 0; } +/*CpAPI*/ PyObject* CpBoolAtom_Unpack(CpBoolAtomObject* self, CpLayerObject* layer) { PyObject* res; - PyObject* value = CpState_Read(layer->m_state, 1); + PyObject* value = CpState_ReadSsize_t(layer->m_state, 1); if (!value) { return NULL; } @@ -84,7 +84,7 @@ CpBoolAtom_Unpack(CpBoolAtomObject* self, CpLayerObject* layer) /* type setup */ PyTypeObject CpBoolAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(boolatom), + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpBoolAtom_NAME), .tp_basicsize = sizeof(CpBoolAtomObject), .tp_dealloc = (destructor)cp_boolatom_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, diff --git a/src/ccaterpillar/atomimpl/builtins/atoffset.c b/src/ccaterpillar/atomimpl/builtins/atoffset.c new file mode 100644 index 0000000..7b11102 --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/atoffset.c @@ -0,0 +1,193 @@ +/* offset atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_offsetatom_type(CpOffsetAtomObject* self) +{ + return CpTypeOf(self->m_atom); +} + +static PyObject* +cp_offsetatom_size(CpOffsetAtomObject* self, CpLayerObject* layer) +{ + return _Cp_SizeOf(self->m_atom, layer); +} + +static PyObject* +cp_offsetatom_repr(CpOffsetAtomObject* self) +{ + return PyUnicode_FromFormat( + "] %R>", Py_TYPE(self->m_offset)->tp_name, self->m_atom); +} + +static PyObject* +cp_offsetatom_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpOffsetAtomObject* self = (CpOffsetAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpOffsetAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpOffsetAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_offsetatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_offsetatom_type; + self->m_offset = 0; + self->m_atom = NULL; + return (PyObject*)self; +} + +static void +cp_offsetatom_dealloc(CpOffsetAtomObject* self) +{ + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = NULL; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = NULL; + CpBuiltinAtom_CATOM(self).ob_type = NULL; + Py_CLEAR(self->m_atom); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_offsetatom_init(CpOffsetAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "offset", "atom", "whence", NULL }; + PyObject *offset = NULL, *atom = NULL; + int whence = 0; + if (!PyArg_ParseTupleAndKeywords( + args, kw, "OO|i", kwlist, &atom, &offset, &whence)) + return -1; + + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_offset, offset); + self->m_whence = PyLong_FromLong(whence); + if (!self->m_whence) { + return -1; + } + self->s_is_number = PyNumber_Check(self->m_offset); + return 0; +} + +_CpEndian_ImplSetByteorder(CpOffsetAtomObject, offsetatom, self->m_atom); + +static PyObject* +cp_offsetatom_get_offset(CpOffsetAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "layer", NULL }; + PyObject* layer = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &layer)) + return NULL; + return CpOffsetAtom_GetOffset(self, layer); +} + +/* Public API */ + +/*CpAPI*/ +PyObject* +CpOffsetAtom_GetOffset(CpOffsetAtomObject* self, PyObject* layer) +{ + if (self->s_is_number) { + return Py_NewRef(self->m_offset); + } + + // asset offset is callable + return PyObject_CallOneArg(self->m_offset, layer); +} + +/*CpAPI*/ +int +CpOffsetAtom_Pack(CpOffsetAtomObject* self, PyObject* obj, CpLayerObject* layer) +{ + // TODO: implement offset table + PyObject* fallback = CpState_Tell(layer->m_state); + if (!fallback) { + return -1; + } + + PyObject* res = CpState_Seek(layer->m_state, self->m_offset, self->m_whence); + if (!res) { + return -1; + } + Py_DECREF(res); + + if (_Cp_Pack(obj, self->m_atom, layer) < 0) { + return -1; + } + + res = CpState_Seek(layer->m_state, fallback, PyLong_FromLong(0)); + if (!res) { + return -1; + } + Py_DECREF(res); + return 0; +} + +/*CpAPI*/ +PyObject* +CpOffsetAtom_Unpack(CpOffsetAtomObject* self, CpLayerObject* layer) +{ + PyObject* fallback = CpState_Tell(layer->m_state); + if (!fallback) { + return NULL; + } + + PyObject* res = CpState_Seek(layer->m_state, self->m_offset, self->m_whence); + if (!res) { + return NULL; + } + Py_DECREF(res); + + PyObject* result = _Cp_Unpack(self->m_atom, layer); + if (!result) { + return NULL; + } + + res = CpState_Seek(layer->m_state, fallback, PyLong_FromLong(0)); + if (!res) { + return NULL; + } + Py_DECREF(res); + return result; +} + +/* docs */ + +/* members */ + +static PyMemberDef CpOffsetAtom_Members[] = { + { "offset", T_OBJECT_EX, offsetof(CpOffsetAtomObject, m_offset), READONLY }, + { "whence", T_OBJECT_EX, offsetof(CpOffsetAtomObject, m_whence), READONLY }, + { "atom", T_OBJECT_EX, offsetof(CpOffsetAtomObject, m_atom), READONLY }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpOffsetAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(offsetatom, NULL), + { "get_offset", + (PyCFunction)cp_offsetatom_get_offset, + METH_VARARGS | METH_KEYWORDS, + NULL }, + { NULL } /* Sentinel */ +}; + +PyTypeObject CpOffsetAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpOffsetAtom_NAME), + .tp_basicsize = sizeof(CpOffsetAtomObject), + .tp_members = CpOffsetAtom_Members, + .tp_methods = CpOffsetAtom_Methods, + .tp_init = (initproc)cp_offsetatom_init, + .tp_dealloc = (destructor)cp_offsetatom_dealloc, + .tp_repr = (reprfunc)cp_offsetatom_repr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_new = (newfunc)cp_offsetatom_new, +}; diff --git a/src/ccaterpillar/atomimpl/builtins/builtin.c b/src/ccaterpillar/atomimpl/builtins/builtin.c new file mode 100644 index 0000000..3799b8c --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/builtin.c @@ -0,0 +1,89 @@ +/* builtinatom C implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_builtinatom_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpBuiltinAtomObject* self; + self = (CpBuiltinAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + self->ob_base.ob_bits = NULL; + self->ob_base.ob_pack = NULL; + self->ob_base.ob_pack_many = NULL; + self->ob_base.ob_unpack = NULL; + self->ob_base.ob_unpack_many = NULL; + self->ob_base.ob_size = NULL; + self->ob_base.ob_type = NULL; + return (PyObject*)self; +} + +static void +cp_builtinatom_dealloc(CpBuiltinAtomObject* self) +{ + self->ob_base.ob_bits = NULL; + self->ob_base.ob_pack = NULL; + self->ob_base.ob_pack_many = NULL; + self->ob_base.ob_unpack = NULL; + self->ob_base.ob_unpack_many = NULL; + self->ob_base.ob_size = NULL; + self->ob_base.ob_type = NULL; + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_builtinatom_init(CpBuiltinAtomObject* self, PyObject* args, PyObject* kw) +{ + _Cp_InitNoArgs(CpBuiltinAtomObject, args, kw); +} + +// TODO member methods +static PyObject* +cp_builtinatom_as_mapping_getitem(CpBuiltinAtomObject* self, PyObject* length) +{ + return (PyObject*)CpRepeatedAtom_New((PyObject*)self, length); +} + +static PyObject* +cp_builtinatom_as_number_rshift(PyObject* self, PyObject* other) +{ + return (PyObject*)CpSwitchAtom_New(self, other); +} + +static PyObject* +cp_builtinatom_as_number_floordiv(PyObject* self, PyObject* other) +{ + return (PyObject*)CpConditionAtom_New(self, other); +} + +static PyObject* +cp_builtinatom_as_number_matmul(PyObject* self, PyObject* other) +{ + return (PyObject*)CpOffsetAtom_New(self, other); +} + +static PyMappingMethods CpBuiltinAtom_MappingMethods = { + .mp_subscript = (binaryfunc)cp_builtinatom_as_mapping_getitem, +}; + +static PyNumberMethods CpBuiltinAtom_NumberMethods = { + .nb_rshift = (binaryfunc)cp_builtinatom_as_number_rshift, + .nb_floor_divide = (binaryfunc)cp_builtinatom_as_number_floordiv, + .nb_matrix_multiply = (binaryfunc)cp_builtinatom_as_number_matmul, +}; + +PyTypeObject CpBuiltinAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpBuiltinAtom_NAME), + .tp_basicsize = sizeof(CpBuiltinAtomObject), + .tp_dealloc = (destructor)cp_builtinatom_dealloc, + .tp_init = (initproc)cp_builtinatom_init, + .tp_new = (newfunc)cp_builtinatom_new, + .tp_as_mapping = &CpBuiltinAtom_MappingMethods, + .tp_as_number = &CpBuiltinAtom_NumberMethods, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, +}; diff --git a/src/ccaterpillar/atomimpl/builtins/condition.c b/src/ccaterpillar/atomimpl/builtins/condition.c new file mode 100644 index 0000000..a50f533 --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/condition.c @@ -0,0 +1,170 @@ +/* condition atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ + +static PyObject* +cp_conditionatom_type(CpConditionAtomObject* self) +{ + PyObject* atom_type = CpTypeOf(self->m_atom); + if (!atom_type) + return NULL; + + _modulestate* state = get_global_module_state(); + Py_XSETREF(atom_type, PyObject_GetItem(state->Optional_Type, atom_type)); + return atom_type; +} + +static PyObject* +cp_conditionatom_size(CpConditionAtomObject* self, CpLayerObject* layer) +{ + return _Cp_SizeOf(self->m_atom, layer); +} + +static PyObject* +cp_conditionatom_repr(CpConditionAtomObject* self) +{ + return PyUnicode_FromFormat( + "] %R>", Py_TYPE(self->m_condition)->tp_name, self->m_atom); +} + +static PyObject* +cp_conditionatom_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpConditionAtomObject* self = (CpConditionAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpConditionAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpConditionAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_conditionatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_conditionatom_type; + return (PyObject*)self; +} + +static void +cp_conditionatom_dealloc(CpConditionAtomObject* self) +{ + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = NULL; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = NULL; + CpBuiltinAtom_CATOM(self).ob_type = NULL; + Py_CLEAR(self->m_atom); + Py_CLEAR(self->m_condition); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_conditionatom_init(CpConditionAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "condition", "atom", NULL }; + PyObject *condition = NULL, *atom = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &condition, &atom)) + return -1; + + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_condition, condition); + return 0; +} + +_CpEndian_ImplSetByteorder(CpConditionAtomObject, conditionatom, self->m_atom); + +static PyObject* +cp_conditionatom_is_enabled(CpConditionAtomObject* self, PyObject* args) +{ + PyObject* layer = NULL; + if (!PyArg_ParseTuple(args, "O", &layer)) { + return NULL; + } + + if (CpConditionAtom_IsEnabled(self, layer)) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +/* Public API */ + +/*CpAPI*/ +int +CpConditionAtom_IsEnabled(CpConditionAtomObject* self, PyObject* context) +{ + if (self->m_condition == NULL) { + return true; + } + + if (PyCallable_Check(self->m_condition)) { + PyObject* result = PyObject_CallOneArg(self->m_condition, context); + if (result == NULL) { + return -1; + } + int truth = PyObject_IsTrue(result); + Py_DECREF(result); + return truth; + } + + return PyObject_IsTrue(self->m_condition); +} + +/*CpAPI*/ +int +CpConditionAtom_Pack(CpConditionAtomObject* self, PyObject* op, PyObject* layer) +{ + if (!CpConditionAtom_IsEnabled(self, layer)) { + return 0; + } + + return _Cp_Pack(op, self->m_atom, (CpLayerObject*)layer); +} + +/*CpAPI*/ +PyObject* +CpConditionAtom_Unpack(CpConditionAtomObject* self, CpLayerObject* layer) +{ + if (!CpConditionAtom_IsEnabled(self, (PyObject*)layer)) { + return NULL; + } + return _Cp_Unpack(self->m_atom, layer); +} + +/* docs */ + +/* members */ +static PyMemberDef CpConditionAtom_Members[] = { + { "condition", + T_OBJECT, + offsetof(CpConditionAtomObject, m_condition), + READONLY }, + { "atom", T_OBJECT, offsetof(CpConditionAtomObject, m_atom), READONLY }, + { NULL } +}; + +/* methods */ +static PyMethodDef CpConditionAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(conditionatom, NULL), + { "is_enabled", (PyCFunction)cp_conditionatom_is_enabled, METH_VARARGS }, + { NULL } +}; + +PyTypeObject CpConditionAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpConditionAtom_NAME), + .tp_basicsize = sizeof(CpConditionAtomObject), + .tp_dealloc = (destructor)cp_conditionatom_dealloc, + .tp_init = (initproc)cp_conditionatom_init, + .tp_members = CpConditionAtom_Members, + .tp_methods = CpConditionAtom_Methods, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_new = (newfunc)cp_conditionatom_new, + .tp_repr = (reprfunc)cp_conditionatom_repr, +}; diff --git a/src/ccaterpillar/atomimpl/builtins/primitive.c b/src/ccaterpillar/atomimpl/builtins/primitive.c new file mode 100644 index 0000000..85a8da7 --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/primitive.c @@ -0,0 +1,83 @@ +/* primitive atom C implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_primitiveatom_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpPrimitiveAtomObject* self; + self = (CpPrimitiveAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + return (PyObject*)self; +} + +static void +cp_primitiveatom_dealloc(CpPrimitiveAtomObject* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_primitiveatom_init(CpPrimitiveAtomObject* self, PyObject* args, PyObject* kw) +{ + _Cp_InitNoArgs(CpPrimitiveAtomObject, args, kw); +} + + +static PyObject * +cp_primitiveatom_repr(CpPrimitiveAtomObject *self) +{ + return PyUnicode_FromString(""); +} + +// TODO member methods +static PyObject* +cp_primitiveatom_as_mapping_getitem(CpPrimitiveAtomObject* self, PyObject* length) +{ + return (PyObject*)CpRepeatedAtom_New((PyObject*)self, length); +} + +static PyObject* +cp_primitiveatom_as_number_rshift(PyObject* self, PyObject* other) +{ + return (PyObject*)CpSwitchAtom_New(self, other); +} + +static PyObject* +cp_primitiveatom_as_number_floordiv(PyObject* self, PyObject* other) +{ + return (PyObject*)CpConditionAtom_New(self, other); +} + +static PyObject* +cp_primitiveatom_as_number_matmul(PyObject* self, PyObject* other) +{ + return (PyObject*)CpOffsetAtom_New(self, other); +} + +static PyMappingMethods CpPrimitiveAtom_MappingMethods = { + .mp_subscript = (binaryfunc)cp_primitiveatom_as_mapping_getitem, +}; + +static PyNumberMethods CpPrimitiveAtom_NumberMethods = { + .nb_rshift = (binaryfunc)cp_primitiveatom_as_number_rshift, + .nb_floor_divide = (binaryfunc)cp_primitiveatom_as_number_floordiv, + .nb_matrix_multiply = (binaryfunc)cp_primitiveatom_as_number_matmul, +}; + +PyTypeObject CpPrimitiveAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpPrimitiveAtom_NAME), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_basicsize = sizeof(CpPrimitiveAtomObject), + .tp_dealloc = (destructor)cp_primitiveatom_dealloc, + .tp_init = (initproc)cp_primitiveatom_init, + .tp_repr = (reprfunc)cp_primitiveatom_repr, + .tp_new = (newfunc)cp_primitiveatom_new, + .tp_doc = NULL, + .tp_as_mapping = &CpPrimitiveAtom_MappingMethods, + .tp_as_number = &CpPrimitiveAtom_NumberMethods, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/builtins/repeated.c b/src/ccaterpillar/atomimpl/builtins/repeated.c new file mode 100644 index 0000000..bf09cdc --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/repeated.c @@ -0,0 +1,350 @@ +/* repeated atom C implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ + +static PyObject* +cp_repeatedatomobj_type(CpRepeatedAtomObject* self) +{ + PyObject* atom_type = CpTypeOf(self->m_atom); + if (!atom_type) + return NULL; + + _modulestate* state = get_global_module_state(); + Py_XSETREF(atom_type, PyObject_GetItem(state->List_Type, atom_type)); + return atom_type; +} + +static PyObject* +cp_repeatedatomobj_size(CpRepeatedAtomObject* self, CpLayerObject* layer) +{ + PyObject* length = CpRepeatedAtom_GetLength(self, (PyObject*)layer); + if (!length) { + return NULL; + } + PyObject* atom_size = _Cp_SizeOf(self->m_atom, layer); + if (!atom_size) { + return NULL; + } + + PyObject* result = PyNumber_Multiply(atom_size, length); + Py_DECREF(length); + return result; +} + +static PyObject* +cp_repeatedatomobj__repr__(CpRepeatedAtomObject* self) +{ + return PyUnicode_FromFormat("", self->m_atom); +} + +static PyObject* +cp_repeatedatomobj_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpRepeatedAtomObject* self; + self = (CpRepeatedAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpRepeatedAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpRepeatedAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_repeatedatomobj_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_repeatedatomobj_type; + self->m_length = NULL; + self->m_atom = NULL; + return (PyObject*)self; +} + +static void +cp_repeatedatomobj_dealloc(CpRepeatedAtomObject* self) +{ + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = NULL; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = NULL; + CpBuiltinAtom_CATOM(self).ob_type = NULL; + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_repeatedatomobj_init(CpRepeatedAtomObject* self, + PyObject* args, + PyObject* kw) +{ + static char* kwlist[] = { "atom", "length", NULL }; + PyObject *atom = NULL, *length = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &atom, &length)) { + return -1; + } + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_length, length); + return 0; +} + +_CpEndian_ImplSetByteorder(CpRepeatedAtomObject, repeatedatom, self->m_atom); + +/* Public API */ + +/*CpAPI*/ +PyObject* +CpRepeatedAtom_GetLength(CpRepeatedAtomObject* self, PyObject* context) +{ + if (PyCallable_Check(self->m_length)) { + PyObject* result = PyObject_CallOneArg(self->m_length, context); + if (!result) + return NULL; + return result; + } + return Py_NewRef(self->m_length); +} + +/*CpAPI*/ +int +CpRepeatedAtom_Pack(CpRepeatedAtomObject* self, + PyObject* op, + CpLayerObject* layer) +{ + CpStateObject* state = layer->m_state; + CpLengthInfoObject* lengthinfo = + (CpLengthInfoObject*)CpObject_CreateNoArgs(&CpLengthInfo_Type); + + bool pack_many_attr = + PyObject_HasAttr(self->m_atom, state->mod->str___pack_many__); + bool is_seq = PySequence_Check(op); + + Py_ssize_t obj_length = 0; + if (is_seq) { + obj_length = PySequence_Length(op); + if (obj_length < 0) { + goto failure; + } + } + + PyObject* length = CpRepeatedAtom_GetLength(self, (PyObject*)layer); + if (!length) { + goto failure; + } + + if (_CpPack_EvalLength(layer, + length, + obj_length, + (bool*)&lengthinfo->m_greedy, + &lengthinfo->m_length) < 0) { + Py_XDECREF(length); + goto failure; + } + Py_XDECREF(length); + + if (pack_many_attr) { + // class explicitly defines __pack_many__ -> use it + PyObject* res = PyObject_CallMethodObjArgs(self->m_atom, + state->mod->str___pack_many__, + op, + (PyObject*)layer, + lengthinfo, + NULL); + PyObject* exc = NULL; + if ((exc = PyErr_GetRaisedException(), + exc && PyErr_GivenExceptionMatches(exc, PyExc_NotImplementedError))) { + // Make sure this method continues to pack the given object + Py_XDECREF(exc); + PyErr_Clear(); + Py_XDECREF(res); + } else { + int success = res ? 0 : -1; + Py_XDECREF(res); + if (success < 0) { + PyErr_SetRaisedException(exc); + } else { + Py_XDECREF(exc); + } + return success; + } + } + + if (!is_seq) { + PyErr_Format(PyExc_TypeError, "input object (%R) is not a sequence", op); + return -1; + } + + if (lengthinfo->m_greedy) { + lengthinfo->m_length = obj_length; + } else { + if (lengthinfo->m_length != obj_length) { + PyErr_Format(PyExc_ValueError, + "given length %d does not match sequence size %d", + lengthinfo->m_length, + obj_length); + goto failure; + } + } + + CpSeqLayerObject* seq_layer = CpSeqLayer_New(state, layer); + if (!seq_layer) { + return -1; + } + + CpSeqLayer_SetSequence( + seq_layer, op, lengthinfo->m_length, lengthinfo->m_greedy); + + PyObject* obj = NULL; + int success = 0; + for (seq_layer->m_index = 0; seq_layer->m_index < seq_layer->m_length; + seq_layer->m_index++) { + obj = PySequence_GetItem(op, seq_layer->m_index); + if (!obj) { + goto failure; + } + seq_layer->ob_base.m_path = PyUnicode_FromFormat( + "%s.%d", _PyUnicode_AsString(layer->m_path), seq_layer->m_index); + if (!seq_layer->ob_base.m_path) { + Py_XDECREF(obj); + goto failure; + } + success = _Cp_Pack(obj, self->m_atom, (CpLayerObject*)seq_layer); + Py_XSETREF(obj, NULL); + if (success < 0) { + goto failure; + } + } + CpLayer_Invalidate((CpLayerObject*)seq_layer); + return 0; +failure: + Py_XDECREF(lengthinfo); + CpLayer_Invalidate((CpLayerObject*)seq_layer); + return -1; +} + +/*CpAPI*/ +PyObject* +CpRepeatedAtom_Unpack(CpRepeatedAtomObject* self, CpLayerObject* layer) +{ + _modulestate* mod = layer->m_state->mod; + CpLengthInfoObject* lengthinfo = + (CpLengthInfoObject*)CpObject_CreateNoArgs(&CpLengthInfo_Type); + if (!lengthinfo) { + return NULL; + } + + bool unpack_many_attr = + PyObject_HasAttr(self->m_atom, mod->str___unpack_many__); + + PyObject* length = CpRepeatedAtom_GetLength(self, (PyObject*)layer); + if (!length) { + goto fail; + } + if (_CpUnpack_EvalLength( + layer, length, (bool*)&lengthinfo->m_greedy, &lengthinfo->m_length) < + 0) { + goto fail; + } + + if (unpack_many_attr) { + PyObject* res = PyObject_CallMethodObjArgs(self->m_atom, + mod->str___unpack_many__, + (PyObject*)layer, + lengthinfo, + NULL); + PyObject* exc = NULL; + if ((exc = PyErr_GetRaisedException(), + exc && PyErr_GivenExceptionMatches(exc, PyExc_NotImplementedError))) { + // Make sure this method continues to unpack + Py_XDECREF(exc); + PyErr_Clear(); + Py_XDECREF(res); + } else { + PyErr_SetRaisedException(exc); + return res; + } + } + + CpSeqLayerObject* seq_layer = CpSeqLayer_New(layer->m_state, layer); + if (!layer) { + goto fail; + } + + // REVISIT: add sequence factory here + PyObject* seq = PyList_New(0); + PyObject* obj = NULL; + if (!seq) { + goto fail; + } + + CpSeqLayer_SetSequence( + seq_layer, seq, lengthinfo->m_length, lengthinfo->m_greedy); + while (seq_layer->s_greedy || (seq_layer->m_index < seq_layer->m_length)) { + seq_layer->ob_base.m_path = PyUnicode_FromFormat( + "%s.%d", _PyUnicode_AsString(layer->m_path), seq_layer->m_index); + if (!seq_layer->ob_base.m_path) { + goto fail; + } + + Py_XSETREF(obj, _Cp_Unpack(self->m_atom, (CpLayerObject*)seq_layer)); + if (!obj) { + + if (seq_layer->s_greedy) { + PyErr_Clear(); + break; + } + goto fail; + } + + if (PyList_Append(seq_layer->m_sequence, Py_NewRef(obj)) < 0) { + goto fail; + } + seq_layer->m_index++; + } + +success: + Py_XDECREF(obj); + Py_XDECREF(length); + Py_XDECREF(lengthinfo); + CpLayer_Invalidate((CpLayerObject*)seq_layer); + seq_layer = NULL; + return Py_NewRef(seq); + +fail: + Py_XDECREF(obj); + Py_XDECREF(length); + Py_XDECREF(lengthinfo); + if (seq_layer) { + CpLayer_Invalidate((CpLayerObject*)seq_layer); + } + return NULL; +} + +/* type setup */ +static PyMemberDef CpRepeatedAtom_Members[] = { + { "length", T_OBJECT, offsetof(CpRepeatedAtomObject, m_length), READONLY }, + { "atom", T_OBJECT, offsetof(CpRepeatedAtomObject, m_atom), READONLY }, + { NULL } +}; + +static PyMethodDef CpRepeatedAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(repeatedatom, NULL), + { + NULL, + } +}; + +PyTypeObject CpRepeatedAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpRepeatedAtom_NAME), + .tp_basicsize = sizeof(CpRepeatedAtomObject), + .tp_dealloc = (destructor)cp_repeatedatomobj_dealloc, + .tp_repr = (reprfunc)cp_repeatedatomobj__repr__, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_new = (newfunc)cp_repeatedatomobj_new, + .tp_init = (initproc)cp_repeatedatomobj_init, + .tp_members = CpRepeatedAtom_Members, + .tp_methods = CpRepeatedAtom_Methods, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/builtins/switch.c b/src/ccaterpillar/atomimpl/builtins/switch.c new file mode 100644 index 0000000..5f341a5 --- /dev/null +++ b/src/ccaterpillar/atomimpl/builtins/switch.c @@ -0,0 +1,265 @@ +/* switch atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_switchatom_type(CpSwitchAtomObject* self) +{ + _modulestate* mod = get_global_module_state(); + if (!self->s_callable) { + return Py_NewRef(mod->Any_Type); + } + + PyObject* types = PyList_New(0); + if (!types) { + return NULL; + } + + PyObject* atomType = CpTypeOf(self->m_atom); + if (!atomType) { + Py_DECREF(types); + return NULL; + } + + if (PyList_Append(types, atomType) < 0) { + Py_DECREF(types); + return NULL; + } + + PyObject* values = PyDict_Values(self->m_cases); + if (!values) { + Py_XDECREF(types); + Py_XDECREF(atomType); + return NULL; + } + + Py_ssize_t length = PyList_GET_SIZE(values); + PyObject* switch_type = NULL; + for (Py_ssize_t i = 0; i < length; i++) { + PyObject* value = PyList_GetItem(values, i); + if (!value) { + Py_XDECREF(values); + Py_XDECREF(types); + Py_XDECREF(switch_type); + return NULL; + } + + switch_type = CpTypeOf(value); + if (!switch_type) { + Py_XDECREF(values); + Py_XDECREF(types); + return NULL; + } + + if (!PySequence_Contains(types, switch_type)) { + PyList_Append(types, switch_type); + } + Py_XSETREF(switch_type, NULL); + } + + Py_XDECREF(values); + PyObject* tuple = PyList_AsTuple(types); + Py_XDECREF(types); + if (!tuple) { + Py_XDECREF(atomType); + Py_XDECREF(switch_type); + return NULL; + } + + PyObject* type = PyObject_GetItem(mod->Union_Type, tuple); + Py_XDECREF(tuple); + Py_XDECREF(atomType); + Py_XDECREF(switch_type); + return type; +} + +static PyObject* +cp_switchatom_size(CpSwitchAtomObject* self, CpLayerObject* layer) +{ + PyErr_SetString(PyExc_TypeError, "Switch atoms does not have a static size!"); + return NULL; +} + +static PyObject* +cp_switchatom_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpSwitchAtomObject* self = (CpSwitchAtomObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpSwitchAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpSwitchAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_switchatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_switchatom_type; + self->s_callable = false; + return (PyObject*)self; +} + +static void +cp_switchatom_dealloc(CpSwitchAtomObject* self) +{ + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = NULL; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = NULL; + CpBuiltinAtom_CATOM(self).ob_type = NULL; + Py_CLEAR(self->m_cases); + Py_CLEAR(self->m_atom); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_switchatom_init(CpSwitchAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "atom", "cases", NULL }; + PyObject* cases = NULL; + PyObject* atom = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &atom, &cases)) { + return -1; + } + _Cp_SetObj(self->m_cases, cases); + _Cp_SetObj(self->m_atom, atom); + self->s_callable = PyCallable_Check(self->m_atom); + return 0; +} + +_CpEndian_ImplSetByteorder(CpSwitchAtomObject, switchatom, self->m_atom); + +static PyObject* +cp_switchatom_get_next(CpSwitchAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "obj", "context", NULL }; + PyObject* op = NULL; + PyObject* context = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &op, &context)) { + return NULL; + } + + return CpSwitchAtom_GetNext(self, op, context); +} + +static PyObject* +cp_switchatom_repr(CpSwitchAtomObject* self) +{ + return PyUnicode_FromFormat( + "] %R>", Py_TYPE(self->m_cases)->tp_name, self->m_atom); +} + +/* Public API */ + +/*CpAPI*/ +PyObject* +CpSwitchAtom_GetNext(CpSwitchAtomObject* self, PyObject* op, PyObject* context) +{ + PyObject* result = NULL; + if (PyCallable_Check(self->m_cases)) { + result = PyObject_CallOneArg(self->m_cases, context); + if (!result) + return NULL; + } else { + result = PyObject_GetItem(self->m_cases, op); + if (!result) { + result = PyObject_GetItem(self->m_cases, CpInvalidDefault); + if (!result) + return NULL; + } + } + // TODO: check for nested struct + return result; +} + +/*CpAPI*/ +PyObject* +CpSwitchAtom_Unpack(CpSwitchAtomObject* self, CpLayerObject* layer) +{ + PyObject* value = _Cp_Unpack(self->m_atom, layer); + if (!value) { + return NULL; + } + + PyObject* next_atom = CpSwitchAtom_GetNext(self, value, (PyObject*)layer); + if (!next_atom) { + Py_DECREF(value); + return NULL; + } + + if (Py_IsNone(next_atom)) { + // TODO: document this behaviour + return value; + } + + PyObject* result = _Cp_Unpack(next_atom, layer); + Py_DECREF(value); + Py_DECREF(next_atom); + return result; +} + +/*CpAPI*/ +int +CpSwitchAtom_Pack(CpSwitchAtomObject* self, PyObject* obj, CpLayerObject* layer) +{ + if (!self->s_callable) { + PyErr_SetString( + PyExc_TypeError, + ("Switch atom currently supports only callable atoms when used as a " + "condition to select the final atom to be used to pack the given " + "value.")); + return -1; + } + + PyObject* value = PyObject_CallOneArg(self->m_atom, (PyObject*)layer); + if (!value) { + return -1; + } + + PyObject* next_atom = CpSwitchAtom_GetNext(self, value, (PyObject*)layer); + if (!next_atom) { + Py_DECREF(value); + return -1; + } + + int result = _Cp_Pack(obj, next_atom, layer); + Py_DECREF(next_atom); + Py_DECREF(value); + return result; +} + +/* docs */ + +/* members */ +static PyMemberDef CpSwitchAtom_Members[] = { + { "cases", T_OBJECT, offsetof(CpSwitchAtomObject, m_cases), READONLY }, + { "atom", T_OBJECT, offsetof(CpSwitchAtomObject, m_atom), READONLY }, + { NULL } +}; + +/* methods */ +static PyMethodDef CpSwitchAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(switchatom, NULL), + { "get_next", + (PyCFunction)cp_switchatom_get_next, + METH_VARARGS | METH_KEYWORDS, + NULL }, + { NULL } +}; + +PyTypeObject CpSwitchAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpConditionAtom_NAME), + .tp_basicsize = sizeof(CpConditionAtomObject), + .tp_dealloc = (destructor)cp_switchatom_dealloc, + .tp_init = (initproc)cp_switchatom_init, + .tp_members = CpSwitchAtom_Members, + .tp_methods = CpSwitchAtom_Methods, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_new = (newfunc)cp_switchatom_new, + .tp_repr = (reprfunc)cp_switchatom_repr, +}; diff --git a/src/ccaterpillar/atomimpl/bytes.c b/src/ccaterpillar/atomimpl/bytes.c new file mode 100644 index 0000000..6a42a38 --- /dev/null +++ b/src/ccaterpillar/atomimpl/bytes.c @@ -0,0 +1,127 @@ +/* constatom C implementation */ +#include "caterpillar/caterpillar.h" + +#include + +/*impl*/ + +static PyObject* +cp_bytesatom_type(CpBytesAtomObject* self) +{ + return Py_NewRef(&PyBytes_Type); +} + +static PyObject* +cp_bytesatom_size(CpBytesAtomObject* self, CpLayerObject* layer) +{ + return CpBytesAtom_GetLength(self, layer); +} + +static PyObject* +cp_bytesatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpBytesAtomObject* self = (CpBytesAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpBytesAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpBytesAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_bytesatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_bytesatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + self->m_length = NULL; + self->s_callable = 0; + } + return (PyObject*)self; +} + +static void +cp_bytesatom_dealloc(CpBytesAtomObject* self) +{ + Py_XDECREF(self->m_length); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_bytesatom_init(CpBytesAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "length", NULL }; + PyObject* length = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &length)) { + return -1; + } + _Cp_SetObj(self->m_length, length); + self->s_callable = PyCallable_Check(self->m_length); + return 0; +} + +static PyObject* +cp_bytesatom_repr(CpBytesAtomObject* self) +{ + return PyUnicode_FromFormat("", self->m_length); +} + +/* Public API */ + +/*CpAPI*/ +PyObject* +CpBytesAtom_GetLength(CpBytesAtomObject* self, CpLayerObject* layer) +{ + if (self->s_callable) { + return PyObject_CallOneArg(self->m_length, (PyObject*)layer); + } + return Py_NewRef(self->m_length); +} + +/*CpAPI*/ +int +CpBytesAtom_Pack(CpBytesAtomObject* self, PyObject* value, CpLayerObject* layer) +{ + PyObject* length = CpBytesAtom_GetLength(self, layer); + if (length == NULL) { + return -1; + } + + if (PyLong_AS_LONG(length) != PyBytes_GET_SIZE(value)) { + PyErr_SetString(PyExc_ValueError, "Invalid length"); + return -1; + } + Py_DECREF(length); + + PyObject* res = CpState_Write(layer->m_state, value); + if (!res) { + return -1; + } + Py_DECREF(res); + return 0; +} + +/*CpAPI*/ +PyObject* +CpBytesAtom_Unpack(CpBytesAtomObject* self, CpLayerObject* layer) +{ + PyObject* length = CpBytesAtom_GetLength(self, layer); + if (length == NULL) { + return NULL; + } + return CpState_Read(layer->m_state, length); +} + +/* docs */ + +/* type setup */ +static PyMemberDef CpBytesAtom_Members[] = { + { "length", T_OBJECT, offsetof(CpBytesAtomObject, m_length), READONLY }, + { NULL } +}; + +PyTypeObject CpBytesAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpBytesAtom_NAME), + .tp_basicsize = sizeof(CpBytesAtomObject), + .tp_members = CpBytesAtom_Members, + .tp_new = (newfunc)cp_bytesatom_new, + .tp_init = (initproc)cp_bytesatom_init, + .tp_dealloc = (destructor)cp_bytesatom_dealloc, + .tp_repr = (reprfunc)cp_bytesatom_repr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, +}; \ No newline at end of file diff --git a/src/atomimpl/charatomobj.c b/src/ccaterpillar/atomimpl/char.c similarity index 72% rename from src/atomimpl/charatomobj.c rename to src/ccaterpillar/atomimpl/char.c index 7a05d88..8087d00 100644 --- a/src/atomimpl/charatomobj.c +++ b/src/ccaterpillar/atomimpl/char.c @@ -1,6 +1,6 @@ /* charatom C implementation */ -#include "caterpillar/atoms/primitive.h" -#include "caterpillar/state.h" +#include "caterpillar/caterpillar.h" + #include static PyObject* @@ -20,13 +20,13 @@ cp_charatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { CpCharAtomObject* self = (CpCharAtomObject*)type->tp_alloc(type, 0); if (self != NULL) { - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpCharAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpCharAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = NULL; - CpFieldCAtom_CATOM(self).ob_unpack_many = NULL; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_charatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_charatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpCharAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpCharAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_charatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_charatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; } return (PyObject*)self; } @@ -44,7 +44,7 @@ cp_charatom_init(CpCharAtomObject* self, PyObject* args, PyObject* kwds) } /* Public API */ - +/*CpAPI*/ int CpCharAtom_Pack(CpCharAtomObject* self, PyObject* value, CpLayerObject* layer) { @@ -66,10 +66,11 @@ CpCharAtom_Pack(CpCharAtomObject* self, PyObject* value, CpLayerObject* layer) return 0; } +/*CpAPI*/ PyObject* CpCharAtom_Unpack(CpCharAtomObject* self, CpLayerObject* layer) { - PyObject* res = CpState_Read(layer->m_state, 1); + PyObject* res = CpState_ReadSsize_t(layer->m_state, 1); if (!res) { return NULL; } @@ -78,7 +79,7 @@ CpCharAtom_Unpack(CpCharAtomObject* self, CpLayerObject* layer) /* type setup */ PyTypeObject CpCharAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(charatom), + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpCharAtom_NAME), .tp_basicsize = sizeof(CpCharAtomObject), .tp_dealloc = (destructor)cp_charatom_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT, diff --git a/src/ccaterpillar/atomimpl/computed.c b/src/ccaterpillar/atomimpl/computed.c new file mode 100644 index 0000000..ae141f7 --- /dev/null +++ b/src/ccaterpillar/atomimpl/computed.c @@ -0,0 +1,109 @@ +/* computed atom C implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_computedatom_type(CpComputedAtomObject* self) +{ + if (self->s_callable) { + return Py_XNewRef(get_global_module_state()->Any_Type); + } + return Py_NewRef(Py_TYPE(self->m_value)); +} + +static PyObject* +cp_computedatom_size(CpComputedAtomObject* self, PyObject* ctx) +{ + // TODO: explain why computed atom objects are of size 0 + return PyLong_FromSize_t(0); +} + +static PyObject* +cp_computedatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpComputedAtomObject* self = (CpComputedAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpComputedAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpComputedAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_computedatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_computedatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + + self->m_value = NULL; + self->s_callable = false; + } + return (PyObject*)self; +} + +static void +cp_computedatom_dealloc(CpComputedAtomObject* self) +{ + Py_XDECREF(self->m_value); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_computedatom_init(CpComputedAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "value", NULL }; + PyObject* value = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &value)) { + return -1; + } + + _Cp_SetObj(self->m_value, value); + self->s_callable = PyCallable_Check(self->m_value); + return 0; +} + +static PyObject* +cp_computedatom_repr(CpComputedAtomObject* self) +{ + return PyUnicode_FromFormat( + "", + self->s_callable ? "" : Py_TYPE(self->m_value)->tp_name); +} + +/* Public API */ + +/*CpAPI*/ +int +CpComputedAtom_Pack(CpComputedAtomObject* self, + PyObject* obj, + CpLayerObject* layer) +{ + return 0; +} + +/*CpAPI*/ +PyObject* +CpComputedAtom_Unpack(CpComputedAtomObject* self, CpLayerObject* layer) +{ + return CpComputedAtom_Value(self, layer); +} + +/* docs */ + +/* type */ + +static PyMemberDef cp_computedatom_members[] = { + { "value", T_OBJECT, offsetof(CpComputedAtomObject, m_value), READONLY, "value" }, + { NULL } +}; + +PyTypeObject CpComputedAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpComputedAtom_NAME), + .tp_basicsize = sizeof(CpComputedAtomObject), + .tp_dealloc = (destructor)cp_computedatom_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_init = (initproc)cp_computedatom_init, + .tp_new = (newfunc)cp_computedatom_new, + .tp_members = cp_computedatom_members, + .tp_repr = (reprfunc)cp_computedatom_repr, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/const.c b/src/ccaterpillar/atomimpl/const.c new file mode 100644 index 0000000..27c476b --- /dev/null +++ b/src/ccaterpillar/atomimpl/const.c @@ -0,0 +1,120 @@ +/* constatom C implementation */ +#include "caterpillar/caterpillar.h" + +#include + +static PyObject* +cp_constatom__type__(CpConstAtomObject* self) +{ + return CpTypeOf(self->m_atom); +} + +static PyObject* +cp_constatom__size__(CpConstAtomObject* self, CpLayerObject* layer) +{ + return _Cp_SizeOf(self->m_atom, layer); +} + +static PyObject* +cp_constatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpConstAtomObject* self = (CpConstAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpConstAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpConstAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_constatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_constatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + + self->m_atom = NULL; + self->m_value = NULL; + } + return (PyObject*)self; +} + +static void +cp_constatom_dealloc(CpConstAtomObject* self) +{ + Py_XDECREF(self->m_atom); + Py_XDECREF(self->m_value); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_constatom_init(CpConstAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "atom", "value", NULL }; + PyObject* atom = NULL; + PyObject* value = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &atom, &value)) { + return -1; + } + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_value, value); + return 0; +} + +static PyObject* +cp_constatom_repr(CpConstAtomObject* self) +{ + return PyUnicode_FromFormat("", self->m_value, self->m_atom); +} + +_CpEndian_ImplSetByteorder(CpConstAtomObject, constatom, self->m_atom); + +/* Public API */ + +/*CpAPI*/ +int +CpConstAtom_Pack(CpConstAtomObject* self, PyObject* value, CpLayerObject* layer) +{ + return _Cp_Pack(self->m_value, self->m_atom, layer); +} + +/*CpAPI*/ +PyObject* +CpConstAtom_Unpack(CpConstAtomObject* self, CpLayerObject* layer) +{ + PyObject* res = _Cp_Unpack(self->m_atom, layer); + if (!res) { + return NULL; + } + + if (PyObject_RichCompareBool(res, self->m_value, Py_NE)) { + PyErr_Format(PyExc_ValueError, + "Value is not equal to constant (parsed=%R, constant=%R)", + res, + self->m_value); + Py_DECREF(res); + return NULL; + } + // REVISIT: Should we free the 'res' object here? + return res; +} + +/* type setup */ + +static PyMemberDef cp_constatom_members[] = { + { "atom", T_OBJECT_EX, offsetof(CpConstAtomObject, m_atom), READONLY }, + { "value", T_OBJECT_EX, offsetof(CpConstAtomObject, m_value), READONLY }, + { NULL }, +}; + +static PyMethodDef cp_constatom_methods[] = { + _CpEndian_ImplSetByteorder_MethDef(constatom, NULL), + { NULL } +}; + +PyTypeObject CpConstAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpConstAtom_NAME), + .tp_basicsize = sizeof(CpConstAtomObject), + .tp_dealloc = (destructor)cp_constatom_dealloc, + .tp_init = (initproc)cp_constatom_init, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = (newfunc)cp_constatom_new, + .tp_repr = (reprfunc)cp_constatom_repr, + .tp_members = cp_constatom_members, + .tp_methods = cp_constatom_methods +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/cstring.c b/src/ccaterpillar/atomimpl/cstring.c new file mode 100644 index 0000000..25e3795 --- /dev/null +++ b/src/ccaterpillar/atomimpl/cstring.c @@ -0,0 +1,317 @@ +/* c-string atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include +#include + +/* impl */ +static PyObject* +cp_cstringatom_type(CpCStringAtomObject* self) +{ + return Py_NewRef(&PyUnicode_Type); +} + +static PyObject* +cp_cstringatom_size(CpCStringAtomObject* self, CpLayerObject* layer) +{ + if (self->s_number) { + return Py_NewRef(self->m_length); + } + PyErr_SetString(PyExc_ValueError, + "dynamic-sized c-string atoms do not have a static size"); + return NULL; +} + +static PyObject* +cp_cstringatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpCStringAtomObject* self = (CpCStringAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpCStringAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpCStringAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_cstringatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_cstringatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + + self->m_length = NULL; + self->m_encoding = NULL; + self->m_errors = NULL; + self->m_terminator = NULL; + self->_m_terminator_bytes = NULL; + self->s_keep_terminator = false; + self->s_number = false; + self->s_callable = false; + self->s_greedy = false; + } + return (PyObject*)self; +} + +static void +cp_cstringatom_dealloc(CpCStringAtomObject* self) +{ + Py_XDECREF(self->m_length); + Py_XDECREF(self->m_encoding); + Py_XDECREF(self->m_errors); + Py_XDECREF(self->m_terminator); + Py_XDECREF(self->_m_terminator_bytes); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_cstringatom_init(CpCStringAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "length", "encoding", "errors", + "sep", "keep", NULL }; + PyObject* length = NULL; + PyObject* encoding = NULL; + PyObject* errors = NULL; + PyObject* terminator = NULL; + int keep_terminator = false; + if (!PyArg_ParseTupleAndKeywords(args, + kwds, + "|OOOOp", + kwlist, + &length, + &encoding, + &errors, + &terminator, + &keep_terminator)) { + return -1; + } + + _modulestate* mod = get_global_module_state(); + + _Cp_SetObj(self->m_length, length); + if (!self->m_length) { + // TODO: document this + _Cp_SetObj(self->m_length, Py_Ellipsis); + } + + _Cp_SetObj(self->m_encoding, encoding); + if (!self->m_encoding) { + Py_XSETREF(self->m_encoding, mod->str_utf8); + } + + _Cp_SetObj(self->m_errors, errors); + if (!self->m_errors) { + Py_XSETREF(self->m_errors, mod->str_strict); + } + + _Cp_SetObj(self->m_terminator, terminator); + if (!self->m_terminator) { + _Cp_SetObj(self->_m_terminator_bytes, mod->cp_bytes__false); + _Cp_SetObj(self->m_terminator, mod->str_cstring_default_pad); + } else { + if (!PyUnicode_Check(self->m_terminator)) { + PyErr_SetString(PyExc_TypeError, "terminator must be a string"); + return -1; + } + self->_m_terminator_bytes = + PyUnicode_AsEncodedString(self->m_terminator, + PyUnicode_AsUTF8AndSize(self->m_encoding, NULL), + PyUnicode_AsUTF8AndSize(self->m_errors, NULL)); + if (!self->_m_terminator_bytes) { + return -1; + } + } + + self->s_keep_terminator = keep_terminator; + self->s_number = PyLong_Check(self->m_length); + self->s_callable = PyCallable_Check(self->m_length); + self->s_greedy = Py_IS_TYPE(self->m_length, &PyEllipsis_Type); + return 0; +} + +static PyObject* +cp_cstringatom_repr(CpCStringAtomObject* self) +{ + return PyUnicode_FromFormat( + "", self->m_encoding, self->m_errors); +} + +/* Public API */ + +/*CpAPI*/ +int +CpCStringAtom_Pack(CpCStringAtomObject* self, + PyObject* value, + CpLayerObject* layer) +{ + if (!PyUnicode_Check(value)) { + PyErr_Format( + PyExc_TypeError, "C strings must be packed from unicode - got %R", value); + return -1; + } + PyObject* bytes = PyUnicode_AsEncodedString( + value, PyUnicode_DATA(self->m_encoding), PyUnicode_DATA(self->m_errors)); + if (!bytes) { + return -1; + } + + if (!self->s_callable && !self->s_number && !self->s_greedy) { + // is atom, must pack length as prefix + PyObject* length = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(value) + 1); + if (!length) { + return -1; + } + if (_Cp_Pack(length, self->m_length, layer)) { + Py_DECREF(length); + return -1; + } + Py_DECREF(length); + } + + PyObject* res = CpState_Write(layer->m_state, bytes); + Py_DECREF(bytes); + if (!res) { + return -1; + } + Py_XDECREF(res); + + if (self->s_greedy || (!self->s_callable && !self->s_number)) { + res = CpState_Write(layer->m_state, self->_m_terminator_bytes); + if (!res) { + return -1; + } + Py_XDECREF(res); + } else { + // NOTE: we know unpack won't be called + PyObject* length = CpCStringAtom_Length(self, layer); + if (length == NULL) { + return -1; + } + PyObject* objLength = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(value)); + if (!objLength) { + Py_DECREF(length); + return -1; + } + + PyObject* realLength = PyNumber_Subtract(length, objLength); + if (!realLength) { + Py_DECREF(length); + return -1; + } + + PyObject* trailer = + PyNumber_Multiply(self->_m_terminator_bytes, realLength); + Py_DECREF(objLength); + Py_DECREF(realLength); + Py_DECREF(length); + if (!trailer) { + return -1; + } + + res = CpState_Write(layer->m_state, trailer); + Py_DECREF(trailer); + if (!res) { + return -1; + } + Py_XDECREF(res); + } + return 0; +} + +/*CpAPI*/ +PyObject* +CpCStringAtom_Unpack(CpCStringAtomObject* self, CpLayerObject* layer) +{ + PyObject* length = CpCStringAtom_Length(self, layer); + if (length == NULL) { + return NULL; + } + + PyObject* result = NULL; + if (Py_IsNone(length)) { + // greedy parsing + result = CpObject_CreateNoArgs(&PyBytes_Type); + if (!result) { + return NULL; + } + // REVISIT: is this efficient? + while (true) { + PyObject* bytes = CpState_ReadSsize_t(layer->m_state, 1); + if (!bytes) { + return NULL; + } + if (PyBytes_AS_STRING(bytes)[0] == + PyBytes_AS_STRING(self->_m_terminator_bytes)[0]) { + break; + } + PyBytes_Concat(&result, bytes); + Py_DECREF(bytes); + if (!result) { + return NULL; + } + } + } else { + result = CpState_Read(layer->m_state, length); + Py_DECREF(length); + if (!result) { + return NULL; + } + } + if (!result) { + return NULL; + } + PyObject* string = PyUnicode_Decode(PyBytes_AS_STRING(result), + PyBytes_GET_SIZE(result), + PyUnicode_DATA(self->m_encoding), + PyUnicode_DATA(self->m_errors)); + Py_XDECREF(result); + if (!string) { + return NULL; + } + + if (!self->s_keep_terminator) { + Py_XSETREF(string, _PyUnicode_XStrip(string, 1, self->m_terminator)); + } + return string; +} + +/* docs */ + +/* type */ +static PyMemberDef CpCStringAtom_Members[] = { + { "encoding", + T_OBJECT_EX, + offsetof(CpCStringAtomObject, m_encoding), + 0, + "the encoding of the string" }, + { "errors", + T_OBJECT_EX, + offsetof(CpCStringAtomObject, m_errors), + 0, + "the error handling for the string" }, + { "terminator", + T_OBJECT_EX, + offsetof(CpCStringAtomObject, m_terminator), + READONLY, + "the terminator for the string" }, + { "keep_terminator", + T_BOOL, + offsetof(CpCStringAtomObject, s_keep_terminator), + 0, + "whether to keep the terminator" }, + { "length", + T_OBJECT_EX, + offsetof(CpCStringAtomObject, m_length), + 0, + "the length of the string" }, + { NULL } +}; + +PyTypeObject CpCStringAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpCStringAtom_NAME), + .tp_basicsize = sizeof(CpCStringAtomObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)cp_cstringatom_dealloc, + .tp_repr = (reprfunc)cp_cstringatom_repr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = (newfunc)cp_cstringatom_new, + .tp_members = CpCStringAtom_Members, + .tp_init = (initproc)cp_cstringatom_init, + .tp_doc = NULL, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/enum.c b/src/ccaterpillar/atomimpl/enum.c new file mode 100644 index 0000000..266778e --- /dev/null +++ b/src/ccaterpillar/atomimpl/enum.c @@ -0,0 +1,187 @@ +/* enum atom C implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_enumatom_type(CpEnumAtomObject* self) +{ + if (self->m_enum_type) { + return Py_NewRef(self->m_enum_type); + } + return CpTypeOf(self->m_atom); +} + +static PyObject* +cp_enumatom_size(CpEnumAtomObject* self, CpLayerObject* layer) +{ + return _Cp_SizeOf(self->m_atom, layer); +} + +static PyObject* +cp_enumatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpEnumAtomObject* atom = (CpEnumAtomObject*)type->tp_alloc(type, 0); + if (atom != NULL) { + CpBuiltinAtom_CATOM(atom).ob_pack = (packfunc)CpEnumAtom_Pack; + CpBuiltinAtom_CATOM(atom).ob_unpack = (unpackfunc)CpEnumAtom_Unpack; + CpBuiltinAtom_CATOM(atom).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(atom).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(atom).ob_size = (sizefunc)cp_enumatom_size; + CpBuiltinAtom_CATOM(atom).ob_type = (typefunc)cp_enumatom_type; + CpBuiltinAtom_CATOM(atom).ob_bits = NULL; + + atom->m_atom = NULL; + atom->m_enum_type = NULL; + atom->m_members = NULL; + atom->m_value2member_map = NULL; + atom->m_default = Py_NewRef(CpInvalidDefault); + } + return (PyObject*)atom; +} + +static void +cp_enumatom_dealloc(CpEnumAtomObject* self) +{ + Py_XDECREF(self->m_atom); + Py_XDECREF(self->m_members); + Py_XDECREF(self->m_enum_type); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_enumatom_init(CpEnumAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "atom", "enum_type", "default", NULL }; + PyObject *atom = NULL, *enum_type = NULL, *default_value = NULL; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "OO|O", kwlist, &atom, &enum_type, &default_value)) { + return -1; + } + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_enum_type, enum_type); + _Cp_SetObj(self->m_default, default_value); + + _modulestate* mod = get_global_module_state(); + PyObject* members = + PyObject_GetAttr(self->m_enum_type, mod->str__member_map_); + if (!members) { + return -1; + } + + _Cp_SetObj(self->m_members, members); + PyObject* value2member_map = + PyObject_GetAttr(self->m_enum_type, mod->str__value2member_map_); + if (!value2member_map) { + return -1; + } + _Cp_SetObj(self->m_value2member_map, value2member_map); + + return 0; +} + +_CpEndian_ImplSetByteorder(CpEnumAtomObject, enumatom, self->m_atom); + +static PyObject* +cp_enumatom_repr(CpEnumAtomObject* self) +{ + return PyUnicode_FromFormat( + "] %R>", ((PyTypeObject *)self->m_enum_type)->tp_name, self->m_atom); +} + +/* Public API */ + +/*CpAPI*/ +int +CpEnumAtom_Pack(CpEnumAtomObject* self, PyObject* value, CpLayerObject* layer) +{ + // TODO: check enum type - add strict flag + // if (!PyObject_TypeCheck(value, (PyTypeObject*)self->m_enum_type)) { + // PyErr_Format(PyExc_TypeError, + // "Expected a %s, got %s", + // Py_TYPE(self->m_enum_type)->tp_name, + // Py_TYPE(value)->tp_name); + // return -1; + // } + int res = 0; + if (PyObject_TypeCheck(value, (PyTypeObject*)self->m_enum_type)) { + PyObject* real_value = PyObject_GetAttrString(value, "value"); + if (!real_value) { + return -1; + } + res = _Cp_Pack(real_value, self->m_atom, layer); + Py_DECREF(real_value); + } else { + res = _Cp_Pack(value, self->m_atom, layer); + } + + return res; +} + +/*CpAPI*/ +PyObject* +CpEnumAtom_Unpack(CpEnumAtomObject* self, CpLayerObject* layer) +{ + PyObject* value = _Cp_Unpack(self->m_atom, layer); + if (!value) { + return NULL; + } + + PyObject* by_name = PyDict_GetItem(self->m_members, value); + if (by_name) { + Py_DECREF(value); + return Py_NewRef(by_name); + } + // not found + PyErr_Clear(); + + PyObject* by_value = PyDict_GetItem(self->m_value2member_map, value); + if (by_value) { + Py_DECREF(value); + return Py_NewRef(by_value); + } + + // not found, fallback to default value + PyErr_Clear(); + + if (Cp_IsInvalidDefault(self->m_default)) { + // TODO: add strict parsing flag here + return value; + } + Py_DECREF(value); + return Py_NewRef(self->m_default); +} + +/* docs */ + +/* type setup */ + +static PyMemberDef CpEnumAtom_Members[] = { + { "atom", T_OBJECT, offsetof(CpEnumAtomObject, m_atom), READONLY }, + { "enum_type", T_OBJECT, offsetof(CpEnumAtomObject, m_enum_type), READONLY }, + { "members", T_OBJECT, offsetof(CpEnumAtomObject, m_members), READONLY }, + { "default", T_OBJECT, offsetof(CpEnumAtomObject, m_default), 0 }, + { NULL } +}; + +static PyMethodDef CpEnumAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(enumatom, NULL), + { + NULL, + } +}; + +PyTypeObject CpEnumAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpEnumAtom_NAME), + .tp_basicsize = sizeof(CpEnumAtomObject), + .tp_dealloc = (destructor)cp_enumatom_dealloc, + .tp_init = (initproc)cp_enumatom_init, + .tp_repr = (reprfunc)cp_enumatom_repr, + .tp_members = CpEnumAtom_Members, + .tp_methods = CpEnumAtom_Methods, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_new = (newfunc)cp_enumatom_new, +}; diff --git a/src/atomimpl/floatatomobj.c b/src/ccaterpillar/atomimpl/float.c similarity index 71% rename from src/atomimpl/floatatomobj.c rename to src/ccaterpillar/atomimpl/float.c index bdf277d..575052c 100644 --- a/src/atomimpl/floatatomobj.c +++ b/src/ccaterpillar/atomimpl/float.c @@ -1,9 +1,5 @@ /* floatatom C implementation */ #include "caterpillar/caterpillar.h" -#include "caterpillar/arch.h" -#include "caterpillar/atoms/float.h" -#include "caterpillar/state.h" -#include "caterpillar/module.h" #include static PyObject* @@ -27,13 +23,13 @@ cp_floatatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) self->_m_bits = 0; self->_m_little_endian = true; - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpFloatAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpFloatAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = NULL; - CpFieldCAtom_CATOM(self).ob_unpack_many = NULL; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_floatatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_floatatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpFloatAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpFloatAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_floatatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_floatatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; } return (PyObject*)self; } @@ -73,8 +69,28 @@ cp_floatatom_init(CpFloatAtomObject* self, PyObject* args, PyObject* kwds) return 0; } -/* Public API */ +static PyObject* +cp_floatatom_set_byteorder(CpFloatAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "byteorder", NULL }; + PyObject* byteorder = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &byteorder)) { + return NULL; + } + if (!CpEndian_Check(byteorder)) { + PyErr_SetString(PyExc_TypeError, "byteorder must be an Endian object"); + return NULL; + } + return CpObject_Create(&CpIntAtom_Type, + "Ii", + self->_m_bits, + CpEndian_IsLittleEndian((CpEndianObject*)byteorder, + get_global_module_state())); +} + +/* Public API */ +/*CpAPI*/ int CpFloatAtom_Pack(CpFloatAtomObject* self, PyObject* value, CpLayerObject* layer) { @@ -89,15 +105,6 @@ CpFloatAtom_Pack(CpFloatAtomObject* self, PyObject* value, CpLayerObject* layer) } int little_endian = self->_m_little_endian; - if (layer->m_field) { - _modulestate* mod = layer->m_state->mod; - PyObject* endian = ((CpFieldObject*)layer->m_field)->m_endian; - - if (CpEndian_Check(endian)) - /* If the field has an endian specified, use that. */ - little_endian = CpEndian_IsLittleEndian((CpEndianObject*)endian, mod); - } - int res = 0; switch (self->_m_byte_count) { case 2: { @@ -135,25 +142,16 @@ CpFloatAtom_Pack(CpFloatAtomObject* self, PyObject* value, CpLayerObject* layer) return 0; } +/*CpAPI*/ PyObject* CpFloatAtom_Unpack(CpFloatAtomObject* self, CpLayerObject* layer) { - PyObject* bytes = CpState_Read(layer->m_state, self->_m_byte_count); + PyObject* bytes = CpState_ReadSsize_t(layer->m_state, self->_m_byte_count); if (!bytes) { return NULL; } int little_endian = self->_m_little_endian; - if (layer->m_field) { - _modulestate* mod = layer->m_state->mod; - PyObject* endian = ((CpFieldObject*)layer->m_field)->m_endian; - - if (CpEndian_Check(endian)) { - /* If the field has an endian specified, use that. */ - little_endian = CpEndian_IsLittleEndian((CpEndianObject*)endian, mod); - } - } - double res; switch (self->_m_byte_count) { case 2: { @@ -188,19 +186,23 @@ static PyMemberDef CpFloatAtom_Members[] = { READONLY, NULL }, { "bits", T_PYSSIZET, offsetof(CpFloatAtomObject, _m_bits), READONLY, NULL }, - { "little_endian", - T_BOOL, - offsetof(CpFloatAtomObject, _m_little_endian)}, + { "little_endian", T_BOOL, offsetof(CpFloatAtomObject, _m_little_endian) }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpFloatAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(floatatom, NULL), { NULL } /* Sentinel */ }; PyTypeObject CpFloatAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(floatatom), + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpFloatAtom_NAME), .tp_basicsize = sizeof(CpFloatAtomObject), .tp_dealloc = (destructor)cp_floatatom_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = NULL, .tp_members = CpFloatAtom_Members, + .tp_methods = CpFloatAtom_Methods, .tp_init = (initproc)cp_floatatom_init, .tp_new = (newfunc)cp_floatatom_new, }; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/int.c b/src/ccaterpillar/atomimpl/int.c new file mode 100644 index 0000000..7714cd2 --- /dev/null +++ b/src/ccaterpillar/atomimpl/int.c @@ -0,0 +1,240 @@ +/* intatom C implementation */ +#include + +#include "caterpillar/caterpillar.h" + +#include + +static PyObject* +cp_intatom__type__(CpIntAtomObject* self) +{ + return Py_XNewRef(&PyLong_Type); +} + +static PyObject* +cp_intatom__size__(CpIntAtomObject* self, PyObject* ctx) +{ + return Py_XNewRef(self->m_byte_count); +} + +static PyObject* +cp_intatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpIntAtomObject* self = (CpIntAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + self->m_byte_count = NULL; + self->_m_bits = 0; + self->_m_signed = true; + self->_m_little_endian = true; + + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpIntAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpIntAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_intatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_intatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + } + return (PyObject*)self; +} + +static void +cp_intatom_dealloc(CpIntAtomObject* self) +{ + Py_XDECREF(self->m_byte_count); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_intatom_init(CpIntAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "nbits", "signed", "little_endian", NULL }; + int _signed = true, little_endian = true; + unsigned int bits = 0; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "I|pp", kwlist, &bits, &_signed, &little_endian)) { + return -1; + } + if (bits == 0) { + PyErr_SetString(PyExc_ValueError, "bytes cannot be zero"); + return -1; + } + if (bits % 8) { + PyErr_SetString(PyExc_ValueError, "nbits must be a multiple of 8"); + return -1; + } + + self->_m_bits = bits; + self->_m_byte_count = bits / 8; + self->_m_signed = _signed; + self->_m_little_endian = little_endian; + self->m_byte_count = PyLong_FromUnsignedLong(self->_m_byte_count); + if (!self->m_byte_count) { + return -1; + } + return 0; +} + +static PyObject* +cp_intatom_repr(CpIntAtomObject* self) +{ + char sign = 'i'; + if (!self->_m_signed) { + sign = 'u'; + } + char endian = 'l'; + if (!self->_m_little_endian) { + endian = 'b'; + } + return PyUnicode_FromFormat("<%ce %cint%d>", endian, sign, self->_m_bits); +} + +static PyObject* +cp_intatom_set_byteorder(CpIntAtomObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "byteorder", NULL }; + PyObject* byteorder = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &byteorder)) { + return NULL; + } + if (!CpEndian_Check(byteorder)) { + PyErr_SetString(PyExc_TypeError, "byteorder must be an Endian object"); + return NULL; + } + + return CpObject_Create(&CpIntAtom_Type, + "Iii", + (unsigned int)self->_m_bits, + self->_m_signed, + CpEndian_IsLittleEndian((CpEndianObject*)byteorder, + get_global_module_state())); +} + +/* Public API */ +/*CpAPI*/ +int +CpIntAtom_Pack(CpIntAtomObject* self, PyObject* op, CpLayerObject* layer) +{ + PyObject* bytes = CpObject_CreateOneArg(&PyBytes_Type, self->m_byte_count); + if (!bytes) { + return -1; + } + + if (!PyLong_Check(op)) { + PyErr_Format(PyExc_TypeError, "op must be an int, got %R", op); + Py_DECREF(bytes); + } + + int little_endian = self->_m_little_endian; + int res = _PyLong_AsByteArray((PyLongObject*)op, + (unsigned char*)PyBytes_AS_STRING(bytes), + self->_m_byte_count, + little_endian, + self->_m_signed); + if (res == -1) { + return -1; + } + + PyObject* result = CpState_Write(layer->m_state, bytes); + if (!result) { + return -1; + } + Py_DECREF(result); + return 0; +} + +/*CpAPI*/ +PyObject* +CpIntAtom_Unpack(CpIntAtomObject* self, CpLayerObject* layer) +{ + PyObject* bytes = CpState_ReadSsize_t(layer->m_state, self->_m_byte_count); + if (!bytes) { + return NULL; + } + + int little_endian = self->_m_little_endian; + PyObject* obj = + _PyLong_FromByteArray((unsigned char*)PyBytes_AS_STRING(bytes), + self->_m_byte_count, + little_endian, + self->_m_signed); + Py_DECREF(bytes); + return obj; +} + +/* docs */ +// clang-format off +PyDoc_STRVAR(cp_intatom_member__nbytes__doc, +"Stores the amount of bytes in the atom (not the number of bits)."); + +PyDoc_STRVAR(cp_intatom_member__nbits__doc, +"Stores the amount of bits in the atom."); + +PyDoc_STRVAR(cp_intatom_member__signed__doc, +"Stores whether the atom is signed or not."); + +PyDoc_STRVAR(cp_intatom_member__little_endian__doc, +"Stores whether the atom is little endian or not."); + +PyDoc_STRVAR(cp_intatom__doc, +"Int(bits, signed=True, little_endian=True)\n" +"--\n" +"\n" +"Implements an integer atom with a variable amount of bits.\n" +"\n" +"bits\n" +" The amount of bits in the atom.\n" +"signed\n" +" Whether the atom is signed or not.\n" +"little_endian\n" +" Whether the atom is little endian or not.\n" +"\n" +"Unlike its Python counterpart (caterpillar.py.FormatField), this C-based " +"class focuses solely on integer operations. It utilizes `int.from_bytes` and " +"`int.to_bytes` internally, optimizing runtime performance. However, it is " +"recommended to use the global instances of this class instead of creating " +"new objects directly."); +// clang-format on + +/* type */ +static PyMemberDef CpIntAtom_Members[] = { + { "nbytes", + T_PYSSIZET, + offsetof(CpIntAtomObject, _m_byte_count), + READONLY, + cp_intatom_member__nbytes__doc }, + { "nbits", + T_PYSSIZET, + offsetof(CpIntAtomObject, _m_bits), + READONLY, + cp_intatom_member__nbits__doc }, + { "signed", + T_BOOL, + offsetof(CpIntAtomObject, _m_signed), + READONLY, + cp_intatom_member__signed__doc }, + { "little_endian", + T_BOOL, + offsetof(CpIntAtomObject, _m_little_endian), + READONLY, + cp_intatom_member__little_endian__doc }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpIntAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(intatom, NULL), + { NULL } /* Sentinel */ +}; + +/* --- */ PyTypeObject CpIntAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpIntAtom_NAME), + .tp_basicsize = sizeof(CpIntAtomObject), + .tp_dealloc = (destructor)cp_intatom_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = cp_intatom__doc, + .tp_members = CpIntAtom_Members, + .tp_new = (newfunc)cp_intatom_new, + .tp_init = (initproc)cp_intatom_init, + .tp_repr = (reprfunc)cp_intatom_repr, + .tp_methods = CpIntAtom_Methods +}; diff --git a/src/ccaterpillar/atomimpl/lazy.c b/src/ccaterpillar/atomimpl/lazy.c new file mode 100644 index 0000000..564f27f --- /dev/null +++ b/src/ccaterpillar/atomimpl/lazy.c @@ -0,0 +1,163 @@ +/* lazy C atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/* impl */ +static PyObject* +cp_lazyatom_type(CpLazyAtomObject* self) +{ + PyObject* atom = CpLazyAtom_Atom(self); + if (!atom) + return NULL; + + PyObject* type = CpTypeOf(atom); + Py_DECREF(atom); + return type; +} + +static PyObject* +cp_lazyatom_size(CpLazyAtomObject* self, CpLayerObject* layer) +{ + PyObject* atom = CpLazyAtom_Atom(self); + if (!atom) + return NULL; + + PyObject* size = _Cp_SizeOf(atom, layer); + Py_DECREF(atom); + return size; +} + +static PyObject* +cp_lazyatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpLazyAtomObject* self = (CpLazyAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpLazyAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpLazyAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_lazyatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_lazyatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + + self->m_atom = NULL; + self->m_fn = NULL; + self->s_always_lazy = false; + } + return (PyObject*)self; +} + +static void +cp_lazyatom_dealloc(CpLazyAtomObject* self) +{ + Py_XDECREF(self->m_atom); + Py_XDECREF(self->m_fn); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_lazyatom_init(CpLazyAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "fn", "always_lazy", NULL }; + PyObject* fn = NULL; + int always_lazy = 0; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|p", kwlist, &fn, &always_lazy)) { + return -1; + } + + _Cp_SetObj(self->m_fn, fn); + self->s_always_lazy = always_lazy; + self->m_atom = NULL; + return 0; +} + +static PyObject* +cp_lazyatom_repr(CpLazyAtomObject* self) +{ + return PyUnicode_FromFormat("<%slazy %s>", + self->s_always_lazy ? "always_" : "", + self->m_atom ? Py_TYPE(self->m_atom)->tp_name + : ""); +} + +static PyObject* +cp_lazyatom_set_byteorder(CpLazyAtomObject* self, PyObject* args, PyObject* kw) +{ + _CpEndian_KwArgsGetByteorder(NULL); + + PyObject* atom = CpLazyAtom_Atom(self); + if (!atom) { + return NULL; + } + PyObject* ret = CpEndian_SetEndian(atom, (CpEndianObject*)byteorder); + if (!ret) { + Py_DECREF(atom); + return NULL; + } + _Cp_SetObj(self->m_atom, ret); + Py_DECREF(atom); + Py_DECREF(ret); + return Py_NewRef((PyObject*)self); +} + +/* Public API */ + +/*CpAPI*/ +int +CpLazyAtom_Pack(CpLazyAtomObject* self, PyObject* obj, CpLayerObject* layer) +{ + PyObject* atom = CpLazyAtom_Atom(self); + if (!atom) { + return -1; + } + int res = _Cp_Pack(obj, atom, layer); + Py_DECREF(atom); + return res; +} + +/*CpAPI*/ +PyObject* +CpLazyAtom_Unpack(CpLazyAtomObject* self, CpLayerObject* layer) +{ + PyObject* atom = CpLazyAtom_Atom(self); + if (!atom) { + return NULL; + } + PyObject* res = _Cp_Unpack(atom, layer); + Py_DECREF(atom); + return res; +} + +/* docs */ + +/* type */ + +static PyMemberDef CpLazyAtom_Members[] = { + { "fn", T_OBJECT_EX, offsetof(CpLazyAtomObject, m_fn), READONLY }, + { "always_lazy", + T_BOOL, + offsetof(CpLazyAtomObject, s_always_lazy), + READONLY }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpLazyAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(lazyatom, NULL), + { NULL } /* Sentinel */ +}; + +PyTypeObject CpLazyAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpLazyAtom_NAME), + .tp_basicsize = sizeof(CpLazyAtomObject), + .tp_dealloc = (destructor)cp_lazyatom_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_init = (initproc)cp_lazyatom_init, + .tp_new = (newfunc)cp_lazyatom_new, + .tp_repr = (reprfunc)cp_lazyatom_repr, + .tp_members = CpLazyAtom_Members, + .tp_methods = CpLazyAtom_Methods, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/pad.c b/src/ccaterpillar/atomimpl/pad.c new file mode 100644 index 0000000..6793354 --- /dev/null +++ b/src/ccaterpillar/atomimpl/pad.c @@ -0,0 +1,190 @@ +/* padding atom implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +static PyObject* +cp_paddingatom_type(PyObject* self) +{ + return Py_XNewRef(&_PyNone_Type); +} + +static PyObject* +cp_paddingatom_size(PyObject* self, PyObject* ctx) +{ + /* NOTE: + We are using the size of one byte here to allow padding atoms to be + statically sized. It helps size calculation as well as defines the + default size for padding atoms. + */ + return PyLong_FromSize_t(1); +} + +static PyObject* +cp_paddingatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpPaddingAtomObject* self = (CpPaddingAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpPaddingAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpPaddingAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = + (packmanyfunc)CpPaddingAtom_PackMany; + CpBuiltinAtom_CATOM(self).ob_unpack_many = + (unpackmanyfunc)CpPaddingAtom_UnpackMany; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_paddingatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_paddingatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + } + return (PyObject*)self; +} + +static void +cp_paddingatom_dealloc(CpPaddingAtomObject* self) +{ + self->_m_padding = 0; + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_paddingatom_init(CpPaddingAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "pad", NULL }; + char value = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|b", kwlist, &value)) + return -1; + + self->_m_padding = value; + return 0; +} + +/* Public API */ +/*CpAPI*/ +int +CpPaddingAtom_Pack(CpPaddingAtomObject* self, + PyObject* value, + CpLayerObject* layer) +{ + /* value will be ignored here */ + PyObject* res; + if (!self->_m_padding) { + res = CpState_Write(layer->m_state, layer->m_state->mod->cp_bytes__false); + } else { + PyObject* bytes = PyBytes_FromStringAndSize((char*)&self->_m_padding, 1); + if (!bytes) { + return -1; + } + res = CpState_Write(layer->m_state, bytes); + Py_DECREF(bytes); + } + + if (!res) { + return -1; + } + Py_XDECREF(res); + return 0; +} + +/*CpAPI*/ +int +CpPaddingAtom_PackMany(CpPaddingAtomObject* self, + PyObject* value, + CpLayerObject* layer, + CpLengthInfoObject* lengthinfo) +{ + PyObject* objSize = PyLong_FromSsize_t(lengthinfo->m_length); + if (!objSize) { + return -1; + } + + PyObject* bytes = CpObject_CreateOneArg(&PyBytes_Type, objSize); + if (!bytes) { + Py_DECREF(objSize); + return -1; + } + /* unsafe { */ + memset(PyBytes_AS_STRING(bytes), self->_m_padding, lengthinfo->m_length); + /* } */ + PyObject* res = CpState_Write(layer->m_state, bytes); + Py_DECREF(bytes); + Py_DECREF(objSize); + if (!res) { + return -1; + } + Py_XDECREF(res); + return 0; +} + +/*CpAPI*/ +PyObject* +CpPaddingAtom_Unpack(CpPaddingAtomObject* self, CpLayerObject* layer) +{ + PyObject* res = CpState_ReadSsize_t(layer->m_state, 1); + if (!res) { + return NULL; + } + Py_XDECREF(res); + Py_RETURN_NONE; +} + +/*CpAPI*/ +PyObject* +CpPaddingAtom_UnpackMany(CpPaddingAtomObject* self, + CpLayerObject* layer, + CpLengthInfoObject* lengthinfo) +{ + PyObject* res = NULL; + + if (lengthinfo->m_greedy) { + res = CpState_ReadFully(layer->m_state); + } else { + res = CpState_ReadSsize_t(layer->m_state, lengthinfo->m_length); + } + + if (!res) { + return NULL; + } + + Py_ssize_t length = PyBytes_GET_SIZE(res); + Py_ssize_t offset = 0; + const char* ptr = PyBytes_AS_STRING(res); + while (offset < length && *(ptr + offset) == self->_m_padding) { + offset++; + } + if (offset != length) { + PyErr_Format(PyExc_ValueError, + ("The parsed padding contains invalid padding characters " + "(possible padding overflow?). " + "Expected %ld bytes of 0x%02x but parsed only %ld bytes."), + length, + self->_m_padding, + offset); + Py_XDECREF(res); + return NULL; + } + + Py_XDECREF(res); + Py_RETURN_NONE; +} + +PyObject* +cp_paddingatom_repr(CpPaddingAtomObject* self) +{ + if (self->_m_padding == 0) { + return PyUnicode_FromFormat(""); + } + return PyUnicode_FromFormat("", self->_m_padding); +} + +/* type setup */ +PyTypeObject CpPaddingAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpPaddingAtom_NAME), + .tp_basicsize = sizeof(CpPaddingAtomObject), + .tp_dealloc = (destructor)cp_paddingatom_dealloc, + .tp_repr = (reprfunc)cp_paddingatom_repr, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_init = (initproc)cp_paddingatom_init, + .tp_new = (newfunc)cp_paddingatom_new, +}; \ No newline at end of file diff --git a/src/ccaterpillar/atomimpl/pstring.c b/src/ccaterpillar/atomimpl/pstring.c new file mode 100644 index 0000000..7fe1cd1 --- /dev/null +++ b/src/ccaterpillar/atomimpl/pstring.c @@ -0,0 +1,168 @@ +/* pascal string implementation */ + +#include "caterpillar/caterpillar.h" + +#include + +/*impl*/ +static PyObject* +cp_pstringatom_type(PyObject* self) +{ + return Py_XNewRef(&PyUnicode_Type); +} + +static PyObject* +cp_pstringatom_size(CpPStringAtomObject* self, CpLayerObject* layer) +{ + PyErr_SetString(PyExc_TypeError, "Pascal strings do not have a static size!"); + return NULL; +} + +static PyObject* +cp_pstringatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpPStringAtomObject* self = (CpPStringAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpPStringAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpPStringAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_pstringatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_pstringatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + + self->m_encoding = NULL; + self->m_errors = NULL; + self->m_atom = NULL; + } + return (PyObject*)self; +} + +static void +cp_pstringatom_dealloc(CpPStringAtomObject* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_pstringatom_init(CpPStringAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "atom", "encoding", "errors", NULL }; + PyObject *encoding = NULL, *errors = NULL, *atom = NULL; + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "O|OO", kwlist, &atom, &encoding, &errors)) { + return -1; + } + _modulestate* mod = get_global_module_state(); + + _Cp_SetObj(self->m_atom, atom); + _Cp_SetObj(self->m_encoding, encoding); + if (!self->m_encoding) { + _Cp_SetObj(self->m_encoding, mod->str_utf8); + } + _Cp_SetObj(self->m_errors, errors); + if (!self->m_errors) { + _Cp_SetObj(self->m_errors, mod->str_strict); + } + return 0; +} + +static PyObject* +cp_pstringatom_repr(CpPStringAtomObject* self) +{ + return PyUnicode_FromFormat( + "", self->m_encoding, self->m_errors, self->m_atom); +} + +_CpEndian_ImplSetByteorder(CpPStringAtomObject, pstringatom, self->m_atom); + +/* Public API */ + +/*CpAPI*/ +int +CpPStringAtom_Pack(CpPStringAtomObject* self, + PyObject* value, + CpLayerObject* layer) +{ + if (!PyUnicode_Check(value)) { + PyErr_Format(PyExc_TypeError, + "Pascal strings must be packed from unicode - got %R", + value); + return -1; + } + + PyObject* length = PyLong_FromSsize_t(PyUnicode_GET_LENGTH(value)); + if (!length) { + return -1; + } + + if (_Cp_Pack(length, self->m_atom, layer)) { + Py_DECREF(length); + return -1; + } + Py_DECREF(length); + + PyObject* bytes = PyUnicode_AsEncodedString( + value, PyUnicode_DATA(self->m_encoding), PyUnicode_DATA(self->m_errors)); + if (!bytes) { + return -1; + } + PyObject* res = CpState_Write(layer->m_state, bytes); + Py_DECREF(bytes); + if (!res) { + return -1; + } + Py_XDECREF(res); + return 0; +} + +/*CpAPI*/ +PyObject* +CpPStringAtom_Unpack(CpPStringAtomObject* self, CpLayerObject* layer) +{ + PyObject* length = _Cp_Unpack(self->m_atom, layer); + if (length == NULL) { + return NULL; + } + + PyObject* bytes = CpState_Read(layer->m_state, length); + Py_DECREF(length); + if (!bytes) { + return NULL; + } + PyObject* string = PyUnicode_Decode(PyBytes_AS_STRING(bytes), + PyBytes_GET_SIZE(bytes), + PyUnicode_DATA(self->m_encoding), + PyUnicode_DATA(self->m_errors)); + Py_DECREF(bytes); + return string; +} + +/* docs */ + +/* type */ +static PyMemberDef CpPStringAtom_Members[] = { + { "atom", T_OBJECT, offsetof(CpPStringAtomObject, m_atom), 0, NULL }, + { "encoding", T_STRING, offsetof(CpPStringAtomObject, m_encoding), 0, NULL }, + { "errors", T_STRING, offsetof(CpPStringAtomObject, m_errors), 0, NULL }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpPStringAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(pstringatom, NULL), + { NULL } /* Sentinel */ +}; + +PyTypeObject CpPStringAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpPStringAtom_NAME), + .tp_basicsize = sizeof(CpPStringAtomObject), + .tp_dealloc = (destructor)cp_pstringatom_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_members = CpPStringAtom_Members, + .tp_init = (initproc)cp_pstringatom_init, + .tp_new = (newfunc)cp_pstringatom_new, + .tp_repr = (reprfunc)cp_pstringatom_repr, + .tp_methods = CpPStringAtom_Methods, +}; \ No newline at end of file diff --git a/src/atomimpl/stringatomobj.c b/src/ccaterpillar/atomimpl/string.c similarity index 79% rename from src/atomimpl/stringatomobj.c rename to src/ccaterpillar/atomimpl/string.c index b480d03..56589c5 100644 --- a/src/atomimpl/stringatomobj.c +++ b/src/ccaterpillar/atomimpl/string.c @@ -1,9 +1,6 @@ /* string atom implementation */ #include "caterpillar/caterpillar.h" -#include "caterpillar/atoms/string.h" -#include "caterpillar/parsing.h" -#include "caterpillar/module.h" #include @@ -34,13 +31,13 @@ cp_stringatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { CpStringAtomObject* self = (CpStringAtomObject*)type->tp_alloc(type, 0); if (self != NULL) { - CpFieldCAtom_CATOM(self).ob_pack = (packfunc)CpStringAtom_Pack; - CpFieldCAtom_CATOM(self).ob_unpack = (unpackfunc)CpStringAtom_Unpack; - CpFieldCAtom_CATOM(self).ob_pack_many = NULL; - CpFieldCAtom_CATOM(self).ob_unpack_many = NULL; - CpFieldCAtom_CATOM(self).ob_size = (sizefunc)cp_stringatom__size__; - CpFieldCAtom_CATOM(self).ob_type = (typefunc)cp_stringatom__type__; - CpFieldCAtom_CATOM(self).ob_bits = NULL; + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpStringAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpStringAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_stringatom__size__; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_stringatom__type__; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; self->m_encoding = NULL; self->m_length = NULL; @@ -64,15 +61,19 @@ cp_stringatom_init(CpStringAtomObject* self, PyObject* args, PyObject* kwds) static char* kwlist[] = { "length", "encoding", "errors", NULL }; PyObject *length = NULL, *encoding = NULL, *errors = NULL; if (!PyArg_ParseTupleAndKeywords( - args, kwds, "OU|U", kwlist, &length, &encoding, &errors)) { + args, kwds, "O|OO", kwlist, &length, &encoding, &errors)) { return -1; } + _modulestate* mod = get_global_module_state(); + _Cp_SetObj(self->m_length, length); _Cp_SetObj(self->m_encoding, encoding); + if (!self->m_encoding) { + _Cp_SetObj(self->m_encoding, mod->str_utf8); + } _Cp_SetObj(self->m_errors, errors); if (!self->m_errors) { - _modulestate* mod = get_global_module_state(); _Cp_SetObj(self->m_errors, mod->str_strict); } return 0; @@ -80,6 +81,7 @@ cp_stringatom_init(CpStringAtomObject* self, PyObject* args, PyObject* kwds) /* Public API */ +/*CpAPI*/ int CpStringAtom_Pack(CpStringAtomObject* self, PyObject* value, @@ -99,6 +101,7 @@ CpStringAtom_Pack(CpStringAtomObject* self, return 0; } +/*CpAPI*/ PyObject* CpStringAtom_Unpack(CpStringAtomObject* self, CpLayerObject* layer) { @@ -111,7 +114,7 @@ CpStringAtom_Unpack(CpStringAtomObject* self, CpLayerObject* layer) if (greedy) { res = CpState_ReadFully(layer->m_state); } else { - res = CpState_Read(layer->m_state, length); + res = CpState_ReadSsize_t(layer->m_state, length); } if (!res) { return NULL; @@ -134,10 +137,10 @@ static PyMemberDef CpStringAtom_Members[] = { }; PyTypeObject CpStringAtom_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(string), + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpStringAtom_NAME), .tp_basicsize = sizeof(CpStringAtomObject), .tp_dealloc = (destructor)cp_stringatom_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = NULL, .tp_members = CpStringAtom_Members, .tp_init = (initproc)cp_stringatom_init, diff --git a/src/ccaterpillar/atomimpl/varint.c b/src/ccaterpillar/atomimpl/varint.c new file mode 100644 index 0000000..6f47fd1 --- /dev/null +++ b/src/ccaterpillar/atomimpl/varint.c @@ -0,0 +1,342 @@ +/* varint implementation for Little Endian and Big Endian */ + +#include "caterpillar/caterpillar.h" + +#include + +// Inspired by https://github.com/google/cityhash/blob/master/src/city.cc +#ifdef _MSC_VER +#include +#define CP_BSWAP_U32(x) _byteswap_ulong(x) +#define CP_BSWAP_U64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) +#include +#define CP_BSWAP_U32(x) OSSwapInt32(x) +#define CP_BSWAP_U64(x) OSSwapInt64(x) + +#elif defined(sun) || defined(__sun) +#include +#define CP_BSWAP_U32(x) BSWAP_32(x) +#define CP_BSWAP_U64(x) BSWAP_64(x) + +#elif defined(__FreeBSD__) +#include +#define CP_BSWAP_U32(x) bswap32(x) +#define CP_BSWAP_U64(x) bswap64(x) + +#elif defined(__OpenBSD__) +#include +#define CP_BSWAP_U32(x) swap32(x) +#define CP_BSWAP_U64(x) swap64(x) + +#else +#include +#define CP_BSWAP_U32(x) bswap_32(x) +#define CP_BSWAP_U64(x) bswap_64(x) +#endif + +/* impl */ +static PyObject* +cp_varintatom_type(PyObject* self) +{ + return Py_XNewRef(&PyLong_Type); +} + +static PyObject* +cp_varintatom_size(CpVarIntAtomObject* self, PyObject* ctx) +{ + PyErr_SetString(PyExc_TypeError, "VarInts do not have a static size!"); + return NULL; +} + +static PyObject* +cp_varintatom_new(PyTypeObject* type, PyObject* args, PyObject* kwds) +{ + CpVarIntAtomObject* self = (CpVarIntAtomObject*)type->tp_alloc(type, 0); + if (self != NULL) { + self->_m_little_endian = true; + self->_m_lsb = false; + + CpBuiltinAtom_CATOM(self).ob_pack = (packfunc)CpVarIntAtom_Pack; + CpBuiltinAtom_CATOM(self).ob_unpack = (unpackfunc)CpVarIntAtom_Unpack; + CpBuiltinAtom_CATOM(self).ob_pack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_unpack_many = NULL; + CpBuiltinAtom_CATOM(self).ob_size = (sizefunc)cp_varintatom_size; + CpBuiltinAtom_CATOM(self).ob_type = (typefunc)cp_varintatom_type; + CpBuiltinAtom_CATOM(self).ob_bits = NULL; + } + return (PyObject*)self; +} + +static void +cp_varintatom_dealloc(CpVarIntAtomObject* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_varintatom_init(CpVarIntAtomObject* self, PyObject* args, PyObject* kwds) +{ + static char* kwlist[] = { "little_endian", "lsb", NULL }; + int little_endian = true; + int lsb = false; + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "|pp", kwlist, &little_endian, &lsb)) { + return -1; + } + self->_m_little_endian = little_endian; + self->_m_lsb = lsb; + return 0; +} + +static PyObject* +cp_varintatom_repr(CpVarIntAtomObject* self) +{ + char endian = 'l'; + if (!self->_m_little_endian) { + endian = 'b'; + } + if (self->_m_lsb) { + return PyUnicode_FromFormat("<%ce varint [lsb]>", endian); + } + return PyUnicode_FromFormat("<%ce varint>", endian); +} + +static PyObject* +cp_varintatom_set_byteorder(CpVarIntAtomObject* self, + PyObject* args, + PyObject* kw) +{ + _CpEndian_KwArgsGetByteorder(NULL); + + _modulestate* mod = get_global_module_state(); + int little_endian = CpEndian_IsLittleEndian((CpEndianObject*)byteorder, mod); + PyObject* new_varint = + (PyObject*)CpVarIntAtom_New(little_endian, self->_m_lsb); + + return new_varint; +} + +/* Public API */ + +#define _CP_VARINT_BSWAP_IMPL(name, type) \ + PyObject* result_int = PyLong_From##name(number); \ + if (!result_int) { \ + return 0; \ + } \ + PyObject* result = CpVarIntAtom_BSwap(result_int, little_endian); \ + Py_DECREF(result_int); \ + if (!result) { \ + return 0; \ + } \ + type res = PyLong_As##name(result); \ + Py_DECREF(result); \ + return res; + +/*CpAPI*/ +unsigned long long +CpVarIntAtom_BSwapUnsignedLongLong(unsigned long long number, + bool little_endian) +{ + _CP_VARINT_BSWAP_IMPL(UnsignedLongLong, unsigned long long); +} + +/*CpAPI*/ +long long +CpVarIntAtom_BSwapLongLong(long long number, bool little_endian) +{ + _CP_VARINT_BSWAP_IMPL(LongLong, long long); +} + +/*CpAPI*/ +Py_ssize_t +CpVarIntAtom_BSwapSsize_t(Py_ssize_t number, bool little_endian) +{ + _CP_VARINT_BSWAP_IMPL(Ssize_t, Py_ssize_t); +} + +#undef _CP_VARINT_BSWAP_IMPL + +/*CpAPI*/ +PyObject* +CpVarIntAtom_BSwap(PyObject* number, bool little_endian) +{ + size_t nbytes = _PyLong_NumBits(number) / 8; + PyObject* bytesSize = PyLong_FromSize_t(nbytes); + if (!bytesSize) { + return NULL; + } + + PyObject* bytes = CpObject_CreateOneArg(&PyBytes_Type, bytesSize); + if (!bytes) { + Py_DECREF(bytesSize); + return NULL; + } + + int res = _PyLong_AsByteArray((PyLongObject*)number, + (unsigned char*)PyBytes_AS_STRING(bytes), + nbytes, + little_endian, + false); + if (res == -1) { + Py_DECREF(bytesSize); + Py_DECREF(bytes); + return NULL; + } + + PyObject* result = _PyLong_FromByteArray( + (unsigned char*)PyBytes_AS_STRING(bytes), nbytes, !little_endian, false); + + Py_DECREF(bytesSize); + Py_DECREF(bytes); + return result; +} + +PyObject* +_CpVarIntAtom_Length(unsigned long long number) +{ + if (number == 0) { + return PyLong_FromLong(1); + } + int length = 1; + while ((number >> 7) != 0) { + number >>= 7; + length++; + } + return PyLong_FromLong(length); +} + +/*CpAPI*/ +int +CpVarIntAtom_Pack(CpVarIntAtomObject* self, + PyObject* value, + CpLayerObject* layer) +{ + unsigned long long raw_value = PyLong_AsUnsignedLongLongMask(value); + if (PyErr_Occurred()) { + return -1; + } + + int little_endian = self->_m_little_endian; + int lsb = self->_m_lsb; + + PyObject* length = _CpVarIntAtom_Length(raw_value); + if (!length) { + return -1; + } + + PyObject* bytes = CpObject_CreateOneArg(&PyBytes_Type, length); + Py_DECREF(length); + if (!bytes) { + return -1; + } + + int raw_length = PyBytes_GET_SIZE(bytes); + for (int i = 0; i < raw_length; i++) { + unsigned char byte = 0; + if (little_endian) { + byte = (raw_value >> (i * 7)) & 0x7f; + } else { + byte = (raw_value >> ((raw_length - 1 - i) * 7)) & 0x7f; + } + if (!lsb && i != raw_length - 1) { + byte |= 0x80; + } else if (lsb && (i == raw_length - 1)) { + byte |= 0x80; + } + + PyBytes_AS_STRING(bytes)[i] = byte; + } + + PyObject* result = CpState_Write(layer->m_state, bytes); + Py_DECREF(bytes); + if (!result) { + return -1; + } + Py_DECREF(result); + return 0; +} + +/*CpAPI*/ +PyObject* +CpVarIntAtom_Unpack(CpVarIntAtomObject* self, CpLayerObject* layer) +{ + PyObject* tmp_val = NULL; + unsigned long long result = 0, tmp = 0; + int shift = 0; + int little_endian = self->_m_little_endian; + int lsb = self->_m_lsb; + int nbytes = 0; + + while (true) { + tmp_val = CpState_ReadSsize_t(layer->m_state, 1); + if (!tmp_val) { + return NULL; + } + // only one byte + tmp = *(PyBytes_AS_STRING(tmp_val)); + Py_DECREF(tmp_val); + + result |= (tmp & 0x7f) << shift; + shift += 7; + nbytes++; + if ((lsb && (tmp & 0x80) == 1) || (!lsb && (tmp & 0x80) == 0)) { + break; + } + } + + if (!little_endian) { + if (nbytes == 1) { + // no swap needed + } else if (nbytes == 2) { + // manually swap + // TODO: explain why we use 7 bits here + result = ((result >> 7) & 0xff) | ((result << 7) & 0xff00); + } else if (nbytes <= 4) { + // swap using compiler directive + result = CP_BSWAP_U32(result); + } else if (nbytes <= 8) { + // compiler directive + result = CP_BSWAP_U64(result); + } else { + // use Python methods + result = CpVarIntAtom_BSwapUnsignedLongLong(result, true); + if (PyErr_Occurred()) { + return NULL; + } + } + } + + return PyLong_FromUnsignedLongLong(result); +} + +/* docs */ + +/* type setup */ +static PyMemberDef CpVarIntAtom_Members[] = { + { "little_endian", + T_BOOL, + offsetof(CpVarIntAtomObject, _m_little_endian), + READONLY }, + { "lsb", T_BOOL, offsetof(CpVarIntAtomObject, _m_lsb), READONLY }, + { NULL } +}; + +static PyMethodDef CpVarIntAtom_Methods[] = { + _CpEndian_ImplSetByteorder_MethDef(varintatom, NULL), + { NULL } /* Sentinel */ +}; + +PyTypeObject CpVarIntAtom_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpVarIntAtom_NAME), + .tp_basicsize = sizeof(CpVarIntAtomObject), + .tp_dealloc = (destructor)cp_varintatom_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_members = CpVarIntAtom_Members, + .tp_methods = CpVarIntAtom_Methods, + .tp_init = (initproc)cp_varintatom_init, + .tp_new = (newfunc)cp_varintatom_new, + .tp_repr = (reprfunc)cp_varintatom_repr, +}; diff --git a/src/atomobj.c b/src/ccaterpillar/atomobj.c similarity index 76% rename from src/atomobj.c rename to src/ccaterpillar/atomobj.c index a75686f..3948553 100644 --- a/src/atomobj.c +++ b/src/ccaterpillar/atomobj.c @@ -3,6 +3,7 @@ #include "caterpillar/atomobj.h" #include "caterpillar/macros.h" +#include #include static PyObject* @@ -111,11 +112,11 @@ static PyMethodDef CpAtom_Methods[] = { PyTypeObject CpAtom_Type = { PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(atom), .tp_basicsize = sizeof(CpAtomObject), - .tp_dealloc =(destructor)cp_atom_dealloc, + .tp_dealloc = (destructor)cp_atom_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = cp_atom_doc, .tp_methods = CpAtom_Methods, - .tp_init =(initproc)cp_atom_init, + .tp_init = (initproc)cp_atom_init, .tp_new = (newfunc)cp_atom_new, }; @@ -193,13 +194,16 @@ cp_catom_pack_many(CpCAtomObject* self, PyObject* args, PyObject* kw) return NULL; } - static char* kwlist[] = { "ops", "context", NULL }; - PyObject *ops = NULL, *context = NULL; - if (PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &ops, &context) < 0) { + static char* kwlist[] = { "ops", "context", "lengthinfo", NULL }; + PyObject *ops = NULL, *context = NULL, *lengthinfo = NULL; + if (PyArg_ParseTupleAndKeywords( + args, kw, "OOO", kwlist, &ops, &context, &lengthinfo) < 0) { return NULL; } - return self->ob_pack_many((PyObject*)self, ops, context) ? NULL : Py_NewRef(Py_None); + return self->ob_pack_many((PyObject*)self, ops, context, lengthinfo) + ? NULL + : Py_NewRef(Py_None); } static PyObject* @@ -232,12 +236,12 @@ cp_catom_unpack_many(CpCAtomObject* self, PyObject* args, PyObject* kw) return NULL; } - static char* kwlist[] = { "context", NULL }; - PyObject* context = NULL; - if (PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &context) < 0) { + static char* kwlist[] = { "context", "lengthinfo", NULL }; + PyObject *context = NULL, *lengthinfo = NULL; + if (PyArg_ParseTupleAndKeywords(args, kw, "OO", kwlist, &context, &lengthinfo) < 0) { return NULL; } - return self->ob_unpack_many((PyObject*)self, context); + return self->ob_unpack_many((PyObject*)self, context, lengthinfo); } static PyObject* @@ -264,6 +268,12 @@ cp_catom_size(CpCAtomObject* self, PyObject* args, PyObject* kw) if (PyArg_ParseTupleAndKeywords(args, kw, "O", kwlist, &context) < 0) { return NULL; } + + if (context == Py_None || context == NULL) { + PyErr_SetString(PyExc_ValueError, "context must be set!"); + return NULL; + } + return self->ob_size((PyObject*)self, context); } @@ -310,4 +320,61 @@ PyTypeObject CpCAtom_Type = { .tp_methods = CpCAtom_Methods, .tp_init = (initproc)cp_catom_init, .tp_new = (newfunc)cp_catom_new, +}; + +// ------------------------------------------------------------------------------ +// length info + +static PyObject* +cp_lengthinfo_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + return type->tp_alloc(type, 0); +} + +static void +cp_lengthinfo_dealloc(CpLengthInfoObject* self) +{ + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_lengthinfo_init(CpLengthInfoObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "length", "greedy", NULL }; + Py_ssize_t length = 0; + int greedy = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|np", kwlist, &length, &greedy)) { + return -1; + } + + self->m_length = length; + self->m_greedy = greedy; + return 0; +} + +static PyObject* +cp_lengthinfo_repr(CpLengthInfoObject* self) +{ + if (self->m_greedy) { + return PyUnicode_FromString(""); + } + return PyUnicode_FromFormat("", self->m_length); +} + +static PyMemberDef CpLengthInfo_Members[] = { + { "length", T_PYSSIZET, offsetof(CpLengthInfoObject, m_length), 0, NULL }, + { "greedy", T_BOOL, offsetof(CpLengthInfoObject, m_greedy), 0, NULL }, + { NULL } /* Sentinel */ +}; + +PyTypeObject CpLengthInfo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpLengthInfo_NAME), + .tp_basicsize = sizeof(CpLengthInfoObject), + .tp_dealloc = (destructor)cp_lengthinfo_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_new = (newfunc)cp_lengthinfo_new, + .tp_init = (initproc)cp_lengthinfo_init, + .tp_repr = (reprfunc)cp_lengthinfo_repr, + .tp_doc = NULL, }; \ No newline at end of file diff --git a/src/ccaterpillar/caterpillarapi.c b/src/ccaterpillar/caterpillarapi.c new file mode 100644 index 0000000..e7b5ef5 --- /dev/null +++ b/src/ccaterpillar/caterpillarapi.c @@ -0,0 +1,175 @@ +/* These pointers will be stored in the C-object for use in other + extension modules +*/ + +void *Cp_API[] = { + (void *) &CpModule, + (void *) &CpAtom_Type, + (void *) &CpCAtom_Type, + (void *) &CpArch_Type, + (void *) &CpEndian_Type, + (void *) &CpContext_Type, + (void *) &CpUnaryExpr_Type, + (void *) &CpBinaryExpr_Type, + (void *) &CpContextPath_Type, + NULL, + NULL, + NULL, + (void *) &CpInvalidDefault_Type, + (void *) &CpDefaultOption_Type, + (void *) &_CpInvalidDefault_Object, + (void *) &_CpDefaultOption_Object, + (void *) &CpOption_Type, + (void *) &CpState_Type, + (void *) &CpLayer_Type, + (void *) &CpStructFieldInfo_Type, + (void *) &CpStruct_Type, + (void *) &CpFloatAtom_Type, + (void *) &CpIntAtom_Type, + (void *) &CpBoolAtom_Type, + (void *) &CpCharAtom_Type, + (void *) &CpPaddingAtom_Type, + (void *) &CpStringAtom_Type, + (void *) &CpConstAtom_Type, + (void *) &CpBuiltinAtom_Type, + (void *) &CpRepeatedAtom_Type, + (void *) &CpSeqLayer_Type, + (void *) &CpObjLayer_Type, + (void *) &CpConditionAtom_Type, + (void *) &CpSwitchAtom_Type, + (void *) &CpOffsetAtom_Type, + (void *) &CpPrimitiveAtom_Type, + (void *) &CpLengthInfo_Type, + (void *) &CpBytesAtom_Type, + (void *) &CpPStringAtom_Type, + (void *) &CpEnumAtom_Type, + (void *) &CpVarIntAtom_Type, + (void *) &CpComputedAtom_Type, + (void *) &CpLazyAtom_Type, + (void *) &CpCStringAtom_Type, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + (void *) &CpEndian_IsLittleEndian, + NULL, + NULL, + (void *) &CpContext_New, + (void *) &CpUnaryExpr_New, + (void *) &CpBinaryExpr_New, + (void *) &CpContextPath_New, + (void *) &CpContextPath_FromString, + (void *) &CpTypeMap_Lookup, + (void *) &CpTypeMap_Register, + NULL, + NULL, + NULL, + NULL, + (void *) &CpTypeOf, + NULL, + (void *) &CpTypeOf_Common, + (void *) &CpPack, + NULL, + NULL, + NULL, + NULL, + (void *) &_CpPack_EvalLength, + (void *) &CpSizeOf, + NULL, + (void *) &CpSizeOf_Struct, + (void *) &CpSizeOf_Common, + (void *) &_Cp_SizeOf, + (void *) &CpUnpack, + NULL, + NULL, + NULL, + NULL, + (void *) &_CpUnpack_EvalLength, + (void *) &CpUnpack_CAtom, + (void *) &CpPack_CAtom, + (void *) &CpSizeOf_CAtom, + (void *) &CpTypeOf_CAtom, + (void *) &CpState_New, + (void *) &CpState_Tell, + (void *) &CpState_Seek, + (void *) &CpState_Read, + (void *) &CpState_ReadSsize_t, + (void *) &CpState_ReadFully, + (void *) &CpState_Write, + (void *) &CpState_SetGlobals, + (void *) &CpLayer_New, + NULL, + (void *) &CpLayer_Invalidate, + (void *) &CpStructFieldInfo_New, + (void *) &CpStruct_AddFieldInfo, + NULL, + (void *) &CpStruct_New, + (void *) &CpStruct_GetAnnotations, + (void *) &CpStruct_ReplaceType, + (void *) &CpStruct_HasOption, + (void *) &CpStruct_Pack, + (void *) &CpStruct_Unpack, + (void *) &CpStruct_SizeOf, + (void *) &CpStructModel_Check, + (void *) &CpStructModel_GetStruct, + (void *) &CpSeqLayer_New, + (void *) &CpSeqLayer_SetSequence, + (void *) &CpObjLayer_New, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + (void *) &CpIntAtom_Pack, + (void *) &CpIntAtom_Unpack, + (void *) &CpFloatAtom_Pack, + (void *) &CpFloatAtom_Unpack, + (void *) &CpBoolAtom_Pack, + (void *) &CpBoolAtom_Unpack, + (void *) &CpCharAtom_Pack, + (void *) &CpCharAtom_Unpack, + (void *) &CpPaddingAtom_Pack, + (void *) &CpPaddingAtom_PackMany, + (void *) &CpPaddingAtom_Unpack, + (void *) &CpPaddingAtom_UnpackMany, + (void *) &CpStringAtom_Pack, + (void *) &CpStringAtom_Unpack, + (void *) &CpConstAtom_Pack, + (void *) &CpConstAtom_Unpack, + (void *) &CpRepeatedAtom_Pack, + (void *) &CpRepeatedAtom_Unpack, + (void *) &CpRepeatedAtom_GetLength, + (void *) &CpConditionAtom_Pack, + (void *) &CpConditionAtom_Unpack, + (void *) &CpConditionAtom_IsEnabled, + (void *) &CpSwitchAtom_GetNext, + (void *) &CpSwitchAtom_Pack, + (void *) &CpSwitchAtom_Unpack, + (void *) &CpOffsetAtom_Pack, + (void *) &CpOffsetAtom_Unpack, + (void *) &CpOffsetAtom_GetOffset, + (void *) &CpBytesAtom_GetLength, + (void *) &CpBytesAtom_Pack, + (void *) &CpBytesAtom_Unpack, + NULL, + (void *) &CpPStringAtom_Pack, + (void *) &CpPStringAtom_Unpack, + (void *) &CpEnumAtom_Pack, + (void *) &CpEnumAtom_Unpack, + (void *) &CpVarIntAtom_Pack, + (void *) &CpVarIntAtom_Unpack, + (void *) &CpVarIntAtom_BSwap, + (void *) &CpVarIntAtom_BSwapUnsignedLongLong, + (void *) &CpVarIntAtom_BSwapLongLong, + (void *) &CpVarIntAtom_BSwapSsize_t, + (void *) &CpComputedAtom_Pack, + (void *) &CpComputedAtom_Unpack, + (void *) &CpLazyAtom_Pack, + (void *) &CpLazyAtom_Unpack, + (void *) &CpCStringAtom_Pack, + (void *) &CpCStringAtom_Unpack +}; + diff --git a/src/caterpillarapi.c.in b/src/ccaterpillar/caterpillarapi.c.in similarity index 100% rename from src/caterpillarapi.c.in rename to src/ccaterpillar/caterpillarapi.c.in diff --git a/src/context.c b/src/ccaterpillar/context.c similarity index 76% rename from src/context.c rename to src/ccaterpillar/context.c index 680b350..f23dd8d 100644 --- a/src/context.c +++ b/src/ccaterpillar/context.c @@ -99,15 +99,6 @@ cp_context__getattro__(CpContextObject* self, PyObject* name) } /* public API */ -/*CpAPI*/ -PyObject* -CpContext_GetAttr(PyObject* ctx, PyObject* key, _modulestate* m) -{ - // TODO - // return PyObject_CallMethodOneArg(ctx, m->str, PyObject *arg) - PyErr_SetString(PyExc_NotImplementedError, "GetAttr not implemented yet"); - return NULL; -} /*CpAPI*/ CpContextObject* @@ -271,6 +262,49 @@ cp_unaryexpr__call__(CpUnaryExprObject* self, PyObject* args, PyObject* kw) return result; } +static PyObject* +cp_unaryexpr_as_number_neg(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpNeg, self); +} + +static PyObject* +cp_unaryexpr_as_number_pos(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpPos, self); +} + +static PyObject* +cp_unaryexpr_as_number_not(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpNot, self); +} + +/* operations */ +#define _CpUnaryExpr_BinaryNumberMethod(name, op) \ + static PyObject* cp_unaryexpr_as_number_##name(PyObject* self, \ + PyObject* other) \ + { \ + return (PyObject*)CpBinaryExpr_New(op, self, other); \ + } + +_CpUnaryExpr_BinaryNumberMethod(add, CpBinaryExpr_OpAdd); +_CpUnaryExpr_BinaryNumberMethod(sub, CpBinaryExpr_OpSub); +_CpUnaryExpr_BinaryNumberMethod(mul, CpBinaryExpr_OpMul); +_CpUnaryExpr_BinaryNumberMethod(div, CpBinaryExpr_OpTrueDiv); +_CpUnaryExpr_BinaryNumberMethod(truediv, CpBinaryExpr_OpTrueDiv); +_CpUnaryExpr_BinaryNumberMethod(floordiv, CpBinaryExpr_OpFloorDiv); +_CpUnaryExpr_BinaryNumberMethod(mod, CpBinaryExpr_OpMod); +_CpUnaryExpr_BinaryNumberMethod(lshift, CpBinaryExpr_OpLShift); +_CpUnaryExpr_BinaryNumberMethod(rshift, CpBinaryExpr_OpRShift); +_CpUnaryExpr_BinaryNumberMethod(and, CpBinaryExpr_OpBitAnd); +_CpUnaryExpr_BinaryNumberMethod(xor, CpBinaryExpr_OpBitXor); +_CpUnaryExpr_BinaryNumberMethod(or, CpBinaryExpr_OpBitOr); +_CpUnaryExpr_BinaryNumberMethod(pow, CpBinaryExpr_OpPow); +_CpUnaryExpr_BinaryNumberMethod(matmul, CpBinaryExpr_OpMatMul); + +#undef _CpUnaryExpr_BinaryNumberMethod + /*CpAPI*/ CpUnaryExprObject* CpUnaryExpr_New(int op, PyObject* value) @@ -294,6 +328,27 @@ static PyMemberDef CpUnaryExpr_Members[] = { { NULL } /* Sentinel */ }; +static PyNumberMethods CpBinaryExpr_NumberMethods = { + // unary + .nb_negative = (unaryfunc)cp_unaryexpr_as_number_neg, + .nb_positive = (unaryfunc)cp_unaryexpr_as_number_pos, + .nb_invert = (unaryfunc)cp_unaryexpr_as_number_not, + // binary + .nb_add = (binaryfunc)cp_unaryexpr_as_number_add, + .nb_subtract = (binaryfunc)cp_unaryexpr_as_number_sub, + .nb_multiply = (binaryfunc)cp_unaryexpr_as_number_mul, + .nb_true_divide = (binaryfunc)cp_unaryexpr_as_number_truediv, + .nb_floor_divide = (binaryfunc)cp_unaryexpr_as_number_floordiv, + .nb_remainder = (binaryfunc)cp_unaryexpr_as_number_mod, + .nb_power = (ternaryfunc)cp_unaryexpr_as_number_pow, + .nb_lshift = (binaryfunc)cp_unaryexpr_as_number_lshift, + .nb_rshift = (binaryfunc)cp_unaryexpr_as_number_rshift, + .nb_and = (binaryfunc)cp_unaryexpr_as_number_and, + .nb_xor = (binaryfunc)cp_unaryexpr_as_number_xor, + .nb_or = (binaryfunc)cp_unaryexpr_as_number_or, + .nb_matrix_multiply = (binaryfunc)cp_unaryexpr_as_number_matmul, +}; + PyTypeObject CpUnaryExpr_Type = { PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(UnaryExpr), .tp_basicsize = sizeof(CpUnaryExprObject), @@ -306,6 +361,7 @@ PyTypeObject CpUnaryExpr_Type = { .tp_members = CpUnaryExpr_Members, .tp_init = (initproc)cp_unaryexpr_init, .tp_new = (newfunc)cp_unaryexpr_new, + .tp_as_number = &CpBinaryExpr_NumberMethods, }; //------------------------------------------------------------------------------ @@ -551,6 +607,49 @@ cp_binaryexpr__call__(CpBinaryExprObject* self, PyObject* args, PyObject* kw) return result; } +static PyObject* +cp_binaryexpr_as_number_neg(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpNeg, self); +} + +static PyObject* +cp_binaryexpr_as_number_pos(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpPos, self); +} + +static PyObject* +cp_binaryexpr_as_number_not(PyObject* self) +{ + return (PyObject*)CpUnaryExpr_New(CpUnaryExpr_OpNot, self); +} + +/* operations */ +#define _CpUnaryExpr_BinaryNumberMethod(name, op) \ + static PyObject* cp_binaryexpr_as_number_##name(PyObject* self, \ + PyObject* other) \ + { \ + return (PyObject*)CpBinaryExpr_New(op, self, other); \ + } + +_CpUnaryExpr_BinaryNumberMethod(add, CpBinaryExpr_OpAdd); +_CpUnaryExpr_BinaryNumberMethod(sub, CpBinaryExpr_OpSub); +_CpUnaryExpr_BinaryNumberMethod(mul, CpBinaryExpr_OpMul); +_CpUnaryExpr_BinaryNumberMethod(div, CpBinaryExpr_OpTrueDiv); +_CpUnaryExpr_BinaryNumberMethod(truediv, CpBinaryExpr_OpTrueDiv); +_CpUnaryExpr_BinaryNumberMethod(floordiv, CpBinaryExpr_OpFloorDiv); +_CpUnaryExpr_BinaryNumberMethod(mod, CpBinaryExpr_OpMod); +_CpUnaryExpr_BinaryNumberMethod(lshift, CpBinaryExpr_OpLShift); +_CpUnaryExpr_BinaryNumberMethod(rshift, CpBinaryExpr_OpRShift); +_CpUnaryExpr_BinaryNumberMethod(and, CpBinaryExpr_OpBitAnd); +_CpUnaryExpr_BinaryNumberMethod(xor, CpBinaryExpr_OpBitXor); +_CpUnaryExpr_BinaryNumberMethod(or, CpBinaryExpr_OpBitOr); +_CpUnaryExpr_BinaryNumberMethod(pow, CpBinaryExpr_OpPow); +_CpUnaryExpr_BinaryNumberMethod(matmul, CpBinaryExpr_OpMatMul); + +#undef _CpUnaryExpr_BinaryNumberMethod + /*CpAPI*/ CpBinaryExprObject* CpBinaryExpr_New(int op, PyObject* left, PyObject* right) @@ -582,6 +681,27 @@ static PyMemberDef CpBinaryExpr_Members[] = { { NULL } /* Sentinel */ }; +static PyNumberMethods CpUnaryExpr_NumberMethods = { + // unary + .nb_negative = (unaryfunc)cp_binaryexpr_as_number_neg, + .nb_positive = (unaryfunc)cp_binaryexpr_as_number_pos, + .nb_invert = (unaryfunc)cp_binaryexpr_as_number_not, + // binary + .nb_add = (binaryfunc)cp_binaryexpr_as_number_add, + .nb_subtract = (binaryfunc)cp_binaryexpr_as_number_sub, + .nb_multiply = (binaryfunc)cp_binaryexpr_as_number_mul, + .nb_true_divide = (binaryfunc)cp_binaryexpr_as_number_truediv, + .nb_floor_divide = (binaryfunc)cp_binaryexpr_as_number_floordiv, + .nb_remainder = (binaryfunc)cp_binaryexpr_as_number_mod, + .nb_power = (ternaryfunc)cp_binaryexpr_as_number_pow, + .nb_lshift = (binaryfunc)cp_binaryexpr_as_number_lshift, + .nb_rshift = (binaryfunc)cp_binaryexpr_as_number_rshift, + .nb_and = (binaryfunc)cp_binaryexpr_as_number_and, + .nb_xor = (binaryfunc)cp_binaryexpr_as_number_xor, + .nb_or = (binaryfunc)cp_binaryexpr_as_number_or, + .nb_matrix_multiply = (binaryfunc)cp_binaryexpr_as_number_matmul, +}; + PyTypeObject CpBinaryExpr_Type = { PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(BinaryExpr), .tp_basicsize = sizeof(CpBinaryExprObject), @@ -593,6 +713,7 @@ PyTypeObject CpBinaryExpr_Type = { .tp_members = CpBinaryExpr_Members, .tp_init = (initproc)cp_binaryexpr_init, .tp_new = (newfunc)cp_binaryexpr_new, + .tp_as_number = &CpUnaryExpr_NumberMethods, }; //------------------------------------------------------------------------------ @@ -639,7 +760,13 @@ cp_contextpath_init(CpContextPathObject* self, PyObject* args, PyObject* kw) static PyObject* cp_contextpath_repr(CpContextPathObject* self) { - return PyUnicode_FromFormat("CpPath(%R)", self->m_path); + return PyUnicode_FromFormat("", self->m_path); +} + +static PyObject* +cp_contextpath_str(CpContextPathObject* self) +{ + return Py_XNewRef(self->m_path); } static Py_hash_t @@ -768,7 +895,6 @@ CpContextPath_FromString(const char* path) return (CpContextPathObject*)CpObject_Create(&CpContextPath_Type, "s", path); } - /* docs */ PyDoc_STRVAR(cp_contextpath__doc__, "\ ContextPath(path)\n\ @@ -816,18 +942,19 @@ static PyNumberMethods CpContextPath_NumberMethods = { /* type */ PyTypeObject CpContextPath_Type = { PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(ContextPath), - .tp_basicsize =sizeof(CpContextPathObject), - .tp_dealloc =(destructor)cp_contextpath_dealloc, - .tp_getattr =(getattrfunc)cp_contextpath__getattr__, - .tp_repr =(reprfunc)cp_contextpath_repr, - .tp_as_number =&CpContextPath_NumberMethods, - .tp_hash =(hashfunc)cp_contextpath_hash, - .tp_call =(ternaryfunc)cp_contextpath__call__, - .tp_flags =Py_TPFLAGS_DEFAULT, - .tp_doc =cp_contextpath__doc__, - .tp_richcompare =(richcmpfunc)cp_contextpath_richcmp, - .tp_methods =CpContextPath_Methods, - .tp_members =CpContextPath_Members, - .tp_init =(initproc)cp_contextpath_init, - .tp_new =(newfunc)cp_contextpath_new, + .tp_basicsize = sizeof(CpContextPathObject), + .tp_dealloc = (destructor)cp_contextpath_dealloc, + .tp_getattr = (getattrfunc)cp_contextpath__getattr__, + .tp_repr = (reprfunc)cp_contextpath_repr, + .tp_as_number = &CpContextPath_NumberMethods, + .tp_hash = (hashfunc)cp_contextpath_hash, + .tp_call = (ternaryfunc)cp_contextpath__call__, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = cp_contextpath__doc__, + .tp_richcompare = (richcmpfunc)cp_contextpath_richcmp, + .tp_methods = CpContextPath_Methods, + .tp_members = CpContextPath_Members, + .tp_init = (initproc)cp_contextpath_init, + .tp_new = (newfunc)cp_contextpath_new, + .tp_str = (reprfunc)cp_contextpath_str, }; \ No newline at end of file diff --git a/src/ccaterpillar/default.c b/src/ccaterpillar/default.c new file mode 100644 index 0000000..cc02764 --- /dev/null +++ b/src/ccaterpillar/default.c @@ -0,0 +1,63 @@ +/* immortal objects */ + +#include "caterpillar/caterpillar.h" + +static PyObject* +cp_invaliddefault_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + if (PyTuple_GET_SIZE(args) || PyDict_GET_SIZE(kw)) { + PyErr_SetString(PyExc_TypeError, + "InvalidDefaultType does not accept arguments"); + return NULL; + } + + Py_INCREF(CpInvalidDefault); + return CpInvalidDefault; +} + +static PyObject* +cp_invaliddefault_repr(PyObject* self) +{ + return PyUnicode_FromString(""); +} + +PyTypeObject CpInvalidDefault_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(InvalidDefaultType), + .tp_repr = (reprfunc)cp_invaliddefault_repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = (newfunc)cp_invaliddefault_new, +}; + +PyObject _CpInvalidDefault_Object = { _PyObject_EXTRA_INIT{ + _Py_IMMORTAL_REFCNT /* ob_refcnt */ }, + &CpInvalidDefault_Type /* ob_type */ }; + +static PyObject* +cp_defaultoption_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + if (PyTuple_GET_SIZE(args) || PyDict_GET_SIZE(kw)) { + PyErr_SetString(PyExc_TypeError, + "InvalidDefaultType does not accept arguments"); + return NULL; + } + + Py_INCREF(CpDefaultOption); + return CpDefaultOption; +} + +static PyObject* +cp_defaultoption_repr(PyObject* self) +{ + return PyUnicode_FromString(""); +} + +PyTypeObject CpDefaultOption_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(DefaultOptionType), + .tp_repr = (reprfunc)cp_defaultoption_repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = (newfunc)cp_defaultoption_new, +}; + +PyObject _CpDefaultOption_Object = { _PyObject_EXTRA_INIT{ + _Py_IMMORTAL_REFCNT /* ob_refcnt */ }, + &CpDefaultOption_Type /* ob_type */ }; diff --git a/src/ccaterpillar/layer.c b/src/ccaterpillar/layer.c new file mode 100644 index 0000000..248e0d0 --- /dev/null +++ b/src/ccaterpillar/layer.c @@ -0,0 +1,380 @@ +/* layer implementation */ +#include "caterpillar/caterpillar.h" + +#include + +/* top level layer impl */ +static PyObject* +cp_layer_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpLayerObject* self; + self = (CpLayerObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + + self->m_path = NULL; + self->m_parent = NULL; + self->m_state = NULL; + self->m_class = CpLayerClass_Default; + return (PyObject*)self; +} + +static void +cp_layer_dealloc(CpLayerObject* self) +{ + Py_XDECREF(self->m_path); + Py_XDECREF(self->m_state); + Py_XDECREF(self->m_parent); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_layer_init(CpLayerObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "state", "path", "parent", NULL }; + PyObject *state = NULL, *path = NULL, *parent = NULL; + if (!PyArg_ParseTupleAndKeywords( + args, kw, "O|OO", kwlist, &state, &path, &parent)) + return -1; + + if (!PyObject_IsInstance(state, (PyObject*)(&CpState_Type))) { + PyErr_SetString(PyExc_TypeError, "state must be an instance of CpState"); + return -1; + } + + Py_XSETREF(self->m_state, (CpStateObject*)Py_NewRef(state)); + _Cp_SetObj(self->m_path, path); + Py_XSETREF(self->m_parent, (CpLayerObject*)Py_XNewRef(parent)); + return 0; +} + +/*CpAPI*/ +CpLayerObject* +CpLayer_New(CpStateObject* state, CpLayerObject* parent) +{ + CpLayerObject* self = + (CpLayerObject*)CpObject_Create(&CpLayer_Type, "O", state); + if (!self) { + return NULL; + } + if (parent) { + Py_XSETREF(self->m_parent, (CpLayerObject*)Py_XNewRef(parent)); + Py_XSETREF(self->m_path, Py_XNewRef(parent->m_path)); + } + return self; +} + +/*CpAPI*/ +int +CpLayer_Invalidate(CpLayerObject* self) +{ + if (!self) { + PyErr_SetString(PyExc_ValueError, "layer must be non-null"); + return -1; + } + + Py_XSETREF(self->m_parent, NULL); + Py_XSETREF(self->m_state, NULL); + Py_XSETREF(self->m_path, NULL); + Py_CLEAR(self); + return 0; +} + +/* docs */ + +/* type */ +static PyMemberDef CpLayer_Members[] = { + { "state", T_OBJECT, offsetof(CpLayerObject, m_state), READONLY, "state" }, + { "path", T_OBJECT, offsetof(CpLayerObject, m_path), 0, "path" }, + { "parent", T_OBJECT, offsetof(CpLayerObject, m_parent), 0, "parent" }, + { NULL } /* Sentinel */ +}; + +PyTypeObject CpLayer_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(layer), + .tp_basicsize = sizeof(CpLayerObject), + .tp_dealloc = (destructor)cp_layer_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_members = CpLayer_Members, + .tp_init = (initproc)cp_layer_init, + .tp_new = (newfunc)cp_layer_new, +}; + +//------------------------------------------------------------------------------- +// seq layer + +static PyObject* +cp_seqlayer_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpSeqLayerObject* self = (CpSeqLayerObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + self->ob_base.m_path = NULL; + self->m_sequence = NULL; + self->m_length = -1; + self->m_index = -1; + self->s_greedy = false; + self->ob_base.m_parent = NULL; + self->ob_base.m_state = NULL; + self->ob_base.m_class = CpLayerClass_Sequence; + return (PyObject*)self; +} + +static void +cp_seqlayer_dealloc(CpSeqLayerObject* self) +{ + Py_XDECREF(self->ob_base.m_path); + Py_XDECREF(self->m_sequence); + Py_XDECREF(self->ob_base.m_parent); + Py_XDECREF(self->ob_base.m_state); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_seqlayer_init(CpSeqLayerObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "state", "path", "parent", NULL }; + PyObject *state = NULL, *sequence = NULL, *path = NULL, *parent = NULL; + if (!PyArg_ParseTupleAndKeywords( + args, kw, "O|OOO", kwlist, &state, &sequence, &path, &parent)) + return -1; + + if (!PyObject_IsInstance(state, (PyObject*)(&CpState_Type))) { + PyErr_SetString(PyExc_TypeError, "state must be an instance of CpState"); + return -1; + } + + Py_XSETREF(self->ob_base.m_state, (CpStateObject*)Py_NewRef(state)); + _Cp_SetObj(self->ob_base.m_path, path); + Py_XSETREF(self->ob_base.m_parent, (CpLayerObject*)Py_XNewRef(parent)); + return 0; +} + +/*CpAPI*/ +int +CpSeqLayer_SetSequence(CpSeqLayerObject* self, + PyObject* sequence, + Py_ssize_t length, + int8_t greedy) +{ + if (!sequence || !self) { + PyErr_SetString(PyExc_ValueError, "sequence and layer must be non-null"); + return -1; + } + + Py_XSETREF(self->m_sequence, Py_NewRef(sequence)); + self->m_length = length; + self->s_greedy = greedy; + self->m_index = 0; + return 0; +} + +/*CpAPI*/ +CpSeqLayerObject* +CpSeqLayer_New(CpStateObject* state, CpLayerObject* parent) +{ + CpSeqLayerObject* self = + (CpSeqLayerObject*)CpObject_Create(&CpSeqLayer_Type, "O", state); + if (!self) { + return NULL; + } + if (parent) { + Py_XSETREF(self->ob_base.m_parent, (CpLayerObject*)Py_XNewRef(parent)); + Py_XSETREF(self->ob_base.m_path, Py_XNewRef(parent->m_path)); + } + return self; +} + +/* docs */ + +/* type */ +static PyMemberDef CpSeqLayer_Members[] = { + { + "sequence", + T_OBJECT, + offsetof(CpSeqLayerObject, m_sequence), + READONLY, + }, + { + "length", + T_PYSSIZET, + offsetof(CpSeqLayerObject, m_length), + 0, + }, + { + "index", + T_PYSSIZET, + offsetof(CpSeqLayerObject, m_index), + 0, + }, + { NULL } /* Sentinel */ +}; + +PyTypeObject CpSeqLayer_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpSeqLayer_NAME), + .tp_basicsize = sizeof(CpSeqLayerObject), + .tp_dealloc = (destructor)cp_seqlayer_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = NULL, + .tp_members = CpSeqLayer_Members, + .tp_init = (initproc)cp_seqlayer_init, + .tp_new = (newfunc)cp_seqlayer_new, +}; + +//------------------------------------------------------------------------------- +// obj layer + +static PyObject* +cp_objlayer_new(PyTypeObject* type, PyObject* args, PyObject* kw) +{ + CpObjLayerObject* self = (CpObjLayerObject*)type->tp_alloc(type, 0); + if (self == NULL) + return NULL; + self->ob_base.m_path = NULL; + self->m_obj = NULL; + self->ob_base.m_parent = NULL; + self->ob_base.m_state = NULL; + self->ob_base.m_class = CpLayerClass_Object; + return (PyObject*)self; +} + +static void +cp_objlayer_dealloc(CpObjLayerObject* self) +{ + Py_XDECREF(self->ob_base.m_path); + Py_XDECREF(self->m_obj); + Py_XDECREF(self->ob_base.m_parent); + Py_XDECREF(self->ob_base.m_state); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static int +cp_objlayer_init(CpObjLayerObject* self, PyObject* args, PyObject* kw) +{ + static char* kwlist[] = { "state", "path", "parent", "obj", NULL }; + PyObject *state = NULL, *path = NULL, *parent = NULL, *obj = NULL; + if (!PyArg_ParseTupleAndKeywords( + args, kw, "O|OOO", kwlist, &state, &path, &parent, &obj)) + return -1; + + if (!PyObject_IsInstance(state, (PyObject*)(&CpState_Type))) { + PyErr_SetString(PyExc_TypeError, "state must be an instance of CpState"); + return -1; + } + + Py_XSETREF(self->ob_base.m_state, (CpStateObject*)Py_NewRef(state)); + Py_XSETREF(self->m_obj, obj ? Py_NewRef(obj) : (PyObject*)CpContext_New()); + Py_XSETREF(self->ob_base.m_path, Py_XNewRef(path)); + Py_XSETREF(self->ob_base.m_parent, (CpLayerObject*)Py_XNewRef(parent)); + return 0; +} + +static PyObject* +cp_objlayer_context_getattr(CpLayerObject* self, PyObject* args) +{ + _modulestate* state = get_global_module_state(); + PyObject *tmp = NULL, *lineValue = NULL, *lastKey = NULL, *key = NULL, + *obj = NULL; + if (!PyArg_ParseTuple(args, "O", &lineValue)) { + return NULL; + } + + PyObject* values = PyUnicode_Split(lineValue, state->str_path_delim, -1); + if (!values) { + return NULL; + } + + size_t length = PyList_Size(values); + if (length == 0) { + Py_XDECREF(values); + PyErr_SetString(PyExc_ValueError, "Empty path"); + return NULL; + } + + key = PyList_GetItem(values, 0); + if (!key) { + Py_XDECREF(values); + return NULL; + } + + tmp = PyObject_GenericGetAttr((PyObject*)self, key); + if (!tmp) { + PyErr_Clear(); + PyErr_Format(PyExc_ValueError, "CpObjLayer has no attribute '%s'", key); + Py_XDECREF(values); + return NULL; + } + + obj = tmp; + Py_XSETREF(lastKey, Py_XNewRef(key)); + for (size_t i = 1; i < length; i++) { + key = PyList_GetItem(values, i); + if (!key) { + Py_XDECREF(values); + return NULL; + } + + tmp = PyObject_GetAttr(obj, key); + if (!tmp) { + PyErr_Clear(); + PyErr_Format( + PyExc_ValueError, "'%s' has no attribute '%s'", lastKey, key); + Py_XDECREF(values); + return NULL; + } + + Py_XSETREF(obj, tmp); + Py_XSETREF(lastKey, Py_XNewRef(key)); + } + Py_XDECREF(values); + return obj; +} + +/*CpAPI*/ +CpObjLayerObject* +CpObjLayer_New(CpStateObject* state, CpLayerObject* parent) +{ + CpObjLayerObject* self = (CpObjLayerObject*)CpObject_CreateOneArg( + &CpObjLayer_Type, (PyObject*)state); + if (!self) { + return NULL; + } + if (parent) { + Py_XSETREF(self->ob_base.m_parent, (CpLayerObject*)Py_XNewRef(parent)); + Py_XSETREF(self->ob_base.m_path, Py_XNewRef(parent->m_path)); + } + return self; +} + +/* docs */ + +/* type */ +static PyMemberDef CpObjLayer_Members[] = { + { + "obj", + T_OBJECT, + offsetof(CpObjLayerObject, m_obj), + 0, + }, + { NULL } /* Sentinel */ +}; + +static PyMethodDef CpObjLayer_Methods[] = { + { "__context_getattr__", + (PyCFunction)cp_objlayer_context_getattr, + METH_VARARGS }, + { NULL } /* Sentinel */ +}; + +PyTypeObject CpObjLayer_Type = { + PyVarObject_HEAD_INIT(NULL, 0) _Cp_NameStr(CpObjLayer_NAME), + .tp_basicsize = sizeof(CpObjLayerObject), + .tp_dealloc = (destructor)cp_objlayer_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = NULL, + .tp_methods = CpObjLayer_Methods, + .tp_members = CpObjLayer_Members, + .tp_init = (initproc)cp_objlayer_init, + .tp_new = (newfunc)cp_objlayer_new, +}; \ No newline at end of file diff --git a/src/module.c b/src/ccaterpillar/module.c similarity index 73% rename from src/module.c rename to src/ccaterpillar/module.c index 5502121..69847d8 100644 --- a/src/module.c +++ b/src/ccaterpillar/module.c @@ -3,8 +3,7 @@ #define _CPMODULE #endif -#include "caterpillar/module.h" /* invalid default object and types through caterpillar.h*/ -#include "caterpillar/parsing.h" /* Pack and unpack functions */ +#include "caterpillar/caterpillar.h" #include "caterpillarapi.c" @@ -34,67 +33,6 @@ return NULL; \ } -/* immortal objects */ -static PyObject* -cp_invaliddefault_new(PyTypeObject* type, PyObject* args, PyObject* kw) -{ - if (PyTuple_GET_SIZE(args) || PyDict_GET_SIZE(kw)) { - PyErr_SetString(PyExc_TypeError, - "InvalidDefaultType does not accept arguments"); - return NULL; - } - - Py_INCREF(CpInvalidDefault); - return CpInvalidDefault; -} - -static PyObject* -cp_invaliddefault_repr(PyObject* self) -{ - return PyUnicode_FromString(""); -} - -PyTypeObject CpInvalidDefault_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(InvalidDefaultType), - .tp_repr = (reprfunc)cp_invaliddefault_repr, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = (newfunc)cp_invaliddefault_new, -}; - -PyObject _CpInvalidDefault_Object = { _PyObject_EXTRA_INIT{ - _Py_IMMORTAL_REFCNT /* ob_refcnt */ }, - &CpInvalidDefault_Type /* ob_type */ }; - -static PyObject* -cp_defaultoption_new(PyTypeObject* type, PyObject* args, PyObject* kw) -{ - if (PyTuple_GET_SIZE(args) || PyDict_GET_SIZE(kw)) { - PyErr_SetString(PyExc_TypeError, - "InvalidDefaultType does not accept arguments"); - return NULL; - } - - Py_INCREF(CpDefaultOption); - return CpDefaultOption; -} - -static PyObject* -cp_defaultoption_repr(PyObject* self) -{ - return PyUnicode_FromString(""); -} - -PyTypeObject CpDefaultOption_Type = { - PyVarObject_HEAD_INIT(NULL, 0) _Cp_Name(DefaultOptionType), - .tp_repr = (reprfunc)cp_defaultoption_repr, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_new = (newfunc)cp_defaultoption_new, -}; - -PyObject _CpDefaultOption_Object = { _PyObject_EXTRA_INIT{ - _Py_IMMORTAL_REFCNT /* ob_refcnt */ }, - &CpDefaultOption_Type /* ob_type */ }; - // ------------------------------------------------------------------------------ // module static PyObject* @@ -137,7 +75,19 @@ cp_core_pack_into(PyObject* m, PyObject* args, PyObject* kw) PyErr_SetString(PyExc_ValueError, "output stream not set!"); goto finish; } - res = CpPack(op, atom, io, globals); + + _modulestate* state = get_module_state(m); + if (CpStructModel_Check(op, state)) { + atom = CpStructModel_GetStruct(op, state); + if (!atom) { + goto finish; + } + res = CpPack(op, atom, io, globals); + Py_DECREF(atom); + } else { + res = CpPack(op, atom, io, globals); + } + finish: Py_XDECREF(globals); return res < 0 ? NULL : Py_None; @@ -174,7 +124,17 @@ cp_core_pack(PyObject* m, PyObject* args, PyObject* kw) goto finish; } - res = CpPack(op, atom, io, globals); + if (CpStructModel_Check(op, state)) { + atom = CpStructModel_GetStruct(op, state); + if (!atom) { + goto finish; + } + res = CpPack(op, atom, io, globals); + Py_DECREF(atom); + } else { + res = CpPack(op, atom, io, globals); + } + finish: Py_XDECREF(globals); if (res < 0) { @@ -194,6 +154,16 @@ cp_core_sizeof(PyObject* m, PyObject* args, PyObject* kw) if (!PyArg_ParseTupleAndKeywords(args, kw, "O|O", kwlist, &op, &globals)) { return NULL; } + _modulestate* state = get_module_state(m); + if (CpStructModel_Check(op, state)) { + PyObject* atom = CpStructModel_GetStruct(op, state); + if (!atom) { + return NULL; + } + PyObject* res = CpSizeOf(atom, globals); + Py_DECREF(atom); + return res; + } return CpSizeOf(op, globals); } @@ -236,7 +206,17 @@ cp_core_unpack(PyObject* m, PyObject* args, PyObject* kw) goto finish; } - res = CpUnpack(atom, io, globals); + if (CpStructModel_Check(atom, state)) { + atom = CpStructModel_GetStruct(atom, state); + if (!atom) { + goto finish; + } + res = CpUnpack(atom, io, globals); + Py_DECREF(atom); + } else { + res = CpUnpack(atom, io, globals); + } + finish: Py_XDECREF(globals); if (wrapped_io) { @@ -275,8 +255,9 @@ cp_module_clear(PyObject* m) Py_CLEAR(state->Any_Type); Py_CLEAR(state->List_Type); Py_CLEAR(state->Union_Type); + Py_CLEAR(state->Optional_Type); - // sttings + // strings Py_CLEAR(state->str_path_delim); Py_CLEAR(state->str___pack__); Py_CLEAR(state->str___unpack__); @@ -304,11 +285,17 @@ cp_module_clear(PyObject* m) Py_CLEAR(state->str___weakref__); Py_CLEAR(state->str___qualname__); Py_CLEAR(state->str_strict); + Py_CLEAR(state->str__value2member_map_); + Py_CLEAR(state->str__member_map_); + Py_CLEAR(state->str_utf8); + Py_CLEAR(state->str_cstring_default_pad); + Py_CLEAR(state->str___bits__); Py_CLEAR(state->cp_regex__unnamed); Py_CLEAR(state->cp_bytes__false); Py_CLEAR(state->cp_bytes__true); + Py_CLEAR(state->cp_typehandler_map); } return 0; } @@ -374,36 +361,61 @@ PyInit__C(void) CpModule_SetupType(&CpBinaryExpr_Type); CpModule_SetupType(&CpUnaryExpr_Type); CpModule_SetupType(&CpContextPath_Type); - CpModule_SetupType(&CpField_Type); - - CpFieldAtom_Type.tp_base = &CpAtom_Type; - CpFieldCAtom_Type.tp_base = &CpCAtom_Type; - CpModule_SetupType(&CpFieldAtom_Type); - CpModule_SetupType(&CpFieldCAtom_Type); CpModule_SetupType(&CpLayer_Type); + + CpSeqLayer_Type.tp_base = &CpLayer_Type; + CpObjLayer_Type.tp_base = &CpLayer_Type; + CpModule_SetupType(&CpSeqLayer_Type); + CpModule_SetupType(&CpObjLayer_Type); CpModule_SetupType(&CpState_Type); CpModule_SetupType(&CpStructFieldInfo_Type); - CpStruct_Type.tp_base = &CpFieldAtom_Type; + CpModule_SetupType(&CpLengthInfo_Type); + + // builtins setup + CpPrimitiveAtom_Type.tp_base = &CpAtom_Type; + CpBuiltinAtom_Type.tp_base = &CpCAtom_Type; + CpRepeatedAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpConditionAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpSwitchAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpOffsetAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpStruct_Type.tp_base = &CpBuiltinAtom_Type; + CpBoolAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpFloatAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpIntAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpCharAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpPaddingAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpStringAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpConstAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpBytesAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpPStringAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpEnumAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpVarIntAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpComputedAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpLazyAtom_Type.tp_base = &CpBuiltinAtom_Type; + CpCStringAtom_Type.tp_base = &CpBuiltinAtom_Type; + + CpModule_SetupType(&CpBuiltinAtom_Type); + CpModule_SetupType(&CpPrimitiveAtom_Type); + CpModule_SetupType(&CpRepeatedAtom_Type); + CpModule_SetupType(&CpConditionAtom_Type); + CpModule_SetupType(&CpSwitchAtom_Type); + CpModule_SetupType(&CpOffsetAtom_Type); CpModule_SetupType(&CpStruct_Type); - - CpIntAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpIntAtom_Type); - - CpFloatAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpFloatAtom_Type); - - CpBoolAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpBoolAtom_Type); - - CpCharAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpCharAtom_Type); - - CpPaddingAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpPaddingAtom_Type); - - CpStringAtom_Type.tp_base = &CpFieldCAtom_Type; CpModule_SetupType(&CpStringAtom_Type); + CpModule_SetupType(&CpConstAtom_Type); + CpModule_SetupType(&CpBytesAtom_Type); + CpModule_SetupType(&CpPStringAtom_Type); + CpModule_SetupType(&CpEnumAtom_Type); + CpModule_SetupType(&CpVarIntAtom_Type); + CpModule_SetupType(&CpComputedAtom_Type); + CpModule_SetupType(&CpLazyAtom_Type); + CpModule_SetupType(&CpCStringAtom_Type); // module setup m = PyModule_Create(&CpModule); @@ -411,33 +423,48 @@ PyInit__C(void) return NULL; } - CpModule_AddObject("atom", &CpAtom_Type); - CpModule_AddObject("catom", &CpCAtom_Type); - CpModule_AddObject("Option", &CpOption_Type); - CpModule_AddObject("Arch", &CpArch_Type); - CpModule_AddObject("Endian", &CpEndian_Type); - CpModule_AddObject("Context", &CpContext_Type); - CpModule_AddObject("BinaryExpr", &CpBinaryExpr_Type); - CpModule_AddObject("UnaryExpr", &CpUnaryExpr_Type); - CpModule_AddObject("ContextPath", &CpContextPath_Type); + CpModule_AddObject(CpAtom_NAME, &CpAtom_Type); + CpModule_AddObject(CpCAtom_NAME, &CpCAtom_Type); + CpModule_AddObject(CpOption_NAME, &CpOption_Type); + CpModule_AddObject(CpArch_NAME, &CpArch_Type); + CpModule_AddObject(CpEndian_NAME, &CpEndian_Type); + CpModule_AddObject(CpContext_NAME, &CpContext_Type); + CpModule_AddObject(CpBinaryExpr_NAME, &CpBinaryExpr_Type); + CpModule_AddObject(CpUnaryExpr_NAME, &CpUnaryExpr_Type); + CpModule_AddObject(CpContextPath_NAME, &CpContextPath_Type); CpModule_AddObject("InvalidDefaultType", &CpInvalidDefault_Type); CpModule_AddObject("InvalidDefault", CpInvalidDefault); CpModule_AddObject("DefaultOptionType", &CpDefaultOption_Type); CpModule_AddObject("DefaultOption", CpDefaultOption); - CpModule_AddObject("Field", &CpField_Type); - CpModule_AddObject("fieldatom", &CpFieldAtom_Type); - CpModule_AddObject("fieldcatom", &CpFieldCAtom_Type); - CpModule_AddObject("layer", &CpLayer_Type); - CpModule_AddObject("State", &CpState_Type); - CpModule_AddObject("fieldinfo", &CpStructFieldInfo_Type); - CpModule_AddObject("Struct", &CpStruct_Type); - - CpModule_AddObject("intatom", &CpIntAtom_Type); - CpModule_AddObject("floatatom", &CpFloatAtom_Type); - CpModule_AddObject("boolatom", &CpBoolAtom_Type); - CpModule_AddObject("charatom", &CpCharAtom_Type); - CpModule_AddObject("paddingatom", &CpPaddingAtom_Type); - CpModule_AddObject("string", &CpStringAtom_Type); + CpModule_AddObject(CpLayer_NAME, &CpLayer_Type); + CpModule_AddObject(CpSeqLayer_NAME, &CpSeqLayer_Type); + CpModule_AddObject(CpObjLayer_NAME, &CpObjLayer_Type); + CpModule_AddObject(CpState_NAME, &CpState_Type); + CpModule_AddObject(CpStructFieldInfo_NAME, &CpStructFieldInfo_Type); + CpModule_AddObject(CpStruct_NAME, &CpStruct_Type); + CpModule_AddObject(CpLengthInfo_NAME, &CpLengthInfo_Type); + + CpModule_AddObject(CpPrimitiveAtom_NAME, &CpPrimitiveAtom_Type); + CpModule_AddObject(CpBuiltinAtom_NAME, &CpBuiltinAtom_Type); + CpModule_AddObject(CpRepeatedAtom_NAME, &CpRepeatedAtom_Type); + CpModule_AddObject(CpConditionAtom_NAME, &CpConditionAtom_Type); + CpModule_AddObject(CpSwitchAtom_NAME, &CpSwitchAtom_Type); + CpModule_AddObject(CpOffsetAtom_NAME, &CpOffsetAtom_Type); + + CpModule_AddObject(CpIntAtom_NAME, &CpIntAtom_Type); + CpModule_AddObject(CpFloatAtom_NAME, &CpFloatAtom_Type); + CpModule_AddObject(CpBoolAtom_NAME, &CpBoolAtom_Type); + CpModule_AddObject(CpCharAtom_NAME, &CpCharAtom_Type); + CpModule_AddObject(CpPaddingAtom_NAME, &CpPaddingAtom_Type); + CpModule_AddObject(CpStringAtom_NAME, &CpStringAtom_Type); + CpModule_AddObject(CpConstAtom_NAME, &CpConstAtom_Type); + CpModule_AddObject(CpBytesAtom_NAME, &CpBytesAtom_Type); + CpModule_AddObject(CpPStringAtom_NAME, &CpPStringAtom_Type); + CpModule_AddObject(CpEnumAtom_NAME, &CpEnumAtom_Type); + CpModule_AddObject(CpVarIntAtom_NAME, &CpVarIntAtom_Type); + CpModule_AddObject(CpComputedAtom_NAME, &CpComputedAtom_Type); + CpModule_AddObject(CpLazyAtom_NAME, &CpLazyAtom_Type); + CpModule_AddObject(CpCStringAtom_NAME, &CpCStringAtom_Type); /* setup custom intatoms */ #define CpModule_DefAtom(name, ...) \ @@ -463,6 +490,8 @@ PyInit__C(void) CpModule_DefIntAtom("u32", 32, false); CpModule_DefIntAtom("i64", 64, true); CpModule_DefIntAtom("u64", 64, false); + CpModule_DefIntAtom("i128", 128, true); + CpModule_DefIntAtom("u128", 128, false); #define CpModule_DefFloatAtom(name, bits) \ CpModule_DefAtom(name, CpObject_Create(&CpFloatAtom_Type, "I", bits)); @@ -477,6 +506,8 @@ PyInit__C(void) CpModule_AddObject("boolean", CpObject_CreateNoArgs(&CpBoolAtom_Type)); CpModule_AddObject("char", CpObject_CreateNoArgs(&CpCharAtom_Type)); CpModule_AddObject("padding", CpObject_CreateNoArgs(&CpPaddingAtom_Type)); + CpModule_AddObject("varint", CpVarIntAtom_New(true, false)); + CpModule_AddObject("lsbvarint", CpVarIntAtom_New(true, true)); /* setup state */ _modulestate* state = get_module_state(m); @@ -518,7 +549,10 @@ PyInit__C(void) CpModuleState_AddObject( cp_option__global_struct_options, "STRUCT_OPTIONS", PySet_New(NULL)); CpModuleState_AddObject( - cp_option__global_struct_options, "FIELD_OPTIONS", PySet_New(NULL)); + cp_option__global_field_options, "FIELD_OPTIONS", PySet_New(NULL)); + + /* typehandler map */ + CpModuleState_AddObject(cp_typehandler_map, "TYPE_MAP", PyDict_New()); /* setup arch and endian */ CpModuleState_AddObject(cp_endian__native, @@ -567,6 +601,10 @@ PyInit__C(void) CACHED_STRING(str___qualname__, "__qualname__"); CACHED_STRING(str_path_delim, "."); CACHED_STRING(str_strict, "strict"); + CACHED_STRING(str__member_map_, "_member_map_"); + CACHED_STRING(str__value2member_map_, "_value2member_map_"); + CACHED_STRING(str_utf8, "utf-8"); + CACHED_STRING(str___bits__, "__bits__"); #undef CACHED_STRING @@ -580,6 +618,12 @@ PyInit__C(void) #undef CACHED_BYTES + if ((state->str_cstring_default_pad = PyUnicode_Decode( + PyBytes_AS_STRING(state->cp_bytes__false), 1, "utf-8", "replace")) == + NULL) { + return NULL; + } + // setup typing constants PyObject* typing = PyImport_ImportModule("typing"); if (!typing) { @@ -590,6 +634,7 @@ PyInit__C(void) CpModuleState_Set(Any_Type, PyObject_GetAttrString(typing, "Any")); CpModuleState_Set(List_Type, PyObject_GetAttrString(typing, "List")); CpModuleState_Set(Union_Type, PyObject_GetAttrString(typing, "Union")); + CpModuleState_Set(Optional_Type, PyObject_GetAttrString(typing, "Optional")); Py_XDECREF(typing); // regex setup diff --git a/src/option.c b/src/ccaterpillar/option.c similarity index 97% rename from src/option.c rename to src/ccaterpillar/option.c index f39665c..1c8e1c6 100644 --- a/src/option.c +++ b/src/ccaterpillar/option.c @@ -37,7 +37,7 @@ static PyObject* cp_option_repr(CpOptionObject* self) { return PyUnicode_FromFormat( - "CpOption(name=%R value=%R)", self->name, self->value); + "