diff --git a/.gitmodules b/.gitmodules index 1c1bd9b62eff6c8d7f491492b292007d41e6e639..b03f7351c6e6a4eaaa5521f6c7a44deded20a9a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "ext/nanogui"] path = ext/nanogui url = https://github.com/wjakob/nanogui.git +[submodule "SDK/C++/public/ext/pybind11"] + path = SDK/C++/public/ext/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 87114cb8b4834724d4930f70557781bebc3f3847..4bf0f37db31f35fe4c97c09f25a6add778d80c90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ include (CheckIncludeFile) include (CheckIncludeFileCXX) include (CheckFunctionExists) include(CheckLanguage) +include(ExternalProject) if (WIN32) set(CMAKE_GENERATOR_TOOLSET "host=x64") @@ -476,6 +477,7 @@ if (WITH_SDK) if (NOT WIN32) add_subdirectory(SDK/C) endif() + add_subdirectory(SDK/C++) endif() if (HAVE_AVFORMAT) diff --git a/SDK/C++/CMakeLists.txt b/SDK/C++/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..8bec8ee83108171e91209a80092813a0915d8466 --- /dev/null +++ b/SDK/C++/CMakeLists.txt @@ -0,0 +1,24 @@ +add_library(voltu SHARED + private/system.cpp + private/feed_impl.cpp + private/room_impl.cpp + private/frame_impl.cpp + private/image_impl.cpp + private/observer_impl.cpp + private/pointcloud_impl.cpp +) + +target_include_directories(voltu + PUBLIC public/include + PRIVATE src) + +target_link_libraries(voltu ftlcommon ftldata ftlctrl ftlrgbd ftlstreams ftlrender Threads::Threads ${OpenCV_LIBS} openvr ftlnet nanogui ${NANOGUI_EXTRA_LIBS} ceres nvidia-ml) + +ExternalProject_Add( + voltu_sdk + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/public" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/sdk" + INSTALL_COMMAND "" + BUILD_ALWAYS true + CMAKE_ARGS -DOpenCV_DIR=${OpenCV_DIR} +) diff --git a/SDK/C++/private/feed_impl.cpp b/SDK/C++/private/feed_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..001b2974f802d5d46091c9b8f820fdba72729ad3 --- /dev/null +++ b/SDK/C++/private/feed_impl.cpp @@ -0,0 +1,25 @@ +#include "feed_impl.hpp" + +using voltu::internal::FeedImpl; + +FeedImpl::FeedImpl(ftl::stream::Feed* feed, uint32_t fsid) + : feed_(feed) +{ + fsids_.insert(fsid); +} + +FeedImpl::~FeedImpl() +{ + remove(); +} + +std::string FeedImpl::getURI() +{ + return feed_->getURI(*fsids_.begin()); +} + +void FeedImpl::remove() +{ + +} + diff --git a/SDK/C++/private/feed_impl.hpp b/SDK/C++/private/feed_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6eae89a31f769385770854475c416b74df5abdaa --- /dev/null +++ b/SDK/C++/private/feed_impl.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <voltu/feed.hpp> +#include <ftl/streams/feed.hpp> +#include <unordered_set> + +namespace voltu +{ +namespace internal +{ + +class FeedImpl : public voltu::Feed +{ +public: + FeedImpl(ftl::stream::Feed*, uint32_t fsid); + ~FeedImpl(); + + std::string getURI() override; + + void remove() override; + +private: + ftl::stream::Feed *feed_; + std::unordered_set<uint32_t> fsids_; +}; + +} +} diff --git a/SDK/C++/private/frame_impl.cpp b/SDK/C++/private/frame_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a6057f093bc149f9877e09cb096a6f661f8e39fe --- /dev/null +++ b/SDK/C++/private/frame_impl.cpp @@ -0,0 +1,60 @@ +#include "frame_impl.hpp" +#include "image_impl.hpp" +#include <ftl/rgbd/frame.hpp> +#include <voltu/types/errors.hpp> + +using voltu::internal::FrameImpl; + +FrameImpl::FrameImpl() +{ + +} + +FrameImpl::~FrameImpl() +{ + +} + +std::list<voltu::ImagePtr> FrameImpl::getImageSet(voltu::Channel c) +{ + std::list<voltu::ImagePtr> result; + ftl::codecs::Channel channel = ftl::codecs::Channel::Colour; + + switch (c) + { + case voltu::Channel::kColour : channel = ftl::codecs::Channel::Colour; break; + case voltu::Channel::kDepth : channel = ftl::codecs::Channel::Depth; break; + default: throw voltu::exceptions::BadImageChannel(); + } + + for (const auto &fs : framesets_) + { + for (const auto &f : fs->frames) + { + if (f.hasChannel(channel)) + { + auto img = std::make_shared<voltu::internal::ImageImpl>( + f.cast<ftl::rgbd::Frame>(), channel + ); + result.push_back(img); + } + } + } + + return result; +} + +voltu::PointCloudPtr FrameImpl::getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt) +{ + return nullptr; +} + +void FrameImpl::pushFrameSet(const std::shared_ptr<ftl::data::FrameSet> &fs) +{ + framesets_.push_back(fs); +} + +int64_t FrameImpl::getTimestamp() +{ + return 0; +} diff --git a/SDK/C++/private/frame_impl.hpp b/SDK/C++/private/frame_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..ee6aec4a7da48c086df4c6475125ab7cc36efc95 --- /dev/null +++ b/SDK/C++/private/frame_impl.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <voltu/types/frame.hpp> +#include <voltu/types/image.hpp> +#include <voltu/types/channel.hpp> +#include <ftl/data/new_frameset.hpp> + +namespace voltu +{ +namespace internal +{ + +class FrameImpl : public voltu::Frame +{ +public: + FrameImpl(); + ~FrameImpl(); + + std::list<voltu::ImagePtr> getImageSet(voltu::Channel) override; + + voltu::PointCloudPtr getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt) override; + + int64_t getTimestamp() override; + + void pushFrameSet(const std::shared_ptr<ftl::data::FrameSet> &fs); + + inline const std::list<std::shared_ptr<ftl::data::FrameSet>> &getInternalFrameSets() const { return framesets_; } + +private: + std::list<std::shared_ptr<ftl::data::FrameSet>> framesets_; +}; + +} +} diff --git a/SDK/C++/private/image_impl.cpp b/SDK/C++/private/image_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f8bf63a27001f0540f008eed4a5f4bb5bd8dfb75 --- /dev/null +++ b/SDK/C++/private/image_impl.cpp @@ -0,0 +1,125 @@ +#include "image_impl.hpp" + +using voltu::internal::ImageImpl; + +ImageImpl::ImageImpl(const ftl::rgbd::Frame& f, ftl::codecs::Channel c) + : frame_(f), channel_(c) +{ + +} + +ImageImpl::~ImageImpl() +{ + +} + +voltu::ImageData ImageImpl::getHost() +{ + cv::Mat m = frame_.get<cv::Mat>(channel_); + voltu::ImageData r; + r.data = m.data; + r.width = m.cols; + r.height = m.rows; + r.pitch = m.step; + + if (m.type() == CV_8UC4) + { + r.format = voltu::ImageFormat::kBGRA8; + } + else if (m.type() == CV_32F) + { + r.format = voltu::ImageFormat::kFloat32; + } + + return r; +} + +voltu::ImageData ImageImpl::getDevice() +{ + cv::cuda::GpuMat m = frame_.get<cv::cuda::GpuMat>(channel_); + voltu::ImageData r; + r.data = m.data; + r.width = m.cols; + r.height = m.rows; + r.pitch = m.step; + + if (m.type() == CV_8UC4) + { + r.format = voltu::ImageFormat::kBGRA8; + } + else if (m.type() == CV_32F) + { + r.format = voltu::ImageFormat::kFloat32; + } + + return r; +} + +bool ImageImpl::isDevice() +{ + return true; +} + +voltu::Channel ImageImpl::getChannel() +{ + switch (channel_) + { + case ftl::codecs::Channel::Colour : return voltu::Channel::kColour; + case ftl::codecs::Channel::Depth : return voltu::Channel::kDepth; + default: return voltu::Channel::kInvalid; + } +} + +std::string ImageImpl::getName() +{ + return std::to_string(frame_.frameset()) + std::string("-") + std::to_string(frame_.source()); +} + +voltu::Intrinsics ImageImpl::getIntrinsics() +{ + auto raw = frame_.getLeft(); + voltu::Intrinsics result; + result.width = raw.width; + result.height = raw.height; + result.principle_x = raw.cx; + result.principle_y = raw.cy; + result.focal_x = raw.fx; + result.focal_y = raw.fy; + return result; +} + +voltu::StereoIntrinsics ImageImpl::getStereoIntrinsics() +{ + auto raw = frame_.getLeft(); + voltu::StereoIntrinsics result; + result.width = raw.width; + result.height = raw.height; + result.principle_x = raw.cx; + result.principle_y = raw.cy; + result.focal_x = raw.fx; + result.focal_y = raw.fy; + result.min_depth = raw.minDepth; + result.max_depth = raw.maxDepth; + result.baseline = raw.baseline; + return result; +} + +Eigen::Matrix4d ImageImpl::getPose() +{ + return frame_.getPose(); +} + +int64_t ImageImpl::getTimestamp() +{ + return frame_.timestamp(); +} + +int ImageImpl::getCameraNumber() +{ + return frame_.source(); +} + +uint32_t ImageImpl::getUniqueId() +{ + return frame_.id().id; +} diff --git a/SDK/C++/private/image_impl.hpp b/SDK/C++/private/image_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..189a1c19fd13366e8728a50494a6ee9b1e2c4c5f --- /dev/null +++ b/SDK/C++/private/image_impl.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <voltu/types/image.hpp> +#include <ftl/rgbd/frame.hpp> + +namespace voltu +{ +namespace internal +{ + +class ImageImpl : public voltu::Image +{ +public: + ImageImpl(const ftl::rgbd::Frame&, ftl::codecs::Channel c); + ~ImageImpl(); + + voltu::ImageData getHost() override; + + voltu::ImageData getDevice() override; + + bool isDevice() override; + + voltu::Channel getChannel() override; + + std::string getName() override; + + voltu::Intrinsics getIntrinsics() override; + + voltu::StereoIntrinsics getStereoIntrinsics() override; + + Eigen::Matrix4d getPose() override; + + int64_t getTimestamp() override; + + //virtual voltu::RoomId getRoomId() override; + + int getCameraNumber() override; + + uint32_t getUniqueId() override; + +private: + const ftl::rgbd::Frame &frame_; + ftl::codecs::Channel channel_; +}; + +} +} diff --git a/SDK/C++/private/observer_impl.cpp b/SDK/C++/private/observer_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..325d24cf39eb8a0a32e5390f6ef0c84a5ab4a7f8 --- /dev/null +++ b/SDK/C++/private/observer_impl.cpp @@ -0,0 +1,182 @@ +#include "observer_impl.hpp" +#include "frame_impl.hpp" +#include <voltu/types/errors.hpp> + +#include <ftl/render/CUDARender.hpp> +#include <ftl/rgbd/frame.hpp> + +using voltu::internal::ObserverImpl; +using ftl::rgbd::Capability; + +ObserverImpl::ObserverImpl(ftl::Configurable *base) +{ + pool_ = new ftl::data::Pool(2,5); + rend_ = ftl::create<ftl::render::CUDARender>(base, "camN"); + + intrinsics_.fx = 700.0f; + intrinsics_.fy = 700.0f; + intrinsics_.width = 1280; + intrinsics_.height = 720; + intrinsics_.cx = -1280.0f/2.0f; + intrinsics_.cy = -720.0f/2.0f; + intrinsics_.minDepth = 0.1f; + intrinsics_.maxDepth = 12.0f; + pose_.setIdentity(); + + calibration_uptodate_.clear(); +} + +ObserverImpl::~ObserverImpl() +{ + +} + +void ObserverImpl::setResolution(uint32_t w, uint32_t h) +{ + if (w < 100 || w > 10000 || h < 100 || h > 10000) + { + throw voltu::exceptions::BadParameterValue(); + } + intrinsics_.width = w; + intrinsics_.height = h; + intrinsics_.cx = -int(w)/2; + intrinsics_.cy = -int(h)/2; + calibration_uptodate_.clear(); +} + +void ObserverImpl::setFocalLength(uint32_t f) +{ + if (f < 100 || f > 10000) + { + throw voltu::exceptions::BadParameterValue(); + } + intrinsics_.fx = f; + intrinsics_.fy = f; + calibration_uptodate_.clear(); +} + +void ObserverImpl::setStereo(bool) +{ + +} + +bool ObserverImpl::waitCompletion(int timeout) +{ + if (frame_complete_) return true; + + frame_complete_ = true; + + try + { + rend_->render(); + rend_->end(); + } + catch(const std::exception& e) + { + throw voltu::exceptions::InternalRenderError(); + } + + cudaSafeCall(cudaStreamSynchronize(frameset_->frames[0].stream())); + return true; +} + +void ObserverImpl::submit(const voltu::FramePtr& frame) +{ + if (frame_complete_) + { + frame_complete_ = false; + frameset_ = _makeFrameSet(); + + ftl::rgbd::Frame &rgbdframe = frameset_->frames[0].cast<ftl::rgbd::Frame>(); + + if (!rgbdframe.has(ftl::codecs::Channel::Calibration) || calibration_uptodate_.test_and_set()) + { + rgbdframe.setLeft() = intrinsics_; + + auto &cap = rgbdframe.create<std::unordered_set<Capability>>(ftl::codecs::Channel::Capabilities); + cap.clear(); + cap.emplace(Capability::VIDEO); + cap.emplace(Capability::MOVABLE); + cap.emplace(Capability::ADJUSTABLE); + cap.emplace(Capability::VIRTUAL); + cap.emplace(Capability::LIVE); + if (rend_->value("projection", 0) == int(ftl::rgbd::Projection::EQUIRECTANGULAR)) cap.emplace(Capability::EQUI_RECT); + + auto &meta = rgbdframe.create<std::map<std::string,std::string>>(ftl::codecs::Channel::MetaData); + meta["name"] = std::string("Virtual Camera"); //host_->value("name", host_->getID()); + //meta["id"] = host_->getID(); + meta["uri"] = std::string("device:render"); + meta["device"] = std::string("CUDA Render"); + } + //if (!rgbdframe.has(ftl::codecs::Channel::Pose)) + //{ + rgbdframe.setPose() = pose_.cast<double>(); + //} + + int width = rgbdframe.getLeft().width; + int height = rgbdframe.getLeft().height; + + // FIXME: Create opengl buffers here and map to cuda? + auto &colour = rgbdframe.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Colour); + colour.create(height, width, CV_8UC4); + rgbdframe.create<cv::cuda::GpuMat>(ftl::codecs::Channel::Depth).create(height, width, CV_32F); + rgbdframe.createTexture<float>(ftl::codecs::Channel::Depth); + + rend_->begin(rgbdframe, ftl::codecs::Channel::Colour); + } + + auto *fimp = dynamic_cast<voltu::internal::FrameImpl*>(frame.get()); + if (!fimp) + { + throw voltu::exceptions::InvalidFrameObject(); + } + + const auto &sets = fimp->getInternalFrameSets(); + for (const auto &fs : sets) + { + Eigen::Matrix4d pose; + pose.setIdentity(); + + try + { + rend_->submit(fs.get(), ftl::codecs::Channels<0>(ftl::codecs::Channel::Colour), pose); + } + catch (...) + { + throw voltu::exceptions::InternalRenderError(); + } + } +} + +void ObserverImpl::setPose(const Eigen::Matrix4f &p) +{ + pose_ = p; +} + +voltu::FramePtr ObserverImpl::getFrame() +{ + waitCompletion(0); + auto f = std::make_shared<voltu::internal::FrameImpl>(); + f->pushFrameSet(frameset_); + return f; +} + +voltu::PropertyPtr ObserverImpl::property(voltu::ObserverProperty) +{ + throw voltu::exceptions::InvalidProperty(); +} + +std::shared_ptr<ftl::data::FrameSet> ObserverImpl::_makeFrameSet() +{ + int64_t timestamp = ftl::timer::get_time(); + + auto newf = std::make_shared<ftl::data::FrameSet>(pool_, ftl::data::FrameID(id_,255), timestamp); + for (size_t i=0; i<size_; ++i) { + newf->frames.push_back(std::move(pool_->allocate(ftl::data::FrameID(id_, i), timestamp))); + } + + newf->mask = 0xFF; + newf->clearFlags(); + newf->store(); + return newf; +} diff --git a/SDK/C++/private/observer_impl.hpp b/SDK/C++/private/observer_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..817e2babdb45b2beb23380f7b6e4ee20ca7e87c2 --- /dev/null +++ b/SDK/C++/private/observer_impl.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include <voltu/observer.hpp> +#include <ftl/render/renderer.hpp> +#include <ftl/data/framepool.hpp> +#include <ftl/data/new_frameset.hpp> +#include <ftl/rgbd/camera.hpp> +#include <Eigen/Eigen> + +namespace voltu +{ +namespace internal +{ + +class ObserverImpl : public voltu::Observer +{ +public: + explicit ObserverImpl(ftl::Configurable *base); + + ~ObserverImpl(); + + void setResolution(uint32_t w, uint32_t h) override; + + void setFocalLength(uint32_t f) override; + + void setStereo(bool) override; + + bool waitCompletion(int timeout) override; + + void submit(const voltu::FramePtr&) override; + + void setPose(const Eigen::Matrix4f &) override; + + voltu::FramePtr getFrame() override; + + voltu::PropertyPtr property(voltu::ObserverProperty) override; + +private: + ftl::render::FSRenderer *rend_; + ftl::data::Pool *pool_; + int id_; + size_t size_ = 1; + std::shared_ptr<ftl::data::FrameSet> frameset_; + bool frame_complete_ = true; + ftl::rgbd::Camera intrinsics_; + Eigen::Matrix4f pose_; + std::atomic_flag calibration_uptodate_; + + std::shared_ptr<ftl::data::FrameSet> _makeFrameSet(); +}; + +} +} diff --git a/SDK/C++/private/pointcloud_impl.cpp b/SDK/C++/private/pointcloud_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9de38019a659f29d434c7c182e06f0cf2813645d --- /dev/null +++ b/SDK/C++/private/pointcloud_impl.cpp @@ -0,0 +1,16 @@ +#include "pointcloud_impl.hpp" + +using voltu::internal::PointCloudUnstructured; +using voltu::PointCloudData; + +PointCloudData PointCloudUnstructured::getHost() +{ + // TODO: Generate point cloud on GPU + return {}; +} + +PointCloudData PointCloudUnstructured::getDevice() +{ + // TODO: Generate point cloud on GPU + return {}; +} diff --git a/SDK/C++/private/pointcloud_impl.hpp b/SDK/C++/private/pointcloud_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6f5f0258e957200b775eb7dfc0feb3ea2906e8e6 --- /dev/null +++ b/SDK/C++/private/pointcloud_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <voltu/types/pointcloud.hpp> + +namespace voltu +{ +namespace internal +{ + +class PointCloudUnstructured : public voltu::PointCloud +{ +public: + voltu::PointCloudData getHost() override; + + voltu::PointCloudData getDevice() override; + +private: +}; + +} +} diff --git a/SDK/C++/private/room_impl.cpp b/SDK/C++/private/room_impl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9df583c86610ecfeb4c0140ee659201ba76a1f29 --- /dev/null +++ b/SDK/C++/private/room_impl.cpp @@ -0,0 +1,90 @@ +#include <voltu/room.hpp> +#include "room_impl.hpp" +#include "frame_impl.hpp" +#include <voltu/types/errors.hpp> + +using voltu::internal::RoomImpl; + +RoomImpl::RoomImpl(ftl::stream::Feed* feed) + : feed_(feed) +{ + +} + +bool RoomImpl::waitNextFrame(int64_t timeout) +{ + if (!filter_) + { + filter_ = feed_->filter(fsids_, {ftl::codecs::Channel::Colour, ftl::codecs::Channel::Depth}); + filter_->on([this](const std::shared_ptr<ftl::data::FrameSet> &fs) + { + UNIQUE_LOCK(mutex_, lk); + last_seen_ = std::max(last_seen_, fs->timestamp()); + cv_.notify_all(); + return true; + }); + } + + std::unique_lock<std::mutex> lk(mutex_); + + if (last_read_ >= last_seen_) + { + if (timeout > 0) + { + cv_.wait_for(lk, std::chrono::seconds(timeout), [this] { + return last_read_ < last_seen_; + }); + + return last_read_ < last_seen_; + } + else if (timeout == 0) + { + return false; + } + else + { + cv_.wait(lk, [this] { + return last_read_ < last_seen_; + }); + } + } + + return true; +} + +voltu::FramePtr RoomImpl::getFrame() +{ + auto f = std::make_shared<voltu::internal::FrameImpl>(); + + std::unique_lock<std::mutex> lk(mutex_); + int count = 0; + + for (auto fsid : fsids_) + { + auto fs = feed_->getFrameSet(fsid); + if (!fs) continue; + + f->pushFrameSet(fs); + last_read_ = std::max(last_read_, fs->timestamp()); + ++count; + } + + if (count == 0) throw voltu::exceptions::NoFrame(); + + return f; +} + +std::string RoomImpl::getName() +{ + return ""; +} + +void RoomImpl::addFrameSet(uint32_t fsid) +{ + fsids_.insert(fsid); +} + +bool RoomImpl::active() +{ + return ftl::running; +} diff --git a/SDK/C++/private/room_impl.hpp b/SDK/C++/private/room_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..6c57dfcbb499c32f487d952c7b021da3ce148faa --- /dev/null +++ b/SDK/C++/private/room_impl.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include <ftl/streams/feed.hpp> +#include <ftl/threads.hpp> +#include <condition_variable> + +namespace voltu +{ +namespace internal +{ + +class RoomImpl : public voltu::Room +{ +public: + explicit RoomImpl(ftl::stream::Feed*); + + bool waitNextFrame(int64_t) override; + + voltu::FramePtr getFrame() override; + + std::string getName() override; + + bool active() override; + + void addFrameSet(uint32_t fsid); + +private: + ftl::stream::Feed* feed_; + std::unordered_set<uint32_t> fsids_; + ftl::stream::Feed::Filter* filter_=nullptr; + MUTEX mutex_; + std::condition_variable cv_; + int64_t last_seen_ = -1; + int64_t last_read_ = -1; +}; + +} +} diff --git a/SDK/C++/private/system.cpp b/SDK/C++/private/system.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f25035dafd59dd81b9e2dad5609cac569b870c8 --- /dev/null +++ b/SDK/C++/private/system.cpp @@ -0,0 +1,94 @@ +#include "system_impl.hpp" +#include "feed_impl.hpp" +#include "room_impl.hpp" +#include "observer_impl.hpp" +#include <voltu/voltu.hpp> +#include <voltu/types/errors.hpp> +#include <ftl/timer.hpp> +#include <iostream> + +using voltu::internal::SystemImpl; + +static bool g_isinit = false; + +#if defined(WIN32) +#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) +#else +#define EXTERN_DLL_EXPORT extern "C" +#endif + +EXTERN_DLL_EXPORT voltu::System* voltu_initialise() +{ + if (!g_isinit) + { + return new SystemImpl(); + } + else + { + throw voltu::exceptions::AlreadyInit(); + } + return nullptr; +} + +SystemImpl::SystemImpl() +{ + int argc = 1; + char arg1[] = {'v','o','l','t','u',0}; + char* argv[] = {arg1,0}; + + root_ = ftl::configure(argc, argv, "sdk"); + net_ = ftl::create<ftl::net::Universe>(root_, "net"); + feed_ = ftl::create<ftl::stream::Feed>(root_, "system", net_); + + net_->start(); + + ftl::timer::start(false); +} + +SystemImpl::~SystemImpl() +{ + ftl::timer::stop(true); + ftl::pool.stop(true); +} + +voltu::Version SystemImpl::getVersion() const +{ + voltu::Version v; + v.major = VOLTU_VERSION_MAJOR; + v.minor = VOLTU_VERSION_MINOR; + v.patch = VOLTU_VERSION_PATCH; + return v; +} + +voltu::FeedPtr SystemImpl::open(const std::string& uri) +{ + try + { + uint32_t fsid = feed_->add(uri); + return std::make_shared<voltu::internal::FeedImpl>(feed_, fsid); + } + catch(const std::exception &e) + { + throw voltu::exceptions::BadSourceURI(); + } +}; + +std::list<voltu::RoomId> SystemImpl::listRooms() +{ + auto fsids = feed_->listFrameSets(); + std::list<voltu::RoomId> res; + for (unsigned int fsid : fsids) res.push_front(static_cast<voltu::RoomId>(fsid)); + return res; +} + +voltu::RoomPtr SystemImpl::getRoom(voltu::RoomId id) +{ + auto s = std::make_shared<voltu::internal::RoomImpl>(feed_); + s->addFrameSet(id); + return s; +} + +voltu::ObserverPtr SystemImpl::createObserver() +{ + return std::make_shared<voltu::internal::ObserverImpl>(root_); +} diff --git a/SDK/C++/private/system_impl.hpp b/SDK/C++/private/system_impl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f11f48891d7a3b838303abdda47f240c63d60da7 --- /dev/null +++ b/SDK/C++/private/system_impl.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include <voltu/system.hpp> + +#include <ftl/streams/feed.hpp> +#include <ftl/net/universe.hpp> + +namespace voltu +{ +namespace internal +{ + +class SystemImpl : public voltu::System +{ +public: + SystemImpl(); + ~SystemImpl(); + + voltu::Version getVersion() const override; + + voltu::RoomPtr createRoom() override { return nullptr; }; + + voltu::ObserverPtr createObserver() override; + + voltu::FeedPtr open(const std::string&) override; + + std::list<voltu::RoomId> listRooms() override; + + voltu::RoomPtr getRoom(voltu::RoomId) override; + +private: + ftl::Configurable* root_; + ftl::stream::Feed* feed_; + ftl::net::Universe* net_; +}; + +} // namespace internal +} // namespace voltu diff --git a/SDK/C++/public/CMakeLists.txt b/SDK/C++/public/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..ae8c7da08bfad909195d334e699fdbba5655e0bf --- /dev/null +++ b/SDK/C++/public/CMakeLists.txt @@ -0,0 +1,68 @@ +cmake_minimum_required (VERSION 3.16.0) + +project (voltu_sdk VERSION 0.0.1) + +include(GNUInstallDirs) + +option(WITH_OPENCV "Build with OpenCV wrapper" ON) +option(WITH_PYTHON "Build Python module" OFF) + +find_package( Eigen3 REQUIRED NO_MODULE ) +find_package( Threads REQUIRED ) + +if (WITH_OPENCV) + find_package( OpenCV REQUIRED ) +endif() + +if(WIN32) + add_definitions(-DWIN32) + set(CMAKE_GENERATOR_TOOLSET "host=x64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2 /MP4 /std:c++14 /wd4996") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /DFTL_DEBUG /Wall") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") + set(OS_LIBS "") +else() + add_definitions(-DUNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fPIC -march=native -mfpmath=sse -Wall -Werror=unused-result -Werror=return-type -pthread") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") + set(OS_LIBS "dl") +endif() + +set(VOLTU_SRCS + voltu.cpp +) + +set(OPTIONAL_DEPENDENCIES) + +if (WITH_OPENCV) + list(APPEND OPTIONAL_DEPENDENCIES ${OpenCV_LIBS}) + list(APPEND VOLTU_SRCS voltu_cv.cpp) + set(CMAKE_REQUIRED_INCLUDES ${OpenCV_INCLUDE_DIRS}) +endif() + +add_library(voltu_sdk STATIC ${VOLTU_SRCS}) + +target_include_directories(voltu_sdk + PUBLIC include) +target_link_libraries(voltu_sdk ${OS_LIBS} Threads::Threads ${OPTIONAL_DEPENDENCIES} Eigen3::Eigen) + +add_executable(voltu_basic_test + samples/basic_test/main.cpp +) +target_link_libraries(voltu_basic_test voltu_sdk) + +add_executable(voltu_basic_file + samples/basic_file/main.cpp +) +target_link_libraries(voltu_basic_file voltu_sdk) + +add_executable(voltu_basic_virtual_cam + samples/basic_virtual_cam/main.cpp +) +target_link_libraries(voltu_basic_virtual_cam voltu_sdk) + +if (WITH_PYTHON) + add_subdirectory(ext/pybind11) + add_subdirectory(python) +endif() diff --git a/SDK/C++/public/ext/pybind11 b/SDK/C++/public/ext/pybind11 new file mode 160000 index 0000000000000000000000000000000000000000..06a54018c8a9fd9a7be5f5b56414b5da9259f637 --- /dev/null +++ b/SDK/C++/public/ext/pybind11 @@ -0,0 +1 @@ +Subproject commit 06a54018c8a9fd9a7be5f5b56414b5da9259f637 diff --git a/SDK/C++/public/include/voltu/defines.hpp b/SDK/C++/public/include/voltu/defines.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4f5ded24e0c89cfdbb5b002d1e2d830c1f90aeb2 --- /dev/null +++ b/SDK/C++/public/include/voltu/defines.hpp @@ -0,0 +1,16 @@ +#pragma once + +#ifndef PY_API +/// include function or method in Python API +#define PY_API +#endif + +#ifndef PY_NO_SHARED_PTR +/// Ownership is not passed with std::shared_ptr<> +#define PY_NO_SHARED_PTR +#endif + +#ifndef PY_RV_LIFETIME_PARENT +/// Lifetime of the return value is tied to the lifetime of a parent object +#define PY_RV_LIFETIME_PARENT +#endif diff --git a/SDK/C++/public/include/voltu/feed.hpp b/SDK/C++/public/include/voltu/feed.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7a58301960f8b065955528d32d309198b85ad96e --- /dev/null +++ b/SDK/C++/public/include/voltu/feed.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "defines.hpp" + +#include <memory> +#include <string> + +namespace voltu +{ + +enum class FeedType +{ + kInvalid = 0, + kMonoCamera = 1, + kStereoCamera = 2, + kDepthCamera = 3, + kVirtual = 4, + kScreen = 5, + kRoom = 6, + kRooms = 7 +}; + +class Feed +{ +public: + PY_API virtual std::string getURI() = 0; + + PY_API virtual void remove() = 0; + + // Get rooms +}; + +typedef std::shared_ptr<voltu::Feed> FeedPtr; + +} diff --git a/SDK/C++/public/include/voltu/initialise.hpp b/SDK/C++/public/include/voltu/initialise.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e44799a67837ad543664b6c81316c82c98f9747e --- /dev/null +++ b/SDK/C++/public/include/voltu/initialise.hpp @@ -0,0 +1,9 @@ +#pragma once +#include <memory> +#include <voltu/system.hpp> +#include "defines.hpp" + +namespace voltu +{ + PY_API std::shared_ptr<voltu::System> instance(); +} diff --git a/SDK/C++/public/include/voltu/observer.hpp b/SDK/C++/public/include/voltu/observer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8f9cad3b74076de36662469829549ddbdd3595bc --- /dev/null +++ b/SDK/C++/public/include/voltu/observer.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "defines.hpp" + +#include <voltu/types/frame.hpp> +#include <voltu/types/property.hpp> +#include <memory> +#include <Eigen/Eigen> + +namespace voltu +{ + +enum class ObserverProperty +{ + kInvalid = 0, + kBackgroundColour = 1001, // String or Int + kPointCloudMode = 1002, // Bool + kProjection = 1003, // Enum or Int + kAntiAliasing = 1004, // Bool + kDepthOnly = 1005, // Bool + kColourSources = 1006, // Bool + kName = 1007, // String +}; + +class Observer +{ +public: + PY_API virtual void setResolution(uint32_t w, uint32_t h) = 0; + + PY_API virtual void setFocalLength(uint32_t f) = 0; + + PY_API virtual void setStereo(bool) = 0; + + PY_API virtual bool waitCompletion(int timeout) = 0; + + PY_API virtual void submit(const voltu::FramePtr&) = 0; + + PY_API virtual void setPose(const Eigen::Matrix4f &) = 0; + + PY_API virtual voltu::FramePtr getFrame() = 0; + + PY_API virtual voltu::PropertyPtr property(voltu::ObserverProperty) = 0; +}; + +typedef std::shared_ptr<Observer> ObserverPtr; + +} diff --git a/SDK/C++/public/include/voltu/opencv.hpp b/SDK/C++/public/include/voltu/opencv.hpp new file mode 100644 index 0000000000000000000000000000000000000000..467a793e8ecff579444b8afede9a1dd740aac05c --- /dev/null +++ b/SDK/C++/public/include/voltu/opencv.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include <opencv2/core/mat.hpp> +#include <opencv2/core/cuda_types.hpp> +#include <voltu/types/image.hpp> + +namespace voltu +{ +namespace cv +{ + +void convert(voltu::ImagePtr img, ::cv::Mat &mat); + +void convert(voltu::ImagePtr img, ::cv::cuda::GpuMat &mat); + +void visualise(voltu::ImagePtr img, ::cv::Mat &mat); + +} +} \ No newline at end of file diff --git a/SDK/C++/public/include/voltu/room.hpp b/SDK/C++/public/include/voltu/room.hpp new file mode 100644 index 0000000000000000000000000000000000000000..da09776227d4e25aeef9f763a0f26270941e090b --- /dev/null +++ b/SDK/C++/public/include/voltu/room.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "defines.hpp" +#include <voltu/types/frame.hpp> +#include <memory> + +namespace voltu +{ + +enum class RoomType +{ + kInvalid = 0, + kPhysical = 1, + kComposite = 2 +}; + +typedef unsigned int RoomId; + +class Room +{ +public: + PY_API virtual bool waitNextFrame(int64_t) = 0; + + PY_API inline bool hasNextFrame() { return waitNextFrame(0); }; + + PY_API virtual voltu::FramePtr getFrame() = 0; + + PY_API virtual std::string getName() = 0; + + PY_API virtual bool active() = 0; +}; + +typedef std::shared_ptr<Room> RoomPtr; + +} diff --git a/SDK/C++/public/include/voltu/source.hpp b/SDK/C++/public/include/voltu/source.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2009a607a477381f9b00395a5cf789b5de1218fb --- /dev/null +++ b/SDK/C++/public/include/voltu/source.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include <memory> + +namespace voltu +{ + +class Source +{ +public: +}; + +typedef std::shared_ptr<Source> SourcePtr; + +} diff --git a/SDK/C++/public/include/voltu/system.hpp b/SDK/C++/public/include/voltu/system.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cf927eeba5cb61615ab598a1af61da9aaec2a997 --- /dev/null +++ b/SDK/C++/public/include/voltu/system.hpp @@ -0,0 +1,35 @@ +#pragma once +#include "defines.hpp" + +#include <voltu/room.hpp> +#include <voltu/observer.hpp> +#include <voltu/feed.hpp> +#include <list> + +namespace voltu +{ + +struct Version +{ + int major; + int minor; + int patch; +}; + +class System +{ +public: + virtual voltu::Version getVersion() const = 0; + + PY_API virtual voltu::RoomPtr createRoom() = 0; + + PY_API virtual voltu::ObserverPtr createObserver() = 0; + + PY_API virtual voltu::FeedPtr open(const std::string&) = 0; + + PY_API virtual std::list<voltu::RoomId> listRooms() = 0; + + PY_API virtual voltu::RoomPtr getRoom(voltu::RoomId) = 0; +}; + +} diff --git a/SDK/C++/public/include/voltu/types/channel.hpp b/SDK/C++/public/include/voltu/types/channel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..07b7c9abadf3caaa36e1fcd8d01f08d496d1a5b0 --- /dev/null +++ b/SDK/C++/public/include/voltu/types/channel.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace voltu { + enum class Channel { + kInvalid = 0, + kColour = 1, + kDepth = 2, + kNormals = 3 + }; +} diff --git a/SDK/C++/public/include/voltu/types/errors.hpp b/SDK/C++/public/include/voltu/types/errors.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9426428014094370c26160ab2f4ec2c893205a97 --- /dev/null +++ b/SDK/C++/public/include/voltu/types/errors.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include <exception> + +namespace voltu +{ +namespace exceptions +{ + +struct Exception : public std::exception +{ + virtual const char* what() const noexcept { return "VolTu General Error"; } +}; + +} +} + +#define VOLTU_EXCEPTION(NAME,BASE,MSG) struct NAME : public voltu::exceptions::BASE { virtual const char* what() const noexcept { return MSG; } }; + +namespace voltu +{ +namespace exceptions +{ + +VOLTU_EXCEPTION(BadImageChannel, Exception, "Invalid image channel"); +VOLTU_EXCEPTION(NoFrame, Exception, "No frame available"); +VOLTU_EXCEPTION(AlreadyInit, Exception, "VolTu already initialised"); +VOLTU_EXCEPTION(LibraryLoadFailed, Exception, "Could not load VolTu library"); +VOLTU_EXCEPTION(LibraryVersionMismatch, Exception, "Wrong version of library found"); +VOLTU_EXCEPTION(BadSourceURI, Exception, "Bad source URI"); +VOLTU_EXCEPTION(InvalidFrameObject, Exception, "Invalid Frame object"); +VOLTU_EXCEPTION(InternalRenderError, Exception, "Internal renderer error"); +VOLTU_EXCEPTION(InvalidProperty, Exception, "Unknown property enum"); +VOLTU_EXCEPTION(BadParameterValue, Exception, "Method parameter is not valid"); + +} +} diff --git a/SDK/C++/public/include/voltu/types/frame.hpp b/SDK/C++/public/include/voltu/types/frame.hpp new file mode 100644 index 0000000000000000000000000000000000000000..baea806fc9db7d32c43f121bee7901d949378afc --- /dev/null +++ b/SDK/C++/public/include/voltu/types/frame.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "../defines.hpp" + +#include <voltu/types/channel.hpp> +#include <voltu/types/image.hpp> +#include <voltu/types/pointcloud.hpp> +#include <voltu/types/intrinsics.hpp> +#include <list> +#include <memory> + +namespace voltu +{ + +class Frame +{ +public: + PY_API PY_RV_LIFETIME_PARENT virtual std::list<voltu::ImagePtr> getImageSet(voltu::Channel channel) = 0; + + PY_API PY_RV_LIFETIME_PARENT virtual voltu::PointCloudPtr getPointCloud(voltu::PointCloudFormat cloudfmt, voltu::PointFormat pointfmt) = 0; + + virtual int64_t getTimestamp() = 0; +}; + +typedef std::shared_ptr<Frame> FramePtr; + +} diff --git a/SDK/C++/public/include/voltu/types/image.hpp b/SDK/C++/public/include/voltu/types/image.hpp new file mode 100644 index 0000000000000000000000000000000000000000..76d5ec5f099223e36d49cdc6dce4f9435d64d8aa --- /dev/null +++ b/SDK/C++/public/include/voltu/types/image.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "../defines.hpp" + +#include <voltu/types/channel.hpp> +#include <voltu/types/intrinsics.hpp> +#include <memory> + +#include <Eigen/Eigen> + +namespace voltu +{ + +enum class ImageFormat +{ + kInvalid = 0, + kFloat32 = 1, + kBGRA8 = 2 +}; + +PY_NO_SHARED_PTR struct ImageData +{ + ImageFormat format = ImageFormat::kInvalid; + unsigned char* data = nullptr; + unsigned int pitch = 0; + unsigned int width = 0; + unsigned int height = 0; +}; + +class Image +{ +public: + PY_API PY_RV_LIFETIME_PARENT virtual ImageData getHost() = 0; + + virtual ImageData getDevice() = 0; + + virtual bool isDevice() = 0; + + PY_API virtual voltu::Channel getChannel() = 0; + + PY_API virtual std::string getName() = 0; + + PY_API virtual voltu::Intrinsics getIntrinsics() = 0; + + PY_API virtual Eigen::Matrix4d getPose() = 0; + + PY_API virtual voltu::StereoIntrinsics getStereoIntrinsics() = 0; + + PY_API virtual int64_t getTimestamp() = 0; + + //virtual voltu::RoomId getRoomId() = 0; + + PY_API virtual int getCameraNumber() = 0; + + PY_API virtual uint32_t getUniqueId() = 0; +}; + +typedef std::shared_ptr<Image> ImagePtr; + +} diff --git a/SDK/C++/public/include/voltu/types/intrinsics.hpp b/SDK/C++/public/include/voltu/types/intrinsics.hpp new file mode 100644 index 0000000000000000000000000000000000000000..70d1dfe2c2f3ba6f8138e9c4dbb1ac618ae25187 --- /dev/null +++ b/SDK/C++/public/include/voltu/types/intrinsics.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace voltu +{ + +struct Intrinsics +{ + unsigned int width; + unsigned int height; + float principle_x; + float principle_y; + float focal_x; + float focal_y; +}; + +struct StereoIntrinsics : public Intrinsics +{ + float baseline; + float doffs; + float min_depth; + float max_depth; +}; + +} diff --git a/SDK/C++/public/include/voltu/types/pointcloud.hpp b/SDK/C++/public/include/voltu/types/pointcloud.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c602a4f6a007b2b2b123bc263f766665e35b8af7 --- /dev/null +++ b/SDK/C++/public/include/voltu/types/pointcloud.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include <memory> + +namespace voltu +{ + +enum class PointCloudFormat +{ + kInvalid = 0, + kUnstructured = 1 +}; + +enum class PointFormat +{ + kInvalid = 0, + kXYZ_Float = 1, + kXYZRGB_Float = 2, + kXYZ_NxNyNz_Float = 3, + kXYZ_NxNyNz_RGB_Float = 4, + kXYZ_Float_RGB_Int8 = 5, + kXYZ_NxNyNz_Float_RGB_Int8 = 6 +}; + +struct PointCloudData +{ + PointCloudFormat format = PointCloudFormat::kInvalid; + unsigned char* data = nullptr; + unsigned int element_size; + size_t size; +}; + +class PointCloud +{ +public: + virtual PointCloudData getHost() = 0; + + virtual PointCloudData getDevice() = 0; +}; + +typedef std::shared_ptr<PointCloud> PointCloudPtr; + +} diff --git a/SDK/C++/public/include/voltu/types/property.hpp b/SDK/C++/public/include/voltu/types/property.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2fa3c43b44ab95503b1cca587ef4aab8cd1cf66b --- /dev/null +++ b/SDK/C++/public/include/voltu/types/property.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "../defines.hpp" +#include <memory> + +namespace voltu +{ + +class Property +{ +public: + PY_API virtual void setInt(int) = 0; + + PY_API virtual void setFloat(float) = 0; + + PY_API virtual void setString(const std::string &) = 0; + + PY_API virtual void setBool(bool) = 0; + + template <typename T> + inline void setEnum(T e) { setInt(static_cast<int>(e)); } + + PY_API virtual int getInt() = 0; + + PY_API virtual float getFloat() = 0; + + PY_API virtual std::string getString() = 0; + + PY_API virtual bool getBool() = 0; + + template <typename T> + inline T getEnum() { return static_cast<int>(getInt()); } +}; + +typedef std::shared_ptr<Property> PropertyPtr; + +} diff --git a/SDK/C++/public/include/voltu/voltu.hpp b/SDK/C++/public/include/voltu/voltu.hpp new file mode 100644 index 0000000000000000000000000000000000000000..acb2836f45b5c643465bf20a999795555d8225de --- /dev/null +++ b/SDK/C++/public/include/voltu/voltu.hpp @@ -0,0 +1,11 @@ +#pragma once + +// Bump these for each release +#define VOLTU_VERSION_MAJOR 0 // For API incompatible changes +#define VOLTU_VERSION_MINOR 1 // For binary compatibility and extensions +#define VOLTU_VERSION_PATCH 0 // Binary compatible internal fixes + +#define VOLTU_VERSION ((VOLTU_VERSION_MAJOR*10000) + (VOLTU_VERSION_MINOR*100) + VOLTU_VERSION_PATCH) + +#include <voltu/system.hpp> +#include <voltu/initialise.hpp> diff --git a/SDK/C++/public/python/CMakeLists.txt b/SDK/C++/public/python/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..20c4ce7c6697e30ffce941c74efbdf71f2f060e7 --- /dev/null +++ b/SDK/C++/public/python/CMakeLists.txt @@ -0,0 +1,33 @@ +set(SDK_AUTO_HEADERS + voltu/types/channel.hpp + voltu/types/frame.hpp + voltu/types/image.hpp + voltu/types/intrinsics.hpp + voltu/observer.hpp + voltu/feed.hpp + voltu/initialise.hpp + voltu/room.hpp + voltu/source.hpp + voltu/system.hpp +) + +add_custom_command( + OUTPUT automatic_bindings.cpp + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen.py + automatic_bindings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../include + ${SDK_AUTO_HEADERS} + + DEPENDS voltu_sdk +) + +pybind11_add_module(voltu_sdk_py MODULE + automatic_bindings.cpp + module.cpp +) + +target_include_directories(voltu_sdk_py PUBLIC include) +target_include_directories(voltu_sdk_py PRIVATE .) + +target_link_libraries(voltu_sdk_py PUBLIC voltu_sdk) +set_target_properties(voltu_sdk_py PROPERTIES OUTPUT_NAME voltu) diff --git a/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py b/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py new file mode 100644 index 0000000000000000000000000000000000000000..af29b45878272ad88a1b03fec08b126d3d41ff8b --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/CppHeaderParser.py @@ -0,0 +1,3715 @@ +#!/usr/bin/python +# +# Author: Jashua R. Cloutier (contact via https://bitbucket.org/senex) +# Project: http://senexcanis.com/open-source/cppheaderparser/ +# +# Copyright (C) 2011, Jashua R. Cloutier +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of Jashua R. Cloutier nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. Stories, +# blog entries etc making reference to this project may mention the +# name Jashua R. Cloutier in terms of project originator/creator etc. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# +# The CppHeaderParser.py script is written in Python 2.4 and released to +# the open source community for continuous improvements under the BSD +# 2.0 new license, which can be found at: +# +# http://www.opensource.org/licenses/bsd-license.php +# + +from __future__ import print_function + + +from collections import deque +import os +import sys +import re +import io + +import inspect + +from .lexer import Lexer +from .doxygen import extract_doxygen_method_params + +try: + from .version import __version__ +except ImportError: + __version__ = "devel" + +version = __version__ + +# Controls error_print +print_errors = 1 +# Controls warning_print +print_warnings = 1 +# Controls debug_print +debug = 1 if os.environ.get("CPPHEADERPARSER_DEBUG") == "1" else 0 +# Controls trace_print +debug_trace = 0 + +if sys.version_info >= (3, 3): + # `raise e from src_e` syntax only supported on python 3.3+ + exec("def raise_exc(e, src_e): raise e from src_e", globals()) +else: + + def raise_exc(e, src_e): + raise e + + +def error_print(fmt, *args): + if print_errors: + fmt = "[%4d] " + fmt + args = (inspect.currentframe().f_back.f_lineno,) + args + print(fmt % args) + + +def warning_print(fmt, *args): + if print_warnings: + fmt = "[%4d] " + fmt + args = (inspect.currentframe().f_back.f_lineno,) + args + print(fmt % args) + + +if debug: + + def debug_print(fmt, *args): + fmt = "[%4d] " + fmt + args = (inspect.currentframe().f_back.f_lineno,) + args + print(fmt % args) + + +else: + + def debug_print(fmt, *args): + pass + + +if debug_trace: + + def trace_print(*args): + sys.stdout.write("[%s] " % (inspect.currentframe().f_back.f_lineno)) + for a in args: + sys.stdout.write("%s " % a) + sys.stdout.write("\n") + + +else: + + def trace_print(*args): + pass + + +#: Access specifiers +supportedAccessSpecifier = ["public", "protected", "private"] + +#: Symbols to ignore, usually special macros +ignoreSymbols = ["Q_OBJECT"] + + +# Track what was added in what order and at what depth +parseHistory = [] + + +def is_namespace(nameStack): + """Determines if a namespace is being specified""" + if len(nameStack) == 0: + return False + if nameStack[0] == "namespace": + return True + return False + + +_fundamentals = { + "size_t", + "struct", + "union", + "unsigned", + "signed", + "bool", + "char", + "short", + "int", + "float", + "double", + "long", + "void", + "*", +} + + +def is_fundamental(s): + for a in s.split(): + if a not in _fundamentals: + return False + return True + + +def is_function_pointer_stack(stack): + """Count how many non-nested paranthesis are in the stack. Useful for determining if a stack is a function pointer""" + paren_depth = 0 + paren_count = 0 + star_after_first_paren = False + last_e = None + for e in stack: + if e == "(": + paren_depth += 1 + elif e == ")" and paren_depth > 0: + paren_depth -= 1 + if paren_depth == 0: + paren_count += 1 + elif e == "*" and last_e == "(" and paren_count == 0 and paren_depth == 1: + star_after_first_paren = True + last_e = e + + if star_after_first_paren and paren_count == 2: + return True + else: + return False + + +def is_method_namestack(stack): + r = False + if "(" not in stack: + r = False + elif stack[0] == "typedef": + r = False # TODO deal with typedef function prototypes + # elif '=' in stack and stack.index('=') < stack.index('(') and stack[stack.index('=')-1] != 'operator': r = False #disabled July6th - allow all operators + elif "operator" in stack: + r = True # allow all operators + elif "{" in stack and stack.index("{") < stack.index("("): + r = False # struct that looks like a method/class + elif "(" in stack and ")" in stack: + if stack[-1] == ":": + r = True + elif "{" in stack and "}" in stack: + r = True + elif stack[-1] == ";": + if is_function_pointer_stack(stack): + r = False + else: + r = True + elif "{" in stack: + r = True # ideally we catch both braces... TODO + else: + r = False + # Test for case of property set to something with parens such as "static const int CONST_A = (1 << 7) - 1;" + if r and "(" in stack and "=" in stack and "operator" not in stack: + if stack.index("=") < stack.index("("): + r = False + return r + + +def is_property_namestack(nameStack): + r = False + if "(" not in nameStack and ")" not in nameStack: + r = True + elif ( + "(" in nameStack + and "=" in nameStack + and nameStack.index("=") < nameStack.index("(") + ): + r = True + # See if we are a function pointer + if not r and is_function_pointer_stack(nameStack): + r = True + return r + + +def is_enum_namestack(nameStack): + """Determines if a namestack is an enum namestack""" + if not nameStack: + return False + if nameStack[0] == "enum": + return True + if len(nameStack) > 1 and nameStack[0] == "typedef" and nameStack[1] == "enum": + return True + return False + + +def set_location_info(thing, location): + filename, line_number = location + if filename: + thing["filename"] = filename + thing["line_number"] = line_number + + +_nhack = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") + + +def _split_namespace(namestack): + """ + Given a list of name elements, find the namespace portion + and return that as a string + + :rtype: Tuple[str, list] + """ + # TODO: this should be using tokens instead of nhack + + last_colon = None + for i, n in enumerate(namestack): + if n == "::": + last_colon = i + if i and n != "::" and not _nhack.match(n): + break + + if last_colon: + ns, namestack = ( + "".join(namestack[: last_colon + 1]), + namestack[last_colon + 1 :], + ) + else: + ns = "" + + return ns, namestack + + +def _iter_ns_str_reversed(namespace): + """ + Take a namespace string, and yield successively smaller portions + of it (ex foo::bar::baz::, foo::bar::, foo::). The last item yielded + will always be an empty string + """ + if namespace: + parts = namespace.split("::") + for i in range(len(parts) - 1, 0, -1): + yield "::".join(parts[:i]) + "::" + + yield "" + + +def _split_by_comma(namestack): + while namestack: + if "," not in namestack: + yield namestack + break + idx = namestack.index(",") + ns = namestack[:idx] + yield ns + namestack = namestack[idx + 1 :] + + +class TagStr(str): + """Wrapper for a string that allows us to store the line number associated with it""" + + def __new__(cls, *args, **kwargs): + location = kwargs.pop("location") + s = str.__new__(cls, *args, **kwargs) + s.location = location + return s + + +class CppParseError(Exception): + def __init__(self, msg, tok=None): + Exception.__init__(self, msg) + self.tok = tok + + +class CppTemplateParam(dict): + """ + Dictionary that contains the following: + + - ``decltype`` - If True, this is a decltype + - ``param`` - Parameter value or typename + - ``params`` - Only present if a template specialization. Is a list of + :class:`.CppTemplateParam` + - ``...`` - If True, indicates a parameter pack + """ + + def __init__(self): + self["param"] = "" + self["decltype"] = False + self["..."] = False + + def __str__(self): + s = [] + if self["decltype"]: + s.append("decltype") + + s.append(self["param"]) + + params = self.get("params") + if params is not None: + s.append("<%s>" % ",".join(str(p) for p in params)) + + if self["..."]: + if s: + s[-1] = s[-1] + "..." + else: + s.append("...") + + return "".join(s) + + +class CppBaseDecl(dict): + """ + Dictionary that contains the following + + - ``access`` - Anything in supportedAccessSpecifier + - ``class`` - Name of the type, along with template specializations + - ``decl_name`` - Name of the type only + - ``decl_params`` - Only present if a template specialization (Foo<int>). + Is a list of :class:`.CppTemplateParam`. + - ``decltype`` - True/False indicates a decltype, the contents are in + ``decl_name`` + - ``virtual`` - True/False indicates virtual inheritance + - ``...`` - True/False indicates a parameter pack + + """ + + def __init__(self, default_access): + self["access"] = default_access + self["class"] = "" + self["decl_name"] = "" + self["decltype"] = False + self["virtual"] = False + self["..."] = False + + def _fix_classname(self): + # set class to the full decl for legacy reasons + params = self.get("decl_params") + + if self["decltype"]: + s = "decltype" + else: + s = "" + + s += self["decl_name"] + if params: + s += "<%s>" % (",".join(str(p) for p in params)) + + if self["..."]: + s += "..." + + return s + + +def _consume_parens(stack): + i = 0 + sl = len(stack) + nested = 1 + while i < sl: + t = stack[i] + i += 1 + if t == ")": + nested -= 1 + if nested == 0: + return i + elif t == "(": + nested += 1 + + raise CppParseError("Unmatched (") + + +def _parse_template_decl(stack): + debug_print("_parse_template_decl: %s", stack) + params = [] + param = CppTemplateParam() + i = 0 + sl = len(stack) + init = True + require_ending = False + while i < sl: + t = stack[i] + i += 1 + if init: + init = False + if t == "decltype": + param["decltype"] = True + continue + + if t == ",": + params.append(param) + init = True + require_ending = False + param = CppTemplateParam() + + continue + elif t == ">": + params.append(param) + return params, i - 1 + + if require_ending: + raise CppParseError("expected comma, found %s" % t) + + if t == "...": + param["..."] = True + require_ending = True + elif t == "(": + s = stack[i:] + n = _consume_parens(s) + i += n + param["param"] = param["param"] + "".join(s[:n]) + else: + if t and t[0] == "<": + param["params"], n = _parse_template_decl([t[1:]] + stack[i:]) + i += n + else: + param["param"] = param["param"] + t + + raise CppParseError("Unmatched <") + + +def _parse_cppclass_name(c, stack): + # ignore first thing + i = 1 + sl = len(stack) + name = "" + require_ending = False + while i < sl: + t = stack[i] + i += 1 + + if t == ":": + if i >= sl: + raise CppParseError("class decl ended with ':'") + break + elif t == "::": + name += "::" + continue + elif t == "final": + c["final"] = True + continue + + if require_ending: + raise CppParseError("Unexpected '%s' in class" % ("".join(stack[i - 1 :]))) + + if t and t[0] == "<": + c["class_params"], n = _parse_template_decl([t[1:]] + stack[i:]) + i += n + require_ending = True + else: + name += t + + c["namespace"] = "" + + # backwards compat + if name.startswith("anon-"): + if ( + name.startswith("anon-class-") + or name.startswith("anon-struct-") + or name.startswith("anon-union-") + ): + name = "<" + name + ">" + c["name"] = name + c["bare_name"] = name + debug_print("Found class '%s'", name) + + # backwards compat + classParams = c.get("class_params") + if classParams is not None: + c["name"] = c["name"] + "<%s>" % ",".join(str(p) for p in classParams) + + return i + + +def _parse_cpp_base(stack, default_access): + debug_print("Parsing base: %s (access %s)", stack, default_access) + inherits = [] + i = 0 + sl = len(stack) + init = True + base = CppBaseDecl(default_access) + require_ending = False + while i < sl: + t = stack[i] + i += 1 + + if init: + if t in supportedAccessSpecifier: + base["access"] = t + continue + elif t == "virtual": + base["virtual"] = True + continue + + init = False + + if t == "decltype": + base["decltype"] = True + continue + + if t == ",": + inherits.append(base) + base = CppBaseDecl(default_access) + init = True + require_ending = False + continue + + if require_ending: + if t == "::": + if "decl_params" in base: + base["decl_name"] = base._fix_classname() + del base["decl_params"] + base["..."] + require_ending = False + else: + raise CppParseError("expected comma, found '%s'" % t) + + if t == "(": + s = stack[i:] + n = _consume_parens(s) + i += n + base["decl_name"] = base["decl_name"] + "".join(s[:n]) + elif t == "...": + base["..."] = True + require_ending = True + else: + if t[0] == "<": + base["decl_params"], n = _parse_template_decl([t[1:]] + stack[i:]) + i += n + require_ending = True + else: + base["decl_name"] = base["decl_name"] + t + + # backwards compat + inherits.append(base) + for base in inherits: + base["class"] = base._fix_classname() + + return inherits + + +class CppClass(dict): + """ + Dictionary that contains at least the following keys: + + * ``name`` - Name of the class + * ``doxygen`` - Doxygen comments associated with the class if they exist + * ``inherits`` - List of Classes that this one inherits. Values are + :class:`.CppBaseDecl` + * ``methods`` - Dictionary where keys are from supportedAccessSpecifier + and values are a lists of :class:`.CppMethod` + * ``namespace`` - Namespace of class + * ``properties`` - Dictionary where keys are from supportedAccessSpecifier + and values are lists of :class:`.CppVariable` + * ``enums`` - Dictionary where keys are from supportedAccessSpecifier and + values are lists of :class:`.CppEnum` + * ``nested_classes`` - Classes and structs defined within this class + * ``final`` - True if final + * ``abstract`` - True if abstract + * ``using`` - Using directives in this class scope: key is name for lookup, + value is :class:`.CppVariable` + * ``parent`` - If not None, the class that this class is nested in + + An example of how this could look is as follows:: + + { + 'name': "" + 'inherits':[] + 'methods': + { + 'public':[], + 'protected':[], + 'private':[] + }, + 'properties': + { + 'public':[], + 'protected':[], + 'private':[] + }, + 'enums': + { + 'public':[], + 'protected':[], + 'private':[] + } + } + """ + + def get_all_methods(self): + r = [] + for typ in supportedAccessSpecifier: + r += self["methods"][typ] + return r + + def get_all_method_names(self): + r = [] + for typ in supportedAccessSpecifier: + r += self.get_method_names(typ) # returns list + return r + + def get_all_pure_virtual_methods(self): + r = {} + for typ in supportedAccessSpecifier: + r.update(self.get_pure_virtual_methods(typ)) # returns dict + return r + + def get_method_names(self, type="public"): + return [meth["name"] for meth in self["methods"][type]] + + def get_pure_virtual_methods(self, type="public"): + r = {} + for meth in self["methods"][type]: + if meth["pure_virtual"]: + r[meth["name"]] = meth + return r + + def _lookup_type(self, name): + # TODO: should have indexes for these lookups... and they + # should be more unified + for access in supportedAccessSpecifier: + for e in self["enums"][access]: + if e.get("name") == name: + return { + "enum": self["name"] + "::" + e["name"], + "type": e["name"], + "namespace": e["namespace"], + } + for n in self["nested_classes"]: + if n["name"] == name: + return {"raw_type": self["name"] + "::" + n["name"], "type": n["name"]} + + def __init__(self, nameStack, curTemplate, doxygen, location, defaultAccess): + self["nested_classes"] = [] + self["parent"] = None + self["abstract"] = False + self["final"] = False + self._public_enums = {} + self._public_typedefs = {} + self._public_forward_declares = [] + self["namespace"] = "" + self["using"] = {} + + debug_print("Class: %s", nameStack) + debug_print("Template: %s", curTemplate) + + if len(nameStack) < 2: + nameStack.insert(1, "") # anonymous struct + if doxygen: + self["doxygen"] = doxygen + + # consume name of class, with any namespaces or templates + n = _parse_cppclass_name(self, nameStack) + + # consume bases + baseStack = nameStack[n:] + if baseStack: + self["inherits"] = _parse_cpp_base(baseStack, defaultAccess) + else: + self["inherits"] = [] + + set_location_info(self, location) + + if curTemplate: + self["template"] = curTemplate + trace_print("Setting template to '%s'" % self["template"]) + + methodAccessSpecificList = {} + propertyAccessSpecificList = {} + enumAccessSpecificList = {} + typedefAccessSpecificList = {} + forwardAccessSpecificList = {} + + for accessSpecifier in supportedAccessSpecifier: + methodAccessSpecificList[accessSpecifier] = [] + propertyAccessSpecificList[accessSpecifier] = [] + enumAccessSpecificList[accessSpecifier] = [] + typedefAccessSpecificList[accessSpecifier] = [] + forwardAccessSpecificList[accessSpecifier] = [] + + self["methods"] = methodAccessSpecificList + self["properties"] = propertyAccessSpecificList + self["enums"] = enumAccessSpecificList + self["typedefs"] = typedefAccessSpecificList + self["forward_declares"] = forwardAccessSpecificList + + def show(self): + """Convert class to a string""" + namespace_prefix = "" + if self["namespace"]: + namespace_prefix = self["namespace"] + "::" + rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"]) + if self["final"]: + rtn += " final" + if self["abstract"]: + rtn += " (abstract)\n" + else: + rtn += "\n" + + if "doxygen" in list(self.keys()): + rtn += self["doxygen"] + "\n" + if "parent" in list(self.keys()) and self["parent"]: + rtn += "parent class: " + self["parent"]["name"] + "\n" + + if "inherits" in list(self.keys()): + rtn += " Inherits: " + for inheritClass in self["inherits"]: + if inheritClass["virtual"]: + rtn += "virtual " + rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"]) + rtn += "\n" + rtn += " {\n" + for accessSpecifier in supportedAccessSpecifier: + rtn += " %s\n" % (accessSpecifier) + # Enums + if len(self["enums"][accessSpecifier]): + rtn += " <Enums>\n" + for enum in self["enums"][accessSpecifier]: + rtn += " %s\n" % (repr(enum)) + # Properties + if len(self["properties"][accessSpecifier]): + rtn += " <Properties>\n" + for property in self["properties"][accessSpecifier]: + rtn += " %s\n" % (repr(property)) + # Methods + if len(self["methods"][accessSpecifier]): + rtn += " <Methods>\n" + for method in self["methods"][accessSpecifier]: + rtn += "\t\t" + method.show() + "\n" + rtn += " }\n" + print(rtn) + + def __str__(self): + """Convert class to a string""" + namespace_prefix = "" + if self["namespace"]: + namespace_prefix = self["namespace"] + "::" + rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"]) + if self["final"]: + rtn += " final" + if self["abstract"]: + rtn += " (abstract)\n" + else: + rtn += "\n" + + if "doxygen" in list(self.keys()): + rtn += self["doxygen"] + "\n" + if "parent" in list(self.keys()) and self["parent"]: + rtn += "parent class: " + self["parent"]["name"] + "\n" + + if "inherits" in list(self.keys()) and len(self["inherits"]): + rtn += "Inherits: " + for inheritClass in self["inherits"]: + if inheritClass.get("virtual", False): + rtn += "virtual " + rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"]) + rtn += "\n" + rtn += "{\n" + for accessSpecifier in supportedAccessSpecifier: + rtn += "%s\n" % (accessSpecifier) + # Enums + if len(self["enums"][accessSpecifier]): + rtn += " // Enums\n" + for enum in self["enums"][accessSpecifier]: + rtn += " %s\n" % (repr(enum)) + # Properties + if len(self["properties"][accessSpecifier]): + rtn += " // Properties\n" + for property in self["properties"][accessSpecifier]: + rtn += " %s\n" % (repr(property)) + # Methods + if len(self["methods"][accessSpecifier]): + rtn += " // Methods\n" + for method in self["methods"][accessSpecifier]: + rtn += " %s\n" % (repr(method)) + rtn += "}\n" + return rtn + + +class CppUnion(CppClass): + """ + Dictionary that contains at least the following keys: + + * ``name`` - Name of the union + * ``doxygen`` - Doxygen comments associated with the union if they exist + * ``members`` - List of members of the union + """ + + def __init__(self, nameStack, doxygen, location): + CppClass.__init__(self, nameStack, None, doxygen, location, "public") + self["members"] = self["properties"]["public"] + + def transform_to_union_keys(self): + print("union keys: %s" % list(self.keys())) + for key in [ + "inherits", + "parent", + "abstract", + "namespace", + "typedefs", + "methods", + ]: + del self[key] + + def show(self): + """Convert class to a string""" + print(self) + + def __str__(self): + """Convert class to a string""" + namespace_prefix = "" + if self["namespace"]: + namespace_prefix = self["namespace"] + "::" + rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"]) + if self["abstract"]: + rtn += " (abstract)\n" + else: + rtn += "\n" + + if "doxygen" in list(self.keys()): + rtn += self["doxygen"] + "\n" + if "parent" in list(self.keys()) and self["parent"]: + rtn += "parent class: " + self["parent"]["name"] + "\n" + + rtn += "{\n" + for member in self["members"]: + rtn += " %s\n" % (repr(member)) + rtn += "}\n" + return rtn + + +class _CppMethod(dict): + def _params_helper1(self, stack): + # deal with "throw" keyword + if "throw" in stack: + stack = stack[: stack.index("throw")] + + ## remove GCC keyword __attribute__(...) and preserve returns ## + cleaned = [] + hit = False + hitOpen = 0 + hitClose = 0 + for a in stack: + if a == "__attribute__": + hit = True + if hit: + if a == "(": + hitOpen += 1 + elif a == ")": + hitClose += 1 + if a == ")" and hitOpen == hitClose: + hit = False + else: + cleaned.append(a) + stack = cleaned + + # also deal with attribute((const)) function prefix # + # TODO this needs to be better # + if len(stack) > 5: + a = "".join(stack) + if a.startswith("((__const__))"): + stack = stack[5:] + elif a.startswith("__attribute__((__const__))"): + stack = stack[6:] + + stack = stack[stack.index("(") + 1 :] + if not stack: + return [] + if ( + len(stack) >= 3 and stack[0] == ")" and stack[1] == ":" + ): # is this always a constructor? + self["constructor"] = True + return [] + + stack.reverse() + _end_ = stack.index(")") + stack.reverse() + stack = stack[: len(stack) - (_end_ + 1)] + if "(" not in stack: + return stack # safe to return, no defaults that init a class + + return stack + + def _params_helper2(self, params): + for p in params: + p["method"] = self # save reference in variable to parent method + p["parent"] = self + if "::" in p["type"]: + ns = p["type"].split("::")[0] + if ns not in Resolver.NAMESPACES and ns in Resolver.CLASSES: + p["type"] = self["namespace"] + p["type"] + else: + p["namespace"] = self["namespace"] + + +class CppMethod(_CppMethod): + """ + Dictionary that contains at least the following keys: + + * ``rtnType`` - Return type of the method (ex. "int") + * ``name`` - Name of the method + * ``doxygen`` - Doxygen comments associated with the method if they exist + * ``parameters`` - List of :class:`.CppVariable` + * ``parent`` - If not None, the class this method belongs to + """ + + def show(self): + r = ["method name: %s (%s)" % (self["name"], self["debug"])] + if self["returns"]: + r.append("returns: %s" % self["returns"]) + if self["parameters"]: + r.append("number arguments: %s" % len(self["parameters"])) + if self["pure_virtual"]: + r.append("pure virtual: %s" % self["pure_virtual"]) + if self["constructor"]: + r.append("constructor") + if self["destructor"]: + r.append("destructor") + return "\n\t\t ".join(r) + + def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location): + debug_print("Method: %s", nameStack) + debug_print("Template: %s", curTemplate) + + if doxygen: + self["doxygen"] = doxygen + + # Remove leading keywords + for i, word in enumerate(nameStack): + if word not in Resolver.C_KEYWORDS: + nameStack = nameStack[i:] + break + + if "operator" in nameStack: + rtnType = " ".join(nameStack[: nameStack.index("operator")]) + self["name"] = "".join( + nameStack[nameStack.index("operator") : nameStack.index("(")] + ) + else: + rtnType = " ".join(nameStack[: nameStack.index("(") - 1]) + self["name"] = " ".join( + nameStack[nameStack.index("(") - 1 : nameStack.index("(")] + ) + + if len(rtnType) == 0 or self["name"] == curClass: + rtnType = "void" + + self["rtnType"] = ( + rtnType.replace(" :: ", "::") + .replace(" < ", "<") + .replace(" > ", "> ") + .replace(">>", "> >") + .replace(" ,", ",") + ) + + # deal with "noexcept" specifier/operator + self["noexcept"] = None + if "noexcept" in nameStack: + noexcept_idx = nameStack.index("noexcept") + hit = True + cleaned = nameStack[:noexcept_idx] + parentCount = 0 + noexcept = "noexcept" + for a in nameStack[noexcept_idx + 1 :]: + if a == "noexcept": + hit = True + if hit: + if a == "(": + parentCount += 1 + elif a == ")": + parentCount -= 1 + elif parentCount == 0 and a != "noexcept": + hit = False + cleaned.append(a) + continue # noexcept without parenthesis + if a == ")" and parentCount == 0: + hit = False + noexcept += a + else: + cleaned.append(a) + self["noexcept"] = noexcept + nameStack = cleaned + + for spec in ["const", "final", "override"]: + self[spec] = False + for i in reversed(nameStack): + if i == spec: + self[spec] = True + break + elif i == ")": + break + + self.update(methinfo) + set_location_info(self, location) + + paramsStack = self._params_helper1(nameStack) + + debug_print("curTemplate: %s", curTemplate) + if curTemplate: + self["template"] = curTemplate + debug_print("SET self['template'] to `%s`", self["template"]) + + params = [] + # See if there is a doxygen comment for the variable + if "doxygen" in self: + doxyVarDesc = extract_doxygen_method_params(self["doxygen"]) + else: + doxyVarDesc = {} + + # non-vararg by default + self["vararg"] = False + # Create the variable now + while len(paramsStack): + # Find commas that are not nexted in <>'s like template types + open_template_count = 0 + open_paren_count = 0 + open_brace_count = 0 + param_separator = 0 + i = 0 + for elm in paramsStack: + if elm in "<>(){},": + if elm == ",": + if ( + open_template_count == 0 + and open_paren_count == 0 + and open_brace_count == 0 + ): + param_separator = i + break + elif "<" == elm: + open_template_count += 1 + elif ">" == elm: + open_template_count -= 1 + elif "(" == elm: + open_paren_count += 1 + elif ")" == elm: + open_paren_count -= 1 + elif "{" == elm: + open_brace_count += 1 + elif "}" == elm: + open_brace_count -= 1 + i += 1 + + if param_separator: + tpstack = paramsStack[0:param_separator] + param = CppVariable( + tpstack, + None, + getattr(tpstack[0], "location", location), + doxyVarDesc=doxyVarDesc, + ) + if len(list(param.keys())): + params.append(param) + paramsStack = paramsStack[param_separator + 1 :] + elif len(paramsStack) and paramsStack[0] == "...": + self["vararg"] = True + paramsStack = paramsStack[1:] + else: + param = CppVariable( + paramsStack, + None, + getattr(paramsStack[0], "location", location), + doxyVarDesc=doxyVarDesc, + ) + if len(list(param.keys())): + params.append(param) + break + + # foo(void) should be zero parameters + if len(params) == 1 and params[0]["type"] == "void": + params = [] + + self["parameters"] = params + self._params_helper2(params) # mods params inplace + + def __str__(self): + filter_keys = ("parent", "defined", "operator", "returns_reference") + cpy = dict((k, v) for (k, v) in list(self.items()) if k not in filter_keys) + return "%s" % cpy + + +_var_keywords = { + n: 0 + for n in "constant constexpr reference pointer static typedefs class fundamental unresolved".split() +} + + +class _CppVariable(dict): + def _name_stack_helper(self, stack): + stack = list(stack) + if "=" not in stack: # TODO refactor me + # check for array[n] and deal with funny array syntax: "int myvar:99" + array = [] + while stack and stack[-1].isdigit(): + array.append(stack.pop()) + if array: + array.reverse() + self["array"] = int("".join(array)) + if stack and stack[-1].endswith(":"): + stack[-1] = stack[-1][:-1] + + while stack and not stack[-1]: + stack.pop() # can be empty + return stack + + def init(self): + # assert self['name'] # allow unnamed variables, methods like this: "void func(void);" + a = [] + self["aliases"] = [] + self["parent"] = None + self["typedef"] = None + self.update(_var_keywords) + for b in self["type"].split(): + if b == "__const__": + b = "const" + a.append(b) + self["type"] = " ".join(a) + + +class CppVariable(_CppVariable): + """ + Dictionary that contains at least the following keys: + + * ``type`` - Type for the variable (ex. "const string &") + * ``name`` - Name of the variable (ex. "numItems") + * ``namespace`` - Namespace + * ``desc`` - If a method/function parameter, doxygen description for this parameter (optional) + * ``doxygen`` - If a normal property/variable, doxygen description for this + * ``default`` - Default value of the variable, this key will only + exist if there is a default value + * ``extern`` - True if its an extern, False if not + * ``parent`` - If not None, either the class this is a property of, or the + method this variable is a parameter in + """ + + Vars = [] + + def __init__(self, nameStack, doxygen, location, **kwargs): + debug_print("var trace %s", nameStack) + if len(nameStack) and nameStack[0] == "extern": + self["extern"] = True + del nameStack[0] + else: + self["extern"] = False + + _stack_ = nameStack + if "[" in nameStack: # strip off array informatin + arrayStack = nameStack[nameStack.index("[") :] + if nameStack.count("[") > 1: + debug_print("Multi dimensional array") + debug_print("arrayStack=%s", arrayStack) + nums = [x for x in arrayStack if x.isdigit()] + # Calculate size by multiplying all dimensions + p = 1 + for n in nums: + p *= int(n) + # Multi dimensional array + self["array_size"] = p + self["multi_dimensional_array"] = 1 + self["multi_dimensional_array_size"] = "x".join(nums) + else: + debug_print("Array") + if len(arrayStack) == 3: + self["array_size"] = arrayStack[1] + nameStack = nameStack[: nameStack.index("[")] + self["array"] = 1 + else: + self["array"] = 0 + nameStack = self._name_stack_helper(nameStack) + + if doxygen: + self["doxygen"] = doxygen + + debug_print("Variable: %s", nameStack) + + set_location_info(self, location) + self["function_pointer"] = 0 + + if len(nameStack) < 2: # +++ + if len(nameStack) == 1: + self["type"] = nameStack[0] + self["name"] = "" + else: + error_print("%s", _stack_) + assert 0 + + elif is_function_pointer_stack(nameStack): # function pointer + self["type"] = " ".join( + nameStack[: nameStack.index("(") + 2] + + nameStack[nameStack.index(")") :] + ) + self["name"] = " ".join( + nameStack[nameStack.index("(") + 2 : nameStack.index(")")] + ) + self["function_pointer"] = 1 + + elif "=" in nameStack: + self["type"] = " ".join(nameStack[: nameStack.index("=") - 1]) + self["name"] = nameStack[nameStack.index("=") - 1] + default = " ".join(nameStack[nameStack.index("=") + 1 :]) + default = self._filter_name(default) + self["default"] = default + # backwards compat; deprecate camelCase in dicts + self["defaultValue"] = default + + elif is_fundamental(nameStack[-1]) or nameStack[-1] in [">", "<", ":", "."]: + # Un named parameter + self["type"] = " ".join(nameStack) + self["name"] = "" + + else: # common case + self["type"] = " ".join(nameStack[:-1]) + self["name"] = nameStack[-1] + + self["type"] = self._filter_name(self["type"]) + + # Optional doxygen description + try: + self["desc"] = kwargs["doxyVarDesc"][self["name"]] + except: + pass + + self.init() + CppVariable.Vars.append(self) # save and resolve later + + def _filter_name(self, name): + name = name.replace(" :", ":").replace(": ", ":") + name = name.replace(" < ", "<") + name = name.replace(" > ", "> ").replace(">>", "> >") + name = name.replace(") >", ")>") + name = name.replace(" {", "{").replace(" }", "}") + name = name.replace(" ,", ",") + return name + + def __str__(self): + keys_white_list = [ + "constant", + "name", + "reference", + "type", + "static", + "pointer", + "desc", + "line_number", + "extern", + ] + cpy = dict((k, v) for (k, v) in list(self.items()) if k in keys_white_list) + if "array_size" in self: + cpy["array_size"] = self["array_size"] + return "%s" % cpy + + +class _CppEnum(dict): + def resolve_enum_values(self, values): + """Evaluates the values list of dictionaries passed in and figures out what the enum value + for each enum is editing in place: + + Example + From: [{'name': 'ORANGE'}, + {'name': 'RED'}, + {'name': 'GREEN', 'value': '8'}] + To: [{'name': 'ORANGE', 'value': 0}, + {'name': 'RED', 'value': 1}, + {'name': 'GREEN', 'value': 8}] + """ + t = int + i = 0 + names = [v["name"] for v in values] + for v in values: + if "value" in v: + a = v["value"].strip() + # Remove single quotes from single quoted chars (unless part of some expression + if len(a) == 3 and a[0] == "'" and a[2] == "'": + a = v["value"] = a[1] + if a.lower().startswith("0x"): + try: + i = a = int(a, 16) + except: + pass + elif a.isdigit(): + i = a = int(a) + elif a in names: + for other in values: + if other["name"] == a: + v["value"] = other["value"] + break + + elif '"' in a or "'" in a: + t = str # only if there are quotes it this a string enum + else: + try: + a = i = ord(a) + except: + pass + # Allow access of what is in the file pre-convert if converted + if v["value"] != str(a): + v["raw_value"] = v["value"] + v["value"] = a + else: + v["value"] = i + try: + v["value"] = v["value"].replace(" < < ", " << ").replace(" >> ", " >> ") + except: + pass + i += 1 + self["type"] = t + + +class CppEnum(_CppEnum): + """Contains the following keys: + + * ``name`` - Name of the enum (ex. "ItemState") + * ``namespace`` - Namespace containing the enum + * ``isclass`` - True if created via 'enum class' or 'enum struct' + * ``values`` - List of values. The values are a dictionary with + the following key/values: + + - ``name`` - name of the key (ex. "PARSING_HEADER"), + - ``value`` - Specified value of the enum, this key will only exist + if a value for a given enum value was defined + """ + + def __init__(self, name, doxygen, location): + if doxygen: + self["doxygen"] = doxygen + if name: + self["name"] = name + self["namespace"] = "" + self["typedef"] = False + self["isclass"] = False + self["values"] = [] + set_location_info(self, location) + + +class _CppPreprocessorLiteral(dict): + """Implementation for #pragmas, #defines and #includes, contains the + following keys: + + * ``value`` the value literal of the preprocessor item + * ``line_number`` line number at which the item was found + """ + + def __init__(self, macro, location): + self["value"] = re.split("[\t ]+", macro, 1)[1].strip() + set_location_info(self, location) + + def __str__(self): + return self["value"] + + +# Implementation is shared between CppPragma, CppDefine, CppInclude but they are +# distinct so that they may diverge if required without interface-breaking +# changes +class CppPragma(_CppPreprocessorLiteral): + pass + + +class CppDefine(_CppPreprocessorLiteral): + pass + + +class CppInclude(_CppPreprocessorLiteral): + pass + + +C99_NONSTANDARD = { + "int8": "signed char", + "int16": "short int", + "int32": "int", + "int64": "int64_t", # this can be: long int (64bit), or long long int (32bit) + "uint": "unsigned int", + "uint8": "unsigned char", + "uint16": "unsigned short int", + "uint32": "unsigned int", + "uint64": "uint64_t", # depends on host bits +} + + +def standardize_fundamental(s): + if s in C99_NONSTANDARD: + return C99_NONSTANDARD[s] + else: + return s + + +class Resolver(object): + C_FUNDAMENTAL = "size_t unsigned signed bool char wchar short int float double long void".split() + C_FUNDAMENTAL += "struct union enum".split() + C_FUNDAMENTAL = set(C_FUNDAMENTAL) + + C_MODIFIERS = "* & const constexpr static mutable".split() + C_MODIFIERS = set(C_MODIFIERS) + + C_KEYWORDS = "extern virtual static explicit inline friend".split() + C_KEYWORDS = set(C_KEYWORDS) + + SubTypedefs = {} # TODO deprecate? + NAMESPACES = [] + CLASSES = {} + + def initextra(self): + self.typedefs = {} + self.typedefs_order = [] + self.classes_order = [] + self.namespaces = Resolver.NAMESPACES # save all namespaces + self.stack = ( + [] + ) # full name stack, good idea to keep both stacks? (simple stack and full stack) + self._classes_brace_level = {} # class name : level + self._forward_decls = [] + self._template_typenames = [] # template<typename XXX> + + def current_namespace(self): + return self.cur_namespace(True) + + def cur_namespace(self, add_double_colon=False): + rtn = "" + i = 0 + while i < len(self.nameSpaces): + rtn += self.nameSpaces[i] + if add_double_colon or i < len(self.nameSpaces) - 1: + rtn += "::" + i += 1 + return rtn + + def guess_ctypes_type(self, string): + pointers = string.count("*") + string = string.replace("*", "") + + a = string.split() + if "unsigned" in a: + u = "u" + else: + u = "" + if "long" in a and "double" in a: + b = "longdouble" # there is no ctypes.c_ulongdouble (this is a 64bit float?) + elif a.count("long") == 2 and "int" in a: + b = "%sint64" % u + elif a.count("long") == 2: + b = "%slonglong" % u + elif "long" in a: + b = "%slong" % u + elif "double" in a: + b = "double" # no udouble in ctypes + elif "short" in a: + b = "%sshort" % u + elif "char" in a: + b = "%schar" % u + elif "wchar" in a: + b = "wchar" + elif "bool" in a: + b = "bool" + elif "float" in a: + b = "float" + + elif "int" in a: + b = "%sint" % u + elif "int8" in a: + b = "int8" + elif "int16" in a: + b = "int16" + elif "int32" in a: + b = "int32" + elif "int64" in a: + b = "int64" + + elif "uint" in a: + b = "uint" + elif "uint8" in a: + b = "uint8" + elif "uint16" in a: + b = "uint16" + elif "uint32" in a: + b = "uint32" + elif "uint64" in a: + b = "uint64" + + elif "size_t" in a: + b = "size_t" + elif "void" in a: + b = "void_p" + + elif string in ("struct", "union"): + b = "void_p" # what should be done here? don't trust struct, it could be a class, no need to expose via ctypes + else: + b = "void_p" + + if not pointers: + return "ctypes.c_%s" % b + else: + x = "" + for i in range(pointers): + x += "ctypes.POINTER(" + x += "ctypes.c_%s" % b + x += ")" * pointers + return x + + def _remove_modifiers(self, vtype): + return " ".join(x for x in vtype.split() if x not in self.C_MODIFIERS) + + def _create_raw_type(self, vtype): + lt = vtype.find("<") + if lt != -1: + gt = vtype.rfind(">") + vtype = ( + self._remove_modifiers(vtype[:lt]) + + vtype[lt : gt + 1] + + self._remove_modifiers(vtype[gt + 1 :]) + ) + else: + vtype = self._remove_modifiers(vtype) + + return vtype + + def resolve_type(self, string, result): # recursive + """ + keeps track of useful things like: how many pointers, number of typedefs, is fundamental or a class, etc... + """ + ## be careful with templates, what is inside <something*> can be a pointer but the overall type is not a pointer + ## these come before a template + s = string.split("<")[0].split() + result["constant"] += s.count("const") + result["constexpr"] += s.count("constexpr") + result["static"] += s.count("static") + result["mutable"] = "mutable" in s + + ## these come after a template + s = string.split(">")[-1] + result["pointer"] += s.count("*") + result["reference"] += s.count("&") + + x = string + alias = False + for a in self.C_MODIFIERS: + x = x.replace(a, "") + for y in x.split(): + if y not in self.C_FUNDAMENTAL: + alias = y + break + + # if alias == 'class': + # result['class'] = result['name'] # forward decl of class + # result['forward_decl'] = True + if alias == "__extension__": + result["fundamental_extension"] = True + elif alias: + if alias in result["aliases"]: + # already resolved + return + result["aliases"].append(alias) + if alias in C99_NONSTANDARD: + result["type"] = C99_NONSTANDARD[alias] + result["typedef"] = alias + result["typedefs"] += 1 + elif alias in self.typedefs: + result["typedefs"] += 1 + result["typedef"] = alias + self.resolve_type(self.typedefs[alias], result) + elif alias in self.classes: + klass = self.classes[alias] + result["fundamental"] = False + result["class"] = klass + result["unresolved"] = False + else: + used = None + + # Search for in parents + if not used: + parent = result["parent"] + while parent: + p_using = parent.get("using") + if p_using: + used = p_using.get(alias) + if used: + break + lookup = getattr(parent, "_lookup_type", None) + if lookup: + used = lookup(alias) + if used: + break + parent = parent["parent"] + + if not used and self.using: + # search for type in all enclosing namespaces + # TODO: would be nice if namespaces were an object? + for ns in _iter_ns_str_reversed(result.get("namespace", "")): + nsalias = ns + alias + used = self.using.get(nsalias) + if used: + break + + if used: + for i in ("enum", "type", "namespace", "ctypes_type", "raw_type"): + if i in used: + result[i] = used[i] + result["unresolved"] = False + else: + result["fundamental"] = True + result["unresolved"] = False + + def finalize_vars(self): + # for c in self.classes.values(): + # for var in c.get_all_properties(): var['parent'] = c['name'] + + ## RESOLVE ## + for var in CppVariable.Vars: + self.resolve_type(var["type"], var) + # if 'method' in var and var['method']['name'] == '_notifyCurrentCamera': print(var); assert 0 + + # then find concrete type and best guess ctypes type # + for var in CppVariable.Vars: + if not var["aliases"]: # var['fundamental']: + var["ctypes_type"] = self.guess_ctypes_type(var["type"]) + else: + var["unresolved"] = False # below may test to True + if var["class"]: + var["ctypes_type"] = "ctypes.c_void_p" + else: + assert var["aliases"] + tag = var["aliases"][0] + + klass = None + nestedEnum = None + nestedStruct = None + nestedTypedef = None + + parent = var["parent"] + while parent: + nestedEnum = getattr(parent, "_public_enums", {}).get(tag) + if nestedEnum: + break + nestedTypedef = getattr(parent, "_public_typedefs", {}).get(tag) + if nestedTypedef: + break + parent = parent["parent"] + + if "<" in tag: # should also contain '>' + var["template"] = tag # do not resolve templates + var["ctypes_type"] = "ctypes.c_void_p" + var["unresolved"] = True + + elif nestedEnum: + enum = nestedEnum + etype = enum.get("type") + if etype is int: + var["ctypes_type"] = "ctypes.c_int" + var["raw_type"] = "int" + + elif etype is str: + var["ctypes_type"] = "ctypes.c_char_p" + var["raw_type"] = "char*" + + if "method" in var: + var["enum"] = var["method"]["path"] + "::" + enum["name"] + else: + var["enum"] = enum["name"] + var["fundamental"] = True + + elif nestedStruct: + var["ctypes_type"] = "ctypes.c_void_p" + var["raw_type"] = ( + var["method"]["path"] + "::" + nestedStruct["type"] + ) + var["fundamental"] = False + + elif nestedTypedef: + var["fundamental"] = is_fundamental(nestedTypedef) + if not var["fundamental"] and "method" in var: + var["raw_type"] = var["method"]["path"] + "::" + tag + + else: + _tag = tag + if "::" in tag and tag.split("::")[0] in self.namespaces: + tag = tag.split("::")[-1] + con = self.concrete_typedef(_tag) + if con: + var["concrete_type"] = con + var["ctypes_type"] = self.guess_ctypes_type( + var["concrete_type"] + ) + + elif tag in self._forward_decls: + var["forward_declared"] = tag + var["ctypes_type"] = "ctypes.c_void_p" + + elif tag in self.global_enums: + enum = self.global_enums[tag] + enum_type = enum.get("type") + if enum_type is int: + var["ctypes_type"] = "ctypes.c_int" + var["raw_type"] = "int" + elif enum_type is str: + var["ctypes_type"] = "ctypes.c_char_p" + var["raw_type"] = "char*" + var["enum"] = enum["namespace"] + enum["name"] + var["fundamental"] = True + + elif var["parent"] and var["unresolved"]: + warning_print("WARN unresolved %s", _tag) + var["ctypes_type"] = "ctypes.c_void_p" + var["unresolved"] = True + + elif tag.count("::") == 1: + trace_print("trying to find nested something in", tag) + a = tag.split("::")[0] + b = tag.split("::")[-1] + if ( + a in self.classes + ): # a::b is most likely something nested in a class + klass = self.classes[a] + if b in klass._public_enums: + trace_print("...found nested enum", b) + enum = klass._public_enums[b] + etype = enum.get("type") + if etype is int: + var["ctypes_type"] = "ctypes.c_int" + var["raw_type"] = "int" + elif etype is str: + var["ctypes_type"] = "ctypes.c_char_p" + var["raw_type"] = "char*" + try: + if "method" in var: + var["enum"] = ( + var["method"]["path"] + + "::" + + enum["name"] + ) + else: # class property + var["unresolved"] = True + except: + var["unresolved"] = True + + var["fundamental"] = True + + else: + var["unresolved"] = True # TODO klass._public_xxx + + elif ( + a in self.namespaces + ): # a::b can also be a nested namespace + if b in self.global_enums: + enum = self.global_enums[b] + trace_print(enum) + trace_print(var) + assert 0 + + elif ( + b in self.global_enums + ): # falling back, this is a big ugly + enum = self.global_enums[b] + assert a in enum["namespace"].split("::") + etype = enum.get("type") + if etype is int: + var["ctypes_type"] = "ctypes.c_int" + var["raw_type"] = "int" + elif etype is str: + var["ctypes_type"] = "ctypes.c_char_p" + var["raw_type"] = "char*" + var["fundamental"] = True + + else: # boost::gets::crazy + trace_print("NAMESPACES", self.namespaces) + trace_print(a, b) + trace_print("---- boost gets crazy ----") + var["ctypes_type"] = "ctypes.c_void_p" + var["unresolved"] = True + + elif "namespace" in var and self.concrete_typedef( + var["namespace"] + tag + ): + # print( 'TRYING WITH NS', var['namespace'] ) + con = self.concrete_typedef(var["namespace"] + tag) + if con: + var["typedef"] = var["namespace"] + tag + var["type"] = con + if "struct" in con.split(): + var["raw_type"] = var["typedef"] + var["ctypes_type"] = "ctypes.c_void_p" + else: + self.resolve_type(var["type"], var) + var["ctypes_type"] = self.guess_ctypes_type( + var["type"] + ) + + elif "::" in var: + var["ctypes_type"] = "ctypes.c_void_p" + var["unresolved"] = True + + elif tag in self.SubTypedefs: # TODO remove SubTypedefs + if ( + "property_of_class" in var + or "property_of_struct" in var + ): + trace_print( + "class:", self.SubTypedefs[tag], "tag:", tag + ) + var["typedef"] = self.SubTypedefs[tag] # class name + var["ctypes_type"] = "ctypes.c_void_p" + else: + trace_print("WARN-this should almost never happen!") + trace_print(var) + trace_print("-" * 80) + var["unresolved"] = True + + elif tag in self._template_typenames: + var["typename"] = tag + var["ctypes_type"] = "ctypes.c_void_p" + var[ + "unresolved" + ] = True # TODO, how to deal with templates? + + elif tag.startswith( + "_" + ): # assume starting with underscore is not important for wrapping + warning_print("WARN unresolved %s", _tag) + var["ctypes_type"] = "ctypes.c_void_p" + var["unresolved"] = True + + else: + trace_print("WARN: unknown type", var) + assert ( + "property_of_class" in var or "property_of_struct" + ) # only allow this case + var["unresolved"] = True + + ## if not resolved and is a method param, not going to wrap these methods ## + if var["unresolved"] and "method" in var: + var["method"]["unresolved_parameters"] = True + + # create stripped raw_type # + for var in CppVariable.Vars: + if "raw_type" not in var: + var["raw_type"] = self._create_raw_type(var["type"]) + + # if 'AutoConstantEntry' in var['raw_type']: print(var); assert 0 + if var["class"]: + if "::" not in var["raw_type"]: + if not var["class"]["parent"]: + var["raw_type"] = ( + var["class"]["namespace"] + "::" + var["raw_type"] + ) + else: + parent = var["class"]["parent"] + var["raw_type"] = ( + parent["namespace"] + + "::" + + var["class"]["name"] + + "::" + + var["raw_type"] + ) + + elif ( + "::" in var["raw_type"] + and var["raw_type"].split("::")[0] not in self.namespaces + ): + var["raw_type"] = ( + var["class"]["namespace"] + "::" + var["raw_type"] + ) + else: + var["unresolved"] = True + + elif "forward_declared" in var and "namespace" in var: + if "::" not in var["raw_type"]: + var["raw_type"] = var["namespace"] + var["raw_type"] + elif ( + "::" in var["raw_type"] + and var["raw_type"].split("::")[0] in self.namespaces + ): + pass + else: + trace_print("-" * 80) + trace_print(var) + raise NotImplementedError + + ## need full name space for classes in raw type ## + if var["raw_type"].startswith("::"): + # print(var) + # print('NAMESPACE', var['class']['namespace']) + # print( 'PARENT NS', var['class']['parent']['namespace'] ) + # assert 0 + var["unresolved"] = True + if "method" in var: + var["method"]["unresolved_parameters"] = True + # var['raw_type'] = var['raw_type'][2:] + + # Take care of #defines and #pragmas etc + trace_print("Processing precomp_macro_buf: %s" % self._precomp_macro_buf) + for m, location in self._precomp_macro_buf: + macro = m.replace("<CppHeaderParser_newline_temp_replacement>\\n", "\n") + ml = macro.lower() + try: + if ml.startswith("#define"): + trace_print("Adding #define %s" % macro) + define = CppDefine(macro, location) + self.defines.append(define["value"]) + self.defines_detail.append(define) + elif ml.startswith("#pragma"): + trace_print("Adding #pragma %s" % macro) + pragma = CppPragma(macro, location) + self.pragmas_detail.append(pragma) + self.pragmas.append(pragma["value"]) + elif ml.startswith("#include"): + trace_print("Adding #include %s" % macro) + include = CppInclude(macro, location) + self.includes.append(include["value"]) + self.includes_detail.append(include) + else: + debug_print("Cant detect what to do with precomp macro '%s'", macro) + except: + pass + self._precomp_macro_buf = None + + def concrete_typedef(self, key): + if key not in self.typedefs: + # print( 'FAILED typedef', key ) + return None + while key in self.typedefs: + prev = key + key = self.typedefs[key] + if "<" in key or ">" in key: + return prev # stop at template + if key.startswith("std::"): + return key # stop at std lib + return key + + +class _CppHeader(Resolver): + def finalize(self): + self.finalize_vars() + # finalize classes and method returns types + for cls in list(self.classes.values()): + for meth in cls.get_all_methods(): + if meth["pure_virtual"]: + cls["abstract"] = True + + # hack + rtnType = { + "aliases": [], + "parent": cls, + "unresolved": True, + "constant": 0, + "constexpr": 0, + "static": 0, + "pointer": 0, + "reference": 0, + "typedefs": 0, + } + self.resolve_type(meth["rtnType"], rtnType) + if not rtnType["unresolved"]: + if "enum" in rtnType: + meth["rtnType"] = rtnType["enum"] + elif "raw_type" in rtnType: + meth["rtnType"] = rtnType["raw_type"] + + # TODO: all of this needs to die and be replaced by CppVariable + + if ( + not meth["returns_fundamental"] + and meth["returns"] in C99_NONSTANDARD + ): + meth["returns"] = C99_NONSTANDARD[meth["returns"]] + meth["returns_fundamental"] = True + + elif not meth["returns_fundamental"]: # describe the return type + con = None + if cls["namespace"] and "::" not in meth["returns"]: + con = self.concrete_typedef( + cls["namespace"] + "::" + meth["returns"] + ) + else: + con = self.concrete_typedef(meth["returns"]) + + if con: + meth["returns_concrete"] = con + meth["returns_fundamental"] = is_fundamental(con) + + elif meth["returns"] in self.classes: + trace_print("meth returns class:", meth["returns"]) + meth["returns_class"] = True + + elif meth["returns"] in self.SubTypedefs: + meth["returns_class"] = True + meth["returns_nested"] = self.SubTypedefs[meth["returns"]] + + elif meth["returns"] in cls._public_enums: + enum = cls._public_enums[meth["returns"]] + meth["returns_enum"] = enum.get("type") + meth["returns_fundamental"] = True + if enum.get("type") == int: + meth["returns"] = "int" + else: + meth["returns"] = "char*" + + elif meth["returns"] in self.global_enums: + enum = self.global_enums[meth["returns"]] + meth["returns_enum"] = enum.get("type") + meth["returns_fundamental"] = True + if enum.get("type") == int: + meth["returns"] = "int" + else: + meth["returns"] = "char*" + + elif meth["returns"].count("::") == 1: + trace_print(meth) + a, b = meth["returns"].split("::") + if a in self.namespaces: + if b in self.classes: + klass = self.classes[b] + meth["returns_class"] = a + "::" + b + elif "<" in b and ">" in b: + meth["returns_unknown"] = True + elif b in self.global_enums: + enum = self.global_enums[b] + meth["returns_enum"] = enum.get("type") + meth["returns_fundamental"] = True + if enum.get("type") == int: + meth["returns"] = "int" + else: + meth["returns"] = "char*" + + else: + trace_print(a, b) + trace_print(meth) + meth["returns_unknown"] = True # +++ + + elif a in self.classes: + klass = self.classes[a] + if b in klass._public_enums: + trace_print("...found nested enum", b) + enum = klass._public_enums[b] + meth["returns_enum"] = enum.get("type") + meth["returns_fundamental"] = True + if enum.get("type") == int: + meth["returns"] = "int" + else: + meth["returns"] = "char*" + + elif b in klass._public_forward_declares: + meth["returns_class"] = True + + elif b in klass._public_typedefs: + typedef = klass._public_typedefs[b] + meth["returns_fundamental"] = is_fundamental(typedef) + + else: + trace_print( + meth + ) # should be a nested class, TODO fix me. + meth["returns_unknown"] = True + + elif "::" in meth["returns"]: + trace_print("TODO namespace or extra nested return:", meth) + meth["returns_unknown"] = True + else: + trace_print( + "WARN: UNKNOWN RETURN", meth["name"], meth["returns"] + ) + meth["returns_unknown"] = True + + if meth["returns"].startswith(":: "): + meth["returns"] = meth["returns"].replace(":: ", "::") + + for cls in list(self.classes.values()): + methnames = cls.get_all_method_names() + pvm = cls.get_all_pure_virtual_methods() + + for d in cls["inherits"]: + c = d["class"] + a = d["access"] # do not depend on this to be 'public' + trace_print("PARENT CLASS:", c) + if c not in self.classes: + trace_print("WARN: parent class not found") + if c in self.classes and self.classes[c]["abstract"]: + p = self.classes[c] + for meth in p.get_all_methods(): # p["methods"]["public"]: + trace_print( + "\t\tmeth", + meth["name"], + "pure virtual", + meth["pure_virtual"], + ) + if meth["pure_virtual"] and meth["name"] not in methnames: + cls["abstract"] = True + break + + _method_type_defaults = { + n: False + for n in "defined deleted pure_virtual operator constructor destructor extern template virtual static explicit inline friend returns returns_pointer returns_fundamental returns_class default".split() + } + + def parse_method_type(self, stack): + trace_print("meth type info", stack) + info = { + "debug": " ".join(stack) + .replace(" :: ", "::") + .replace(" < ", "<") + .replace(" > ", "> ") + .replace(" >", ">") + .replace(">>", "> >") + .replace(">>", "> >"), + "class": None, + "namespace": self.cur_namespace(add_double_colon=True), + } + + info.update(self._method_type_defaults) + + header = stack[: stack.index("(")] + header = " ".join(header) + header = header.replace(" :: ", "::") + header = header.replace(" < ", "<") + header = header.replace(" > ", "> ") + header = header.replace("default ", "default") + header = header.strip() + + if stack[-1] == "{": + info["defined"] = True + self._discard_contents("{", "}") + self.braceHandled = True + elif stack[-1] == ";": + info["defined"] = False + elif stack[-1] == ":": + info["defined"] = True + self._discard_ctor_initializer() + self.braceHandled = True + else: + assert 0 + + if len(stack) > 3 and stack[-1] == ";" and stack[-3] == "=": + if stack[-2] == "0": + info["pure_virtual"] = True + elif stack[-2] == "delete": + info["deleted"] = True + + r = header.split() + name = None + if "operator" in stack: # rare case op overload defined outside of class + op = stack[stack.index("operator") + 1 : stack.index("(")] + op = "".join(op) + if not op: + if " ".join(["operator", "(", ")", "("]) in " ".join(stack): + op = "()" + else: + trace_print("Error parsing operator") + return None + + info["operator"] = op + name = "operator" + op + a = stack[: stack.index("operator")] + + elif r: + name = r[-1] + a = r[:-1] # strip name + + if name is None: + return None + # if name.startswith('~'): name = name[1:] + + while a and a[0] == "}": # strip - can have multiple } } + a = a[1:] + + if "::" in name: + # klass,name = name.split('::') # methods can be defined outside of class + klass = name[: name.rindex("::")] + name = name.split("::")[-1] + info["class"] = klass + if klass in self.classes and not self.curClass: + # Class function defined outside the class + return None + # info['name'] = name + # else: info['name'] = name + + if name.startswith("~"): + info["destructor"] = True + if "default;" in stack: + info["defined"] = True + info["default"] = True + name = name[1:] + elif not a or (name == self.curClass and len(self.curClass)): + info["constructor"] = True + if "default;" in stack: + info["defined"] = True + info["default"] = True + + info["name"] = name + + for tag in self.C_KEYWORDS: + if tag in a: + info[tag] = True + a.remove(tag) # inplace + if "template" in a: + a.remove("template") + b = " ".join(a) + if ">" in b: + info["template"] = b[: b.index(">") + 1] + info["returns"] = b[ + b.index(">") + 1 : + ] # find return type, could be incorrect... TODO + if "<typename" in info["template"].split(): + typname = info["template"].split()[-1] + typname = typname[:-1] # strip '>' + if typname not in self._template_typenames: + self._template_typenames.append(typname) + else: + info["returns"] = " ".join(a) + else: + info["returns"] = " ".join(a) + info["returns"] = info["returns"].replace(" <", "<").strip() + + ## be careful with templates, do not count pointers inside template + info["returns_pointer"] = info["returns"].split(">")[-1].count("*") + if info["returns_pointer"]: + info["returns"] = info["returns"].replace("*", "").strip() + + info["returns_reference"] = "&" in info["returns"] + if info["returns"]: + info["returns"] = info["returns"].replace("&", "").strip() + + a = [] + for b in info["returns"].split(): + if b == "__const__": + info["returns_const"] = True + elif b == "const": + info["returns_const"] = True + else: + a.append(b) + info["returns"] = " ".join(a) + + info["returns_fundamental"] = is_fundamental(info["returns"]) + return info + + def _evaluate_method_stack(self): + """Create a method out of the name stack""" + + info = self.parse_method_type(self.stack) + if info: + if ( + info["class"] and info["class"] in self.classes + ): # case where methods are defined outside of class + newMethod = CppMethod( + self.nameStack, + info["name"], + info, + self.curTemplate, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + klass = self.classes[info["class"]] + klass["methods"]["public"].append(newMethod) + newMethod["parent"] = klass + if klass["namespace"]: + newMethod["path"] = klass["namespace"] + "::" + klass["name"] + else: + newMethod["path"] = klass["name"] + + elif self.curClass: # normal case + newMethod = CppMethod( + self.nameStack, + self.curClass, + info, + self.curTemplate, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + klass = self.classes[self.curClass] + klass["methods"][self.curAccessSpecifier].append(newMethod) + newMethod["parent"] = klass + if klass["namespace"]: + newMethod["path"] = klass["namespace"] + "::" + klass["name"] + else: + newMethod["path"] = klass["name"] + else: # non class functions + debug_print("FREE FUNCTION") + newMethod = CppMethod( + self.nameStack, + None, + info, + self.curTemplate, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + newMethod["parent"] = None + self.functions.append(newMethod) + global parseHistory + parseHistory.append( + { + "braceDepth": self.braceDepth, + "item_type": "method", + "item": newMethod, + } + ) + else: + trace_print("free function?", self.nameStack) + + self.stack = [] + self.stmtTokens = [] + + def _parse_typedef(self, stack, namespace=""): + if not stack or "typedef" not in stack: + return + stack = list(stack) # copy just to be safe + if stack[-1] == ";": + stack.pop() + + while stack and stack[-1].isdigit(): + stack.pop() # throw away array size for now + + idx = stack.index("typedef") + if stack[-1] == "]": + try: + name = namespace + "".join(stack[-4:]) + # Strip off the array part so the rest of the parsing is better + stack = stack[:-3] + except: + name = namespace + stack[-1] + else: + name = namespace + stack[-1] + s = "" + for a in stack[idx + 1 : -1]: + if a == "{": + break + if not s or s[-1] in ":<>" or a in ":<>": + s += a # keep compact + else: + s += " " + a # spacing + + r = {"name": name, "raw": s, "type": s} + if not is_fundamental(s): + if "struct" in s.split(): + pass # TODO is this right? "struct ns::something" + elif "::" not in s: + s = ( + namespace + s + ) # only add the current name space if no namespace given + r["type"] = s + if s: + return r + + def _evaluate_typedef(self): + ns = self.cur_namespace(add_double_colon=True) + res = self._parse_typedef(self.stack, ns) + if res: + name = res["name"] + self.typedefs[name] = res["type"] + if name not in self.typedefs_order: + self.typedefs_order.append(name) + + def _evaluate_property_stack(self, clearStack=True, addToVar=None): + """Create a Property out of the name stack""" + global parseHistory + debug_print("trace") + if self.nameStack[0] == "typedef": + assert self.stack and self.stack[-1] == ";" + if self.curClass: + typedef = self._parse_typedef(self.stack) + name = typedef["name"] + klass = self.classes[self.curClass] + klass["typedefs"][self.curAccessSpecifier].append(name) + if self.curAccessSpecifier == "public": + klass._public_typedefs[name] = typedef["type"] + Resolver.SubTypedefs[name] = self.curClass + else: + assert 0 + elif self.curClass: + if len(self.nameStack) == 1: + # See if we can de anonymize the type + filteredParseHistory = [ + h for h in parseHistory if h["braceDepth"] == self.braceDepth + ] + if ( + len(filteredParseHistory) + and filteredParseHistory[-1]["item_type"] == "class" + ): + self.nameStack.insert(0, filteredParseHistory[-1]["item"]["name"]) + debug_print( + "DEANONYMOIZING %s to type '%s'", + self.nameStack[1], + self.nameStack[0], + ) + if "," in self.nameStack: # Maybe we have a variable list + # Figure out what part is the variable separator but remember templates of function pointer + # First find left most comma outside of a > and ) + leftMostComma = 0 + for i in range(0, len(self.nameStack)): + name = self.nameStack[i] + if name in (">", ")"): + leftMostComma = 0 + if leftMostComma == 0 and name == ",": + leftMostComma = i + # Is it really a list of variables? + if leftMostComma != 0: + trace_print( + "Multiple variables for namestack in %s. Separating processing" + % self.nameStack + ) + orig_nameStack = self.nameStack[:] + + type_nameStack = orig_nameStack[: leftMostComma - 1] + for name in orig_nameStack[leftMostComma - 1 :: 2]: + self.nameStack = type_nameStack + [name] + self._evaluate_property_stack( + clearStack=False, addToVar=addToVar + ) + return + + newVar = CppVariable( + self.nameStack, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + newVar["namespace"] = self.current_namespace() + if self.curClass: + klass = self.classes[self.curClass] + klass["properties"][self.curAccessSpecifier].append(newVar) + newVar["property_of_class"] = klass["name"] + newVar["parent"] = klass + parseHistory.append( + {"braceDepth": self.braceDepth, "item_type": "variable", "item": newVar} + ) + if addToVar: + newVar.update(addToVar) + else: + debug_print("Found Global variable") + newVar = CppVariable( + self.nameStack, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + if addToVar: + newVar.update(addToVar) + self.variables.append(newVar) + + if clearStack: + self.stack = [] # CLEAR STACK + self.stmtTokens = [] + + def _evaluate_class_stack(self): + """Create a Class out of the name stack (but not its parts)""" + # dont support sub classes today + # print( 'eval class stack', self.nameStack ) + parent = self.curClass + if parent: + debug_print("found nested subclass") + self.accessSpecifierStack.append(self.curAccessSpecifier) + + # When dealing with typedefed structs, get rid of typedef keyword to handle later on + if self.nameStack[0] == "typedef": + del self.nameStack[0] + + if len(self.nameStack) == 1: + if self.nameStack[0] == "struct": + self.anon_struct_counter += 1 + # We cant handle more than 1 anonymous struct, so name them uniquely + self.nameStack.append("anon-struct-%d" % self.anon_struct_counter) + elif self.nameStack[0] == "union": + self.anon_union_counter += 1 + # We cant handle more than 1 anonymous union, so name them uniquely + self.nameStack.append("anon-union-%d" % self.anon_union_counter) + elif self.nameStack[0] == "class": + self.anon_class_counter += 1 + # We cant handle more than 1 anonymous class, so name them uniquely + self.nameStack.append("anon-class-%d" % self.anon_class_counter) + + if self.nameStack[0] == "class": + self.curAccessSpecifier = "private" + else: # struct/union + self.curAccessSpecifier = "public" + debug_print( + "curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier + ) + if self.nameStack[0] == "union": + newClass = CppUnion( + self.nameStack, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + ) + else: + newClass = CppClass( + self.nameStack, + self.curTemplate, + self._get_stmt_doxygen(), + self._get_location(self.nameStack), + self.curAccessSpecifier, + ) + newClass["declaration_method"] = self.nameStack[0] + self.classes_order.append(newClass) # good idea to save ordering + self.stack = [] # fixes if class declared with ';' in closing brace + self.stmtTokens = [] + classKey = newClass["name"] + + if parent: + newClass["namespace"] = self.classes[parent]["namespace"] + "::" + parent + newClass["parent"] = self.classes[parent] + newClass["access_in_parent"] = self.accessSpecifierStack[-1] + self.classes[parent]["nested_classes"].append(newClass) + ## supports nested classes with the same name ## + self.curClass = key = parent + "::" + classKey + self._classes_brace_level[key] = self.braceDepth + + elif newClass["parent"]: # nested class defined outside of parent. A::B {...} + pcls = newClass["parent"] + parent = pcls["name"] + newClass["namespace"] = pcls["namespace"] + "::" + parent + pcls["nested_classes"].append(newClass) + ## supports nested classes with the same name ## + self.curClass = key = parent + "::" + classKey + self._classes_brace_level[key] = self.braceDepth + + else: + newClass["namespace"] = self.cur_namespace() + self.curClass = key = classKey + self._classes_brace_level[classKey] = self.braceDepth + + if not key.endswith("::") and not key.endswith(" ") and len(key) != 0: + if key in self.classes: + trace_print("ERROR name collision:", key) + self.classes[key].show() + trace_print("-" * 80) + newClass.show() + assert key not in self.classes # namespace collision + self.classes[key] = newClass + global parseHistory + parseHistory.append( + {"braceDepth": self.braceDepth, "item_type": "class", "item": newClass} + ) + + def evalute_forward_decl(self): + trace_print("FORWARD DECL", self.nameStack) + assert self.nameStack[0] in ("class", "struct") + name = self.nameStack[-1] + if self.curClass: + klass = self.classes[self.curClass] + klass["forward_declares"][self.curAccessSpecifier].append(name) + if self.curAccessSpecifier == "public": + klass._public_forward_declares.append(name) + else: + self._forward_decls.append(name) + + +# fmt: off +_namestack_append_tokens = { + "{", + "}", + "[", + "]", + "=", + ",", + "\\", + "DIVIDE", + "|", + "%", + "^", + "!", + "NUMBER", + "FLOAT_NUMBER", + "-", + "+", + "STRING_LITERAL", + "ELLIPSIS", + "DBL_COLON", + "SHIFT_LEFT", +} + +_namestack_pass_tokens = { + "'", + "." # preserve behaviour and eat individual fullstops +} + +_namestack_str_tokens = { + "NAME", + "&", + "*", + "<", + ">", + "CHAR_LITERAL" +} +# fmt: on + + +class CppHeader(_CppHeader): + """Parsed C++ class header""" + + IGNORE_NAMES = "__extension__".split() + + def show(self): + for className in list(self.classes.keys()): + self.classes[className].show() + + def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): + """Create the parsed C++ header file parse tree + + headerFileName - Name of the file to parse OR actual file contents (depends on argType) + argType - Indicates how to interpret headerFileName as a file string or file name + kwargs - Supports the following keywords + """ + ## reset global state ## + CppVariable.Vars = [] + + if argType == "file": + self.headerFileName = os.path.expandvars(headerFileName) + self.mainClass = os.path.split(self.headerFileName)[1][:-2] + headerFileStr = "" + elif argType == "string": + self.headerFileName = "" + self.mainClass = "???" + headerFileStr = headerFileName + else: + raise Exception("Arg type must be either file or string") + self.curClass = "" + + # nested classes have parent::nested, but no extra namespace, + # this keeps the API compatible, TODO proper namespace for everything. + Resolver.CLASSES = {} + + #: Dictionary of classes found in the header file. The key is the name + #: of the class, value is :class:`.CppClass` + self.classes = Resolver.CLASSES + + #: List of free functions as :class:`.CppMethod` + self.functions = [] + + #: List of #pragma directives found as strings + self.pragmas = [] + + #: List of pragmas with location information + self.pragmas_detail = [] + + #: List of #define directives found + self.defines = [] + + #: List of #define directives found, with location information + self.defines_detail = [] + + #: List of #include directives found + self.includes = [] + + #: List of #include directives found with location information + self.includes_detail = [] + + #: Filenames encountered in #line directives while parsing + self.headerFileNames = [] + + self._precomp_macro_buf = ( + [] + ) # for internal purposes, will end up filling out pragmras and defines at the end + + #: List of enums in this header as :class:`.CppEnum` + self.enums = [] + + #: List of variables in this header as :class:`.CppVariable` + self.variables = [] + self.global_enums = {} + self.nameStack = [] + + #: Namespaces in this header + self.nameSpaces = [] + self.curAccessSpecifier = "private" # private is default + self.curTemplate = None + self.accessSpecifierStack = [] + debug_print( + "curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier + ) + self.initextra() + # Old namestacks for a given level + self.nameStackHistory = [] + self.anon_struct_counter = 0 + self.anon_union_counter = 0 + self.anon_class_counter = 0 + + #: Using directives in this header outside of class scope: key is + #: full name for lookup, value is :class:`.CppVariable` + self.using = {} + + if len(self.headerFileName): + fd = io.open(self.headerFileName, "r", encoding=encoding) + headerFileStr = "".join(fd.readlines()) + fd.close() + + # Make sure supportedAccessSpecifier are sane + for i in range(0, len(supportedAccessSpecifier)): + if " " not in supportedAccessSpecifier[i]: + continue + supportedAccessSpecifier[i] = re.sub( + "[ ]+", " ", supportedAccessSpecifier[i] + ).strip() + + # Change multi line #defines and expressions to single lines maintaining line nubmers + # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements + matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr) + is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]") + for m in matches: + # Keep the newlines so that linecount doesnt break + num_newlines = len([a for a in m if a == "\n"]) + if is_define.match(m): + new_m = m.replace("\n", "<CppHeaderParser_newline_temp_replacement>\\n") + else: + # Just expression taking up multiple lines, make it take 1 line for easier parsing + new_m = m.replace("\\\n", " ") + if num_newlines > 0: + new_m += "\n" * (num_newlines) + headerFileStr = headerFileStr.replace(m, new_m) + + # Filter out Extern "C" statements. These are order dependent + matches = re.findall( + re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr + ) + for m in matches: + # Keep the newlines so that linecount doesnt break + num_newlines = len([a for a in m if a == "\n"]) + headerFileStr = headerFileStr.replace(m, "\n" * num_newlines) + headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr) + + # Filter out any ignore symbols that end with "()" to account for #define magic functions + for ignore in ignoreSymbols: + if not ignore.endswith("()"): + continue + while True: + locStart = headerFileStr.find(ignore[:-1]) + if locStart == -1: + break + locEnd = None + # Now walk till we find the last paren and account for sub parens + parenCount = 1 + inQuotes = False + for i in range(locStart + len(ignore) - 1, len(headerFileStr)): + c = headerFileStr[i] + if not inQuotes: + if c == "(": + parenCount += 1 + elif c == ")": + parenCount -= 1 + elif c == '"': + inQuotes = True + if parenCount == 0: + locEnd = i + 1 + break + else: + if c == '"' and headerFileStr[i - 1] != "\\": + inQuotes = False + + if locEnd: + # Strip it out but keep the linecount the same so line numbers are right + match_str = headerFileStr[locStart:locEnd] + debug_print("Striping out '%s'", match_str) + num_newlines = len([a for a in match_str if a == "\n"]) + headerFileStr = headerFileStr.replace( + headerFileStr[locStart:locEnd], "\n" * num_newlines + ) + + self.braceDepth = 0 + + lex = Lexer(self.headerFileName) + lex.input(headerFileStr) + self.lex = lex + self.headerFileNames = lex.filenames + + # + # A note on parsing methodology + # + # The idea here is to consume as many tokens as needed to determine + # what the thing is that we're parsing. While some items can be identified + # early, typically the code below consumes until a '{', '}', or ; and + # then looks at the accumulated tokens to figure out what it is. + # + # Unfortunately, the code isn't always particularly consistent (but + # it's slowly getting there!), so take this with a grain of salt. + # + + self._doxygen_cache = None + self.braceHandled = False + tok = None + self.stmtTokens = [] + parenDepth = 0 + + try: + while True: + tok = lex.token(eof_ok=True) + if not tok: + break + tok.value = TagStr(tok.value, location=tok.location) + + # debug_print("TOK: %s", tok) + if tok.type == "NAME": + if tok.value in self.IGNORE_NAMES: + continue + elif tok.value == "template": + self._doxygen_cache = self.lex.get_doxygen() + self._parse_template() + continue + elif tok.value == "alignas": + self._parse_attribute_specifier_seq(tok) + continue + elif tok.value == "__attribute__": + self._parse_gcc_attribute() + continue + elif not self.stack and tok.value == "static_assert": + self._next_token_must_be("(") + self._discard_contents("(", ")") + continue + + elif tok.type == "DBL_LBRACKET": + self._parse_attribute_specifier_seq(tok) + continue + + # TODO: get rid of stack, move to stmtTokens + self.stack.append(tok.value) + self.stmtTokens.append(tok) + + nslen = len(self.nameStack) + + if tok.type in ("PRECOMP_MACRO", "PRECOMP_MACRO_CONT"): + debug_print("PRECOMP: %s", tok) + self._precomp_macro_buf.append((tok.value, tok.location)) + self.stack = [] + self.stmtTokens = [] + self.nameStack = [] + continue + + if parenDepth == 0 and tok.type == "{": + if len(self.nameStack) >= 2 and is_namespace( + self.nameStack + ): # namespace {} with no name used in boost, this sets default? + if ( + self.nameStack[1] + == "__IGNORED_NAMESPACE__CppHeaderParser__" + ): # Used in filtering extern "C" + self.nameStack[1] = "" + self.nameSpaces.append(self.nameStack[1]) + ns = self.cur_namespace() + self.stack = [] + self.stmtTokens = [] + if ns not in self.namespaces: + self.namespaces.append(ns) + # Detect special condition of macro magic before class declaration so we + # can filter it out + if "class" in self.nameStack and self.nameStack[0] != "class": + classLocationNS = self.nameStack.index("class") + classLocationS = self.stack.index("class") + if ( + "(" not in self.nameStack[classLocationNS:] + and self.nameStack[classLocationNS - 1] != "enum" + ): + debug_print( + "keyword 'class' found in unexpected location in nameStack, must be following #define magic. Process that before moving on" + ) + origNameStack = self.nameStack + origStack = self.stack + # Process first part of stack which is probably #define macro magic and may cause issues + self.nameStack = self.nameStack[:classLocationNS] + self.stack = self.stack[:classLocationS] + try: + self._evaluate_stack() + except: + debug_print("Error processing #define magic... Oh well") + # Process rest of stack + self.nameStack = origNameStack[classLocationNS:] + self.stack = origStack[classLocationS:] + + # If set to True, indicates that the callee consumed + # all of the tokens between { and } + self.braceHandled = False + if self.nameStack: + self._evaluate_stack() + if self.stack and self.stack[0] == "class": + self.stack = [] + self.stmtTokens = [] + if not self.braceHandled: + self.braceDepth += 1 + + elif parenDepth == 0 and tok.type == "}": + if self.braceDepth == 0: + continue + if self.braceDepth == len(self.nameSpaces): + tmp = self.nameSpaces.pop() + self.stack = [] # clear stack when namespace ends? + self.stmtTokens = [] + else: + self._evaluate_stack() + self.braceDepth -= 1 + + # if self.curClass: + # debug_print( + # "CURBD %s", self._classes_brace_level[self.curClass] + # ) + + if (self.braceDepth == 0) or ( + self.curClass + and self._classes_brace_level[self.curClass] == self.braceDepth + ): + trace_print("END OF CLASS DEF") + if self.accessSpecifierStack: + self.curAccessSpecifier = self.accessSpecifierStack[-1] + self.accessSpecifierStack = self.accessSpecifierStack[:-1] + if self.curClass and self.classes[self.curClass]["parent"]: + thisClass = self.classes[self.curClass] + self.curClass = self.curClass[ + : -(len(thisClass["name"]) + 2) + ] + + # Detect anonymous union members + if ( + self.curClass + and thisClass["declaration_method"] == "union" + and thisClass["name"].startswith("<") + and self.lex.token_if(";") + ): + debug_print("Creating anonymous union") + # Force the processing of an anonymous union + self.nameStack = [""] + self.stack = self.nameStack + [";"] + debug_print("pre eval anon stack") + self._evaluate_stack(";") + debug_print("post eval anon stack") + self.stack = [] + self.nameStack = [] + self.stmtTokens = [] + else: + self.curClass = "" + self.stack = [] + self.stmtTokens = [] + elif tok.type in _namestack_append_tokens: + self.nameStack.append(tok.value) + nameStackAppended = True + elif tok.type in _namestack_pass_tokens: + pass + elif tok.type in _namestack_str_tokens: + if tok.value in ignoreSymbols: + debug_print("Ignore symbol %s", tok.value) + elif tok.value == "class": + self.nameStack.append(tok.value) + else: + self.nameStack.append(tok.value) + elif tok.type == ":": + if self.nameStack and self.nameStack[0] in supportedAccessSpecifier: + specifier = " ".join(self.nameStack) + if specifier in supportedAccessSpecifier: + self.curAccessSpecifier = specifier + else: + self.curAccessSpecifier = self.nameStack[0] + debug_print( + "curAccessSpecifier updated to %s", self.curAccessSpecifier + ) + self.nameStack = [] + self.stack = [] + self.stmtTokens = [] + elif is_method_namestack(self.stack): + debug_print("trace") + self._evaluate_method_stack() + self.nameStack = [] + self.stack = [] + self.stmtTokens = [] + else: + self.nameStack.append(tok.value) + + elif tok.type == ";": + self._evaluate_stack(tok.type) + self.stack = [] + self.nameStack = [] + self.stmtTokens = [] + elif tok.type == "(": + parenDepth += 1 + self.nameStack.append(tok.value) + nameStackAppended = True + elif tok.type == ")": + self.nameStack.append(tok.value) + nameStackAppended = True + if parenDepth != 0: + parenDepth -= 1 + + newNsLen = len(self.nameStack) + if nslen != newNsLen and newNsLen == 1: + if not self.curTemplate: + self._doxygen_cache = self.lex.get_doxygen() + + except Exception as e: + if debug: + raise + context = "" + if isinstance(e, CppParseError): + context = ": " + str(e) + if e.tok: + tok = e.tok + + if tok: + filename, lineno = tok.location + msg = ( + "Not able to parse %s on line %d evaluating '%s'%s\nError around: %s" + % (filename, lineno, tok.value, context, " ".join(self.nameStack)) + ) + else: + msg = "Error parsing %s%s\nError around: %s" % ( + self.headerFileName, + context, + " ".join(self.nameStack), + ) + + raise_exc( + CppParseError(msg), + e, + ) + + self.finalize() + global parseHistory + parseHistory = [] + # Delete some temporary variables + for key in [ + "_precomp_macro_buf", + "_doxygen_cache", + "braceHandled", + "lex", + "nameStack", + "nameSpaces", + "curAccessSpecifier", + "accessSpecifierStack", + "nameStackHistory", + "anon_struct_counter", + "anon_union_counter", + "anon_class_counter", + "_classes_brace_level", + "_forward_decls", + "stack", + "mainClass", + "_template_typenames", + "braceDepth", + "stmtTokens", + "typedefs_order", + "curTemplate", + ]: + del self.__dict__[key] + + def _get_location(self, stack): + if stack: + location = getattr(stack[0], "location", None) + if location is not None: + return location + + return self.lex.current_location() + + def _get_stmt_doxygen(self): + # retrieves the doxygen comment associated with an accumulated + # statement (since doxygen comments have to be retrieved immediately) + doxygen, self._doxygen_cache = self._doxygen_cache, "" + if not doxygen: + doxygen = self.lex.get_doxygen() + return doxygen + + def _parse_error(self, tokens, expected): + if not tokens: + # common case after a failed token_if + errtok = self.lex.token() + else: + errtok = tokens[-1] + if expected: + expected = ", expected '" + expected + "'" + + msg = "unexpected '%s'%s" % (errtok.value, expected) + + # TODO: better error message + return CppParseError(msg, errtok) + + def _next_token_must_be(self, *tokenTypes): + tok = self.lex.token() + if tok.type not in tokenTypes: + raise self._parse_error((tok,), "' or '".join(tokenTypes)) + return tok + + _end_balanced_tokens = {">", "}", "]", ")", "DBL_RBRACKET"} + _balanced_token_map = { + "<": ">", + "{": "}", + "(": ")", + "[": "]", + "DBL_LBRACKET": "DBL_RBRACKET", + } + + def _consume_balanced_tokens(self, *init_tokens): + + _balanced_token_map = self._balanced_token_map + + consumed = list(init_tokens) + match_stack = deque((_balanced_token_map[tok.type] for tok in consumed)) + get_token = self.lex.token + + while True: + tok = get_token() + consumed.append(tok) + + if tok.type in self._end_balanced_tokens: + expected = match_stack.pop() + if tok.type != expected: + # hack: ambiguous right-shift issues here, really + # should be looking at the context + if tok.type == ">": + tok = self.lex.token_if(">") + if tok: + consumed.append(tok) + match_stack.append(expected) + continue + + raise self._parse_error(consumed, expected) + if len(match_stack) == 0: + return consumed + + continue + + next_end = _balanced_token_map.get(tok.type) + if next_end: + match_stack.append(next_end) + + def _discard_contents(self, start_type, end_type): + # use this instead of consume_balanced_tokens because + # we don't care at all about the internals + level = 1 + get_token = self.lex.token + while True: + tok = get_token() + if tok.type == start_type: + level += 1 + elif tok.type == end_type: + level -= 1 + if level == 0: + break + + def _discard_ctor_initializer(self): + """ + ctor_initializer: ":" mem_initializer_list + + mem_initializer_list: mem_initializer ["..."] + | mem_initializer "," mem_initializer_list ["..."] + + mem_initializer: mem_initializer_id "(" [expression_list] ")" + | mem_initializer_id braced_init_list + + mem_initializer_id: class_or_decltype + | IDENTIFIER + """ + debug_print("discarding ctor intializer") + # all of this is discarded.. the challenge is to determine + # when the initializer ends and the function starts + while True: + tok = self.lex.token() + if tok.type == "DBL_COLON": + tok = self.lex.token() + + if tok.type == "decltype": + tok = self._next_token_must_be("(") + self._consume_balanced_tokens(tok) + tok = self.lex.token() + + # each initializer is either foo() or foo{}, so look for that + while True: + if tok.type not in ("{", "("): + tok = self.lex.token() + continue + + if tok.type == "{": + self._discard_contents("{", "}") + elif tok.type == "(": + self._discard_contents("(", ")") + + tok = self.lex.token() + break + + # at the end + if tok.type == "ELLIPSIS": + tok = self.lex.token() + + if tok.type == ",": + continue + elif tok.type == "{": + # reached the function + self._discard_contents("{", "}") + return + else: + raise self._parse_error((tok,), ",' or '{") + + def _evaluate_stack(self, token=None): + """Evaluates the current name stack""" + + nameStackCopy = self.nameStack[:] + + debug_print( + "Evaluating stack %s\n BraceDepth: %s (called from %d)", + self.nameStack, + self.braceDepth, + inspect.currentframe().f_back.f_lineno, + ) + + # Handle special case of overloading operator () + if "operator()(" in "".join(self.nameStack): + operator_index = self.nameStack.index("operator") + self.nameStack.pop(operator_index + 2) + self.nameStack.pop(operator_index + 1) + self.nameStack[operator_index] = "operator()" + + if len(self.curClass): + debug_print("%s (%s) ", self.curClass, self.curAccessSpecifier) + else: + debug_print("<anonymous> (%s) ", self.curAccessSpecifier) + + # Filter special case of array with casting in it + try: + bracePos = self.nameStack.index("[") + parenPos = self.nameStack.index("(") + if bracePos == parenPos - 1: + endParen = self.nameStack.index(")") + self.nameStack = ( + self.nameStack[: bracePos + 1] + self.nameStack[endParen + 1 :] + ) + debug_print("Filtered namestack to=%s", self.nameStack) + except: + pass + + # if 'typedef' in self.nameStack: self._evaluate_typedef() # allows nested typedefs, probably a bad idea + if ( + not self.curClass + and "typedef" in self.nameStack + and ( + ( + "struct" not in self.nameStack + and "union" not in self.nameStack + and "enum" not in self.nameStack + ) + or self.stack[-1] == ";" + ) + ): + debug_print("trace") + trace_print("typedef %s", self.stack) + self._evaluate_typedef() + return + + elif len(self.nameStack) == 0: + debug_print("trace (Empty Stack)") + return + elif self.nameStack[0] == "namespace": + # Taken care of outside of here + pass + elif len(self.nameStack) == 2 and self.nameStack[0] == "extern": + debug_print("trace extern") + self.stack = [] + self.stmtTokens = [] + elif ( + len(self.nameStack) == 2 and self.nameStack[0] == "friend" + ): # friend class declaration + pass + elif len(self.nameStack) >= 2 and self.nameStack[0] == "using": + if self.nameStack[1] == "namespace": + pass + else: + if len(self.nameStack) > 3 and self.nameStack[2] == "=": + # using foo = ns::bar + # -> type alias: same behavior in all scopes + alias = self.nameStack[1] + ns, stack = _split_namespace(self.nameStack[3:]) + atype = CppVariable( + stack, self._get_stmt_doxygen(), self._get_location(stack) + ) + + # namespace refers to the embedded type + atype["namespace"] = ns + atype["using_type"] = "typealias" + atype["typealias"] = alias + else: + # using foo::bar + # -> in global scope this is bringing in something + # from a different namespace + # -> in class scope this is bringing in a member + # from a base class + ns, stack = _split_namespace(self.nameStack[1:]) + atype = CppVariable( + stack, self._get_stmt_doxygen(), self._get_location(stack) + ) + alias = atype["type"] + atype["using_type"] = "declaration" + if self.curClass: + atype["baseclass"] = ns + else: + atype["namespace"] = ns + + atype["raw_type"] = ns + atype["type"] + + if self.curClass: + klass = self.classes[self.curClass] + klass["using"][alias] = atype + else: + # lookup is done + alias = self.current_namespace() + alias + self.using[alias] = atype + elif is_method_namestack(self.stack) and "(" in self.nameStack: + debug_print("trace") + self._evaluate_method_stack() + elif is_enum_namestack(self.nameStack): + debug_print("trace") + self._parse_enum() + self.stack = [] + self.stmtTokens = [] + elif ( + len(self.nameStack) == 1 + and len(self.nameStackHistory) > self.braceDepth + and ( + self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "struct"] + or self.nameStackHistory[self.braceDepth][0][0:2] + == ["typedef", "union"] + ) + ): + # Look for the name of a typedef struct: struct typedef {...] StructName; or unions to get renamed + debug_print("found the naming of a union") + type_name_to_rename = self.nameStackHistory[self.braceDepth][1] + new_name = self.nameStack[0] + type_to_rename = self.classes[type_name_to_rename] + type_to_rename["name"] = self.nameStack[0] + # Now re install it in its new location + self.classes[new_name] = type_to_rename + if new_name != type_name_to_rename: + del self.classes[type_name_to_rename] + elif is_property_namestack(self.nameStack) and self.stack[-1] == ";": + debug_print("trace") + if self.nameStack[0] in ("class", "struct") and len(self.stack) == 3: + self.evalute_forward_decl() + elif len(self.nameStack) >= 2 and ( + self.nameStack[0] == "friend" and self.nameStack[1] == "class" + ): + pass + else: + self._evaluate_property_stack() # catches class props and structs in a namespace + + elif ( + self.nameStack[0] in ("class", "struct", "union") + or self.nameStack[0] == "typedef" + and self.nameStack[1] in ("struct", "union") + ): + # Parsing a union can reuse much of the class parsing + debug_print("trace") + self._evaluate_class_stack() + + elif not self.curClass: + debug_print("trace") + elif self.braceDepth < 1: + debug_print("trace") + # Ignore global stuff for now + debug_print("Global stuff: %s" % self.nameStack) + elif self.braceDepth > len(self.nameSpaces) + 1: + debug_print("trace") + else: + debug_print("Discarded statement %s" % (self.nameStack,)) + + try: + self.nameStackHistory[self.braceDepth] = (nameStackCopy, self.curClass) + except: + self.nameStackHistory.append((nameStackCopy, self.curClass)) + + # its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here + self.nameStack = [] + self.lex.doxygenCommentCache = "" + self.curTemplate = None + + def _parse_template(self): + tok = self._next_token_must_be("<") + consumed = self._consume_balanced_tokens(tok) + tmpl = " ".join(tok.value for tok in consumed) + tmpl = ( + tmpl.replace(" :: ", "::") + .replace(" <", "<") + .replace("< ", "<") + .replace(" >", ">") + .replace("> ", ">") + .replace(" , ", ", ") + .replace(" = ", "=") + ) + self.curTemplate = "template" + tmpl + + def _parse_gcc_attribute(self): + tok1 = self._next_token_must_be("(") + tok2 = self._next_token_must_be("(") + self._consume_balanced_tokens(tok1, tok2) + + _attribute_specifier_seq_start_types = ("DBL_LBRACKET", "NAME") + _attribute_specifier_seq_start_values = ("[[", "alignas") + + def _parse_attribute_specifier_seq(self, tok): + # TODO: retain the attributes and do something with them + # attrs = [] + + while True: + if tok.type == "DBL_LBRACKET": + tokens = self._consume_balanced_tokens(tok) + # attrs.append(Attribute(tokens)) + elif tok.type == "NAME" and tok.value == "alignas": + next_tok = self._next_token_must_be("(") + tokens = self._consume_balanced_tokens(next_tok) + # attrs.append(AlignasAttribute(tokens)) + else: + self.lex.return_token(tok) + break + + # multiple attributes can be specified + tok = self.lex.token_if(*self._attribute_specifier_seq_start_types) + if tok is None: + break + + # return attrs + + def _parse_enum(self): + """ + opaque_enum_declaration: enum_key [attribute_specifier_seq] IDENTIFIER [enum_base] ";" + + enum_specifier: enum_head "{" [enumerator_list] "}" + | enum_head "{" enumerator_list "," "}" + + enum_head: enum_key [attribute_specifier_seq] [IDENTIFIER] [enum_base] + | enum_key [attribute_specifier_seq] nested_name_specifier IDENTIFIER [enum_base] + + enum_key: "enum" + | "enum" "class" + | "enum" "struct" + + enum_base: ":" type_specifier_seq + """ + debug_print("parsing enum") + + is_typedef = False + self.lex.return_tokens(self.stmtTokens) + + doxygen = self._get_stmt_doxygen() + + tok = self.lex.token() + if tok.value == "typedef": + is_typedef = True + tok = self.lex.token() + + if tok.value != "enum": + raise self._parse_error((tok,), "enum") + + location = tok.location + + is_class = False + nametok = self.lex.token() + if nametok.value in ("class", "struct"): + is_class = True + nametok = self.lex.token() + + if nametok.value == "__attribute__": + self._parse_gcc_attribute() + nametok = self.lex.token() + + if nametok.value in self._attribute_specifier_seq_start_values: + self._parse_attribute_specifier_seq(nametok) + nametok = self.lex.token() + + # TODO: nested_name_specifier + name = "" + if nametok.type == "NAME": + name = nametok.value + debug_print("enum name is '%s'", name) + tok = self.lex.token() + else: + debug_print("anonymous enum") + tok = nametok + + base = [] + if tok.type == ":": + while True: + tok = self.lex.token() + if tok.type in ("{", ";"): + break + base.append(tok.value) + + newEnum = CppEnum(name, doxygen, location) + if is_typedef: + newEnum["typedef"] = True + if is_class: + newEnum["isclass"] = True + if base: + newEnum["type"] = "".join(base) + + instancesData = [] + + if tok.type == "{": + self.braceHandled = True + self._parse_enumerator_list(newEnum["values"]) + newEnum.resolve_enum_values(newEnum["values"]) + tok = self.lex.token() + + if tok.value == "__attribute__": + self._parse_gcc_attribute() + tok = self.lex.token() + + if tok.type == "NAME": + if newEnum["typedef"]: + newEnum["name"] = tok.value + self._next_token_must_be(";") + else: + # this is an instance of the enum + instancesData.append(tok.value) + while True: + tok = self.lex.token() + if tok.type == ";": + break + instancesData.append(tok.value) + elif tok.type != ";": + raise self._parse_error((tok,), ";") + + self._install_enum(newEnum, instancesData) + + def _install_enum(self, newEnum, instancesData): + if len(self.curClass): + newEnum["namespace"] = self.cur_namespace(False) + klass = self.classes[self.curClass] + klass["enums"][self.curAccessSpecifier].append(newEnum) + if self.curAccessSpecifier == "public" and "name" in newEnum: + klass._public_enums[newEnum["name"]] = newEnum + else: + newEnum["namespace"] = self.cur_namespace(True) + self.enums.append(newEnum) + if "name" in newEnum and newEnum["name"]: + self.global_enums[newEnum["name"]] = newEnum + + # This enum has instances, turn them into properties + if instancesData: + instances = list(_split_by_comma(instancesData)) + instanceType = "enum" + if "name" in newEnum: + instanceType = newEnum["name"] + addToVar = {"enum_type": newEnum} + for instance in instances: + self.nameStack = [instanceType] + instance + self._evaluate_property_stack(clearStack=False, addToVar=addToVar) + + def _parse_enumerator_list(self, values): + """ + enumerator_list: enumerator_definition + | enumerator_list "," enumerator_definition + + enumerator_definition: enumerator + | enumerator "=" constant_expression + + enumerator: IDENTIFIER + """ + while True: + name_tok = self._next_token_must_be("}", "NAME") + if name_tok.value == "}": + return + + value = {"name": name_tok.value} + doxygen = self.lex.get_doxygen() + if doxygen: + value["doxygen"] = doxygen + values.append(value) + + debug_print("enumerator value '%s'", value["name"]) + + tok = self._next_token_must_be("}", ",", "=", "DBL_LBRACKET") + if tok.type == "DBL_LBRACKET": + self._parse_attribute_specifier_seq(tok) + tok = self._next_token_must_be("}", ",", "=") + + if tok.type == "}": + return + elif tok.type == ",": + continue + elif tok.type == "=": + v = [] + while True: + tok = self.lex.token() + if tok.type == "}": + value["value"] = " ".join(v) + return + elif tok.type == ",": + value["value"] = " ".join(v) + break + elif tok.type in self._balanced_token_map: + v.extend(t.value for t in self._consume_balanced_tokens(tok)) + else: + v.append(tok.value) + + def _strip_parent_keys(self): + """Strip all parent (and method) keys to prevent loops""" + obj_queue = [self] + while len(obj_queue): + obj = obj_queue.pop() + trace_print("pop %s type %s" % (obj, type(obj))) + try: + if "parent" in obj.keys(): + del obj["parent"] + trace_print("Stripped parent from %s" % obj.keys()) + except: + pass + try: + if "method" in obj.keys(): + del obj["method"] + trace_print("Stripped method from %s" % obj.keys()) + except: + pass + # Figure out what sub types are one of ours + try: + if not hasattr(obj, "keys"): + obj = obj.__dict__ + for k in obj.keys(): + trace_print("-Try key %s" % (k)) + trace_print("-type %s" % (type(obj[k]))) + if k in ["nameStackHistory", "parent", "_public_typedefs"]: + continue + if type(obj[k]) == list: + for i in obj[k]: + trace_print("push l %s" % i) + obj_queue.append(i) + elif type(obj[k]) == dict: + if len(obj): + trace_print("push d %s" % obj[k]) + obj_queue.append(obj[k]) + elif type(obj[k]) == type(type(0)): + if type(obj[k]) == int: + obj[k] = "int" + elif type(obj[k]) == str: + obj[k] = "string" + else: + obj[k] = "???" + trace_print("next key\n") + except: + trace_print("Exception") + + def toJSON(self, indent=4, separators=None): + """Converts a parsed structure to JSON""" + import json + + self._strip_parent_keys() + try: + del self.__dict__["classes_order"] + except: + pass + return json.dumps(self.__dict__, indent=indent, separators=separators) + + def __repr__(self): + rtn = { + "classes": self.classes, + "functions": self.functions, + "enums": self.enums, + "variables": self.variables, + } + return repr(rtn) + + def __str__(self): + rtn = "" + for className in list(self.classes.keys()): + rtn += "%s\n" % self.classes[className] + if self.functions: + rtn += "// functions\n" + for f in self.functions: + rtn += "%s\n" % f + if self.variables: + rtn += "// variables\n" + for f in self.variables: + rtn += "%s\n" % f + if self.enums: + rtn += "// enums\n" + for f in self.enums: + rtn += "%s\n" % f + return rtn diff --git a/SDK/C++/public/python/CppHeaderParser/LICENSE.txt b/SDK/C++/public/python/CppHeaderParser/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..21a244d477c68e67dc235c21978ec324d0298a34 --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/LICENSE.txt @@ -0,0 +1,33 @@ +Copyright (C) 2011, Jashua R. Cloutier +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +* Neither the name of Jashua R. Cloutier nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. Stories, + blog entries etc making reference to this project may mention the + name Jashua R. Cloutier in terms of project originator/creator etc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/SDK/C++/public/python/CppHeaderParser/README.rst b/SDK/C++/public/python/CppHeaderParser/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..77e2eb6516a8b08b78eab03894923cb7bcb5bfc0 --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/README.rst @@ -0,0 +1,67 @@ +robotpy-cppheaderparser +======================= + +|Build Status| + +CppHeaderParser is a pure python C++ header parser that parses C++ +headers and creates a data structure that you can use to do many types +of things. We’ve found it particularly useful for creating programs that +generate python wrappers around existing C++ programs. + +robotpy-cppheaderparser is a fork of the `CppHeaderParser`_ library +originally created by @senex. CppHeaderParser is an excellent library +and critical to some of the stuff we do in the RobotPy project. +Unfortunately, the maintainer seems to be busy, so +robotpy-cppheaderparser was born. + +We aim to maintain (some) compatibility with the existing code and make +improvements and bugfixes as we need them -- though some decisions made +early on in this code's development means some compatibility may be broken +as things get fixed. + +If you find an bug, we encourage you to submit a pull request! New +changes will only be accepted if there are tests to cover the change you +made (and if they don’t break existing tests). + +.. note:: CppHeaderParser only does some very minimal interpretation of + preprocessor directives -- and we're looking at removing some + of that from this library. If you need anything complex, you + should preprocess the code yourself. You can use the excellent + pure python preprocessor `pcpp`_, or the preprocessing facilities + provided by your favorite compiler. + +Documentation +------------- + +Documentation can be found at https://cppheaderparser.readthedocs.io + +Install +------- + +:: + + pip install robotpy-cppheaderparser + +License +------- + +BSD License + +Authors +------- + +Originally developed by Jashua Cloutier, this fork is maintained by the +RobotPy project. + +Past contributors include: + +* Jashua Cloutier +* Chris Love +* HartsAntler + +.. _CppHeaderParser: https://bitbucket.org/senex/cppheaderparser + +.. _pcpp: https://github.com/ned14/pcpp + +.. |Build Status| image:: https://travis-ci.org/robotpy/robotpy-cppheaderparser.svg?branch=master + :target: https://travis-ci.org/robotpy/robotpy-cppheaderparser \ No newline at end of file diff --git a/SDK/C++/public/python/CppHeaderParser/__init__.py b/SDK/C++/public/python/CppHeaderParser/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..facdf9b7b972cfda0148c0017cff5c5f80cb4cb6 --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/__init__.py @@ -0,0 +1,7 @@ +# CppHeaderParser package +# Author: Jashua Cloutier (contact via sourceforge username:senexcanis) + +from .CppHeaderParser import * +from .CppHeaderParser import __version__ + +# __all__ = ['CppHeaderParser'] diff --git a/SDK/C++/public/python/CppHeaderParser/doxygen.py b/SDK/C++/public/python/CppHeaderParser/doxygen.py new file mode 100644 index 0000000000000000000000000000000000000000..cac2b35e1e535832de78c75474b7bbe75956592f --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/doxygen.py @@ -0,0 +1,32 @@ +def extract_doxygen_method_params(doxystr): + """ + Given a doxygen string for a method, extract parameter descriptions + """ + doxyVarDesc = {} + doxyLines = doxystr.split("\n") + lastParamDesc = "" + for doxyLine in doxyLines: + if " @param " in doxyLine or " \\param " in doxyLine: + try: + # Strip out the param + doxyLine = doxyLine[doxyLine.find("param ") + 6 :] + (var, desc) = doxyLine.split(" ", 1) + doxyVarDesc[var] = desc.strip() + lastParamDesc = var + except: + pass + elif " @return " in doxyLine or " \return " in doxyLine: + lastParamDesc = "" + # not handled for now + elif lastParamDesc: + try: + doxyLine = doxyLine.strip() + if " " not in doxyLine: + lastParamDesc = "" + continue + doxyLine = doxyLine[doxyLine.find(" ") + 1 :] + doxyVarDesc[lastParamDesc] += " " + doxyLine + except: + pass + + return doxyVarDesc diff --git a/SDK/C++/public/python/CppHeaderParser/lexer.py b/SDK/C++/public/python/CppHeaderParser/lexer.py new file mode 100644 index 0000000000000000000000000000000000000000..fa62a1a419d0c41f811f3e39fb90f72c54ce62f9 --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/lexer.py @@ -0,0 +1,233 @@ +from collections import deque +import ply.lex as lex +import re + +_line_re = re.compile(r'^#line (\d+) "(.*)"') + + +class Lexer(object): + + tokens = [ + "NUMBER", + "FLOAT_NUMBER", + "NAME", + "COMMENT_SINGLELINE", + "COMMENT_MULTILINE", + "PRECOMP_MACRO", + "PRECOMP_MACRO_CONT", + "DIVIDE", + "CHAR_LITERAL", + "STRING_LITERAL", + "NEWLINE", + "ELLIPSIS", + "DBL_LBRACKET", + "DBL_RBRACKET", + "DBL_COLON", + "SHIFT_LEFT", + ] + + literals = [ + "<", + ">", + "(", + ")", + "{", + "}", + "[", + "]", + ";", + ":", + ",", + "\\", + "|", + "%", + "^", + "!", + "*", + "-", + "+", + "&", + "=", + "'", + ".", + ] + + t_ignore = " \t\r?@\f" + t_NUMBER = r"[0-9][0-9XxA-Fa-f]*" + t_FLOAT_NUMBER = r"[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?" + t_NAME = r"[A-Za-z_~][A-Za-z0-9_]*" + + def t_PRECOMP_MACRO(self, t): + r"\#.*" + m = _line_re.match(t.value) + if m: + filename = m.group(2) + if filename not in self._filenames_set: + self.filenames.append(filename) + self._filenames_set.add(filename) + self.filename = filename + + self.line_offset = 1 + self.lex.lineno - int(m.group(1)) + + else: + return t + + t_PRECOMP_MACRO_CONT = r".*\\\n" + + def t_COMMENT_SINGLELINE(self, t): + r"\/\/.*\n?" + if t.value.startswith("///") or t.value.startswith("//!"): + self.comments.append(t.value.lstrip("\t ").rstrip("\n")) + t.lexer.lineno += t.value.count("\n") + return t + + t_DIVIDE = r"/(?!/)" + t_CHAR_LITERAL = "'.'" + t_ELLIPSIS = r"\.\.\." + t_DBL_LBRACKET = r"\[\[" + t_DBL_RBRACKET = r"\]\]" + t_DBL_COLON = r"::" + t_SHIFT_LEFT = r"<<" + # SHIFT_RIGHT introduces ambiguity + + # found at http://wordaligned.org/articles/string-literals-and-regular-expressions + # TODO: This does not work with the string "bla \" bla" + t_STRING_LITERAL = r'"([^"\\]|\\.)*"' + + # Found at http://ostermiller.org/findcomment.html + def t_COMMENT_MULTILINE(self, t): + r"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/\n?" + if t.value.startswith("/**") or t.value.startswith("/*!"): + # not sure why, but get double new lines + v = t.value.replace("\n\n", "\n") + # strip prefixing whitespace + v = re.sub("\n[\\s]+\\*", "\n*", v) + self.comments = v.splitlines() + t.lexer.lineno += t.value.count("\n") + return t + + def t_NEWLINE(self, t): + r"\n+" + t.lexer.lineno += len(t.value) + del self.comments[:] + return t + + def t_error(self, v): + print("Lex error: ", v) + + def __init__(self, filename): + self.lex = lex.lex(module=self) + self.input = self.lex.input + + # For tracking current file/line position + self.filename = filename + self.line_offset = 0 + + self.filenames = [] + self._filenames_set = set() + + if self.filename: + self.filenames.append(filename) + self._filenames_set.add(filename) + + # Doxygen comments + self.comments = [] + + self.lookahead = deque() + + def current_location(self): + if self.lookahead: + return self.lookahead[0].location + return self.filename, self.lex.lineno - self.line_offset + + def get_doxygen(self): + """ + This should be called after the first element of something has + been consumed. + + It will lookahead for comments that come after the item, if prior + comments don't exist. + """ + + # Assumption: This function is either called at the beginning of a + # statement or at the end of a statement + + if self.comments: + comments = self.comments + else: + comments = [] + # only look for comments until a newline (including lookahead) + for tok in self.lookahead: + if tok.type == "NEWLINE": + return "" + + while True: + tok = self.lex.token() + comments.extend(self.comments) + + if tok is None: + break + + tok.location = (self.filename, tok.lineno - self.line_offset) + ttype = tok.type + if ttype == "NEWLINE": + self.lookahead.append(tok) + break + + if ttype not in self._discard_types: + self.lookahead.append(tok) + + if ttype == "NAME": + break + + del self.comments[:] + + comments = "\n".join(comments) + del self.comments[:] + return comments + + _discard_types = {"NEWLINE", "COMMENT_SINGLELINE", "COMMENT_MULTILINE"} + + def token(self, eof_ok=False): + tok = None + while self.lookahead: + tok = self.lookahead.popleft() + if tok.type not in self._discard_types: + return tok + + while True: + tok = self.lex.token() + if tok is None: + if not eof_ok: + raise EOFError("unexpected end of file") + break + + if tok.type not in self._discard_types: + tok.location = (self.filename, tok.lineno - self.line_offset) + break + + return tok + + def token_if(self, *types): + tok = self.token(eof_ok=True) + if tok is None: + return None + if tok.type not in types: + # put it back on the left in case it was retrieved + # from the lookahead buffer + self.lookahead.appendleft(tok) + return None + return tok + + def return_token(self, tok): + self.lookahead.appendleft(tok) + + def return_tokens(self, toks): + self.lookahead.extendleft(reversed(toks)) + + +if __name__ == "__main__": + try: + lex.runmain(lexer=Lexer(None)) + except EOFError: + pass diff --git a/SDK/C++/public/python/CppHeaderParser/tojson.py b/SDK/C++/public/python/CppHeaderParser/tojson.py new file mode 100644 index 0000000000000000000000000000000000000000..e9ea24a2f3bab6eb5e13a416d3c9c93f0aef6825 --- /dev/null +++ b/SDK/C++/public/python/CppHeaderParser/tojson.py @@ -0,0 +1,10 @@ +# Utility module + +import sys +import json + +from .CppHeaderParser import CppHeader + +if __name__ == "__main__": + for arg in sys.argv[1:]: + print(CppHeader(arg).toJSON()) diff --git a/SDK/C++/public/python/README.md b/SDK/C++/public/python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a1a955eba963313d9d1f1d2eaca1466715fc070d --- /dev/null +++ b/SDK/C++/public/python/README.md @@ -0,0 +1,36 @@ +# Python API generator + +Dependencies + * python3-ply (cpp header parser) + +Build system uses Pybind11 to generate a python module. Most of the bindings are +automatically generated by gen.py script which is automatically called by CMake +when necessary. + +Headers included for automatic binding generation are defined in +SDK_AUTO_HEADERS variable in CMakeLists.txt. Included pybind headers are defined +in automatic_bindings.cpp.in, which to which generated code is inserted before +build. Several (empty) macros are used in headers to annoate Python API details. + + * PY_API function/method is to be included in Python API + * PY_NO_SHARED_PTR shared_ptr<> is not used with instances of this class. + https://pybind11.readthedocs.io/en/latest/advanced/smart_ptrs.html?#std-shared-ptr + * PY_RV_LIFETIME_PARENT lifetime of method's return valued is tied to + lifetime of parent objects (this). (return_value_policy::reference_internal + is set for this method) + https://pybind11.readthedocs.io/en/latest/advanced/functions.html#return-value-policies + +## Notes: + * Binding to default constructor is generated for structs. Class constructors + are not available via Python at the moment. + * Keyword arguments are supported in Python when function arguments are named + in the header. + * Default arguments are supported (extracted from header). + * Public class properties are available in python, read-only if const, + otherwise read write. + +## Not supported (yet) in automatic binding generation: + * Nested classes + * Static members + * Constructors + * Automatic documentation (Doxygen) diff --git a/SDK/C++/public/python/automatic_bindings.cpp.in b/SDK/C++/public/python/automatic_bindings.cpp.in new file mode 100644 index 0000000000000000000000000000000000000000..291de8913a266dd029a6e7ccbc4e7f3796ed82d3 --- /dev/null +++ b/SDK/C++/public/python/automatic_bindings.cpp.in @@ -0,0 +1,15 @@ +{includes} + +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include <pybind11/eigen.h> + +#include "types/image.hpp" + +namespace py = pybind11; + +using namespace voltu; + +void py_automatic_bindings(py::module& m) {{ + {code} +}} diff --git a/SDK/C++/public/python/examples/example1.py b/SDK/C++/public/python/examples/example1.py new file mode 100644 index 0000000000000000000000000000000000000000..1cc1a26681470b9212662da2959224daf35bf89c --- /dev/null +++ b/SDK/C++/public/python/examples/example1.py @@ -0,0 +1,33 @@ +# Simple example which opens file provided as command line argument +# and displays the first source in a window (using OpenCV) + +import cv2 +import voltu +import time +import sys +import os + +if len(sys.argv) != 2: + print("%s filename" % os.path.basename(__file__)) + exit(1) + +#if not os.path.exists(sys.argv[1]): +# print("can't find %s" % sys.argv[1]) +# exit(1) + +api = voltu.instance() +api.open(sys.argv[1]) +room = api.getRoom(0) + +while True: + try: + room.waitNextFrame(1000) + frames = room.getFrame().getImageSet(voltu.Channel.kColour) + im = frames[0].getHost() + cv2.imshow("im", im) + + if cv2.waitKey(10) == 27: + break + + except Exception as e: + print(e) diff --git a/SDK/C++/public/python/examples/example2.py b/SDK/C++/public/python/examples/example2.py new file mode 100644 index 0000000000000000000000000000000000000000..629fec948de0f93c9d94184e8a79033afb211896 --- /dev/null +++ b/SDK/C++/public/python/examples/example2.py @@ -0,0 +1,35 @@ +# Simple example which opens file provided as command line argument +# and renders a virtual camera into window (using OpenCV) + +import cv2 +import voltu +import time +import sys +import os + +if len(sys.argv) != 2: + print("%s filename" % os.path.basename(__file__)) + exit(1) + +#if not os.path.exists(sys.argv[1]): +# print("can't find %s" % sys.argv[1]) +# exit(1) + +api = voltu.instance() +api.open(sys.argv[1]) +room = api.getRoom(0) +cam = api.createCamera() + +while True: + try: + room.waitNextFrame(1000) + cam.submit(room.getFrame()) + frames = cam.getFrame().getImageSet(voltu.Channel.kColour) + im = frames[0].getHost() + cv2.imshow("im", im) + + if cv2.waitKey(10) == 27: + break + + except Exception as e: + print(e) diff --git a/SDK/C++/public/python/gen.py b/SDK/C++/public/python/gen.py new file mode 100755 index 0000000000000000000000000000000000000000..00e8252c7efea6975acc331c5076453aa5c3695b --- /dev/null +++ b/SDK/C++/public/python/gen.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 + +import sys +import os + +from CppHeaderParser import CppHeader, CppParseError + +# Extract line from file, as not directly with CppHeader +def read_line(file, lineno): + with open(file) as f: + for i, line in enumerate(f, 1): + if i == lineno: + return line + + +def get_loc_msg(data): + return "({0}:{1})".format(data["filename"], data["line_number"]) + +def print_warn(msg, loc=None): + if loc is not None: + msg += " " + get_loc_msg(loc) + print("WARNING: %s" % msg, file=sys.stderr) + +def print_err(msg, loc=None): + if loc is not None: + msg += " " + get_loc_msg(loc) + print("ERROR: %s" % msg, file=sys.stderr) + +def include_in_api(data): + return "PY_API" in data["debug"] + +def create_enum_bindigs(enum, parent=[], pybind_handle="", export_values=False): + name_full = parent + [enum["name"]] + name_py = enum["name"] + cpp = [] + cpp.append("py::enum_<{name_full}>({handle}, \"{name}\")".format( + name_full = "::".join(name_full), + handle = pybind_handle, + name = name_py + )) + for val in enum["values"]: + cpp.append("\t.value(\"{name}\", {name_cpp})".format( + name = val["name"], + name_cpp = "::".join(name_full + [val["name"]]) + )) + + if export_values: + cpp.append("\t.export_values()") + + return "\n\t".join(cpp) + +def create_function_bindings(func, parent=[], pybind_handle=None): + func_name = func["name"] + full_name = parent + [func_name] + full_name_cpp = "::".join(full_name) + + if "PY_API" not in func["debug"]: + print_err("%s not included in Python API" % full_name_cpp, func) + raise ValueError("No PY_API") + + args = ["\"{0}\"".format(func_name), "&{0}".format(full_name_cpp)] + + for param in func["parameters"]: + param_name = param["name"] + + if param_name == "&": + print_warn("Argument name missing for %s" % full_name_cpp, func) + + continue + + if "default" in param: + args.append("py::arg(\"{0}\") = {1}".format( + param_name, param["default"])) + else: + args.append("py::arg(\"{0}\")".format(param_name)) + + if "PY_RV_LIFETIME_PARENT" in func["debug"]: + if func["parent"] is None: + print_err("PY_RV_LIFETIME_PARENT used for function", func) + raise ValueError() + + args.append("py::return_value_policy::reference_internal") + + cpp = "def({0})".format(", ".join(args)) + if pybind_handle is not None: + cpp = pybind_handle + "." + cpp + + return cpp + +def create_class_bindings(cls, parent=[], pybind_handle=""): + cls_name = cls["name"] + full_name = parent + [cls_name] + cpp = [] + + if "PY_NO_SHARED_PTR" not in cls["debug"]: + cls_cpp = "py::class_<{name}, std::shared_ptr<{name}>>({handle}, \"{name}\")" + else: + cls_cpp = "py::class_<{name}>({handle}, \"{name}\")" + cpp.append(cls_cpp.format(handle=pybind_handle, name=cls_name)) + + if cls["declaration_method"] == "struct": + cpp.append(".def(py::init<>())") + + for method in cls["methods"]["public"]: + if include_in_api(method): + cpp.append("." + create_function_bindings(method, full_name)) + + for field in cls["properties"]["public"]: + if field["constant"]: + field_cpp = ".def_property_readonly(\"{name}\", &{cpp_name})" + else: + field_cpp = ".def_readwrite(\"{name}\", &{cpp_name})" + field_name = field["name"] + field_name_cpp = "::".join(full_name + [field_name]) + cpp.append(field_cpp.format(name=field_name, cpp_name=field_name_cpp)) + + return "\n\t\t".join(cpp) + +if __name__ == "__main__": + + from pprint import pprint + + if (len(sys.argv) < 4): + print("gen.py output include_directory input files ...") + exit(1) + + handle = "m" + fout = sys.argv[1] + includedir = sys.argv[2] + fsin = sys.argv[3:] + + out = [] + includes = [] + for fname in fsin: + includes.append(fname) + + hdr = CppHeader(os.path.join(includedir, fname)) + # note .strip("::"), inconsistent behavior for classes vs enum/func + + for data in hdr.enums: + ns = data["namespace"].strip("::") # bug? in parser + out.append(create_enum_bindigs(data, [ns], handle) + ";") + + for data in hdr.classes.values(): + ns = data["namespace"] + # workaround: parser does not save debug in same way as for funcs + data["debug"] = read_line(data["filename"], data["line_number"]) + out.append(create_class_bindings(data, [ns], handle) + ";") + + for data in hdr.functions: + ns = data["namespace"].strip("::") # bug? in parser + if include_in_api(data): + out.append(create_function_bindings(data, [ns], handle) + ";") + + includes = "\n".join("#include <{0}>".format(i) for i in includes) + template_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "automatic_bindings.cpp.in") + with open(template_file, "r") as f: + template = f.read() + out = template.format(includes=includes, code="\n\t".join(out)) + with open(fout, "w",) as f: + f.write(out) diff --git a/SDK/C++/public/python/module.cpp b/SDK/C++/public/python/module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ba4e9e2a48f24cbe34353796bf23728f2f93656 --- /dev/null +++ b/SDK/C++/public/python/module.cpp @@ -0,0 +1,20 @@ + +#include "module.hpp" + +#include <voltu/initialise.hpp> +#include <voltu/types/errors.hpp> + +namespace py = pybind11; + +void py_exceptions(pybind11::module& m) { + py::register_exception<voltu::exceptions::Exception>(m, "Error"); + py::register_exception<voltu::exceptions::BadImageChannel>(m, "ErrorBadImageChannel"); + py::register_exception<voltu::exceptions::NoFrame>(m, "ErrorNoFrame"); + py::register_exception<voltu::exceptions::AlreadyInit>(m, "ErrorAlreadyInit"); + py::register_exception<voltu::exceptions::BadSourceURI>(m, "ErrorBadSourceURI"); +} + +PYBIND11_MODULE(voltu, m) { + py_exceptions(m); + py_automatic_bindings(m); +} diff --git a/SDK/C++/public/python/module.hpp b/SDK/C++/public/python/module.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b605cf7f6b7030701858cf8d4cf2ffbba95636bf --- /dev/null +++ b/SDK/C++/public/python/module.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include <pybind11/pybind11.h> + +void py_automatic_bindings(pybind11::module& m); diff --git a/SDK/C++/public/python/types/image.hpp b/SDK/C++/public/python/types/image.hpp new file mode 100644 index 0000000000000000000000000000000000000000..15cd41034f8cd748dd9413793e1bbe9ce8825496 --- /dev/null +++ b/SDK/C++/public/python/types/image.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include <pybind11/pybind11.h> +#include <pybind11/numpy.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <voltu/types/channel.hpp> +#include <voltu/types/image.hpp> + +#include <cstdint> +#include <exception> + +namespace pybind11 { +namespace detail { + +template<> +struct type_caster<voltu::ImageData> { +public: + PYBIND11_TYPE_CASTER(voltu::ImageData, _("Image")); + + bool load(py::handle src, bool convert) { + throw std::runtime_error("ImageData conversion python->c++ not supported"); + return true; + } + + static py::handle cast(const voltu::ImageData& src, py::return_value_policy policy, py::handle parent) { + switch(src.format) { + case voltu::ImageFormat::kFloat32: + return py::array( + { size_t(src.height), size_t(src.width) }, + { size_t(src.pitch), sizeof(float) }, + reinterpret_cast<float*>(src.data) + ).release(); + + case voltu::ImageFormat::kBGRA8: + return py::array( + { size_t(src.height), size_t(src.width), size_t(4) }, + { size_t(src.pitch), size_t(4)*sizeof(uint8_t), size_t(1)*sizeof(uint8_t) }, + reinterpret_cast<uint8_t*>(src.data) + ).release(); + + default: + return py::array(); + } + } +}; + +} // namespace detail +} // namespace pybind11 + diff --git a/SDK/C++/public/samples/basic_file/main.cpp b/SDK/C++/public/samples/basic_file/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c983864275a217fc1d2cc6628012dc698f8ad19e --- /dev/null +++ b/SDK/C++/public/samples/basic_file/main.cpp @@ -0,0 +1,56 @@ +#include <voltu/voltu.hpp> +#include <voltu/opencv.hpp> +#include <iostream> +#include <thread> +#include <chrono> + +#include <opencv2/highgui.hpp> + +using std::cout; +using std::endl; +using std::string; + +int main(int argc, char **argv) +{ + if (argc != 2) return -1; + + auto vtu = voltu::instance(); + + if (!vtu->open(argv[1])) + { + cout << "Could not open source" << endl; + return -1; + } + + while (vtu->listRooms().size() == 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + auto room = vtu->getRoom(vtu->listRooms().front()); + if (!room) + { + cout << "Could not get room" << endl; + return -1; + } + cout << "Room Name: " << room->getName() << endl; + + while (room->active()) + { + if (!room->waitNextFrame(1000)) break; + + auto frame = room->getFrame(); + auto imgset = frame->getImageSet(voltu::Channel::kDepth); + + for (auto img : imgset) + { + cv::Mat m; + voltu::cv::visualise(img, m); + cv::imshow(string("Image-") + img->getName(), m); + } + + if (cv::waitKey(1) == 27) break; + } + + return 0; +} diff --git a/SDK/C++/public/samples/basic_test/main.cpp b/SDK/C++/public/samples/basic_test/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f41fc38a592097f032cedb90836c142381036aa --- /dev/null +++ b/SDK/C++/public/samples/basic_test/main.cpp @@ -0,0 +1,36 @@ +#include <voltu/voltu.hpp> +#include <iostream> +#include <thread> +#include <chrono> + +using std::cout; +using std::endl; +using std::string; + +int main(int argc, char **argv) +{ + if (argc != 2) return -1; + + auto vtu = voltu::instance(); + + if (!vtu) + { + cout << "Failed to start VolTu" << endl; + return -1; + } + + if (!vtu->open(argv[1])) + { + cout << "Could not open source" << endl; + return -1; + } + + while (vtu->listRooms().size() == 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + auto room = vtu->getRoom(vtu->listRooms().front()); + + return 0; +} diff --git a/SDK/C++/public/samples/basic_virtual_cam/main.cpp b/SDK/C++/public/samples/basic_virtual_cam/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f1b2634b7135bfb3c303fdd6fd54e81da6a69a38 --- /dev/null +++ b/SDK/C++/public/samples/basic_virtual_cam/main.cpp @@ -0,0 +1,80 @@ +#include <voltu/voltu.hpp> +#include <voltu/opencv.hpp> +#include <iostream> +#include <thread> +#include <chrono> + +#include <opencv2/highgui.hpp> + +using std::cout; +using std::endl; +using std::string; + +int main(int argc, char **argv) +{ + if (argc != 2) return -1; + + auto vtu = voltu::instance(); + + if (!vtu->open(argv[1])) + { + cout << "Could not open source" << endl; + return -1; + } + + while (vtu->listRooms().size() == 0) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + auto room = vtu->getRoom(vtu->listRooms().front()); + if (!room) + { + cout << "Could not get room" << endl; + return -1; + } + cout << "Room Name: " << room->getName() << endl; + + auto obs = vtu->createObserver(); + + obs->setResolution(1280, 720); + obs->setFocalLength(900); + obs->setStereo(false); + + Eigen::Matrix4f pose; + pose.setIdentity(); + float posz = 0.0f; + float dz = 0.05f; + + while (room->active()) + { + // Note: waitNextFrame is optional, getFrame will get old frame otherwise + //if (!room->waitNextFrame(1000)) break; + auto rframe = room->getFrame(); + + Eigen::Affine3f aff = Eigen::Affine3f::Identity(); + aff.translate(Eigen::Vector3f(0.0f, 0.0f, posz)); + pose = aff.matrix(); + posz += dz; + if (posz > 1.5f) dz = -dz; + if (posz < -1.0f) dz = -dz; + obs->setPose(pose); + + obs->submit(rframe); + if (!obs->waitCompletion(1000)) break; + + auto cframe = obs->getFrame(); + auto imgset = cframe->getImageSet(voltu::Channel::kColour); + + for (auto img : imgset) + { + cv::Mat m; + voltu::cv::convert(img, m); + cv::imshow(string("Camera-") + img->getName(), m); + } + + if (cv::waitKey(20) == 27) break; + } + + return 0; +} diff --git a/SDK/C++/public/voltu.cpp b/SDK/C++/public/voltu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25b0f4019ccd8c5df7c3090b7c7ea3198162baef --- /dev/null +++ b/SDK/C++/public/voltu.cpp @@ -0,0 +1,95 @@ +#include <voltu/initialise.hpp> +#include <voltu/types/errors.hpp> +#include <voltu/voltu.hpp> + +#if defined(WIN32) +#include <windows.h> +#else +#include <dlfcn.h> +#endif + +static bool g_init = false; + +typedef void* Library; + +static Library loadLibrary(const char *file) +{ +#if defined(WIN32) + return (Library)LoadLibraryEx(file, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); +#else + return (Library)dlopen(file, RTLD_LOCAL | RTLD_NOW); +#endif +} + +static void *getFunction(Library lib, const char *fname) +{ +#if defined(WIN32) + return (void*)GetProcAddress((HMODULE)lib, fname); +#else + return dlsym(lib, fname); +#endif +} + + +static void unloadLibrary(Library lib) +{ + if (!lib) return; + +#if defined(WIN32) + FreeLibrary((HMODULE)lib); +#else + dlclose(lib); +#endif +} + +static std::string locateLibrary() +{ +#if defined(WIN32) + return "voltu.dll"; +#else + return "libvoltu.so"; +#endif +} + +std::shared_ptr<voltu::System> voltu::instance() +{ + if (g_init) return nullptr; + + std::string name = locateLibrary(); + Library handle = loadLibrary(name.c_str()); + + if (handle) + { + voltu::System* (*init)(); + init = (voltu::System* (*)())getFunction(handle, "voltu_initialise"); + + if (init) + { + g_init = true; + std::shared_ptr<voltu::System> instance(init()); + + if (!instance) + { + throw voltu::exceptions::LibraryLoadFailed(); + } + + auto ver = instance->getVersion(); + if (ver.major != VOLTU_VERSION_MAJOR || ver.minor != VOLTU_VERSION_MINOR) + { + throw voltu::exceptions::LibraryVersionMismatch(); + } + + return instance; + } + else + { + throw voltu::exceptions::LibraryLoadFailed(); + } + } + else + { + throw voltu::exceptions::LibraryLoadFailed(); + } + + return nullptr; +} diff --git a/SDK/C++/public/voltu_cv.cpp b/SDK/C++/public/voltu_cv.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b77155b6450560eb7808f7294013926fea382e7 --- /dev/null +++ b/SDK/C++/public/voltu_cv.cpp @@ -0,0 +1,50 @@ +#include <voltu/opencv.hpp> + +#include <opencv2/imgproc.hpp> + +void voltu::cv::convert(voltu::ImagePtr img, ::cv::Mat &mat) +{ + voltu::ImageData data = img->getHost(); + + if (data.format == voltu::ImageFormat::kBGRA8) + { + mat = ::cv::Mat(data.height, data.width, CV_8UC4, data.data); + } + else if (data.format == voltu::ImageFormat::kFloat32) + { + mat = ::cv::Mat(data.height, data.width, CV_32FC1, data.data); + } + else + { + mat = ::cv::Mat(); + } +} + +void voltu::cv::convert(voltu::ImagePtr img, ::cv::cuda::GpuMat &mat) +{ + +} + +void voltu::cv::visualise(voltu::ImagePtr img, ::cv::Mat &mat) +{ + voltu::ImageData data = img->getHost(); + + if (data.format == voltu::ImageFormat::kBGRA8) + { + voltu::cv::convert(img, mat); + } + else if (data.format == voltu::ImageFormat::kFloat32) + { + ::cv::Mat tmp; + voltu::cv::convert(img, tmp); + + ::cv::normalize(tmp, tmp, 0, 255, ::cv::NORM_MINMAX); + tmp.convertTo(tmp, CV_8U); + + //#if OPENCV_VERSION >= 40102 + //cv::applyColorMap(tmp, mat, cv::COLORMAP_TURBO); + //#else + ::cv::applyColorMap(tmp, mat, ::cv::COLORMAP_INFERNO); + //#endif + } +}