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

Feat/cpp concepts #218

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/test_python.yaml
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ jobs:
- name: Install Doxygen (Windows)
if: contains(matrix.os, 'windows')
uses: ssciwr/doxygen-install@v1
with:
with:
version: "1.9.6"
##################################################################################
- name: Doxygen Version Dump
4 changes: 2 additions & 2 deletions docs/reference.rst
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ and the following (simplified, read the Sphinx docs for the full story) occurs:
- Exhale requests notification of the `builder-inited`__ event, which is the first
event where the configuration variables have been populated.

__ https://www.sphinx-doc.org/en/master/extdev/appapi.html#event-builder-inited
__ https://www.sphinx-doc.org/en/master/extdev/event_callbacks.html#event-builder-inited

- It would be nice to one-day support incremental builds and a clean target, but at
this time I have no idea how to do these.
@@ -86,7 +86,7 @@ and the following (simplified, read the Sphinx docs for the full story) occurs:

Even if you don't have a solution, it would be great to hear of ideas!

__ https://www.sphinx-doc.org/en/master/extdev/appapi.html#event-env-get-outdated
__ https://www.sphinx-doc.org/en/master/extdev/event_callbacks.html#event-env-get-outdated

3. Now that the extensions have been setup, the rest of ``conf.py`` is processed. For
all intensive purposes, you can assume that as soon as this is complete is when
30 changes: 29 additions & 1 deletion exhale/graph.py
Original file line number Diff line number Diff line change
@@ -199,6 +199,9 @@ def __init__(self, name, kind, refid):
self.parameters = [] # list of strings: ["int", "int"] for foo(int x, int y)
self.template = None # list of strings

if self.kind == "concept":
self.template = None # list of strings

def __lt__(self, other):
'''
The ``ExhaleRoot`` class stores a bunch of lists of ``ExhaleNode`` objects.
@@ -321,8 +324,13 @@ def full_signature(self):
name=self.name,
parameters=", ".join(self.parameters)
)
if self.kind == "concept":
return "{template} {name}".format(
template="template <{0}> ".format(", ".join(self.template)) if self.template is not None else "",
name=self.name)

raise RuntimeError(
"full_signature may only be called for a 'function', but {name} is a '{kind}' node.".format(
"full_signature may only be called for a 'function' or 'concept', but {name} is a '{kind}' node.".format(
name=self.name, kind=self.kind
)
)
@@ -1020,6 +1028,9 @@ class ExhaleRoot(object):
ExhaleNode it came from. Storing it this way is convenient for when the
Doxygen xml file is being parsed.

``concepts`` (list)
The full list of ExhaleNodes of kind ``concept``

``class_like`` (list)
The full list of ExhaleNodes of kind ``struct`` or ``class``

@@ -1084,6 +1095,8 @@ def __init__(self):
# doxygenindex <-+-> IGNORE |
# autodoxygenindex <-+-> IGNORE |
# -------------------+----------------+
# doxygenconcept <-+-> "concept" |
self.concepts = [] # |
# doxygenclass <-+-> "class" |
# doxygenstruct <-+-> "struct" |
self.class_like = [] # |
@@ -1668,6 +1681,8 @@ def trackNodeIfUnseen(self, node):
node.set_owner(self)
self.all_nodes.append(node)
self.node_by_refid[node.refid] = node
if node.kind == "concept":
self.concepts.append(node)
if node.kind == "class" or node.kind == "struct":
self.class_like.append(node)
elif node.kind == "namespace":
@@ -2218,6 +2233,7 @@ def sortInternals(self):
# have each node sort its children
# leaf-like lists: no child sort
self.defines.sort()
self.concepts.sort()
self.enums.sort()
self.enum_values.sort()
self.functions.sort()
@@ -3092,6 +3108,7 @@ def generateNamespaceChildrenString(self, nspace):
# sort the children
nsp_namespaces = []
nsp_nested_class_like = []
nsp_concepts = []
nsp_enums = []
nsp_functions = []
nsp_typedefs = []
@@ -3112,6 +3129,8 @@ def generateNamespaceChildrenString(self, nspace):
child.findNestedClassLike(nsp_nested_class_like)
child.findNestedEnums(nsp_enums)
child.findNestedUnions(nsp_unions)
elif child.kind == "concept":
nsp_concepts.append(child)
elif child.kind == "enum":
nsp_enums.append(child)
elif child.kind == "function":
@@ -3126,6 +3145,7 @@ def generateNamespaceChildrenString(self, nspace):
# generate their headings if they exist (no Defines...that's not a C++ thing...)
children_stream = StringIO()
self.generateSortedChildListString(children_stream, "Namespaces", nsp_namespaces)
self.generateSortedChildListString(children_stream, "Concepts", nsp_concepts)
self.generateSortedChildListString(children_stream, "Classes", nsp_nested_class_like)
self.generateSortedChildListString(children_stream, "Enums", nsp_enums)
self.generateSortedChildListString(children_stream, "Functions", nsp_functions)
@@ -3338,6 +3358,7 @@ def generateFileNodeDocuments(self):

# generate their headings if they exist --- DO NOT USE findNested*, these are included recursively
file_structs = []
file_concepts = []
file_classes = []
file_enums = []
file_functions = []
@@ -3348,6 +3369,8 @@ def generateFileNodeDocuments(self):
for child in f.children:
if child.kind == "struct":
file_structs.append(child)
elif child.kind == "concept":
file_concepts.append(child)
elif child.kind == "class":
file_classes.append(child)
elif child.kind == "enum":
@@ -3366,6 +3389,7 @@ def generateFileNodeDocuments(self):
# generate the listing of children referenced to from this file
children_stream = StringIO()
self.generateSortedChildListString(children_stream, "Namespaces", f.namespaces_used)
self.generateSortedChildListString(children_stream, "Concepts", file_concepts)
self.generateSortedChildListString(children_stream, "Classes", file_structs + file_classes)
self.generateSortedChildListString(children_stream, "Enums", file_enums)
self.generateSortedChildListString(children_stream, "Functions", file_functions)
@@ -4028,6 +4052,7 @@ class view hierarchy. It will be present in the file page it was declared in
Currently, the API is generated in the following (somewhat arbitrary) order:

- Namespaces
- Concepts
- Classes and Structs
- Enums
- Unions
@@ -4105,6 +4130,7 @@ def __len__(self):

dump_order = [
("Namespaces", "namespace"),
("Concepts", "concept"),
("Classes and Structs", "class"), # NOTE: class/struct stored together!
("Enums", "enum"),
("Unions", "union"),
@@ -4197,6 +4223,7 @@ def toConsole(self):
console. Unused in the release, but is helpful for debugging ;)
'''
fmt_spec = {
"concept": utils.AnsiColors.BOLD_MAGENTA,
"class": utils.AnsiColors.BOLD_MAGENTA,
"struct": utils.AnsiColors.BOLD_CYAN,
"define": utils.AnsiColors.BOLD_YELLOW,
@@ -4215,6 +4242,7 @@ def toConsole(self):

self.consoleFormat(
"{0} and {1}".format(
utils._use_color("Concepts", fmt_spec["concept"], sys.stderr),
utils._use_color("Classes", fmt_spec["class"], sys.stderr),
utils._use_color("Structs", fmt_spec["struct"], sys.stderr),
),
6 changes: 6 additions & 0 deletions exhale/utils.py
Original file line number Diff line number Diff line change
@@ -106,6 +106,7 @@ def get_time():


AVAILABLE_KINDS = [
"concept",
"class",
"define",
"dir",
@@ -131,6 +132,7 @@ def get_time():
"define",
"enum",
"function",
"concept",
"class",
"struct",
"typedef",
@@ -405,6 +407,8 @@ def qualifyKind(kind):
+=============+==================+
| "class" | "Class" |
+-------------+------------------+
| "concept" | "Concept" |
+-------------+------------------+
| "define" | "Define" |
+-------------+------------------+
| "enum" | "Enum" |
@@ -460,6 +464,8 @@ def kindAsBreatheDirective(kind):
+-------------+--------------------+
| Input Kind | Output Directive |
+=============+====================+
| "concept" | "doxygenconcept" |
+-------------+--------------------+
| "class" | "doxygenclass" |
+-------------+--------------------+
| "define" | "doxygendefine" |
3 changes: 2 additions & 1 deletion testing/projects/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ project(exhale-projects)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_BUILD_TYPE "Debug") # required for code coverage
@@ -116,6 +116,7 @@ endmacro()
# List of projects to validate have code that actually compiles xD
set(EXHALE_PROJECTS
c_maths
cpp_concepts
cpp_func_overloads
cpp_long_names
cpp_dir_underscores
22 changes: 22 additions & 0 deletions testing/projects/cpp_concepts/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
########################################################################################
# This file is dedicated to the public domain. If your jurisdiction requires a #
# specific license: #
# #
# Copyright (c) Stephen McDowell, 2017-2022 #
# License: CC0 1.0 Universal #
# License Text: https://creativecommons.org/publicdomain/zero/1.0/legalcode #
########################################################################################
cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(cpp-concepts LANGUAGES CXX)

# "Header only library": add tests and include the directory
target_sources(exhale-projects-unit-tests
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src/tests.cpp
)
target_include_directories(exhale-projects-tests-interface
INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/include
)

add_open_cpp_coverage_source_dirs(include src)
5 changes: 5 additions & 0 deletions testing/projects/cpp_concepts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
The ``cpp concepts`` test project.
"""

# TODO(svenevs): add final hierarchies for the project and python tests once complete.
27 changes: 27 additions & 0 deletions testing/projects/cpp_concepts/include/concepts/concepts.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/***************************************************************************************
* This file is dedicated to the public domain. If your jurisdiction requires a *
* specific license: *
* *
* Copyright (c) Stephen McDowell, 2017-2022 *
* License: CC0 1.0 Universal *
* License Text: https://creativecommons.org/publicdomain/zero/1.0/legalcode *
**************************************************************************************/
#pragma once

namespace concepts {

/// See https://en.cppreference.com/w/cpp/language/constraints
template <class T, class U>
concept Derived = std::is_base_of<U, T>::value;

/// A pure virtual base class that defines a useless interface.
struct Base {
/// The value of something.
virtual int value() const = 0;
};

/// Constrained function example made concrete from
/// https://en.cppreference.com/w/cpp/language/constraints
template <Derived<Base> T>
int get_value(const T& t) { return t.value(); }
} // namespace concepts
21 changes: 21 additions & 0 deletions testing/projects/cpp_concepts/src/tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/***************************************************************************************
* This file is dedicated to the public domain. If your jurisdiction requires a *
* specific license: *
* *
* Copyright (c) Stephen McDowell, 2017-2022 *
* License: CC0 1.0 Universal *
* License Text: https://creativecommons.org/publicdomain/zero/1.0/legalcode *
**************************************************************************************/
#include <catch2/catch.hpp>

/* ================================================================================== */

#include <concepts/concepts.hpp>
TEST_CASE( "concepts.hpp", "[cpp-concepts]" ) {
struct Concrete : public concepts::Base {
int value() const { return 11; }
};
Concrete c;

REQUIRE(concepts::get_value(c) == 11);
}