Skip to content

Commit

Permalink
Merge pull request #20 from TeskaLabs/feature/c-api
Browse files Browse the repository at this point in the history
Add a C API (initial version)
  • Loading branch information
ateska authored Jul 5, 2021
2 parents d49e6a7 + d030543 commit 4cf6a2e
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.vscode
build
cysimdjson/cysimdjson.cpp
cysimdjson/cysimdjson.h
cysimdjson/cysimdjson_api.h
cysimdjson.*.so
__pycache__
dist/
Expand Down
8 changes: 8 additions & 0 deletions cysimdjson/cysimdjson.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ cdef extern from "jsoninter.h":

cdef const char * PyUnicode_AsUTF8AndSize(object, Py_ssize_t *)

cdef simdjson_element extract_element(void *)


cdef class JSONObject:

Expand Down Expand Up @@ -313,6 +315,12 @@ cdef class JSONParser:
return get_active_implementation()


# This method is used by C-level callers who want to wrap `simdjson::dom::element` into a cysimdjson object instance
cdef public api object cysimdjson_wrap_element(void * element):
cdef simdjson_element v = extract_element(element)
return _wrap_element(v)


cdef inline object _wrap_element(simdjson_element v):
cdef simdjson_element_type et = v.type()

Expand Down
92 changes: 92 additions & 0 deletions cysimdjson/cysimdjsonc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// We have to include "Python.h" b/c otherwise parser.parse() crashes
// Assumption is that Python.h sets some definition that is used by `simdjson.h`
// TODO: Find what `ifdef` is set and set that without `Python.h`
#include "Python.h"

#include "simdjson/simdjson.h"

extern "C" {
#include "cysimdjsonc.h"
}

void * cysimdjson_parser_new(void) {
simdjson::dom::parser * parser = new simdjson::dom::parser();
void * p = static_cast<void*>(parser);
return p;
}

void cysimdjson_parser_del(void * p) {
assert(p != NULL);
simdjson::dom::parser * parser = static_cast<simdjson::dom::parser *>(p);
delete parser;
}


size_t cysimdjson_element_sizeof(void) {
return sizeof(simdjson::dom::element);
}


bool cysimdjson_parser_parse(void * p, void * memory, const uint8_t * data, size_t datalen) {
simdjson::dom::parser * parser = static_cast<simdjson::dom::parser *>(p);

try {
// Initialize the element at the memory provided by a caller
// See: https://www.geeksforgeeks.org/placement-new-operator-cpp/
// `memory` is a pointer to a pre-allocated memory space with >= cysimdjson_element_sizeof() bytes
simdjson::dom::element * element = new(memory) simdjson::dom::element();

// Parse the JSON
auto err = parser->parse(
data, datalen,
true // Create a copy if needed (TODO: this may be optimized eventually to save data copy)
).get(*element);

if (err) {
// Likely syntax error in JSON
return true;
}

} catch (const std::bad_alloc& e) {
// Error when allocating memory
return true;
}
catch (...) {
return true;
}

// No error
return false;
}


bool cysimdjson_element_get_str(const char * attrname, size_t attrlen, void * e, const char ** output, size_t * outputlen) {
simdjson::dom::element * element = static_cast<simdjson::dom::element *>(e);
std::string_view pointer = std::string_view(attrname, attrlen);

std::string_view result;
auto err = element->at_pointer(pointer).get(result);
if (err) {
return true;
}

*output = result.data();
*outputlen = result.size();
return false;
}


// This is here for an unit test
void cysimdjson_parser_test() {
printf("cysimdjson_parser_test started ...\n");

simdjson::dom::parser parser;
simdjson::dom::object object;

const char * jsond = R"({"key":"value"} )";
const size_t jsond_len = std::strlen(jsond);

auto error = parser.parse(jsond, jsond_len).get(object);

printf("cysimdjson_parser_test OK!\n");
}
21 changes: 21 additions & 0 deletions cysimdjson/cysimdjsonc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef CYSIMDJSONAPI_H
#define CYSIMDJSONAPI_H

// This is API for C (not C++) level
// This header has to be C compliant

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>

void * cysimdjson_parser_new(void);
void cysimdjson_parser_del(void * parser);

size_t cysimdjson_element_sizeof(void);
bool cysimdjson_parser_parse(void * parser, void * memory, const uint8_t * data, size_t datalen);

bool cysimdjson_element_get_str(const char * attrname, size_t attrlen, void * element, const char ** output, size_t * outputlen);

void cysimdjson_parser_test(void);

#endif
6 changes: 6 additions & 0 deletions cysimdjson/jsoninter.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ inline PyObject * element_to_py_string(dom::element & value) {
inline std::string get_active_implementation() {
return simdjson::active_implementation->description();
}


inline dom::element extract_element(void * p) {
dom::element * element = static_cast<dom::element *>(p);
return *element;
}
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
extensions = [
Extension(
"cysimdjson",
[
[
'cysimdjson/cysimdjson.pyx',
'cysimdjson/simdjson/simdjson.cpp',
'cysimdjson/pysimdjson/errors.cpp',
'cysimdjson/cysimdjsonc.cpp',
],
language="c++",
extra_compile_args=[
Expand Down
1 change: 1 addition & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .test_array import *
from .test_document import *
from .test_capi import *
27 changes: 27 additions & 0 deletions test/test_capi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ctypes
import unittest

import cysimdjson


class CySIMDJSONCAPITestCases(unittest.TestCase):


def test_capi_01(self):

ctypes.pythonapi.cysimdjson_parser_new.restype = ctypes.c_void_p
ctypes.pythonapi.cysimdjson_parser_del.argtypes = [ctypes.c_void_p]

parser = ctypes.pythonapi.cysimdjson_parser_new()

ctypes.pythonapi.cysimdjson_parser_del(parser)


def test_capi_02(self):
ctypes.pythonapi.cysimdjson_parser_new.restype = ctypes.c_int
element_sizeof = ctypes.pythonapi.cysimdjson_element_sizeof()
print("element_sizeof:", element_sizeof)


def test_capi_03(self):
ctypes.pythonapi.cysimdjson_parser_test()

0 comments on commit 4cf6a2e

Please sign in to comment.