This implementation of the Calculator App uses a Context-Free Grammar (CFG) to define the structure of valid expressions and instructions. CFG is a formal set of production rules that describe how expressions in a language can be formed.
The grammar is based on BNF (Backus-Naur Form) — a notation used to formally specify the syntax of programming languages and other structured formats. BNF is considered a meta-syntax, meaning it provides a systematic way to describe the rules that govern the structure of a language. It is commonly used for defining document formats, instruction sets, and communication protocols.
There are several variations of BNF, including:
- EBNF (Extended Backus-Naur Form) – introduces additional syntactic sugar for readability.
- ABNF (Augmented Backus-Naur Form) – often used in internet protocol specifications.
BNF-based grammars consist of three fundamental components:
- Non-terminal symbols – These represent abstract language constructs and are typically enclosed in angle brackets (e.g., , ). They can be replaced by other non-terminals or terminals according to the grammar rules.
- Terminal symbols – These are the actual symbols or keywords used in the language (e.g., +, -, numbers, parentheses).
- Production rules (derivations) – These define how non-terminal symbols can be rewritten as a combination of terminals and/or non-terminals.
Simplified BNF Grammar of the Calculator:
<expression> ::= <term>
| <expression> + <term>
| <expression> - <term>
<term> ::= <power>
| <term> * <power>
| <term> / <power>
<power> ::= <postfix>
| <postfix> ^ <power>
<postfix> ::= <primary>
| <postfix> !
<primary> ::= <number>
| ( <expression> )
| - <primary>
| sqrt <primary>
This grammar allows for arithmetic expressions using addition, subtraction, multiplication, division, parentheses, and unary negation. Parsing is done recursively, reflecting the hierarchical structure defined by these grammar rules.
To build this project, a few prerequisites are required:
- Qt 6.9.1 (used for the GUI)
- g++ compiler
- CMake build tools
- CXXOPTS library (header), SPDLOG library (static lib)
To simplify the build process and ensure consistency, the entire build environment is isolated within a Docker container. The Dockerfile is structured into multiple stages to enable layer caching. This means that only the first build may take a while; subsequent builds will be much faster — as long as there are no changes to the build environment.
Note: Building the project requires access to the Qt Essentials libraries, which are available to registered Qt users.
To obtain access, please create a free Qt account at:
👉 https://login.qt.io/register
You will need to provide your Qt credentials as build arguments.
- Install Docker following the official instructions. Example for Ubuntu: Docker Installation Guide for Ubuntu
- Run the following command, replacing the placeholder values with your Qt account credentials:
sudo docker build \
-f Dockerfile.build \
--build-arg QT_USERNAME=<email-used-for-qt-registration> \
--build-arg QT_PASSWORD=<password-used-for-qt-registration> \
--output type=local,dest=./ .The build output will be available in the ./artifacts directory.
To run the application, essential OpenGL-related libraries are required. These libraries
will be installed automatically during the installation of the Calculator .deb package:
apt install -y ./artifacts/calculator_*.debOnce installed, you can launch the application by running:
cd /opt/calculator/usr/bin/
./calculator -uAlternatively, the application can be containerized with Docker:
docker build -t calculator .To run the application from within the Docker container:
xhost +local:docker
docker run \
--rm -it \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix:/tmp/.X11-unix \
calculatorThe application supports multiple run modes, as described in the sections below.
For usage details and available options, execute the binary with the --help flag:
./calculator --helpTo pass arguments to the application when running in Docker, use the APP_ARGS
environment variable:
-e APP_ARGS="--help"To launch the graphical user interface, run:
./calculator -uThis will display the GUI:
To directly evaluate an expression, use the -e flag:
./calculator -e <expression>;Example:
./calculator -e "(1+2)*3;"Note: Don’t forget the semicolon (;) at the end of the expression. It is required to signal the end of the input. If omitted, the calculator will continue to wait for additional tokens.
To enter interactive mode, run:
./calculator -iThis mode displays a prompt and allows you to enter expressions one at a time,
confirming each with the <ENTER> key:
Launching calculator in interactive mode.
Enter expression (or q to quit):
> (1+2)*3;
= 9
> q
Exiting calculator.Two logging destinations are available: console and file. By default, console
logging is set to the warn level, and file logging is set to the trace level.
To change the logging level for both loggers, set the CALC_LOG_LEVEL environment
variable:
export CALC_LOG_LEVEL="main=warn"To change the logging level for the console logger only:
export CALC_CONSOLE_LOG_LEVEL="warn"To change the logging level for the file logger only:
export CALC_FILE_LOG_LEVEL="warn"Supported logging levels (from most to least verbose):
trace > debug > info > warn > err > critical > off
If you prefer to set up the development environment directly on your host machine for greater control during development, follow the tips below.
This project requires the following external dependencies:
- CXXOPTS: Header-only library
- SPDLOG: Static library
- Qt 6 Framework: Essential modules
Ensure that the following environment variables are set to the appropriate paths where these dependencies are located. For example:
export Qt6_DIR="/opt/Qt/6.9.1/gcc_64/lib/cmake/Qt6"
export SPDLOG_DIR="/opt/spdlog/lib"
export SPDLOG_H_DIR="/opt/spdlog/include"
export CXXOPTS_H_DIR="/opt/cxxops/include"Recommended configuration files are provided in the ./.vscode directory. These allow you to:
- Configure the project (this generates the appropriate Makefile):
Ctrl + Shift + P
> Type: "Run Task"
> Select: "CMake Configure [<Type of build>]- Build the project (this compiles the executable binary):
Ctrl + Shift + P
> Type: "Run Task"
> Select: "CMake Build [<Type of build>]By default, the build type is set to Debug, so pressing Ctrl + Shift + B will rebuild the project
in Debug mode.
Alternatively, you can configure and build the project directly using the CMake command-line tools:
cmake -S . -B "./build/Debug" -DCMAKE_BUILD_TYPE="Debug"
cmake --build "./build/Debug" --config "Debug"To create .deb package:
cpack -G DEB -B "./build/deb" --config "./build/Debug/CPackConfig.cmake"Basic unit tests are currently provided in the tests folder, utilizing the gtest framework. The appropriate test binary should be built using CMake. You can run the tests with the following command:
./build/Debug/bin/test_calculatorOptionally, you can generate a JUnit-compliant report:
./build/Debug/bin/test_calculator --gtest_output=xml:test-results.xmlTo convert the report into a human-readable HTML format, use the provided script:
python3 ./scripts/gtest_to_html.py --input test-results.xml --output test-results.htmlYou can also use the run-tests.yaml GitHub Actions workflow to execute tests on a selected branch.
To release a new version of the Calculator application, first tag the source code you wish to use for the release using semantic versioning. Then, push the tag to the repository. For example:
git tag v1.0.0
git push origin v1.0.0