Skip to content

satya-das/cppparser

Repository files navigation

CppParser

Build Status Codacy Badge License: MIT

An easy, fast, and robust library to parse C/C++ source code.

Features

  • No preprocessing; preprocessor constructs are part of the AST.
  • Preserves most comments.
  • Implemented from scratch using backtracking Yacc (BtYacc), so no dependency on libclang.
  • Parses into a structured AST where file elements form a tree.
  • Minimal dependencies: Flex on Unix-like platforms (Linux, macOS, etc.). On Windows, Flex for Windows is bundled for out-of-the-box usage.
  • Supports parsing multi-file programs.

Motivation

CppParser can be used to build tools that need parsing of C/C++ files. It is also used to develop cib, which implements ABI-stable SDK architecture for C++ libraries.

Example

To begin with we will see an example of parsing a hello-world program and see the AST that CppParser creates:

#include <iostream>

int main()
{
  std::cout << "Hello, World!\n";

  return 0;
}

For the above hello-world program we can expect the generated AST to look like the following: AST for Hello World program

So, how do we access these elements of AST using CppParser? Below is the program written as a unit test for validating the correctness of the generated AST:

#include <catch/catch.hpp>

#include "cppparser/cppparser.h"

#include <filesystem>

namespace fs = std::filesystem;

TEST_CASE("Parsing hello world program")
{
  cppparser::CppParser parser;
  const auto           testFilePath = fs::path(__FILE__).parent_path() / "test-files/hello-world.cpp";
  const auto           ast          = parser.parseFile(testFilePath.string());
  REQUIRE(ast);

  const auto members = GetAllOwnedEntities(*ast);
  REQUIRE(members.size() == 2);

  const cppast::CppConstPreprocessorIncludeEPtr hashInclude = members[0];
  REQUIRE(hashInclude);
  CHECK(hashInclude->name() == "<iostream>");

  cppast::CppConstFunctionEPtr func = members[1];
  REQUIRE(func);
  CHECK(func->name() == "main");

  REQUIRE(func->defn());
  const auto mainBodyMembers = GetAllOwnedEntities(*func->defn());
  REQUIRE(mainBodyMembers.size() == 2);

  cppast::CppConstBinomialExprEPtr coutHelloWorld = mainBodyMembers[0];
  REQUIRE(coutHelloWorld);
  CHECK(coutHelloWorld->oper() == cppast::CppBinaryOperator::INSERTION);

  cppast::CppConstNameExprEPtr coutOperand1 = &(coutHelloWorld->term1());
  REQUIRE(coutOperand1);
  CHECK(coutOperand1->value() == "std::cout");

  cppast::CppConstStringLiteralExprEPtr coutOperand2 = &(coutHelloWorld->term2());
  REQUIRE(coutOperand2);
  CHECK(coutOperand2->value() == "\"Hello, World!\\n\"");
}

This example is real and is part of the actual unit tests for CppParser.

For AST traversal, see CppWriter, which uses the generated AST to create files.

Building CppParser

Get the source

git clone https://github.com/satya-das/CppParser.git

Configure and build

cd cppparser
mkdir builds
cd builds
cmake ..
make

Alternatively, if you prefer Ninja instead of make:

cd cppparser
mkdir builds
cd builds
cmake -G Ninja ..
ninja

For contributors who need to run tests

Make sure you also clone the dependencies. Run the following command in the parent directory of the root of cppparser.

git clone https://github.com/satya-das/common.git

After this command the common and cppparser folders should be side by side.

Configure and run tests

cd cppparser
mkdir builds
cd builds
cmake -G Ninja .. -DCPPPARSER_BUILD_TESTS=ON
ninja && ninja test

Modify the command by removing -G Ninja if you prefer make.