Skip to content

Commit

Permalink
Added C++ implementation (#173)
Browse files Browse the repository at this point in the history
Co-authored-by: Remy Chibois <[email protected]>
  • Loading branch information
chybz and Remy Chibois authored Sep 13, 2023
1 parent e87f74b commit 9307e30
Show file tree
Hide file tree
Showing 73 changed files with 11,459 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/test-cpp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: test-cpp

on:
push:
branches:
- main
- renovate/**
pull_request:
branches:
- main
workflow_call:

jobs:
test-cpp:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: install cmake and libraries
run: |
sudo apt-get update
sudo apt-get install ninja-build cmake
sudo apt-get install nlohmann-json3-dev
ninja --version
cmake --version
gcc --version
- name: configure and build
env:
NPROCS: 1
run: |
make acceptance
working-directory: cpp
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
- (i18n) Added Dutch translation of "Rule"
- (i18n) Added Esperanto translation of "Rule"
- [Ruby] Added `Gherkin::Query#parent_locations` for determining a scenario's parents' line numbers ([#89](https://github.com/cucumber/gherkin/pull/89))
- C++ implementation [#117](https://github.com/cucumber/gherkin/pull/117)

## [26.2.0] - 2023-04-07
### Changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Gherkin is currently implemented for the following platforms (in order of birthd
- [Perl](./perl) - Actively tested -> [workflow](./.github/workflows/test-perl.yml)
- [PHP](./php) - Actively tested -> [workflow](./.github/workflows/test-php.yml)
- [Dart](./dart) - Actively tested -> [workflow](./.github/workflows/test-dart.yml)
- [C++](./cpp) - Actively tested -> [workflow](./.github/workflows/test-cpp.yml)

The CI will run using the linked workflow when that specific language implementation is changed

Expand Down
7 changes: 7 additions & 0 deletions cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ext/
build/
acceptance/
stage/
.built
.configured
.deps-installed
40 changes: 40 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(gherkin-cpp VERSION 1.0.0 LANGUAGES C CXX)

include(GNUInstallDirs)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

find_package(nlohmann_json CONFIG REQUIRED)
find_package(cucumber-messages CONFIG REQUIRED)

add_subdirectory(src/lib/gherkin)
add_subdirectory(src/bin/gherkin)
add_subdirectory(src/bin/gherkin-generate-tokens)

install(
TARGETS
gherkin-cpp
gherkin-bin
gherkin-generate-tokens-bin
EXPORT gherkin-cpp-config
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(
DIRECTORY "${CMAKE_SOURCE_DIR}/include/"
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gherkin
)

install(
EXPORT gherkin-cpp-config
FILE gherkin-cpp-config.cmake
NAMESPACE gherkin-cpp::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/gherkin-cpp
)
121 changes: 121 additions & 0 deletions cpp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
SHELL := /usr/bin/env bash

GRAMMAR_GENERATED = \
include/gherkin/rule_type.hpp \
include/gherkin/parser.hpp
LANGUAGE_GENERATED = \
src/lib/gherkin/dialect.cpp
ALL_GENERATED := $(GRAMMAR_GENERATED) $(LANGUAGE_GENERATED)
ALL_SOURCE_FILES = $(shell find include src -name "*.[ch]*")
SOURCE_FILES := $(filter-out $(ALL_GENERATED),$(ALL_SOURCE_FILES))

HERE = $(shell pwd)
CMAKE_BUILDROOT = $(HERE)/build/root
CMAKELISTS = $(shell find src -name CMakeLists.txt)

GHERKIN = stage/bin/gherkin
GHERKIN_GENERATE_TOKENS = stage/bin/gherkin-generate-tokens

GOOD_FEATURE_FILES = $(shell find ../testdata/good -name "*.feature")
BAD_FEATURE_FILES = $(shell find ../testdata/bad -name "*.feature")

TOKENS = $(patsubst ../testdata/%,acceptance/testdata/%.tokens,$(GOOD_FEATURE_FILES))
ASTS = $(patsubst ../testdata/%,acceptance/testdata/%.ast.ndjson,$(GOOD_FEATURE_FILES))
PICKLES = $(patsubst ../testdata/%,acceptance/testdata/%.pickles.ndjson,$(GOOD_FEATURE_FILES))
SOURCES = $(patsubst ../testdata/%,acceptance/testdata/%.source.ndjson,$(GOOD_FEATURE_FILES))
ERRORS = $(patsubst ../testdata/%,acceptance/testdata/%.errors.ndjson,$(BAD_FEATURE_FILES))

.DEFAULT_GOAL = help

help: ## Show this help
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make <target>\n\nWhere <target> is one of:\n"} /^[$$()% a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

generate: $(GRAMMAR_GENERATED) ## Generate gherkin parser files

generate-all: $(ALL_GENERATED)

clean-generate: ## Remove generated Gherkin parser files ## Generate gherkin parser files
rm -f $(GRAMMAR_GENERATED)

copy-gherkin-languages:
echo "Nothing to do"

clean-gherkin-languages: ## Remove gherkin-languages.json and any derived files
echo "Nothing to do"

clean: clean-deps ## Remove all build artifacts and files generated by the acceptance tests
rm -rf .built .configured
rm -rf acceptance
rm -rf build

.DELETE_ON_ERROR:

acceptance: .built $(TOKENS) $(ASTS) $(PICKLES) $(ERRORS) $(SOURCES) ## Build acceptance test dir and compare results with reference

.built: .configured $(SOURCE_FILES)
cmake --build build/gherkin --parallel $(NPROCS)
cmake --install build/gherkin
touch $@

.configured: .deps-installed
rm -rf build/gherkin && mkdir -p build/gherkin
cmake \
-G Ninja \
-DCMAKE_PREFIX_PATH=$(CMAKE_BUILDROOT) \
-DCMAKE_INSTALL_PREFIX=$(HERE)/stage \
-S . \
-B build/gherkin \
--toolchain cmake/toolchains/ext.cmake
touch $@

define berp-generate-parser =
berp -g ../gherkin.berp -t $< -o $@ --noBOM
endef

include/gherkin/rule_type.hpp: gherkin-cpp-rule-type.razor ../gherkin.berp
$(berp-generate-parser)

include/gherkin/parser.hpp: gherkin-cpp-parser.razor ../gherkin.berp
$(berp-generate-parser)

src/lib/gherkin/dialect.cpp: ../gherkin-languages.json dialect.cpp.jq
jq -f dialect.cpp.jq -r -c < $< > $@

acceptance/testdata/%.tokens: ../testdata/% ../testdata/%.tokens
mkdir -p $(@D)
$(GHERKIN_GENERATE_TOKENS) $< > $@
diff --unified $<.tokens $@

acceptance/testdata/%.ast.ndjson: ../testdata/% ../testdata/%.ast.ndjson
mkdir -p $(@D)
$(GHERKIN) --no-source --no-pickles --predictable-ids $< | jq --sort-keys --compact-output "." > $@
diff --unified <(jq "." $<.ast.ndjson) <(jq "." $@)

acceptance/testdata/%.pickles.ndjson: ../testdata/% ../testdata/%.pickles.ndjson
mkdir -p $(@D)
$(GHERKIN) --no-source --no-ast --predictable-ids $< | jq --sort-keys --compact-output "." > $@
diff --unified <(jq "." $<.pickles.ndjson) <(jq "." $@)

acceptance/testdata/%.source.ndjson: ../testdata/% ../testdata/%.source.ndjson
mkdir -p $(@D)
$(GHERKIN) --no-ast --no-pickles --predictable-ids $< | jq --sort-keys --compact-output "." > $@
diff --unified <(jq "." $<.source.ndjson) <(jq "." $@)

acceptance/testdata/%.errors.ndjson: ../testdata/% ../testdata/%.errors.ndjson
mkdir -p $(@D)
$(GHERKIN) --no-source --predictable-ids $< | jq --sort-keys --compact-output "." > $@
diff --unified <(jq "." $<.errors.ndjson) <(jq "." $@)

#
# External dependencies
#
install-deps: .deps-installed
.PHONY: install-deps

.deps-installed:
./scripts/build-externals deps.txt
touch $@

clean-deps:
rm -rf .deps-installed build/root build/ext
.PHONY: clean-deps
13 changes: 13 additions & 0 deletions cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Gherkin for C++

Gherkin parser/compiler for C++. Please see [Gherkin](https://github.com/cucumber/gherkin) for details.

## Developers

Some files are generated from the `gherkin-*.razor` file. Please run the
following command to generate the C++ files.

~~~bash
cd <project_root>/cpp
make generate-all
~~~
18 changes: 18 additions & 0 deletions cpp/cmake/toolchains/ext.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# CMake toolchain to be used when building external libraries
#

# use, i.e. don't skip the full RPATH for the build tree
set(CMAKE_SKIP_BUILD_RPATH FALSE)

find_program(CCACHE ccache)

if(CCACHE)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE}")
endif()

if(DEFINED ENV{GHERKIN_DEBUG})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fsanitize=address")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fsanitize=address")
endif()
2 changes: 2 additions & 0 deletions cpp/deps.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
https://github.com/nlohmann/json/archive/refs/tags/v3.11.2.zip -DJSON_BuildTests=OFF
https://github.com/cucumber/messages/archive/refs/heads/main.zip --src-dir=cpp
44 changes: 44 additions & 0 deletions cpp/dialect.cpp.jq
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
. as $root |
[
"#include <gherkin/dialect.hpp>\n\n",
"namespace gherkin {\n\n",
"const keywords_maps&\n",
"all_keywords()\n",
"{\n",
" static const keywords_maps kwms = {\n",
" ",
(
[
to_entries[] | .key as $lang | .value |
[
("{\n \"",$lang,"\",\n {\n"),
(" "),
(
[
{
"and", "background", "but", "examples", "feature", "given",
"rule", "scenario", "scenarioOutline", "then", "when"
} | to_entries[] |
[
"{ \"", .key, "\", { ",
(
[.value[] | [@json] | add] | join(", ")
),
" } }"
] | add
] | join(",\n ")
),
("\n }\n }")
] | add
] | join(",\n ")
),
"\n };\n\n",
" return kwms;",
"}\n\n",
"const keywords_map&\n",
"keywords(const std::string_view& language)\n",
"{\n",
" return all_keywords().at(language);\n",
"}\n\n",
"}\n"
] | add
Loading

0 comments on commit 9307e30

Please sign in to comment.