Table of Contents
JuCC is developed in C++17
.
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/ortype_traits
such asstd::enable_if
,std::is_same
.
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 intobenchmark
.test
: This is where Google Tests and their utility code reside.src
should not have dependencies going intotest
.benchmark
may have dependencies going intotest
.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.).
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.
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
).
See here.
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?
- 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.
- CMake uses either make (the default) or ninja (requested by passing
-GNinja
as an argument tocmake
).- We strongly encourage using
ninja
, which is faster and can intelligently build in parallel by default.
- We strongly encourage using
For example, to manually compile JuCC, this is what you would do:
- Clone the JuCC repo:
git clone https://github.com/TheSYNcoder/JuCC.git
- Create a build folder to build everything in:
mkdir build
- Go to the build folder:
cd build
- Generate a build system with CMake, passing in whatever arguments are desired:
cmake -GNinja -DCMAKE_BUILD_TYPE=Release
- Invoke the build system:
ninja jucc
, more generallyninja <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
.
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.
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.
See here.