/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "orcus/env.hpp" #include "orcus/json_parser.hpp" #include "orcus/json_document_tree.hpp" #include "orcus/config.hpp" #include #include #include #include #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) using namespace std; namespace orcus { namespace python { namespace { class python_json_error : public general_error { public: python_json_error(const std::string& msg) : general_error("python_json_error", msg) {} }; struct module_state { PyObject* error; }; int orcus_traverse(PyObject* m, visitproc visit, void* arg) { Py_VISIT(GETSTATE(m)->error); return 0; } int orcus_clear(PyObject* m) { Py_CLEAR(GETSTATE(m)->error); return 0; } struct parser_stack { PyObject* key; PyObject* node; json::node_t type; parser_stack(PyObject* _node, json::node_t _type) : key(nullptr), node(_node), type(_type) {} }; class json_parser_handler { PyObject* m_root; std::vector m_stack; PyObject* push_value(PyObject* value) { if (!value) { std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": Empty value is passed."; throw python_json_error(os.str()); } if (m_stack.empty()) { std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; throw python_json_error(os.str()); } parser_stack& cur = m_stack.back(); switch (cur.type) { case json::node_t::array: { PyList_Append(cur.node, value); return value; } break; case json::node_t::object: { assert(cur.key); PyDict_SetItem(cur.node, cur.key, value); cur.key = nullptr; return value; } break; default: Py_DECREF(value); } std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": unstackable JSON value type."; throw python_json_error(os.str()); } public: json_parser_handler() : m_root(nullptr) {} ~json_parser_handler() { if (m_root) Py_XDECREF(m_root); std::for_each(m_stack.begin(), m_stack.end(), [](parser_stack& ps) { if (ps.key) { Py_XDECREF(ps.key); ps.key = nullptr; } } ); } void begin_parse() { if (m_root) { std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": Root JSON value already exists."; throw python_json_error(os.str()); } } void end_parse() {} void begin_array() { if (m_root) { PyObject* array = push_value(PyList_New(0)); m_stack.push_back(parser_stack(array, json::node_t::array)); } else { m_root = PyList_New(0); m_stack.push_back(parser_stack(m_root, json::node_t::array)); } } void end_array() { if (m_stack.empty()) { std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; throw python_json_error(os.str()); } m_stack.pop_back(); } void begin_object() { if (m_root) { PyObject* dict = push_value(PyDict_New()); m_stack.push_back(parser_stack(dict, json::node_t::object)); } else { m_root = PyDict_New(); m_stack.push_back(parser_stack(m_root, json::node_t::object)); } } void object_key(std::string_view key, bool /*transient*/) { parser_stack& cur = m_stack.back(); cur.key = PyUnicode_FromStringAndSize(key.data(), key.size()); } void end_object() { if (m_stack.empty()) { std::ostringstream os; os << BOOST_CURRENT_FUNCTION << ": Stack is unexpectedly empty."; throw python_json_error(os.str()); } m_stack.pop_back(); } void boolean_true() { Py_INCREF(Py_True); push_value(Py_True); } void boolean_false() { Py_INCREF(Py_False); push_value(Py_False); } void null() { Py_INCREF(Py_None); push_value(Py_None); } void string(std::string_view val, bool /*transient*/) { push_value(PyUnicode_FromStringAndSize(val.data(), val.size())); } void number(double val) { push_value(PyFloat_FromDouble(val)); } PyObject* get_root() { PyObject* o = m_root; m_root = nullptr; return o; } }; PyObject* json_loads(PyObject* /*module*/, PyObject* args, PyObject* kwargs) { char* stream = nullptr; static const char* kwlist[] = { "s", nullptr }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", const_cast(kwlist), &stream)) { PyErr_SetString(PyExc_TypeError, "The method must be given a string."); return nullptr; } json_parser_handler hdl; orcus::json_parser parser(stream, hdl); try { parser.parse(); return hdl.get_root(); } catch (const orcus::parse_error& e) { PyErr_SetString(PyExc_TypeError, e.what()); } return nullptr; } PyMethodDef orcus_methods[] = { { "loads", (PyCFunction)json_loads, METH_VARARGS | METH_KEYWORDS, "Load JSON string into a Python object." }, { nullptr, nullptr, 0, nullptr } }; struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_orcus_json", nullptr, sizeof(struct module_state), orcus_methods, nullptr, orcus_traverse, orcus_clear, nullptr }; } }} extern "C" { ORCUS_DLLPUBLIC PyObject* PyInit__orcus_json() { PyObject* m = PyModule_Create(&orcus::python::moduledef); return m; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */