diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..259148f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/.multi-arch.sh b/.multi-arch.sh new file mode 100755 index 0000000..6988ba9 --- /dev/null +++ b/.multi-arch.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +VERSION=$1 + +cat </tmp/multi.yml +image: chalmersrevere/argb2i420-multi:$VERSION +manifests: + - image: chalmersrevere/argb2i420-amd64:$VERSION + platform: + architecture: amd64 + os: linux + - image: chalmersrevere/argb2i420-armhf:$VERSION + platform: + architecture: arm + os: linux + - image: chalmersrevere/argb2i420-aarch64:$VERSION + platform: + architecture: arm64 + os: linux +EOF +manifest-tool-linux-amd64 push from-spec /tmp/multi.yml diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5746c7b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,91 @@ +# Copyright (C) 2018 Christian Berger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +cmake_minimum_required(VERSION 3.2) + +project(argb2i420) + +################################################################################ +# Defining the relevant version of libcluon. +set(CLUON_COMPLETE cluon-complete-v0.0.108.hpp) + +################################################################################ +# Set the search path for .cmake files. +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" ${CMAKE_MODULE_PATH}) + +################################################################################ +# This project requires C++14 or newer. +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +# Build a static binary. +set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++") +# Add further warning levels. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ + -D_XOPEN_SOURCE=700 \ + -D_FORTIFY_SOURCE=2 \ + -O2 \ + -fstack-protector \ + -fomit-frame-pointer \ + -pipe \ + -Weffc++ \ + -Wall -Wextra -Wshadow -Wdeprecated \ + -Wdiv-by-zero -Wfloat-equal -Wfloat-conversion -Wsign-compare -Wpointer-arith \ + -Wuninitialized -Wunreachable-code \ + -Wunused -Wunused-function -Wunused-label -Wunused-parameter -Wunused-but-set-parameter -Wunused-but-set-variable \ + -Wunused-value -Wunused-variable -Wunused-result \ + -Wmissing-field-initializers -Wmissing-format-attribute -Wmissing-include-dirs -Wmissing-noreturn") +# Threads are necessary for linking the resulting binaries as UDPReceiver is running in parallel. +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +################################################################################ +# Create proper symlink cluon-complete.hpp. +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/cluon-complete.hpp + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/src/${CLUON_COMPLETE} ${CMAKE_BINARY_DIR}/cluon-complete.hpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/${CLUON_COMPLETE}) +# Add current build directory as include directory as it contains generated files. +include_directories(SYSTEM ${CMAKE_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) + +################################################################################ +# Gather all object code first to avoid double compilation. +set(LIBRARIES Threads::Threads) + +if(UNIX) + if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") + find_package(LibRT REQUIRED) + set(LIBRARIES ${LIBRARIES} ${LIBRT_LIBRARIES}) + include_directories(SYSTEM ${LIBRT_INCLUDE_DIR}) + endif() +endif() + +find_package(X11 REQUIRED) +include_directories(SYSTEM ${X11_INCLUDE_DIR}) +set(LIBRARIES ${LIBRARIES} ${X11_X11_LIB}) + +find_package(Libyuv REQUIRED) +include_directories(SYSTEM ${YUV_INCLUDE_DIRS}) +set(LIBRARIES ${LIBRARIES} ${YUV_LIBRARIES}) + +################################################################################ +# Create executable. +add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/src/${PROJECT_NAME}.cpp ${CMAKE_BINARY_DIR}/cluon-complete.hpp) +target_link_libraries(${PROJECT_NAME} ${LIBRARIES}) + +################################################################################ +# Install executable. +install(TARGETS ${PROJECT_NAME} DESTINATION bin COMPONENT ${PROJECT_NAME}) diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 new file mode 100644 index 0000000..904dff5 --- /dev/null +++ b/Dockerfile.aarch64 @@ -0,0 +1,65 @@ +# Copyright (C) 2018 Christian Berger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Part to build argb2i420. +FROM docker.io/project31/aarch64-alpine-qemu:3.5-7 as builder +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN [ "cross-build-start" ] + +RUN cat /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/v3.7/main > /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/v3.7/community >> /etc/apk/repositories + +RUN apk update && \ + apk --no-cache add \ + cmake \ + g++ \ + git \ + libx11-dev \ + make +RUN cd tmp && \ + git clone --depth 1 https://chromium.googlesource.com/libyuv/libyuv && \ + cd libyuv &&\ + make -f linux.mk libyuv.a && cp libyuv.a /usr/lib && cd include && cp -r * /usr/include +ADD . /opt/sources +WORKDIR /opt/sources +RUN mkdir build && \ + cd build && \ + cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/tmp .. && \ + make && make install + +RUN [ "cross-build-end" ] + + +# Part to deploy argb2i420. +FROM docker.io/project31/aarch64-alpine-qemu:3.5-7 +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN [ "cross-build-start" ] + +RUN echo http://dl-4.alpinelinux.org/alpine/edge/main > /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && \ + apk update && \ + apk --no-cache add \ + libx11 + +RUN [ "cross-build-end" ] + +WORKDIR /usr/bin +COPY --from=builder /tmp/bin/argb2i420 . +ENTRYPOINT ["/usr/bin/argb2i420"] + diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 new file mode 100644 index 0000000..aa484c2 --- /dev/null +++ b/Dockerfile.amd64 @@ -0,0 +1,53 @@ +# Copyright (C) 2018 Christian Berger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Part to build argb2i420. +FROM alpine:3.7 as builder +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN apk update && \ + apk --no-cache add \ + cmake \ + g++ \ + git \ + libx11-dev \ + make +RUN cd tmp && \ + git clone --depth 1 https://chromium.googlesource.com/libyuv/libyuv && \ + cd libyuv &&\ + make -f linux.mk libyuv.a && cp libyuv.a /usr/lib && cd include && cp -r * /usr/include +ADD . /opt/sources +WORKDIR /opt/sources +RUN mkdir build && \ + cd build && \ + cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/tmp .. && \ + make && make install + + +# Part to deploy argb2i420. +FROM alpine:3.7 +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN echo http://dl-4.alpinelinux.org/alpine/edge/main > /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && \ + apk update && \ + apk --no-cache add \ + libx11 + +WORKDIR /usr/bin +COPY --from=builder /tmp/bin/argb2i420 . +ENTRYPOINT ["/usr/bin/argb2i420"] + diff --git a/Dockerfile.armhf b/Dockerfile.armhf new file mode 100644 index 0000000..e432362 --- /dev/null +++ b/Dockerfile.armhf @@ -0,0 +1,65 @@ +# Copyright (C) 2018 Christian Berger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Part to build argb2i420. +FROM pipill/armhf-alpine:edge as builder +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN [ "cross-build-start" ] + +RUN cat /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/v3.7/main > /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/v3.7/community >> /etc/apk/repositories + +RUN apk update && \ + apk --no-cache add \ + cmake \ + g++ \ + git \ + libx11-dev \ + make +RUN cd tmp && \ + git clone --depth 1 https://chromium.googlesource.com/libyuv/libyuv && \ + cd libyuv &&\ + make -f linux.mk libyuv.a && cp libyuv.a /usr/lib && cd include && cp -r * /usr/include +ADD . /opt/sources +WORKDIR /opt/sources +RUN mkdir build && \ + cd build && \ + cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/tmp .. && \ + make && make install + +RUN [ "cross-build-end" ] + + +# Part to deploy argb2i420. +FROM pipill/armhf-alpine:edge +MAINTAINER Christian Berger "christian.berger@gu.se" + +RUN [ "cross-build-start" ] + +RUN echo http://dl-4.alpinelinux.org/alpine/edge/main > /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \ + echo http://dl-4.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories && \ + apk update && \ + apk --no-cache add \ + libx11 + +RUN [ "cross-build-end" ] + +WORKDIR /usr/bin +COPY --from=builder /tmp/bin/argb2i420 . +ENTRYPOINT ["/usr/bin/argb2i420"] + diff --git a/FindLibRT.cmake b/FindLibRT.cmake new file mode 100644 index 0000000..1fe94d9 --- /dev/null +++ b/FindLibRT.cmake @@ -0,0 +1,121 @@ +# You may redistribute this program and/or modify it under the terms of +# the GNU General Public License as published by the Free Software Foundation, +# either version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +if(NOT LIBRT_FOUND) + + IF(${CMAKE_C_COMPILER} MATCHES "arm") + # We are on ARM. + find_path(LIBRT_INCLUDE_DIR + NAMES + time.h + PATHS + ${LIBRTDIR}/include/ + ) + + find_file( + LIBRT_LIBRARIES librt.a + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/arm-linux-gnueabihf/ + /usr/lib/arm-linux-gnueabi/ + ) + set (LIBRT_DYNAMIC "Using static library.") + + if (NOT LIBRT_LIBRARIES) + find_library( + LIBRT_LIBRARIES rt + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/arm-linux-gnueabihf/ + /usr/lib/arm-linux-gnueabi/ + ) + set (LIBRT_DYNAMIC "Using dynamic library.") + endif (NOT LIBRT_LIBRARIES) + ELSE() + IF("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + # We are on x86_64. + find_path(LIBRT_INCLUDE_DIR + NAMES + time.h + PATHS + ${LIBRTDIR}/include/ + ) + + find_file( + LIBRT_LIBRARIES librt.a + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/x86_64-linux-gnu/ + /usr/local/lib64/ + /usr/lib64/ + /usr/lib/ + ) + set (LIBRT_DYNAMIC "Using static library.") + + if (NOT LIBRT_LIBRARIES) + find_library( + LIBRT_LIBRARIES rt + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/x86_64-linux-gnu/ + /usr/local/lib64/ + /usr/lib64/ + /usr/lib/ + ) + set (LIBRT_DYNAMIC "Using dynamic library.") + endif (NOT LIBRT_LIBRARIES) + ELSE() + # We are on x86. + find_path(LIBRT_INCLUDE_DIR + NAMES + time.h + PATHS + ${LIBRTDIR}/include/ + ) + + find_file( + LIBRT_LIBRARIES librt.a + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/i386-linux-gnu/ + /usr/local/lib/ + /usr/lib/ + ) + set (LIBRT_DYNAMIC "Using static library.") + + if (NOT LIBRT_LIBRARIES) + find_library( + LIBRT_LIBRARIES rt + PATHS + ${LIBRTDIR}/lib/ + /usr/lib/i386-linux-gnu/ + /usr/local/lib/ + /usr/lib/ + ) + set (LIBRT_DYNAMIC "Using dynamic library.") + endif (NOT LIBRT_LIBRARIES) + ENDIF() + ENDIF() + + if (LIBRT_INCLUDE_DIR AND LIBRT_LIBRARIES) + set (LIBRT_FOUND TRUE) + endif (LIBRT_INCLUDE_DIR AND LIBRT_LIBRARIES) + + if (LIBRT_FOUND) + message(STATUS "Found librt: ${LIBRT_INCLUDE_DIR}, ${LIBRT_LIBRARIES} ${LIBRT_DYNAMIC}") + else (LIBRT_FOUND) + if (Librt_FIND_REQUIRED) + message (FATAL_ERROR "Could not find librt, try to setup LIBRT_PREFIX accordingly") + endif (Librt_FIND_REQUIRED) + endif (LIBRT_FOUND) + +endif (NOT LIBRT_FOUND) diff --git a/FindLibyuv.cmake b/FindLibyuv.cmake new file mode 100644 index 0000000..188ebb9 --- /dev/null +++ b/FindLibyuv.cmake @@ -0,0 +1,49 @@ +# Copyright (C) 2018 Christian Berger +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +########################################################################### +# Find libyuv. +FIND_PATH(YUV_INCLUDE_DIR + NAMES libyuv.h + PATHS /usr/local/include/ + /usr/include/) +MARK_AS_ADVANCED(YUV_INCLUDE_DIR) +FIND_LIBRARY(YUV_LIBRARY + NAMES yuv + PATHS ${LIBYUVDIR}/lib/ + /usr/lib/arm-linux-gnueabihf/ + /usr/lib/arm-linux-gnueabi/ + /usr/lib/x86_64-linux-gnu/ + /usr/local/lib64/ + /usr/lib64/ + /usr/lib/) +MARK_AS_ADVANCED(YUV_LIBRARY) + +########################################################################### +IF (YUV_INCLUDE_DIR + AND YUV_LIBRARY) + SET(YUV_FOUND 1) + SET(YUV_LIBRARIES ${YUV_LIBRARY}) + SET(YUV_INCLUDE_DIRS ${YUV_INCLUDE_DIR}) +ENDIF() + +MARK_AS_ADVANCED(YUV_LIBRARIES) +MARK_AS_ADVANCED(YUV_INCLUDE_DIRS) + +IF (YUV_FOUND) + MESSAGE(STATUS "Found libyuv: ${YUV_INCLUDE_DIRS}, ${YUV_LIBRARIES}") +ELSE () + MESSAGE(STATUS "Could not find libyuv") +ENDIF() diff --git a/README.md b/README.md index 55be63d..a612840 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,76 @@ -# argb2i420 -Microservice to convert an image in shared memory in (A)RGB layout to an image in shared memory in i420 format suitable to be used for video compression. +## OpenDLV Microservice to decode video frames from VP8 or VP9 into a shared memory + +This repository provides source code to decode broadcasted video frames in +VP8 or VP9 format into a shared memory area for the OpenDLV software ecosystem. + +[![License: GPLv3](https://img.shields.io/badge/license-GPL--3-blue.svg +)](https://www.gnu.org/licenses/gpl-3.0.txt) + + +## Table of Contents +* [Dependencies](#dependencies) +* [Usage](#usage) +* [Build from sources on the example of Ubuntu 16.04 LTS](#build-from-sources-on-the-example-of-ubuntu-1604-lts) +* [License](#license) + + +## Dependencies +You need a C++14-compliant compiler to compile this project. + +The following dependency is part of the source distribution: +* [libcluon](https://github.com/chrberger/libcluon) - [![License: GPLv3](https://img.shields.io/badge/license-GPL--3-blue.svg +)](https://www.gnu.org/licenses/gpl-3.0.txt) + +The following dependencies are downloaded and installed during the Docker-ized build: +* [libvpx 1.7.0](https://github.com/webmproject/libvpx/releases/tag/v1.7.0) - [![License: BSD 3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) - [Google Patent License Conditions](https://raw.githubusercontent.com/webmproject/libvpx/f80be22a1099b2a431c2796f529bb261064ec6b4/PATENTS) +* [libyuv](https://chromium.googlesource.com/libyuv/libyuv/+/master) - [![License: BSD 3-Clause](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) - [Google Patent License Conditions](https://chromium.googlesource.com/libyuv/libyuv/+/master/PATENTS) + + +## Usage +To run this microservice using `docker-compose`, you can simply add the following +section to your `docker-compose.yml`: + +```yml +version: '2' # Must be present exactly once at the beginning of the docker-compose.yml file +services: # Must be present exactly once at the beginning of the docker-compose.yml file + video-vpx-decoder: + image: chalmersrevere/opendlv-video-vpx-decoder-multi:v0.0.5 + restart: on-failure + network_mode: "host" + ipc: "host" + volumes: + - /tmp:/tmp + environment: + - DISPLAY=${DISPLAY} + command: "--cid=111 --name=imageData" +``` + +As this microservice is connecting to an OD4Session to receive VP8 or VP9 frames +to decode them into a shared memory area using SysV IPC, the `docker-compose.yml` +file specifies the use of `ipc:host`. The parameter `network_mode: "host"` is +necessary to receive VP8 or VP9 frames broadcasted from other microservices running +in an `OD4Session` from OpenDLV. The folder `/tmp` is shared into the Docker +container to provide tokens describing the shared memory area. +The parameters to the application are: + +* `--cid=111`: Identifier of the OD4Session to listen for VP8 or VP9 frames +* `--id=2`: Optional identifier to listen only for those VP8 or VP9 frames with the matching senderStamp of the OD4Session +* `--name=XYZ`: Name of the shared memory area to create for storing the ARGB image data +* `--verbose`: Display decoding information and render the image to screen (requires X11; run `xhost +` to allow access to you X11 server) + + +## Build from sources on the example of Ubuntu 16.04 LTS +To build this software, you need cmake, C++14 or newer, libyuv, libvpx, and make. +Having these preconditions, just run `cmake` and `make` as follows: + +``` +mkdir build && cd build +cmake -D CMAKE_BUILD_TYPE=Release .. +make && make test && make install +``` + + +## License + +* This project is released under the terms of the GNU GPLv3 License + diff --git a/src/argb2i420.cpp b/src/argb2i420.cpp new file mode 100644 index 0000000..1db67f7 --- /dev/null +++ b/src/argb2i420.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2018 Christian Berger + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "cluon-complete.hpp" + +#include +#include + +#include +#include +#include +#include + +int32_t main(int32_t argc, char **argv) { + int32_t retCode{1}; + auto commandlineArguments = cluon::getCommandlineArguments(argc, argv); + auto formatCounter{ + commandlineArguments.count("argb") + + commandlineArguments.count("rgb") + + commandlineArguments.count("abgr") + + commandlineArguments.count("bgr") + }; + if ( (0 == commandlineArguments.count("in")) || + (0 == commandlineArguments.count("out")) || + (0 == commandlineArguments.count("width")) || + (0 == commandlineArguments.count("height")) || + (1 != formatCounter)) { + std::cerr << argv[0] << " waits on a shared memory containing an image in (A)RGB/(A)BGR format to transform it into a corresponding image in I420 format in another shared memory." << std::endl; + std::cerr << "Usage: " << argv[0] << " --in= --width= --height= --out= --argb|--rgb|--abgr|--bgr [--verbose]" << std::endl; + std::cerr << " --in: name of the shared memory area containing the (A)RBG/(A)BGR image" << std::endl; + std::cerr << " --out: name of the shared memory area to be created for the I420 image" << std::endl; + std::cerr << " --width: width of the input image" << std::endl; + std::cerr << " --height: height of the input image" << std::endl; + std::cerr << " --argb: format of the input image (choose exactly one!)" << std::endl; + std::cerr << " --rgb: format of the input image (choose exactly one!)" << std::endl; + std::cerr << " --abgr: format of the input image (choose exactly one!)" << std::endl; + std::cerr << " --bgr: format of the input image (choose exactly one!)" << std::endl; + std::cerr << " --verbose: display input image" << std::endl; + std::cerr << "Example: " << argv[0] << " --in=img.argb --width=640 --height=480 --argb --out=imgout.i420 --verbose" << std::endl; + } + else { + const std::string IN{commandlineArguments["in"]}; + const std::string OUT{commandlineArguments["out"]}; + const uint32_t WIDTH{static_cast(std::stoi(commandlineArguments["width"]))}; + const uint32_t HEIGHT{static_cast(std::stoi(commandlineArguments["height"]))}; + const bool ARGB{commandlineArguments.count("argb") != 0}; + const bool RGB{commandlineArguments.count("rgb") != 0}; + const bool ABGR{commandlineArguments.count("abgr") != 0}; + const bool BGR{commandlineArguments.count("bgr") != 0}; + const bool VERBOSE{commandlineArguments.count("verbose") != 0}; + + std::unique_ptr sharedMemoryIN(new cluon::SharedMemory{IN}); + if (sharedMemoryIN && sharedMemoryIN->valid()) { + std::clog << "[argb2i420]: Attached to '" << sharedMemoryIN->name() << "' (" << sharedMemoryIN->size() << " bytes)." << std::endl; + + std::unique_ptr sharedMemoryOUT(new cluon::SharedMemory{OUT, WIDTH * HEIGHT * 3/2}); + if (sharedMemoryOUT && sharedMemoryOUT->valid()) { + std::clog << "[argb2i420]: Created shared memory " << OUT << " (" << sharedMemoryOUT->size() << " bytes) for an I420 image (width = " << WIDTH << ", height = " << HEIGHT << ")." << std::endl; + + Display *display{nullptr}; + Visual *visual{nullptr}; + Window window{0}; + XImage *ximage{nullptr}; + + if (VERBOSE) { + display = XOpenDisplay(NULL); + visual = DefaultVisual(display, 0); + window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0, WIDTH, HEIGHT, 1, 0, 0); + ximage = XCreateImage(display, visual, 24, ZPixmap, 0, reinterpret_cast(sharedMemoryIN->data()), WIDTH, HEIGHT, 32, 0); + XMapWindow(display, window); + } + + while (!cluon::TerminateHandler::instance().isTerminated) { + sharedMemoryIN->wait(); + sharedMemoryIN->lock(); + { + sharedMemoryOUT->lock(); + { + if (ARGB) { + libyuv::ARGBToI420(reinterpret_cast(sharedMemoryIN->data()), WIDTH * 4 /* 4*WIDTH for ARGB*/, + reinterpret_cast(sharedMemoryOUT->data()), WIDTH, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT)), WIDTH/2, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT + ((WIDTH * HEIGHT) >> 2))), WIDTH/2, + WIDTH, HEIGHT); + } + else if (RGB) { + libyuv::RGB24ToI420(reinterpret_cast(sharedMemoryIN->data()), WIDTH * 3 /* 3*WIDTH for RGB24*/, + reinterpret_cast(sharedMemoryOUT->data()), WIDTH, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT)), WIDTH/2, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT + ((WIDTH * HEIGHT) >> 2))), WIDTH/2, + WIDTH, HEIGHT); + } + else if (ABGR) { + libyuv::ABGRToI420(reinterpret_cast(sharedMemoryIN->data()), WIDTH * 4 /* 4*WIDTH for ARGB*/, + reinterpret_cast(sharedMemoryOUT->data()), WIDTH, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT)), WIDTH/2, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT + ((WIDTH * HEIGHT) >> 2))), WIDTH/2, + WIDTH, HEIGHT); + } + else if (BGR) { + libyuv::RAWToI420(reinterpret_cast(sharedMemoryIN->data()), WIDTH * 3 /* 3*WIDTH for RGB24*/, + reinterpret_cast(sharedMemoryOUT->data()), WIDTH, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT)), WIDTH/2, + reinterpret_cast(sharedMemoryOUT->data()+(WIDTH * HEIGHT + ((WIDTH * HEIGHT) >> 2))), WIDTH/2, + WIDTH, HEIGHT); + } + } + sharedMemoryOUT->unlock(); + if (VERBOSE) { + XPutImage(display, window, DefaultGC(display, 0), ximage, 0, 0, 0, 0, WIDTH, HEIGHT); + } + } + sharedMemoryIN->unlock(); + + // Notify listeners. + sharedMemoryOUT->notifyAll(); + } + + if (VERBOSE) { + XCloseDisplay(display); + } + retCode = 0; + } + else { + std::cerr << "[argb2i420]: Failed to create shared memory for output image." << std::endl; + } + } + else { + std::cerr << "[argb2i420]: Failed to attach to shared memory '" << IN << "'." << std::endl; + } + } + return retCode; +} + diff --git a/src/cluon-complete-v0.0.108.hpp b/src/cluon-complete-v0.0.108.hpp new file mode 100644 index 0000000..7faa2ae --- /dev/null +++ b/src/cluon-complete-v0.0.108.hpp @@ -0,0 +1,17179 @@ +// This is an auto-generated header-only single-file distribution of libcluon. +// Date: Mon, 13 Aug 2018 19:56:15 +0200 +// Version: 0.0.108 +// +// +// Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers. +// +// See also: +// + http://en.cppreference.com/w/cpp/any +// + http://en.cppreference.com/w/cpp/experimental/any +// + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any +// + https://cplusplus.github.io/LWG/lwg-active.html#2509 +// +// +// Copyright (c) 2016 Denilson das Mercês Amorim +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#ifndef LINB_ANY_HPP +#define LINB_ANY_HPP +//#pragma once +#include +#include +#include + +namespace linb +{ + +class bad_any_cast : public std::bad_cast +{ +public: + const char* what() const noexcept override + { + return "bad any cast"; + } +}; + +class any final +{ +public: + /// Constructs an object of type any with an empty state. + any() : + vtable(nullptr) + { + } + + /// Constructs an object of type any with an equivalent state as other. + any(const any& rhs) : + vtable(rhs.vtable) + { + if(!rhs.empty()) + { + rhs.vtable->copy(rhs.storage, this->storage); + } + } + + /// Constructs an object of type any with a state equivalent to the original state of other. + /// rhs is left in a valid but otherwise unspecified state. + any(any&& rhs) noexcept : + vtable(rhs.vtable) + { + if(!rhs.empty()) + { + rhs.vtable->move(rhs.storage, this->storage); + rhs.vtable = nullptr; + } + } + + /// Same effect as this->clear(). + ~any() + { + this->clear(); + } + + /// Constructs an object of type any that contains an object of type T direct-initialized with std::forward(value). + /// + /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + template::type, any>::value>::type> + any(ValueType&& value) + { + static_assert(std::is_copy_constructible::type>::value, + "T shall satisfy the CopyConstructible requirements."); + this->construct(std::forward(value)); + } + + /// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. + any& operator=(const any& rhs) + { + any(rhs).swap(*this); + return *this; + } + + /// Has the same effect as any(std::move(rhs)).swap(*this). + /// + /// The state of *this is equivalent to the original state of rhs and rhs is left in a valid + /// but otherwise unspecified state. + any& operator=(any&& rhs) noexcept + { + any(std::move(rhs)).swap(*this); + return *this; + } + + /// Has the same effect as any(std::forward(value)).swap(*this). No effect if a exception is thrown. + /// + /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + template::type, any>::value>::type> + any& operator=(ValueType&& value) + { + static_assert(std::is_copy_constructible::type>::value, + "T shall satisfy the CopyConstructible requirements."); + any(std::forward(value)).swap(*this); + return *this; + } + + /// If not empty, destroys the contained object. + void clear() noexcept + { + if(!empty()) + { + this->vtable->destroy(storage); + this->vtable = nullptr; + } + } + + /// Returns true if *this has no contained object, otherwise false. + bool empty() const noexcept + { + return this->vtable == nullptr; + } + + /// If *this has a contained object of type T, typeid(T); otherwise typeid(void). + const std::type_info& type() const noexcept + { + return empty()? typeid(void) : this->vtable->type(); + } + + /// Exchange the states of *this and rhs. + void swap(any& rhs) noexcept + { + if(this->vtable != rhs.vtable) + { + any tmp(std::move(rhs)); + + // move from *this to rhs. + rhs.vtable = this->vtable; + if(this->vtable != nullptr) + { + this->vtable->move(this->storage, rhs.storage); + //this->vtable = nullptr; -- uneeded, see below + } + + // move from tmp (previously rhs) to *this. + this->vtable = tmp.vtable; + if(tmp.vtable != nullptr) + { + tmp.vtable->move(tmp.storage, this->storage); + tmp.vtable = nullptr; + } + } + else // same types + { + if(this->vtable != nullptr) + this->vtable->swap(this->storage, rhs.storage); + } + } + +private: // Storage and Virtual Method Table + + union storage_union + { + using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of::value>::type; + + void* dynamic; + stack_storage_t stack; // 2 words for e.g. shared_ptr + }; + + /// Base VTable specification. + struct vtable_type + { + // Note: The caller is responssible for doing .vtable = nullptr after destructful operations + // such as destroy() and/or move(). + + /// The type of the object this vtable is for. + const std::type_info& (*type)() noexcept; + + /// Destroys the object in the union. + /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. + void(*destroy)(storage_union&) noexcept; + + /// Copies the **inner** content of the src union into the yet unitialized dest union. + /// As such, both inner objects will have the same state, but on separate memory locations. + void(*copy)(const storage_union& src, storage_union& dest); + + /// Moves the storage from src to the yet unitialized dest union. + /// The state of src after this call is unspecified, caller must ensure not to use src anymore. + void(*move)(storage_union& src, storage_union& dest) noexcept; + + /// Exchanges the storage between lhs and rhs. + void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; + }; + + /// VTable for dynamically allocated storage. + template + struct vtable_dynamic + { + static const std::type_info& type() noexcept + { + return typeid(T); + } + + static void destroy(storage_union& storage) noexcept + { + //assert(reinterpret_cast(storage.dynamic)); + delete reinterpret_cast(storage.dynamic); + } + + static void copy(const storage_union& src, storage_union& dest) + { + dest.dynamic = new T(*reinterpret_cast(src.dynamic)); + } + + static void move(storage_union& src, storage_union& dest) noexcept + { + dest.dynamic = src.dynamic; + src.dynamic = nullptr; + } + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + // just exchage the storage pointers. + std::swap(lhs.dynamic, rhs.dynamic); + } + }; + + /// VTable for stack allocated storage. + template + struct vtable_stack + { + static const std::type_info& type() noexcept + { + return typeid(T); + } + + static void destroy(storage_union& storage) noexcept + { + reinterpret_cast(&storage.stack)->~T(); + } + + static void copy(const storage_union& src, storage_union& dest) + { + new (&dest.stack) T(reinterpret_cast(src.stack)); + } + + static void move(storage_union& src, storage_union& dest) noexcept + { + // one of the conditions for using vtable_stack is a nothrow move constructor, + // so this move constructor will never throw a exception. + new (&dest.stack) T(std::move(reinterpret_cast(src.stack))); + destroy(src); + } + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + storage_union tmp_storage; + move(rhs, tmp_storage); + move(lhs, rhs); + move(tmp_storage, lhs); + } + }; + + /// Whether the type T must be dynamically allocated or can be stored on the stack. + template + struct requires_allocation : + std::integral_constant::value // N4562 �6.3/3 [any.class] + && sizeof(T) <= sizeof(storage_union::stack) + && std::alignment_of::value <= std::alignment_of::value)> + {}; + + /// Returns the pointer to the vtable of the type T. + template + static vtable_type* vtable_for_type() + { + using VTableType = typename std::conditional::value, vtable_dynamic, vtable_stack>::type; + static vtable_type table = { + VTableType::type, VTableType::destroy, + VTableType::copy, VTableType::move, + VTableType::swap, + }; + return &table; + } + +protected: + template + friend const T* any_cast(const any* operand) noexcept; + template + friend T* any_cast(any* operand) noexcept; + + /// Same effect as is_same(this->type(), t); + bool is_typed(const std::type_info& t) const + { + return is_same(this->type(), t); + } + + /// Checks if two type infos are the same. + /// + /// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the + /// type infos, otherwise does an actual comparision. Checking addresses is + /// only a valid approach when there's no interaction with outside sources + /// (other shared libraries and such). + static bool is_same(const std::type_info& a, const std::type_info& b) + { +#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE + return &a == &b; +#else + return a == b; +#endif + } + + /// Casts (with no type_info checks) the storage pointer as const T*. + template + const T* cast() const noexcept + { + return requires_allocation::type>::value? + reinterpret_cast(storage.dynamic) : + reinterpret_cast(&storage.stack); + } + + /// Casts (with no type_info checks) the storage pointer as T*. + template + T* cast() noexcept + { + return requires_allocation::type>::value? + reinterpret_cast(storage.dynamic) : + reinterpret_cast(&storage.stack); + } + +private: + storage_union storage; // on offset(0) so no padding for align + vtable_type* vtable; + + template + typename std::enable_if::value>::type + do_construct(ValueType&& value) + { + storage.dynamic = new T(std::forward(value)); + } + + template + typename std::enable_if::value>::type + do_construct(ValueType&& value) + { + new (&storage.stack) T(std::forward(value)); + } + + /// Chooses between stack and dynamic allocation for the type decay_t, + /// assigns the correct vtable, and constructs the object on our storage. + template + void construct(ValueType&& value) + { + using T = typename std::decay::type; + + this->vtable = vtable_for_type(); + + do_construct(std::forward(value)); + } +}; + + + +namespace detail +{ + template + inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::true_type) + { + return std::move(*p); + } + + template + inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::false_type) + { + return *p; + } +} + +/// Performs *any_cast>>(&operand), or throws bad_any_cast on failure. +template +inline ValueType any_cast(const any& operand) +{ + auto p = any_cast::type>::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return *p; +} + +/// Performs *any_cast>(&operand), or throws bad_any_cast on failure. +template +inline ValueType any_cast(any& operand) +{ + auto p = any_cast::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return *p; +} + +/// +/// If ANY_IMPL_ANYCAST_MOVEABLE is not defined, does as N4562 specifies: +/// Performs *any_cast>(&operand), or throws bad_any_cast on failure. +/// +/// If ANY_IMPL_ANYCAST_MOVEABLE is defined, does as LWG Defect 2509 specifies: +/// If ValueType is MoveConstructible and isn't a lvalue reference, performs +/// std::move(*any_cast>(&operand)), otherwise +/// *any_cast>(&operand). Throws bad_any_cast on failure. +/// +template +inline ValueType any_cast(any&& operand) +{ +#ifdef ANY_IMPL_ANY_CAST_MOVEABLE + // https://cplusplus.github.io/LWG/lwg-active.html#2509 + using can_move = std::integral_constant::value + && !std::is_lvalue_reference::value>; +#else + using can_move = std::false_type; +#endif + + auto p = any_cast::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return detail::any_cast_move_if_true(p, can_move()); +} + +/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object +/// contained by operand, otherwise nullptr. +template +inline const T* any_cast(const any* operand) noexcept +{ + if(operand == nullptr || !operand->is_typed(typeid(T))) + return nullptr; + else + return operand->cast(); +} + +/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object +/// contained by operand, otherwise nullptr. +template +inline T* any_cast(any* operand) noexcept +{ + if(operand == nullptr || !operand->is_typed(typeid(T))) + return nullptr; + else + return operand->cast(); +} + +} + +namespace std +{ + inline void swap(linb::any& lhs, linb::any& rhs) noexcept + { + lhs.swap(rhs); + } +} + +#endif +// +// peglib.h +// +// Copyright (c) 2015-18 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPPEGLIB_PEGLIB_H +#define CPPPEGLIB_PEGLIB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// guard for older versions of VC++ +#ifdef _MSC_VER +// VS2013 has no constexpr +#if (_MSC_VER == 1800) +#define PEGLIB_NO_CONSTEXPR_SUPPORT +#elif (_MSC_VER >= 1800) +// good to go +#else (_MSC_VER < 1800) +#error "Requires C+11 support" +#endif +#endif + +// define if the compiler doesn't support unicode characters reliably in the +// source code +//#define PEGLIB_NO_UNICODE_CHARS + +namespace peg { + +#if __clang__ == 1 && __clang_major__ == 5 && __clang_minor__ == 0 && __clang_patchlevel__ == 0 +static void* enabler = nullptr; // workaround for Clang 5.0.0 +#else +extern void* enabler; +#endif + +/*----------------------------------------------------------------------------- + * any + *---------------------------------------------------------------------------*/ + +class any +{ +public: + any() : content_(nullptr) {} + + any(const any& rhs) : content_(rhs.clone()) {} + + any(any&& rhs) : content_(rhs.content_) { + rhs.content_ = nullptr; + } + + template + any(const T& value) : content_(new holder(value)) {} + + any& operator=(const any& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.clone(); + } + return *this; + } + + any& operator=(any&& rhs) { + if (this != &rhs) { + if (content_) { + delete content_; + } + content_ = rhs.content_; + rhs.content_ = nullptr; + } + return *this; + } + + ~any() { + delete content_; + } + + bool is_undefined() const { + return content_ == nullptr; + } + + template < + typename T, + typename std::enable_if::value>::type*& = enabler + > + T& get() { + if (!content_) { + throw std::bad_cast(); + } + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value>::type*& = enabler + > + T& get() { + return *this; + } + + template < + typename T, + typename std::enable_if::value>::type*& = enabler + > + const T& get() const { + assert(content_); + auto p = dynamic_cast*>(content_); + assert(p); + if (!p) { + throw std::bad_cast(); + } + return p->value_; + } + + template < + typename T, + typename std::enable_if::value>::type*& = enabler + > + const any& get() const { + return *this; + } + +private: + struct placeholder { + virtual ~placeholder() {} + virtual placeholder* clone() const = 0; + }; + + template + struct holder : placeholder { + holder(const T& value) : value_(value) {} + placeholder* clone() const override { + return new holder(value_); + } + T value_; + }; + + placeholder* clone() const { + return content_ ? content_->clone() : nullptr; + } + + placeholder* content_; +}; + +/*----------------------------------------------------------------------------- + * scope_exit + *---------------------------------------------------------------------------*/ + +// This is based on "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +template +struct scope_exit +{ + explicit scope_exit(EF&& f) + : exit_function(std::move(f)) + , execute_on_destruction{true} {} + + scope_exit(scope_exit&& rhs) + : exit_function(std::move(rhs.exit_function)) + , execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { + this->exit_function(); + } + } + + void release() { + this->execute_on_destruction = false; + } + +private: + scope_exit(const scope_exit&) = delete; + void operator=(const scope_exit&) = delete; + scope_exit& operator=(scope_exit&&) = delete; + + EF exit_function; + bool execute_on_destruction; +}; + +template +auto make_scope_exit(EF&& exit_function) -> scope_exit { + return scope_exit::type>(std::forward(exit_function)); +} + +/*----------------------------------------------------------------------------- + * PEG + *---------------------------------------------------------------------------*/ + +/* +* Line information utility function +*/ +inline std::pair line_info(const char* start, const char* cur) { + auto p = start; + auto col_ptr = p; + auto no = 1; + + while (p < cur) { + if (*p == '\n') { + no++; + col_ptr = p + 1; + } + p++; + } + + auto col = p - col_ptr + 1; + + return std::make_pair(no, col); +} + +/* +* Semantic values +*/ +struct SemanticValues : protected std::vector +{ + // Input text + const char* path; + const char* ss; + + // Matched string + const char* c_str() const { return s_; } + size_t length() const { return n_; } + + std::string str() const { + return std::string(s_, n_); + } + + // Line number and column at which the matched string is + std::pair line_info() const { + return peg::line_info(ss, s_); + } + + // Choice number (0 based index) + size_t choice() const { return choice_; } + + // Tokens + std::vector> tokens; + + std::string token(size_t id = 0) const { + if (!tokens.empty()) { + assert(id < tokens.size()); + const auto& tok = tokens[id]; + return std::string(tok.first, tok.second); + } + return std::string(s_, n_); + } + + // Transform the semantic value vector to another vector + template + auto transform(size_t beg = 0, size_t end = static_cast(-1)) const -> vector { + return this->transform(beg, end, [](const any& v) { return v.get(); }); + } + + SemanticValues() : s_(nullptr), n_(0), choice_(0) {} + + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; + +private: + friend class Context; + friend class PrioritizedChoice; + friend class Holder; + + const char* s_; + size_t n_; + size_t choice_; + + template + auto transform(F f) const -> vector::type> { + vector::type> r; + for (const auto& v: *this) { + r.emplace_back(f(v)); + } + return r; + } + + template + auto transform(size_t beg, size_t end, F f) const -> vector::type> { + vector::type> r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(f((*this)[i])); + } + return r; + } +}; + +/* + * Semantic action + */ +template < + typename R, typename F, + typename std::enable_if::value>::type*& = enabler, + typename... Args> +any call(F fn, Args&&... args) { + fn(std::forward(args)...); + return any(); +} + +template < + typename R, typename F, + typename std::enable_if::type, any>::value>::type*& = enabler, + typename... Args> +any call(F fn, Args&&... args) { + return fn(std::forward(args)...); +} + +template < + typename R, typename F, + typename std::enable_if< + !std::is_void::value && + !std::is_same::type, any>::value>::type*& = enabler, + typename... Args> +any call(F fn, Args&&... args) { + return any(fn(std::forward(args)...)); +} + +class Action +{ +public: + Action() = default; + + Action(const Action& rhs) : fn_(rhs.fn_) {} + + template ::value && !std::is_same::value>::type*& = enabler> + Action(F fn) : fn_(make_adaptor(fn, &F::operator())) {} + + template ::value>::type*& = enabler> + Action(F fn) : fn_(make_adaptor(fn, fn)) {} + + template ::value>::type*& = enabler> + Action(F /*fn*/) {} + + template ::value && !std::is_same::value>::type*& = enabler> + void operator=(F fn) { + fn_ = make_adaptor(fn, &F::operator()); + } + + template ::value>::type*& = enabler> + void operator=(F fn) { + fn_ = make_adaptor(fn, fn); + } + + template ::value>::type*& = enabler> + void operator=(F /*fn*/) {} + + Action& operator=(const Action& rhs) = default; + + operator bool() const { + return bool(fn_); + } + + any operator()(const SemanticValues& sv, any& dt) const { + return fn_(sv, dt); + } + +private: + template + struct TypeAdaptor { + TypeAdaptor(std::function fn) + : fn_(fn) {} + any operator()(const SemanticValues& sv, any& /*dt*/) { + return call(fn_, sv); + } + std::function fn_; + }; + + template + struct TypeAdaptor_c { + TypeAdaptor_c(std::function fn) + : fn_(fn) {} + any operator()(const SemanticValues& sv, any& dt) { + return call(fn_, sv, dt); + } + std::function fn_; + }; + + typedef std::function Fty; + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv) const) { + return TypeAdaptor(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor(fn); + } + + template + Fty make_adaptor(F fn, R (* /*mf*/)(const SemanticValues& sv)) { + return TypeAdaptor(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt) const) { + return TypeAdaptor_c(fn); + } + + template + Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_c(fn); + } + + template + Fty make_adaptor(F fn, R(* /*mf*/)(const SemanticValues& sv, any& dt)) { + return TypeAdaptor_c(fn); + } + + Fty fn_; +}; + +/* + * Semantic predicate + */ +// Note: 'parse_error' exception class should be be used in sematic action handlers to reject the rule. +struct parse_error { + parse_error() = default; + parse_error(const char* s) : s_(s) {} + const char* what() const { return s_.empty() ? nullptr : s_.c_str(); } +private: + std::string s_; +}; + +/* + * Result + */ +inline bool success(size_t len) { + return len != static_cast(-1); +} + +inline bool fail(size_t len) { + return len == static_cast(-1); +} + +/* + * Context + */ +class Context; +class Ope; +class Definition; + +typedef std::function Tracer; + +class Context +{ +public: + const char* path; + const char* s; + const size_t l; + + const char* error_pos; + const char* message_pos; + std::string message; // TODO: should be `int`. + + std::vector> value_stack; + size_t value_stack_size; + + size_t nest_level; + + bool in_token; + + std::shared_ptr whitespaceOpe; + bool in_whitespace; + + std::shared_ptr wordOpe; + + std::unordered_map captures; + + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; + + std::map, std::tuple> cache_values; + + std::function tracer; + + Context( + const char* a_path, + const char* a_s, + size_t a_l, + size_t a_def_count, + std::shared_ptr a_whitespaceOpe, + std::shared_ptr a_wordOpe, + bool a_enablePackratParsing, + Tracer a_tracer) + : path(a_path) + , s(a_s) + , l(a_l) + , error_pos(nullptr) + , message_pos(nullptr) + , value_stack_size(0) + , nest_level(0) + , in_token(false) + , whitespaceOpe(a_whitespaceOpe) + , in_whitespace(false) + , wordOpe(a_wordOpe) + , def_count(a_def_count) + , enablePackratParsing(a_enablePackratParsing) + , cache_registered(enablePackratParsing ? def_count * (l + 1) : 0) + , cache_success(enablePackratParsing ? def_count * (l + 1) : 0) + , tracer(a_tracer) + { + } + + template + void packrat(const char* a_s, size_t def_id, size_t& len, any& val, T fn) { + if (!enablePackratParsing) { + fn(val); + return; + } + + auto col = a_s - s; + auto idx = def_count * static_cast(col) + def_id; + + if (cache_registered[idx]) { + if (cache_success[idx]) { + auto key = std::make_pair(col, def_id); + std::tie(len, val) = cache_values[key]; + return; + } else { + len = static_cast(-1); + return; + } + } else { + fn(val); + cache_registered[idx] = true; + cache_success[idx] = success(len); + if (success(len)) { + auto key = std::make_pair(col, def_id); + cache_values[key] = std::make_pair(len, val); + } + return; + } + } + + SemanticValues& push() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared()); + } + auto& sv = *value_stack[value_stack_size++]; + if (!sv.empty()) { + sv.clear(); + } + sv.path = path; + sv.ss = s; + sv.s_ = nullptr; + sv.n_ = 0; + sv.tokens.clear(); + return sv; + } + + void pop() { + value_stack_size--; + } + + void set_error_pos(const char* a_s) { + if (error_pos < a_s) error_pos = a_s; + } + + void trace(const char* name, const char* a_s, size_t n, SemanticValues& sv, any& dt) const { + if (tracer) tracer(name, a_s, n, sv, *this, dt); + } +}; + +/* + * Parser operators + */ +class Ope +{ +public: + struct Visitor; + + virtual ~Ope() {} + virtual size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const = 0; + virtual void accept(Visitor& v) = 0; +}; + +class Sequence : public Ope +{ +public: + Sequence(const Sequence& rhs) : opes_(rhs.opes_) {} + +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + Sequence(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + Sequence(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + Sequence(const std::vector>& opes) : opes_(opes) {} + Sequence(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Sequence", s, n, sv, dt); + size_t i = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + const auto& rule = *ope; + auto len = rule.parse(s + i, n - i, sv, c, dt); + if (fail(len)) { + return static_cast(-1); + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::vector> opes_; +}; + +class PrioritizedChoice : public Ope +{ +public: +#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 + // NOTE: Compiler Error C2797 on Visual Studio 2013 + // "The C++ compiler in Visual Studio does not implement list + // initialization inside either a member initializer list or a non-static + // data member initializer. Before Visual Studio 2013 Update 3, this was + // silently converted to a function call, which could lead to bad code + // generation. Visual Studio 2013 Update 3 reports this as an error." + template + PrioritizedChoice(const Args& ...args) { + opes_ = std::vector>{ static_cast>(args)... }; + } +#else + template + PrioritizedChoice(const Args& ...args) : opes_{ static_cast>(args)... } {} +#endif + + PrioritizedChoice(const std::vector>& opes) : opes_(opes) {} + PrioritizedChoice(std::vector>&& opes) : opes_(opes) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("PrioritizedChoice", s, n, sv, dt); + size_t id = 0; + for (const auto& ope : opes_) { + c.nest_level++; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + }); + const auto& rule = *ope; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + if (!chldsv.empty()) { + sv.insert(sv.end(), chldsv.begin(), chldsv.end()); + } + sv.s_ = chldsv.c_str(); + sv.n_ = chldsv.length(); + sv.choice_ = id; + sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + return len; + } + id++; + } + return static_cast(-1); + } + + void accept(Visitor& v) override; + + size_t size() const { return opes_.size(); } + + std::vector> opes_; +}; + +class ZeroOrMore : public Ope +{ +public: + ZeroOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("ZeroOrMore", s, n, sv, dt); + auto save_error_pos = c.error_pos; + size_t i = 0; + while (n - i > 0) { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + auto len = rule.parse(s + i, n - i, sv, c, dt); + if (fail(len)) { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class OneOrMore : public Ope +{ +public: + OneOrMore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("OneOrMore", s, n, sv, dt); + size_t len = 0; + { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + const auto& rule = *ope_; + len = rule.parse(s, n, sv, c, dt); + if (fail(len)) { + return static_cast(-1); + } + } + auto save_error_pos = c.error_pos; + auto i = len; + while (n - i > 0) { + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + const auto& rule = *ope_; + len = rule.parse(s + i, n - i, sv, c, dt); + if (fail(len)) { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + break; + } + i += len; + } + return i; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Option : public Ope +{ +public: + Option(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Option", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto save_sv_size = sv.size(); + auto save_tok_size = sv.tokens.size(); + auto se = make_scope_exit([&]() { c.nest_level--; }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + return len; + } else { + if (sv.size() != save_sv_size) { + sv.erase(sv.begin() + static_cast(save_sv_size)); + } + if (sv.tokens.size() != save_tok_size) { + sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); + } + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class AndPredicate : public Ope +{ +public: + AndPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AndPredicate", s, n, sv, dt); + c.nest_level++; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + return 0; + } else { + return static_cast(-1); + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class NotPredicate : public Ope +{ +public: + NotPredicate(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("NotPredicate", s, n, sv, dt); + auto save_error_pos = c.error_pos; + c.nest_level++; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.nest_level--; + c.pop(); + }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, chldsv, c, dt); + if (success(len)) { + c.set_error_pos(s); + return static_cast(-1); + } else { + c.error_pos = save_error_pos; + return 0; + } + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class LiteralString : public Ope +{ +public: + LiteralString(const std::string& s) + : lit_(s) + , init_is_word_(false) + , is_word_(false) + {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string lit_; + mutable bool init_is_word_; + mutable bool is_word_; +}; + +class CharacterClass : public Ope +{ +public: + CharacterClass(const std::string& chars) : chars_(chars) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("CharacterClass", s, n, sv, dt); + // TODO: UTF8 support + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + auto ch = s[0]; + auto i = 0u; + while (i < chars_.size()) { + if (i + 2 < chars_.size() && chars_[i + 1] == '-') { + if (chars_[i] <= ch && ch <= chars_[i + 2]) { + return 1; + } + i += 3; + } else { + if (chars_[i] == ch) { + return 1; + } + i += 1; + } + } + c.set_error_pos(s); + return static_cast(-1); + } + + void accept(Visitor& v) override; + + std::string chars_; +}; + +class Character : public Ope +{ +public: + Character(char ch) : ch_(ch) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("Character", s, n, sv, dt); + // TODO: UTF8 support + if (n < 1 || s[0] != ch_) { + c.set_error_pos(s); + return static_cast(-1); + } + return 1; + } + + void accept(Visitor& v) override; + + char ch_; +}; + +class AnyCharacter : public Ope +{ +public: + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + c.trace("AnyCharacter", s, n, sv, dt); + // TODO: UTF8 support + if (n < 1) { + c.set_error_pos(s); + return static_cast(-1); + } + return 1; + } + + void accept(Visitor& v) override; +}; + +class Capture : public Ope +{ +public: + typedef std::function MatchAction; + + Capture(const std::shared_ptr& ope, MatchAction ma) + : ope_(ope), match_action_(ma) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len) && match_action_) { + match_action_(s, len, c); + } + return len; + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; + +private: + MatchAction match_action_; +}; + +class TokenBoundary : public Ope +{ +public: + TokenBoundary(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class Ignore : public Ope +{ +public: + Ignore(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& /*sv*/, Context& c, any& dt) const override { + const auto& rule = *ope_; + auto& chldsv = c.push(); + auto se = make_scope_exit([&]() { + c.pop(); + }); + return rule.parse(s, n, chldsv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +typedef std::function Parser; + +class WeakHolder : public Ope +{ +public: + WeakHolder(const std::shared_ptr& ope) : weak_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + auto ope = weak_.lock(); + assert(ope); + const auto& rule = *ope; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::weak_ptr weak_; +}; + +class Holder : public Ope +{ +public: + Holder(Definition* outer) + : outer_(outer) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + any reduce(const SemanticValues& sv, any& dt) const; + + std::shared_ptr ope_; + Definition* outer_; + + friend class Definition; +}; + +class DefinitionReference : public Ope +{ +public: + DefinitionReference( + const std::unordered_map& grammar, const std::string& name, const char* s) + : grammar_(grammar) + , name_(name) + , s_(s) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::shared_ptr get_rule() const; + + const std::unordered_map& grammar_; + const std::string name_; + const char* s_; + +private: + mutable std::once_flag init_; + mutable std::shared_ptr rule_; +}; + +class Whitespace : public Ope +{ +public: + Whitespace(const std::shared_ptr& ope) : ope_(ope) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + if (c.in_whitespace) { + return 0; + } + c.in_whitespace = true; + auto se = make_scope_exit([&]() { c.in_whitespace = false; }); + const auto& rule = *ope_; + return rule.parse(s, n, sv, c, dt); + } + + void accept(Visitor& v) override; + + std::shared_ptr ope_; +}; + +class BackReference : public Ope +{ +public: + BackReference(const std::string& name) : name_(name) {} + + size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + + void accept(Visitor& v) override; + + std::string name_; +}; + +/* + * Visitor + */ +struct Ope::Visitor +{ + virtual ~Visitor() {} + virtual void visit(Sequence& /*ope*/) {} + virtual void visit(PrioritizedChoice& /*ope*/) {} + virtual void visit(ZeroOrMore& /*ope*/) {} + virtual void visit(OneOrMore& /*ope*/) {} + virtual void visit(Option& /*ope*/) {} + virtual void visit(AndPredicate& /*ope*/) {} + virtual void visit(NotPredicate& /*ope*/) {} + virtual void visit(LiteralString& /*ope*/) {} + virtual void visit(CharacterClass& /*ope*/) {} + virtual void visit(Character& /*ope*/) {} + virtual void visit(AnyCharacter& /*ope*/) {} + virtual void visit(Capture& /*ope*/) {} + virtual void visit(TokenBoundary& /*ope*/) {} + virtual void visit(Ignore& /*ope*/) {} + virtual void visit(WeakHolder& /*ope*/) {} + virtual void visit(Holder& /*ope*/) {} + virtual void visit(DefinitionReference& /*ope*/) {} + virtual void visit(Whitespace& /*ope*/) {} + virtual void visit(BackReference& /*ope*/) {} +}; + +struct AssignIDToDefinition : public Ope::Visitor +{ + using Ope::Visitor::visit; + + void visit(Sequence& ope) override { + for (auto op: ope.opes_) { + op->accept(*this); + } + } + void visit(PrioritizedChoice& ope) override { + for (auto op: ope.opes_) { + op->accept(*this); + } + } + void visit(ZeroOrMore& ope) override { ope.ope_->accept(*this); } + void visit(OneOrMore& ope) override { ope.ope_->accept(*this); } + void visit(Option& ope) override { ope.ope_->accept(*this); } + void visit(AndPredicate& ope) override { ope.ope_->accept(*this); } + void visit(NotPredicate& ope) override { ope.ope_->accept(*this); } + void visit(Capture& ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary& ope) override { ope.ope_->accept(*this); } + void visit(Ignore& ope) override { ope.ope_->accept(*this); } + void visit(WeakHolder& ope) override { ope.weak_.lock()->accept(*this); } + void visit(Holder& ope) override; + void visit(DefinitionReference& ope) override { ope.get_rule()->accept(*this); } + + std::unordered_map ids; +}; + +struct IsToken : public Ope::Visitor +{ + IsToken() : has_token_boundary(false), has_rule(false) {} + + using Ope::Visitor::visit; + + void visit(Sequence& ope) override { + for (auto op: ope.opes_) { + op->accept(*this); + } + } + void visit(PrioritizedChoice& ope) override { + for (auto op: ope.opes_) { + op->accept(*this); + } + } + void visit(ZeroOrMore& ope) override { ope.ope_->accept(*this); } + void visit(OneOrMore& ope) override { ope.ope_->accept(*this); } + void visit(Option& ope) override { ope.ope_->accept(*this); } + void visit(Capture& ope) override { ope.ope_->accept(*this); } + void visit(TokenBoundary& /*ope*/) override { has_token_boundary = true; } + void visit(Ignore& ope) override { ope.ope_->accept(*this); } + void visit(WeakHolder& ope) override { ope.weak_.lock()->accept(*this); } + void visit(DefinitionReference& /*ope*/) override { has_rule = true; } + + bool is_token() const { + return has_token_boundary || !has_rule; + } + + bool has_token_boundary; + bool has_rule; +}; + +static const char* WHITESPACE_DEFINITION_NAME = "%whitespace"; +static const char* WORD_DEFINITION_NAME = "%word"; + +/* + * Definition + */ +class Definition +{ +public: + struct Result { + bool ret; + size_t len; + const char* error_pos; + const char* message_pos; + const std::string message; + }; + + Definition() + : ignoreSemanticValue(false) + , enablePackratParsing(false) + , is_token(false) + , has_token_boundary(false) + , holder_(std::make_shared(this)) {} + + Definition(const Definition& rhs) + : name(rhs.name) + , ignoreSemanticValue(false) + , enablePackratParsing(false) + , is_token(false) + , has_token_boundary(false) + , holder_(rhs.holder_) + { + holder_->outer_ = this; + } + + Definition(Definition&& rhs) + : name(std::move(rhs.name)) + , ignoreSemanticValue(rhs.ignoreSemanticValue) + , whitespaceOpe(rhs.whitespaceOpe) + , wordOpe(rhs.wordOpe) + , enablePackratParsing(rhs.enablePackratParsing) + , is_token(rhs.is_token) + , has_token_boundary(rhs.has_token_boundary) + , holder_(std::move(rhs.holder_)) + { + holder_->outer_ = this; + } + + Definition(const std::shared_ptr& ope) + : ignoreSemanticValue(false) + , enablePackratParsing(false) + , is_token(false) + , has_token_boundary(false) + , holder_(std::make_shared(this)) + { + *this <= ope; + } + + operator std::shared_ptr() { + return std::make_shared(holder_); + } + + Definition& operator<=(const std::shared_ptr& ope) { + IsToken isToken; + ope->accept(isToken); + is_token = isToken.is_token(); + has_token_boundary = isToken.has_token_boundary; + + holder_->ope_ = ope; + + return *this; + } + + Result parse(const char* s, size_t n, const char* path = nullptr) const { + SemanticValues sv; + any dt; + return parse_core(s, n, sv, dt, path); + } + + Result parse(const char* s, const char* path = nullptr) const { + auto n = strlen(s); + return parse(s, n, path); + } + + Result parse(const char* s, size_t n, any& dt, const char* path = nullptr) const { + SemanticValues sv; + return parse_core(s, n, sv, dt, path); + } + + Result parse(const char* s, any& dt, const char* path = nullptr) const { + auto n = strlen(s); + return parse(s, n, dt, path); + } + + template + Result parse_and_get_value(const char* s, size_t n, T& val, const char* path = nullptr) const { + SemanticValues sv; + any dt; + auto r = parse_core(s, n, sv, dt, path); + if (r.ret && !sv.empty() && !sv.front().is_undefined()) { + val = sv[0].get(); + } + return r; + } + + template + Result parse_and_get_value(const char* s, T& val, const char* path = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, val, path); + } + + template + Result parse_and_get_value(const char* s, size_t n, any& dt, T& val, const char* path = nullptr) const { + SemanticValues sv; + auto r = parse_core(s, n, sv, dt, path); + if (r.ret && !sv.empty() && !sv.front().is_undefined()) { + val = sv[0].get(); + } + return r; + } + + template + Result parse_and_get_value(const char* s, any& dt, T& val, const char* path = nullptr) const { + auto n = strlen(s); + return parse_and_get_value(s, n, dt, val, path); + } + + Definition& operator=(Action a) { + action = a; + return *this; + } + + template + Definition& operator,(T fn) { + operator=(fn); + return *this; + } + + Definition& operator~() { + ignoreSemanticValue = true; + return *this; + } + + void accept(Ope::Visitor& v) { + holder_->accept(v); + } + + std::shared_ptr get_core_operator() { + return holder_->ope_; + } + + std::string name; + size_t id; + Action action; + std::function enter; + std::function leave; + std::function error_message; + bool ignoreSemanticValue; + std::shared_ptr whitespaceOpe; + std::shared_ptr wordOpe; + bool enablePackratParsing; + bool is_token; + bool has_token_boundary; + Tracer tracer; + +private: + friend class DefinitionReference; + + Definition& operator=(const Definition& rhs); + Definition& operator=(Definition&& rhs); + + Result parse_core(const char* s, size_t n, SemanticValues& sv, any& dt, const char* path) const { + AssignIDToDefinition assignId; + holder_->accept(assignId); + + std::shared_ptr ope = holder_; + if (whitespaceOpe) { + ope = std::make_shared(whitespaceOpe, ope); + } + + Context cxt(path, s, n, assignId.ids.size(), whitespaceOpe, wordOpe, enablePackratParsing, tracer); + auto len = ope->parse(s, n, sv, cxt, dt); + return Result{ success(len), len, cxt.error_pos, cxt.message_pos, cxt.message }; + } + + std::shared_ptr holder_; +}; + +/* + * Implementations + */ + +inline size_t parse_literal(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt, + const std::string& lit, bool& init_is_word, bool& is_word) +{ + size_t i = 0; + for (; i < lit.size(); i++) { + if (i >= n || s[i] != lit[i]) { + c.set_error_pos(s); + return static_cast(-1); + } + } + + // Word check + static Context dummy_c(nullptr, lit.data(), lit.size(), 0, nullptr, nullptr, false, nullptr); + static SemanticValues dummy_sv; + static any dummy_dt; + + if (!init_is_word) { // TODO: Protect with mutex + if (c.wordOpe) { + auto len = c.wordOpe->parse(lit.data(), lit.size(), dummy_sv, dummy_c, dummy_dt); + is_word = success(len); + } + init_is_word = true; + } + + if (is_word) { + auto ope = std::make_shared(c.wordOpe); + auto len = ope->parse(s + i, n - i, dummy_sv, dummy_c, dummy_dt); + if (fail(len)) { + return static_cast(-1); + } + i += len; + } + + // Skip whiltespace + if (!c.in_token) { + if (c.whitespaceOpe) { + auto len = c.whitespaceOpe->parse(s + i, n - i, sv, c, dt); + if (fail(len)) { + return static_cast(-1); + } + i += len; + } + } + + return i; +} + +inline size_t LiteralString::parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const { + c.trace("LiteralString", s, n, sv, dt); + return parse_literal(s, n, sv, c, dt, lit_, init_is_word_, is_word_); +} + +inline size_t TokenBoundary::parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const { + c.in_token = true; + auto se = make_scope_exit([&]() { c.in_token = false; }); + const auto& rule = *ope_; + auto len = rule.parse(s, n, sv, c, dt); + if (success(len)) { + sv.tokens.push_back(std::make_pair(s, len)); + + if (c.whitespaceOpe) { + auto l = c.whitespaceOpe->parse(s + len, n - len, sv, c, dt); + if (fail(l)) { + return static_cast(-1); + } + len += l; + } + } + return len; +} + +inline size_t Holder::parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const { + if (!ope_) { + throw std::logic_error("Uninitialized definition ope was used..."); + } + + c.trace(outer_->name.c_str(), s, n, sv, dt); + c.nest_level++; + auto se = make_scope_exit([&]() { c.nest_level--; }); + + size_t len; + any val; + + c.packrat(s, outer_->id, len, val, [&](any& a_val) { + auto& chldsv = c.push(); + + if (outer_->enter) { + outer_->enter(dt); + } + + auto se2 = make_scope_exit([&]() { + c.pop(); + + if (outer_->leave) { + outer_->leave(dt); + } + }); + + const auto& rule = *ope_; + len = rule.parse(s, n, chldsv, c, dt); + + // Invoke action + if (success(len)) { + chldsv.s_ = s; + chldsv.n_ = len; + + try { + a_val = reduce(chldsv, dt); + } catch (const parse_error& e) { + if (e.what()) { + if (c.message_pos < s) { + c.message_pos = s; + c.message = e.what(); + } + } + len = static_cast(-1); + } + } + }); + + if (success(len)) { + if (!outer_->ignoreSemanticValue) { + sv.emplace_back(val); + } + } else { + if (outer_->error_message) { + if (c.message_pos < s) { + c.message_pos = s; + c.message = outer_->error_message(); + } + } + } + + return len; +} + +inline any Holder::reduce(const SemanticValues& sv, any& dt) const { + if (outer_->action) { + return outer_->action(sv, dt); + } else if (sv.empty()) { + return any(); + } else { + return sv.front(); + } +} + +inline size_t DefinitionReference::parse( + const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const { + const auto& rule = *get_rule(); + return rule.parse(s, n, sv, c, dt); +} + +inline std::shared_ptr DefinitionReference::get_rule() const { + if (!rule_) { + std::call_once(init_, [this]() { + rule_ = grammar_.at(name_).holder_; + }); + } + assert(rule_); + return rule_; +} + +inline size_t BackReference::parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const { + c.trace("BackReference", s, n, sv, dt); + if (c.captures.find(name_) == c.captures.end()) { + throw std::runtime_error("Invalid back reference..."); + } + const auto& lit = c.captures[name_]; + bool init_is_word = false; + bool is_word = false; + return parse_literal(s, n, sv, c, dt, lit, init_is_word, is_word); +} + +inline void Sequence::accept(Visitor& v) { v.visit(*this); } +inline void PrioritizedChoice::accept(Visitor& v) { v.visit(*this); } +inline void ZeroOrMore::accept(Visitor& v) { v.visit(*this); } +inline void OneOrMore::accept(Visitor& v) { v.visit(*this); } +inline void Option::accept(Visitor& v) { v.visit(*this); } +inline void AndPredicate::accept(Visitor& v) { v.visit(*this); } +inline void NotPredicate::accept(Visitor& v) { v.visit(*this); } +inline void LiteralString::accept(Visitor& v) { v.visit(*this); } +inline void CharacterClass::accept(Visitor& v) { v.visit(*this); } +inline void Character::accept(Visitor& v) { v.visit(*this); } +inline void AnyCharacter::accept(Visitor& v) { v.visit(*this); } +inline void Capture::accept(Visitor& v) { v.visit(*this); } +inline void TokenBoundary::accept(Visitor& v) { v.visit(*this); } +inline void Ignore::accept(Visitor& v) { v.visit(*this); } +inline void WeakHolder::accept(Visitor& v) { v.visit(*this); } +inline void Holder::accept(Visitor& v) { v.visit(*this); } +inline void DefinitionReference::accept(Visitor& v) { v.visit(*this); } +inline void Whitespace::accept(Visitor& v) { v.visit(*this); } +inline void BackReference::accept(Visitor& v) { v.visit(*this); } + +inline void AssignIDToDefinition::visit(Holder& ope) { + auto p = static_cast(ope.outer_); + if (ids.count(p)) { + return; + } + auto id = ids.size(); + ids[p] = id; + ope.outer_->id = id; + ope.ope_->accept(*this); +} + +/* + * Factories + */ +template +std::shared_ptr seq(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +template +std::shared_ptr cho(Args&& ...args) { + return std::make_shared(static_cast>(args)...); +} + +inline std::shared_ptr zom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr oom(const std::shared_ptr& ope) { + return std::make_shared(ope); +} + +inline std::shared_ptr opt(const std::shared_ptr& ope) { + return std::make_shared