Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added C++ implementation #173

Merged
merged 1 commit into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


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