Skip to content

Latest commit

 

History

History
118 lines (77 loc) · 7.64 KB

cpp_guidelines.md

File metadata and controls

118 lines (77 loc) · 7.64 KB

C++ Guidelines

Table of Contents

Language

JuCC is developed in C++17.

C++ Crash Course

C++ provides a lot of leeway in compiler development compared to other high-level languages. For instance, it supports both manual and automated memory management, varied styles of programming, stronger type checking, different kinds of polymorphism etc.

Here's a list of useful references :

  • cppreference is an online reference of the powerful Standard Template Library (STL).
  • C++ FAQ covers a lots of topics.

Here's a list of modern features that you might want to make use of:

  • auto type inference
  • Range-based for loops
  • Smart pointers, in particular unique_ptr.
  • STL data structures, such as unordered_set, unordered_map, etc.
  • Threads, deleted member functions, lambdas, etc.
  • static_assert and/or type_traits such as std::enable_if, std::is_same.

C++ Project Structure

Directory Structure

Organize source code files into relevant folders based on their functionality. Separate binary files from source files, and production code from testing code.

Code directories

  • src: This is where the bulk of the code for JuCC lives. Anything that you expect to be compiled into the release should be here.
  • benchmark: This is where Google Benchmarks and their utility code reside. src should not have dependencies going into benchmark.
  • test: This is where Google Tests and their utility code reside. src should not have dependencies going into test. benchmark may have dependencies going into test.
  • third_party: This is where we add code which was not written by us but which we need to modify.

Infrastructure directories

  • script: Scripts that support development and testing lives here. (e.g. dependency installation).
  • build-support: Files that support Continuous Integration CMake targets (lint, format, etc.).
src

There can be at most 2-levels of directories under src, the first level will be general system components (e.g. storage, execution, network, sql, common), and the second level will be either for a class of similar files, or for a self-contained sub-component.

Translated into coding guidelines, you should rarely need to create a new first-level subdirectory, and should probably ask on Slack if you believe you do. To create a new secondary directory, make sure you meet the following criteria:

  • There are more than 2 (exclusive) files you need to put into this folder
  • Each file is stand-alone, i.e. either the contents don't make sense living in a single file, or that putting them in a single file makes the file large and difficult to navigate. (This is open to interpretation, but if, for example, you have 3 files containing 10-line class definitions, maybe they should not be spread out that much).

And one of the two:

  • The subdirectory is a self-contained sub-component. This probably means that the folder only has one outward facing API. A good rule of thumb is when outside code files only need to include one header from this folder, where said API is defined.
  • The subdirectory contains a logical grouping of files, and there are enough of them that leaving them ungrouped makes the upper level hard to navigate. (e.g. all the plans, all the common data structures, etc.)

A good rule of thumb is if you have subdirectory As, you should be able to say with a straight face that everything under As is an A. (e.g. Everything under containers is a container)

Every class and/or function under these directories should be in namespaces. All code will be under namespace JuCC, and namespace the same as their first-level directory name (e.g common, storage). Secondary sub-directories do not have associated namespaces.

test

The directory structure of the test folder should generally reflect the directory structure of the src folder, ignoring the include. Each test should be under the same path as the file they test, and named "XXX_test".

Generally, there can be no code sharing between tests since they are different build targets. There are cases, however, where it makes sense for tests to share some common utility function. In that case, you can write a utility file under test/util.

The test/util folder should have no sub-directories. In most cases, one test util file for every directory under src should suffice. (e.g. test/util/storage_test_util.h) Sometimes it will make sense to have a util file for stand-alone modules (e.g. test/include/util/random_test_util.h).

C++ Code Style

See here.

Compiler

We support GCC and LLVM Clang. We do NOT support AppleClang aka whatever compiler comes on macOS by default.

How is the compiler actually invoked?

  1. CMake is a build system generator that is commonly used for C++ development.
    • CMake does not compile your program.
    • Running cmake <PATH TO FOLDER WITH CMakeLists.txt> <OPTIONAL ARGUMENTS> generates a system that will compile your program.
  2. CMake uses either make (the default) or ninja (requested by passing -GNinja as an argument to cmake).
    • We strongly encourage using ninja, which is faster and can intelligently build in parallel by default.

For example, to manually compile JuCC, this is what you would do:

  1. Clone the JuCC repo: git clone https://github.com/TheSYNcoder/JuCC.git
  2. Create a build folder to build everything in: mkdir build
  3. Go to the build folder: cd build
  4. Generate a build system with CMake, passing in whatever arguments are desired: cmake -GNinja -DCMAKE_BUILD_TYPE=Release
  5. Invoke the build system: ninja jucc, more generally ninja <NAME OF TARGET> for any valid target.

We have configured the build system so that it will produce a compile_commands.json file. This contains the exact compiler invocations that will be used. You can check this file if you're curious. This file is also used by tools like clang-tidy to check for correctness. If you are not sure what a compiler flag does, look it up on Google or on the man page for gcc or clang.

Debugging

You should use a debugger to find any bugs where possible.

If you need to do complex debugging, you may want to check out the following links:

  • gdb: General GDB documentation.
  • lldb: General LLDB documentation. A competitor to GDB.
  • rr: A reversible debugger that lets you go backwards in time. Very powerful, but requires some level of hardware support.

Testing

Unit tests are critical for ensuring the correct functionality of your modules and reduce time spent on debugging. It can help prevent regressions. We use googletest, a nice unit-testing framework for C++ projects.

You should write unit test cases for each class/algorithm that you have added or modified. See the testing section for detail. Try to come up with test cases that make sure that the module exhibits the desired behavior. Some developers even suggest writing the unit tests before implementing the code. Make sure that you include corner cases, and try to find off-by-one errors.

Documentation

See here.