Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a C API #20

Merged
merged 1 commit into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()