Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7de6e82

Browse files
author
Bowen Fu
committedAug 23, 2021
cmake.
1 parent d0c7ab2 commit 7de6e82

19 files changed

+1356
-0
lines changed
 

‎ .clang-format

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BasedOnStyle: Microsoft

‎.github/FUNDING.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# These are supported funding model platforms
2+
3+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4+
patreon: bfu # Replace with a single Patreon username
5+
open_collective: # Replace with a single Open Collective username
6+
ko_fi: bowenfu # Replace with a single Ko-fi username
7+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9+
liberapay: bfu # Replace with a single Liberapay username
10+
issuehunt: # Replace with a single IssueHunt username
11+
otechie: # Replace with a single Otechie username
12+
custom: http://paypal.me/bowfu # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

‎.github/workflows/cmake.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: CMake
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
11+
BUILD_TYPE: Release
12+
13+
jobs:
14+
build:
15+
# The CMake configure and build commands are platform agnostic and should work equally
16+
# well on Windows or Mac. You can convert this to a matrix build if you need
17+
# cross-platform coverage.
18+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
19+
runs-on: ${{ matrix.os }}
20+
21+
strategy:
22+
matrix:
23+
os: [macos-latest, ubuntu-latest, windows-latest]
24+
std: [17, 20]
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
29+
- name: Configure CMake
30+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
31+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
32+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}}
33+
34+
- name: Build
35+
# Build your program with the given configuration
36+
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4
37+
38+
- name: Test
39+
working-directory: ${{github.workspace}}/build
40+
# Execute tests defined by the CMake configuration.
41+
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
42+
run: ctest --output-on-failure --parallel 4 -C ${{env.BUILD_TYPE}}
43+

‎.github/workflows/codeql-analysis.yml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL"
13+
14+
on:
15+
push:
16+
branches: [ main ]
17+
pull_request:
18+
# The branches below must be a subset of the branches above
19+
branches: [ main ]
20+
schedule:
21+
- cron: '24 14 * * 1'
22+
23+
jobs:
24+
analyze:
25+
name: Analyze
26+
runs-on: ubuntu-latest
27+
permissions:
28+
actions: read
29+
contents: read
30+
security-events: write
31+
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
language: [ 'cpp' ]
36+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37+
# Learn more:
38+
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39+
40+
steps:
41+
- name: Checkout repository
42+
uses: actions/checkout@v2
43+
44+
# Initializes the CodeQL tools for scanning.
45+
- name: Initialize CodeQL
46+
uses: github/codeql-action/init@v1
47+
with:
48+
languages: ${{ matrix.language }}
49+
# If you wish to specify custom queries, you can do so here or in a config file.
50+
# By default, queries listed here will override any specified in a config file.
51+
# Prefix the list here with "+" to use these queries and those in the config file.
52+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
53+
54+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55+
# If this step fails, then you should remove it and run the build manually (see below)
56+
- name: Autobuild
57+
uses: github/codeql-action/autobuild@v1
58+
59+
# ℹ️ Command-line programs to run using the OS shell.
60+
# 📚 https://git.io/JvXDl
61+
62+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63+
# and modify them (or add more) to build your code if your project
64+
# uses a compiled language
65+
66+
#- run: |
67+
# make bootstrap
68+
# make release
69+
70+
- name: Perform CodeQL Analysis
71+
uses: github/codeql-action/analyze@v1

‎.github/workflows/coverage.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Test Coverage
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
11+
BUILD_TYPE: Coverage
12+
13+
jobs:
14+
build:
15+
# The CMake configure and build commands are platform agnostic and should work equally
16+
# well on Windows or Mac. You can convert this to a matrix build if you need
17+
# cross-platform coverage.
18+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
19+
runs-on: ${{ matrix.os }}
20+
21+
strategy:
22+
matrix:
23+
os: [ubuntu-latest]
24+
std: [17]
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
29+
- name: Install dependencies
30+
run: sudo apt install gcovr
31+
32+
- name: Configure CMake
33+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
34+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
35+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}}
36+
37+
- name: Build
38+
# Build your program with the given configuration
39+
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4
40+
41+
- name: Test
42+
working-directory: ${{github.workspace}}/build
43+
# Execute tests defined by the CMake configuration.
44+
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
45+
run: ctest --output-on-failure --parallel 4 -C ${{env.BUILD_TYPE}}
46+
47+
- name: Compute Coverage
48+
working-directory: ${{github.workspace}}/build
49+
run: gcovr -r .. -f ../include/lisp/ -f ../src/ -x coverage.xml .
50+
51+
- name: Upload Coverage
52+
working-directory: ${{github.workspace}}/build
53+
shell: bash
54+
run: bash <(curl -s https://codecov.io/bash) -f coverage.xml

‎.github/workflows/sanitizers.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Sanitizers
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
# The CMake configure and build commands are platform agnostic and should work equally
12+
# well on Windows or Mac. You can convert this to a matrix build if you need
13+
# cross-platform coverage.
14+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
15+
runs-on: ${{ matrix.os }}
16+
17+
strategy:
18+
matrix:
19+
os: [ubuntu-latest]
20+
# os: [macos-latest]
21+
std: [17]
22+
cxx: [clang++]
23+
build_type: [ASAN, TSAN, UBSAN, LSAN] # MSAN not supported by macos
24+
25+
steps:
26+
- uses: actions/checkout@v2
27+
28+
- name: Configure CMake
29+
env:
30+
CXX: ${{ matrix.cxx }}
31+
# ASAN_OPTIONS: suppressions=MyASan.supp
32+
33+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
34+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
35+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_CXX_STANDARD=${{matrix.std}}
36+
37+
- name: Build
38+
# Build your program with the given configuration
39+
run: cmake --build ${{github.workspace}}/build --config ${{ matrix.build_type }} --parallel 4
40+
41+
- name: Test
42+
working-directory: ${{github.workspace}}/build
43+
# Execute tests defined by the CMake configuration.
44+
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
45+
run: ctest --output-on-failure --parallel 4 -C ${{ matrix.build_type }}
46+

‎.github/workflows/valgrind.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: valgrind
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
11+
BUILD_TYPE: RelWithDebInfo
12+
13+
jobs:
14+
build:
15+
# The CMake configure and build commands are platform agnostic and should work equally
16+
# well on Windows or Mac. You can convert this to a matrix build if you need
17+
# cross-platform coverage.
18+
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
19+
runs-on: ${{ matrix.os }}
20+
21+
strategy:
22+
matrix:
23+
os: [ubuntu-latest]
24+
std: [17]
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
29+
- name: Install valgrind
30+
run: sudo apt-get install valgrind
31+
32+
- name: Configure CMake
33+
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
34+
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
35+
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -DCTEST_MEMORYCHECK_COMMAND=/usr/bin/valgrind
36+
37+
- name: Build
38+
# Build your program with the given configuration
39+
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4
40+
41+
- name: Test
42+
working-directory: ${{github.workspace}}/build
43+
# Execute tests defined by the CMake configuration.
44+
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
45+
run: |
46+
ctest --output-on-failure --parallel 4 -C ${{env.BUILD_TYPE}} --overwrite MemoryCheckCommandOptions="--leak-check=full --error-exitcode=100" -T memcheck
47+
# --overwrite MemoryCheckSuppressionFile=/path/to/valgrind.suppressions \
48+

‎CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
cmake_minimum_required(VERSION 3.15...3.19)
2+
3+
project(
4+
Lisp
5+
VERSION 0.0.1
6+
LANGUAGES CXX)
7+
8+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
9+
10+
if (NOT CMAKE_CXX_STANDARD)
11+
set(CMAKE_CXX_STANDARD 17)
12+
endif()
13+
14+
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
15+
16+
list(APPEND
17+
BASE_COMPILE_FLAGS
18+
"$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:-Wall;-Wextra;-pedantic;-Werror;-Wno-shadow;-Wconversion;-Wsign-conversion>"
19+
"$<$<CXX_COMPILER_ID:MSVC>:/W4>") # /WX for -Werror
20+
21+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
22+
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
23+
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
24+
25+
if (CMAKE_BUILD_TYPE STREQUAL "Coverage")
26+
include(CodeCoverage)
27+
list(APPEND BASE_COMPILE_FLAGS "-g;-O0;-fprofile-arcs;-ftest-coverage")
28+
endif() #CMAKE_BUILD_TYPE STREQUAL "Coverage"
29+
30+
if (CMAKE_BUILD_TYPE STREQUAL "MSAN")
31+
add_link_options("-L${PROJECT_SOURCE_DIR}/libcxx_msan/lib;-lc++abi")
32+
endif() #CMAKE_BUILD_TYPE STREQUAL "MSAN"
33+
34+
# Target.
35+
add_library(lisp)
36+
37+
add_subdirectory(src)
38+
39+
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
40+
include(CTest)
41+
if(BUILD_TESTING)
42+
include(Sanitizers)
43+
add_subdirectory(test)
44+
add_subdirectory(sample)
45+
endif()
46+
endif()

‎cmake/CodeCoverage.cmake

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#
2+
# 2012-01-31, Lars Bilke
3+
# - Enable Code Coverage
4+
#
5+
# 2013-09-17, Joakim Söderberg
6+
# - Added support for Clang.
7+
# - Some additional usage instructions.
8+
#
9+
# USAGE:
10+
11+
# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here:
12+
# http://stackoverflow.com/a/22404544/80480
13+
#
14+
# 1. Copy this file into your cmake modules path.
15+
#
16+
# 2. Add the following line to your CMakeLists.txt:
17+
# INCLUDE(CodeCoverage)
18+
#
19+
# 3. Set compiler flags to turn off optimization and enable coverage:
20+
# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
21+
# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
22+
#
23+
# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target
24+
# which runs your test executable and produces a lcov code coverage report:
25+
# Example:
26+
# SETUP_TARGET_FOR_COVERAGE(
27+
# my_coverage_target # Name for custom target.
28+
# test_driver # Name of the test driver executable that runs the tests.
29+
# # NOTE! This should always have a ZERO as exit code
30+
# # otherwise the coverage generation will not complete.
31+
# coverage # Name of output directory.
32+
# )
33+
#
34+
# 4. Build a Debug build:
35+
# cmake -DCMAKE_BUILD_TYPE=Debug ..
36+
# make
37+
# make my_coverage_target
38+
#
39+
#
40+
41+
# Check prereqs
42+
FIND_PROGRAM(GCOV_PATH gcov)
43+
FIND_PROGRAM(LCOV_PATH lcov)
44+
FIND_PROGRAM(GENHTML_PATH genhtml)
45+
FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests)
46+
47+
IF(NOT GCOV_PATH)
48+
MESSAGE(FATAL_ERROR "gcov not found! Aborting...")
49+
ENDIF() # NOT GCOV_PATH
50+
51+
IF(NOT CMAKE_COMPILER_IS_GNUCXX)
52+
# Clang version 3.0.0 and greater now supports gcov as well.
53+
MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.")
54+
55+
IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
56+
MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
57+
ENDIF()
58+
ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX
59+
60+
SET(CMAKE_CXX_FLAGS_COVERAGE
61+
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
62+
CACHE STRING "Flags used by the C++ compiler during coverage builds."
63+
FORCE )
64+
SET(CMAKE_C_FLAGS_COVERAGE
65+
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
66+
CACHE STRING "Flags used by the C compiler during coverage builds."
67+
FORCE )
68+
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
69+
""
70+
CACHE STRING "Flags used for linking binaries during coverage builds."
71+
FORCE )
72+
SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
73+
""
74+
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
75+
FORCE )
76+
MARK_AS_ADVANCED(
77+
CMAKE_CXX_FLAGS_COVERAGE
78+
CMAKE_C_FLAGS_COVERAGE
79+
CMAKE_EXE_LINKER_FLAGS_COVERAGE
80+
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
81+
82+
IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage"))
83+
MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" )
84+
ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
85+
86+
87+
# Param _targetname The name of new the custom make target
88+
# Param _testrunner The name of the target which runs the tests.
89+
# MUST return ZERO always, even on errors.
90+
# If not, no coverage report will be created!
91+
# Param _outputname lcov output is generated as _outputname.info
92+
# HTML report is generated in _outputname/index.html
93+
# Optional fourth parameter is passed as arguments to _testrunner
94+
# Pass them in list form, e.g.: "-j;2" for -j 2
95+
FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
96+
97+
IF(NOT LCOV_PATH)
98+
MESSAGE(FATAL_ERROR "lcov not found! Aborting...")
99+
ENDIF() # NOT LCOV_PATH
100+
101+
IF(NOT GENHTML_PATH)
102+
MESSAGE(FATAL_ERROR "genhtml not found! Aborting...")
103+
ENDIF() # NOT GENHTML_PATH
104+
105+
# Setup target
106+
ADD_CUSTOM_TARGET(${_targetname}
107+
108+
# Cleanup lcov
109+
${LCOV_PATH} --directory . --zerocounters
110+
111+
# Run tests
112+
COMMAND ${_testrunner} ${ARGV3}
113+
114+
# Capturing lcov counters and generating report
115+
#COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info
116+
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info --rc lcov_branch_coverage=1
117+
COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'build/*' 'tests/*' '/usr/*' --output-file ${_outputname}.info.cleaned
118+
COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned
119+
COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned
120+
121+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
122+
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
123+
)
124+
125+
# Show info where to find the report
126+
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
127+
COMMAND ;
128+
COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report."
129+
)
130+
131+
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE
132+
133+
# Param _targetname The name of new the custom make target
134+
# Param _testrunner The name of the target which runs the tests
135+
# Param _outputname cobertura output is generated as _outputname.xml
136+
# Optional fourth parameter is passed as arguments to _testrunner
137+
# Pass them in list form, e.g.: "-j;2" for -j 2
138+
FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname)
139+
140+
IF(NOT PYTHON_EXECUTABLE)
141+
MESSAGE(FATAL_ERROR "Python not found! Aborting...")
142+
ENDIF() # NOT PYTHON_EXECUTABLE
143+
144+
IF(NOT GCOVR_PATH)
145+
MESSAGE(FATAL_ERROR "gcovr not found! Aborting...")
146+
ENDIF() # NOT GCOVR_PATH
147+
148+
ADD_CUSTOM_TARGET(${_targetname}
149+
150+
# Run tests
151+
${_testrunner} ${ARGV3}
152+
153+
# Running gcovr
154+
COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/'-e'${CMAKE_SOURCE_DIR}/build/' -o ${_outputname}.xml
155+
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
156+
COMMENT "Running gcovr to produce Cobertura code coverage report."
157+
)
158+
159+
# Show info where to find the report
160+
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
161+
COMMAND ;
162+
COMMENT "Cobertura code coverage report saved in ${_outputname}.xml."
163+
)
164+
165+
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA

‎cmake/FetchGTest.cmake

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
include(FetchContent)
2+
3+
FetchContent_Declare(
4+
googletest
5+
GIT_REPOSITORY https://github.com/google/googletest.git
6+
GIT_TAG dcc92d0ab6c4ce022162a23566d44f673251eee4)
7+
8+
FetchContent_GetProperties(googletest)
9+
if(NOT googletest_POPULATED)
10+
FetchContent_Populate(googletest)
11+
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR}
12+
EXCLUDE_FROM_ALL)
13+
endif()
14+
15+
message(STATUS "GTest binaries are present at ${googletest_BINARY_DIR}")

‎cmake/Sanitizers.cmake

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Build Types
2+
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE}
3+
CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel TSAN ASAN LSAN MSAN UBSAN"
4+
FORCE)
5+
6+
# ThreadSanitizer
7+
set(CMAKE_C_FLAGS_TSAN
8+
"-fsanitize=thread -g -O1"
9+
CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds."
10+
FORCE)
11+
set(CMAKE_CXX_FLAGS_TSAN
12+
"-fsanitize=thread -g -O1"
13+
CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds."
14+
FORCE)
15+
16+
# AddressSanitize
17+
set(CMAKE_C_FLAGS_ASAN
18+
"-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
19+
ignorelist.txt
20+
CACHE STRING "Flags used by the C compiler during AddressSanitizer builds."
21+
FORCE)
22+
set(CMAKE_CXX_FLAGS_ASAN
23+
"-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1"
24+
CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds."
25+
FORCE)
26+
27+
# LeakSanitizer
28+
set(CMAKE_C_FLAGS_LSAN
29+
"-fsanitize=leak -fno-omit-frame-pointer -g -O1"
30+
CACHE STRING "Flags used by the C compiler during LeakSanitizer builds."
31+
FORCE)
32+
set(CMAKE_CXX_FLAGS_LSAN
33+
"-fsanitize=leak -fno-omit-frame-pointer -g -O1"
34+
CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds."
35+
FORCE)
36+
37+
# MemorySanitizer
38+
set(CMAKE_C_FLAGS_MSAN
39+
"-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/msan.blacklist -fsanitize-recover=memory"
40+
CACHE STRING "Flags used by the C compiler during MemorySanitizer builds."
41+
FORCE)
42+
set(CMAKE_CXX_FLAGS_MSAN
43+
"-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/msan.blacklist -fsanitize-recover=memory"
44+
CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds."
45+
FORCE)
46+
47+
# UndefinedBehaviour
48+
set(CMAKE_C_FLAGS_UBSAN
49+
"-fsanitize=undefined"
50+
CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds."
51+
FORCE)
52+
set(CMAKE_CXX_FLAGS_UBSAN
53+
"-fsanitize=undefined"
54+
CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds."
55+
FORCE)

‎include/lisp/evaluator.h

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
#ifndef LISP_EVALUATOR_H
2+
#define LISP_EVALUATOR_H
3+
4+
#include <cstdlib>
5+
#include <memory>
6+
#include <sstream>
7+
#include <variant>
8+
#include <iostream>
9+
#include <numeric>
10+
#include <vector>
11+
#include <map>
12+
13+
class Expr;
14+
using ExprPtr = std::shared_ptr<Expr>;
15+
16+
class Variable;
17+
18+
class Env
19+
{
20+
std::map<std::string, ExprPtr> mFrame;
21+
Env* mEnclosingEnvironment;
22+
public:
23+
Env() = default;
24+
Env(std::map<std::string, ExprPtr> frame, Env* enclosingEnvironment)
25+
: mFrame{frame}
26+
, mEnclosingEnvironment{enclosingEnvironment}
27+
{}
28+
ExprPtr lookupVariableValue(std::string const& variableName)
29+
{
30+
Env* env = this;
31+
while (env)
32+
{
33+
auto iter = env->mFrame.find(variableName);
34+
if (iter != env->mFrame.end())
35+
{
36+
return iter->second;
37+
}
38+
env = env->mEnclosingEnvironment;
39+
}
40+
// assert(false);
41+
throw std::runtime_error{variableName};
42+
}
43+
ExprPtr setVariableValue(std::string const& variableName, ExprPtr value)
44+
{
45+
auto iter = mFrame.find(variableName);
46+
assert(iter != mFrame.end());
47+
iter->second = value;
48+
return value;
49+
}
50+
ExprPtr defineVariable(std::string const& variableName, ExprPtr value)
51+
{
52+
auto iter = mFrame.find(variableName);
53+
assert(iter == mFrame.end());
54+
(void)iter;
55+
mFrame.insert({variableName, value});
56+
return value;
57+
}
58+
std::shared_ptr<Env> extend(std::vector<std::string> const& parameters, std::vector<ExprPtr> const& arguments)
59+
{
60+
assert(parameters.size() == arguments.size());
61+
std::map<std::string, ExprPtr> frame;
62+
for (size_t i = 0; i < parameters.size(); ++i)
63+
{
64+
frame.insert({parameters.at(i), arguments.at(i)});
65+
}
66+
67+
return std::make_shared<Env>(frame, this);
68+
}
69+
};
70+
71+
72+
class Expr
73+
{
74+
public:
75+
virtual ExprPtr eval(std::shared_ptr<Env> const& env) = 0;
76+
virtual std::string toString() const = 0;
77+
virtual ~Expr() = default;
78+
};
79+
80+
template <typename Value>
81+
class Literal : public Expr
82+
{
83+
Value mValue;
84+
public:
85+
Literal(Value value)
86+
: mValue{value}
87+
{}
88+
ExprPtr eval(std::shared_ptr<Env> const& /* env */) override
89+
{
90+
return ExprPtr{new Literal(mValue)};
91+
}
92+
std::string toString() const override
93+
{
94+
std::ostringstream o;
95+
o << mValue;
96+
return o.str();
97+
}
98+
Value get() const
99+
{
100+
return mValue;
101+
}
102+
};
103+
104+
class Variable final : public Expr
105+
{
106+
std::string mName;
107+
public:
108+
Variable(std::string const& name)
109+
: mName{name}
110+
{}
111+
ExprPtr eval(std::shared_ptr<Env> const& env) override
112+
{
113+
return env->lookupVariableValue(mName);
114+
}
115+
std::string name() const
116+
{
117+
return mName;
118+
}
119+
std::string toString() const override
120+
{
121+
return mName;
122+
}
123+
};
124+
125+
class Quoted final : public Expr
126+
{
127+
ExprPtr mContent;
128+
public:
129+
ExprPtr eval(std::shared_ptr<Env> const& /* env */) override
130+
{
131+
return mContent;
132+
}
133+
std::string toString() const override;
134+
};
135+
136+
class Assignment final : public Expr
137+
{
138+
std::shared_ptr<Expr> mVariable;
139+
std::shared_ptr<Expr> mValue;
140+
public:
141+
Assignment(std::shared_ptr<Expr> var, std::shared_ptr<Expr> value)
142+
: mVariable{var}
143+
, mValue{value}
144+
{
145+
}
146+
ExprPtr eval(std::shared_ptr<Env> const& env) override
147+
{
148+
return env->setVariableValue(dynamic_cast<Variable*>(mVariable.get())->name(), mValue->eval(env));
149+
}
150+
std::string toString() const override
151+
{
152+
return "Assignment";
153+
}
154+
};
155+
156+
class Definition final : public Expr
157+
{
158+
std::shared_ptr<Expr> mVariable;
159+
std::shared_ptr<Expr> mValue;
160+
public:
161+
Definition(std::shared_ptr<Expr> var, std::shared_ptr<Expr> value)
162+
: mVariable{var}
163+
, mValue{value}
164+
{
165+
}
166+
ExprPtr eval(std::shared_ptr<Env> const& env) override;
167+
std::string toString() const override
168+
{
169+
return "Definition ( " + mVariable->toString() + " : " + mValue->toString() + " )";
170+
}
171+
};
172+
173+
inline bool isTrue(ExprPtr expr)
174+
{
175+
using T = Literal<bool>;
176+
T const* v = dynamic_cast<T const*>(expr.get());
177+
return v != nullptr && v->get();
178+
}
179+
180+
class If final : public Expr
181+
{
182+
ExprPtr mPredicate;
183+
ExprPtr mConsequent;
184+
ExprPtr mAlternative;
185+
public:
186+
If(ExprPtr const& predicate, ExprPtr const& consequent, ExprPtr const& alternative)
187+
: mPredicate{predicate}
188+
, mConsequent{consequent}
189+
, mAlternative{alternative}
190+
{}
191+
ExprPtr eval(std::shared_ptr<Env> const& env) override
192+
{
193+
return isTrue(mPredicate->eval(env)) ? mConsequent->eval(env) : mAlternative->eval(env);
194+
}
195+
std::string toString() const override
196+
{
197+
return "(if " + mPredicate->toString() + " " + mConsequent->toString() + " " + mAlternative->toString() + ")";
198+
}
199+
};
200+
201+
class Sequence final : public Expr
202+
{
203+
std::vector<ExprPtr> mActions;
204+
public:
205+
Sequence(std::vector<ExprPtr> actions)
206+
: mActions{actions}
207+
{}
208+
ExprPtr eval(std::shared_ptr<Env> const& env) override
209+
{
210+
assert(mActions.size() >= 1);
211+
for (size_t i = 0; i < mActions.size() - 1; i++)
212+
{
213+
mActions.at(i)->eval(env);
214+
}
215+
return mActions.back()->eval(env);
216+
}
217+
std::string toString() const override
218+
{
219+
return "Sequence";
220+
}
221+
};
222+
223+
class Lambda final : public Expr
224+
{
225+
std::vector<std::string> mParameters;
226+
std::shared_ptr<Sequence> mBody;
227+
public:
228+
Lambda(std::vector<std::string> const& params, std::shared_ptr<Sequence> body)
229+
: mParameters{params}
230+
, mBody{body}
231+
{
232+
}
233+
ExprPtr eval(std::shared_ptr<Env> const& env) override;
234+
std::string toString() const override
235+
{
236+
return "Lambda";
237+
}
238+
};
239+
240+
class Cond final : public Expr
241+
{
242+
std::shared_ptr<Expr> mClauses;
243+
public:
244+
ExprPtr eval(std::shared_ptr<Env> const& env) override;
245+
std::string toString() const override;
246+
};
247+
248+
inline std::vector<ExprPtr> listOfValues(std::vector<ExprPtr> const& exprs, std::shared_ptr<Env> const& env)
249+
{
250+
std::vector<ExprPtr> values;
251+
std::transform(exprs.begin(), exprs.end(), std::back_insert_iterator(values), [&env](ExprPtr const& e)
252+
{
253+
return e->eval(env);
254+
}
255+
);
256+
return values;
257+
}
258+
259+
class Procedure : public Expr
260+
{
261+
public:
262+
virtual std::shared_ptr<Expr> apply(std::vector<std::shared_ptr<Expr>> const& args) = 0;
263+
};
264+
265+
class PrimitiveProcedure : public Procedure
266+
{
267+
std::function<std::shared_ptr<Expr>(std::vector<std::shared_ptr<Expr>> const& args)> mImplementation;
268+
public:
269+
template <typename Func>
270+
PrimitiveProcedure(Func func)
271+
: mImplementation{func}
272+
{}
273+
ExprPtr eval(std::shared_ptr<Env> const& /* env */) override
274+
{
275+
return ExprPtr{new PrimitiveProcedure{mImplementation}};
276+
}
277+
std::shared_ptr<Expr> apply(std::vector<std::shared_ptr<Expr>> const& args) override
278+
{
279+
return mImplementation(args);
280+
}
281+
std::string toString() const override
282+
{
283+
return "PrimitiveProcedure";
284+
}
285+
};
286+
287+
class CompoundProcedure : public Procedure
288+
{
289+
std::shared_ptr<Sequence> mBody;
290+
std::vector<std::string> mParameters;
291+
std::shared_ptr<Env> mEnvironment;
292+
public:
293+
CompoundProcedure(std::shared_ptr<Sequence> body, std::vector<std::string> parameters, std::shared_ptr<Env> const& environment)
294+
: mBody{body}
295+
, mParameters{parameters}
296+
, mEnvironment{environment}
297+
{}
298+
ExprPtr eval(std::shared_ptr<Env> const& /* env */) override
299+
{
300+
return ExprPtr{new CompoundProcedure{mBody, mParameters, mEnvironment}};
301+
}
302+
std::shared_ptr<Expr> apply(std::vector<std::shared_ptr<Expr>> const& args) override
303+
{
304+
return mBody->eval(mEnvironment->extend(mParameters, args));
305+
}
306+
std::string toString() const override
307+
{
308+
return "CompoundProcedure";
309+
}
310+
};
311+
312+
class Application final : public Expr
313+
{
314+
ExprPtr mOperator;
315+
std::vector<ExprPtr> mOperands;
316+
public:
317+
Application(ExprPtr const& op, std::vector<ExprPtr> const& params)
318+
: mOperator{op}
319+
, mOperands{params}
320+
{}
321+
ExprPtr eval(std::shared_ptr<Env> const& env) override
322+
{
323+
auto op = mOperator->eval(env);
324+
auto args = listOfValues(mOperands, env);
325+
return dynamic_cast<Procedure&>(*op).apply(args);
326+
}
327+
std::string toString() const override
328+
{
329+
return "Application (" + mOperator->toString() + ")";
330+
}
331+
};
332+
333+
#endif // LISP_EVALUATOR_H

‎include/lisp/parser.h

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
#ifndef LISP_PARSER_H
2+
#define LISP_PARSER_H
3+
4+
#include <string>
5+
#include <sstream>
6+
#include <iostream>
7+
#include "lisp/evaluator.h"
8+
#include <cctype>
9+
10+
enum class TokenType
11+
{
12+
kL_PAREN,
13+
kR_PAREN,
14+
kWORD,
15+
kEOF
16+
};
17+
18+
struct Token
19+
{
20+
TokenType type;
21+
std::string text;
22+
};
23+
24+
inline bool operator==(Token const& lhs, Token const& rhs)
25+
{
26+
return lhs.type == rhs.type && lhs.text == rhs.text;
27+
}
28+
29+
template <typename T, typename C = std::initializer_list<T>>
30+
bool elem(T t, C c)
31+
{
32+
return std::any_of(c.begin(), c.end(), [t](T e){ return e == t; });
33+
}
34+
35+
class Lexer
36+
{
37+
public:
38+
Lexer(std::string const& input)
39+
: mInput{input}
40+
, mPos{}
41+
{}
42+
bool isWS(char c)
43+
{
44+
return elem(c, {' ', '\t', '\n', '\r'});
45+
}
46+
void consume()
47+
{
48+
++mPos;
49+
}
50+
Token nextToken()
51+
{
52+
while (mPos < mInput.size())
53+
{
54+
auto c = mInput.at(mPos);
55+
if (isWS(c))
56+
{
57+
consume();
58+
continue;
59+
}
60+
switch(c)
61+
{
62+
case '(':
63+
consume();
64+
return Token{TokenType::kL_PAREN, std::string{c}};
65+
case ')':
66+
consume();
67+
return Token{TokenType::kR_PAREN, std::string{c}};
68+
default:
69+
return wordToken();
70+
}
71+
}
72+
return Token{TokenType::kEOF, "<EOF>"};
73+
}
74+
Token wordToken()
75+
{
76+
std::ostringstream word{};
77+
while (mPos < mInput.size())
78+
{
79+
auto c = mInput.at(mPos);
80+
if(isWS(c) || elem(c, {'(', ')'}))
81+
{
82+
break;
83+
}
84+
word << c;
85+
consume();
86+
}
87+
std::string wordStr = word.str();
88+
assert(!wordStr.empty());
89+
return Token{TokenType::kWORD, wordStr};
90+
}
91+
private:
92+
std::string mInput;
93+
size_t mPos;
94+
};
95+
96+
class Parser
97+
{
98+
public:
99+
Parser(Lexer const& input)
100+
: mInput{input}
101+
, mLookAhead{mInput.nextToken()}
102+
{}
103+
void consume()
104+
{
105+
mLookAhead = mInput.nextToken();
106+
}
107+
bool match(TokenType t)
108+
{
109+
if (mLookAhead.type == t)
110+
{
111+
consume();
112+
return true;
113+
}
114+
else
115+
{
116+
return false;
117+
}
118+
}
119+
bool match(Token const& token)
120+
{
121+
if (mLookAhead == token)
122+
{
123+
consume();
124+
return true;
125+
}
126+
else
127+
{
128+
return false;
129+
}
130+
}
131+
bool eof() const
132+
{
133+
return mLookAhead.type == TokenType::kEOF;
134+
}
135+
136+
ExprPtr number()
137+
{
138+
double num = std::stod(mLookAhead.text);
139+
auto result = ExprPtr{new Literal<double>(num)};
140+
consume();
141+
return result;
142+
}
143+
ExprPtr variable()
144+
{
145+
auto result = ExprPtr{new Variable(mLookAhead.text)};
146+
consume();
147+
return result;
148+
}
149+
ExprPtr atomic()
150+
{
151+
if (mLookAhead.type != TokenType::kWORD)
152+
{
153+
throw std::runtime_error(mLookAhead.text);
154+
}
155+
auto c = mLookAhead.text.front();
156+
if (isdigit(c) || (mLookAhead.text.size() > 1 && c == '-'))
157+
{
158+
return number();
159+
}
160+
return variable();
161+
}
162+
ExprPtr list()
163+
{
164+
assert(match(TokenType::kL_PAREN));
165+
auto result = listContext();
166+
assert(result);
167+
assert(match(TokenType::kR_PAREN));
168+
return result;
169+
}
170+
ExprPtr sexpr()
171+
{
172+
if (mLookAhead.type == TokenType::kL_PAREN)
173+
{
174+
return list();
175+
}
176+
return atomic();
177+
}
178+
ExprPtr listContext()
179+
{
180+
if (mLookAhead.type == TokenType::kWORD)
181+
{
182+
if (mLookAhead.text == "define")
183+
{
184+
return definition();
185+
}
186+
else if (mLookAhead.text == "set!")
187+
{
188+
return assignment();
189+
}
190+
else if (mLookAhead.text == "lambda")
191+
{
192+
return lambda();
193+
}
194+
else if (mLookAhead.text == "if")
195+
{
196+
return if_();
197+
}
198+
else
199+
{
200+
return application();
201+
}
202+
}
203+
if (mLookAhead.type == TokenType::kL_PAREN)
204+
{
205+
return list();
206+
}
207+
else
208+
{
209+
throw std::runtime_error("Not implemented: " + mLookAhead.text);
210+
}
211+
return {};
212+
}
213+
ExprPtr definition()
214+
{
215+
assert(match({TokenType::kWORD, "define"}));
216+
auto var = variable();
217+
auto value = sexpr();
218+
return ExprPtr{new Definition(var, value)};
219+
}
220+
ExprPtr assignment()
221+
{
222+
assert(match({TokenType::kWORD, "set!"}));
223+
auto var = variable();
224+
auto value = sexpr();
225+
return ExprPtr{new Definition(var, value)};
226+
}
227+
auto sequence()
228+
{
229+
std::vector<ExprPtr> actions;
230+
while (mLookAhead.type != TokenType::kR_PAREN)
231+
{
232+
actions.push_back(sexpr());
233+
}
234+
return std::make_shared<Sequence>(actions);
235+
}
236+
ExprPtr lambda()
237+
{
238+
assert(match({TokenType::kWORD, "lambda"}));
239+
assert(match(TokenType::kL_PAREN));
240+
std::vector<std::string> params;
241+
while (mLookAhead.type != TokenType::kR_PAREN)
242+
{
243+
params.push_back(dynamic_cast<Variable*>(variable().get())->name());
244+
}
245+
assert(match(TokenType::kR_PAREN));
246+
auto body = sequence();
247+
return ExprPtr{new Lambda(params, body)};
248+
}
249+
ExprPtr if_()
250+
{
251+
assert(match({TokenType::kWORD, "if"}));
252+
auto predicate = sexpr();
253+
auto consequent = sexpr();
254+
auto alternative = sexpr();
255+
return ExprPtr{new If(predicate, consequent, alternative)};
256+
}
257+
ExprPtr application()
258+
{
259+
auto op = sexpr();
260+
std::vector<ExprPtr> params;
261+
while (mLookAhead.type != TokenType::kR_PAREN)
262+
{
263+
params.push_back(sexpr());
264+
}
265+
266+
return ExprPtr{new Application(op, params)};
267+
}
268+
private:
269+
Lexer mInput;
270+
Token mLookAhead;
271+
};
272+
273+
#endif // LISP_PARSER_H

‎sample/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
set(lisp_SAMPLES
2+
)
3+
4+
foreach(sample ${lisp_SAMPLES})
5+
add_executable(${sample} ${sample}.cpp)
6+
target_compile_options(${sample} PRIVATE ${BASE_COMPILE_FLAGS})
7+
target_link_libraries(${sample} PRIVATE lisp)
8+
set_target_properties(${sample} PROPERTIES CXX_EXTENSIONS OFF)
9+
add_test(${sample} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${sample})
10+
endforeach()

‎src/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
target_include_directories(lisp PUBLIC
2+
${PROJECT_SOURCE_DIR}/include)
3+
4+
target_sources(lisp PRIVATE
5+
evaluator.cpp
6+
)
7+
8+
target_compile_options(lisp PRIVATE ${BASE_COMPILE_FLAGS})
9+
10+
set_target_properties(lisp PROPERTIES CXX_EXTENSIONS OFF)

‎src/evaluator.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "lisp/evaluator.h"
2+
3+
ExprPtr Lambda::eval(std::shared_ptr<Env> const& env)
4+
{
5+
CompoundProcedure proc{mBody, mParameters, env};
6+
return std::shared_ptr<Expr>(new CompoundProcedure(proc));
7+
}
8+
9+
ExprPtr Definition::eval(std::shared_ptr<Env> const& env)
10+
{
11+
return env->defineVariable(dynamic_cast<Variable*>(mVariable.get())->name(), mValue->eval(env));
12+
}
13+
14+
#if 0
15+
int32_t main()
16+
{
17+
using Number = Literal<double>;
18+
auto mul = [](std::vector<std::shared_ptr<Expr>> const& args)
19+
{
20+
auto result = std::accumulate(args.begin(), args.end(), 1.0, [](auto p, std::shared_ptr<Expr> const& arg)
21+
{
22+
auto num = dynamic_cast<Number&>(*arg);
23+
return p * num.get();
24+
}
25+
);
26+
return std::shared_ptr<Expr>(new Number(result));
27+
};
28+
auto a = std::shared_ptr<Expr>(new Number(5));
29+
auto b = std::shared_ptr<Expr>(new Number(0.5));
30+
Env env{};
31+
Variable variableA;
32+
auto defA = Definition(variableA, a);
33+
defA.eval(env);
34+
std::cout << variableA.eval(env)->toString() << std::endl;
35+
auto assignA = Assignment(variableA, b);
36+
assignA.eval(env);
37+
std::cout << variableA.eval(env)->toString() << std::endl;
38+
auto mulOp = ExprPtr{new PrimitiveProcedure{mul}};
39+
Variable variableMul;
40+
auto defMul = Definition(variableMul, mulOp);
41+
defMul.eval(env);
42+
auto e = dynamic_cast<Procedure&>(*variableMul.eval(env)).apply({a, b});
43+
std::cout << e->eval(env)->toString() << std::endl;
44+
return 0;
45+
}
46+
#endif

‎test/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include(FetchGTest)
2+
include(GoogleTest)
3+
4+
add_subdirectory(lisp)

‎test/lisp/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
add_executable(unittests
2+
test.cpp
3+
)
4+
target_include_directories(unittests PRIVATE
5+
${PROJECT_SOURCE_DIR}/src)
6+
target_compile_options(unittests PRIVATE ${BASE_COMPILE_FLAGS})
7+
target_link_libraries(unittests PRIVATE lisp gtest_main)
8+
set_target_properties(unittests PROPERTIES CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS OFF)
9+
gtest_discover_tests(unittests)

‎test/lisp/test.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include "lisp/parser.h"
2+
#include "gtest/gtest.h"
3+
4+
TEST(Lexer, 1)
5+
{
6+
auto expected = {"(", "define", "square", "(", "lambda", "(", "y", ")", "(", "*", "y", "y", ")", ")", ")", "(", "square", "7", ")"};
7+
8+
Lexer lex("(define square (lambda (y) (* y y))) (square 7)");
9+
10+
auto t = lex.nextToken();
11+
for (auto e : expected)
12+
{
13+
EXPECT_EQ(t.text, e);
14+
EXPECT_NE(t.type, TokenType::kEOF);
15+
t = lex.nextToken();
16+
}
17+
EXPECT_EQ(t.type, TokenType::kEOF);
18+
}
19+
20+
TEST(Parser, 1)
21+
{
22+
std::initializer_list<std::pair<std::string, std::string>> expected = {{"Definition ( square : Lambda )", "CompoundProcedure"}, {"Application (square)", "49"}};
23+
24+
Lexer lex("(define square (lambda (y) (* y y))) (square 7)");
25+
Parser p(lex);
26+
27+
auto env = std::make_shared<Env>();
28+
29+
using Number = Literal<double>;
30+
auto mul = [](std::vector<std::shared_ptr<Expr>> const& args)
31+
{
32+
auto result = std::accumulate(args.begin(), args.end(), 1.0, [](auto p, std::shared_ptr<Expr> const& arg)
33+
{
34+
auto num = dynamic_cast<Number&>(*arg);
35+
return p * num.get();
36+
}
37+
);
38+
return std::shared_ptr<Expr>(new Number(result));
39+
};
40+
auto defMul = Definition(ExprPtr{new Variable{"*"}}, ExprPtr{new PrimitiveProcedure{mul}});
41+
defMul.eval(env);
42+
43+
for (auto s : expected)
44+
{
45+
auto e = p.sexpr();
46+
EXPECT_EQ(e->toString(), s.first);
47+
EXPECT_EQ(e->eval(env)->toString(), s.second);
48+
}
49+
EXPECT_TRUE(p.eof());
50+
}
51+
52+
TEST(Parser, 2)
53+
{
54+
std::initializer_list<std::pair<std::string, std::string> > expected =
55+
{{"Definition ( factorial : Lambda )", "CompoundProcedure"}, {"Application (factorial)", "120"}};
56+
57+
Lexer lex("(define factorial (lambda (y) (if (= y 0) 1 (* y (factorial (- y 1)))))) (factorial 5)");
58+
Parser p(lex);
59+
60+
auto env = std::make_shared<Env>();
61+
62+
using Number = Literal<double>;
63+
auto mul = [](std::vector<std::shared_ptr<Expr>> const& args)
64+
{
65+
auto result = std::accumulate(args.begin(), args.end(), 1.0, [](auto p, std::shared_ptr<Expr> const& arg)
66+
{
67+
auto num = dynamic_cast<Number&>(*arg);
68+
return p * num.get();
69+
}
70+
);
71+
return std::shared_ptr<Expr>(new Number(result));
72+
};
73+
auto defMul = Definition(ExprPtr{new Variable{"*"}}, ExprPtr{new PrimitiveProcedure{mul}});
74+
defMul.eval(env);
75+
76+
auto gt = [](std::vector<std::shared_ptr<Expr>> const& args)
77+
{
78+
assert(args.size() == 2);
79+
auto num1 = dynamic_cast<Number&>(*args.at(0));
80+
auto num2 = dynamic_cast<Number&>(*args.at(1));
81+
using Bool = Literal<bool>;
82+
return std::shared_ptr<Expr>(new Bool(num1.get() > num2.get()));
83+
};
84+
auto defGT = Definition(ExprPtr{new Variable{">"}}, ExprPtr{new PrimitiveProcedure{gt}});
85+
defGT.eval(env);
86+
87+
auto eq = [](std::vector<std::shared_ptr<Expr>> const& args)
88+
{
89+
assert(args.size() == 2);
90+
auto num1 = dynamic_cast<Number&>(*args.at(0));
91+
auto num2 = dynamic_cast<Number&>(*args.at(1));
92+
using Bool = Literal<bool>;
93+
return std::shared_ptr<Expr>(new Bool(num1.get() == num2.get()));
94+
};
95+
auto defEQ = Definition(ExprPtr{new Variable{"="}}, ExprPtr{new PrimitiveProcedure{eq}});
96+
defEQ.eval(env);
97+
98+
auto sub = [](std::vector<std::shared_ptr<Expr>> const& args)
99+
{
100+
assert(args.size() == 2);
101+
auto num1 = dynamic_cast<Number&>(*args.at(0));
102+
auto num2 = dynamic_cast<Number&>(*args.at(1));
103+
return std::shared_ptr<Expr>(new Number(num1.get() - num2.get()));
104+
};
105+
auto defSub = Definition(ExprPtr{new Variable{"-"}}, ExprPtr{new PrimitiveProcedure{sub}});
106+
defSub.eval(env);
107+
108+
for (auto s : expected)
109+
{
110+
auto e = p.sexpr();
111+
EXPECT_EQ(e->toString(), s.first);
112+
EXPECT_EQ(e->eval(env)->toString(), s.second);
113+
}
114+
EXPECT_TRUE(p.eof());
115+
}

0 commit comments

Comments
 (0)
Please sign in to comment.