Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
Checks: >
bugprone-*,
cert-*,
clang-analyzer-*,
cppcoreguidelines-init-variables,
cppcoreguidelines-pro-type-member-init,
misc-*,
modernize-use-nullptr,
performance-*,
readability-braces-around-statements,
readability-container-size-empty,
readability-implicit-bool-conversion,
readability-isolate-declaration,
readability-redundant-member-init,
readability-simplify-boolean-expr,
-bugprone-easily-swappable-parameters,
-bugprone-macro-parentheses,
-cert-err58-cpp,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-vararg,
-misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion,
-misc-use-anonymous-namespace,
-modernize-avoid-c-arrays,
-modernize-use-trailing-return-type,
-performance-avoid-endl,
-readability-identifier-length,
-readability-magic-numbers,
-readability-function-cognitive-complexity
WarningsAsErrors: ''
HeaderFilterRegex: 'smallfolk.*\.h'
CheckOptions:
- key: readability-implicit-bool-conversion.AllowIntegerConditions
value: 1
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,27 @@ jobs:

- name: Test
run: ${{ matrix.test }}

static-analysis:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6

- name: Install analysis tools
run: sudo apt-get update && sudo apt-get install -y clang-tidy cppcheck

- name: Configure
run: cmake -B build -DSMALLFOLK_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

- name: Build
run: cmake --build build --config Release

- name: cppcheck
run: cmake --build build --target smallfolk_cppcheck

- name: clang-tidy
run: cmake --build build --target smallfolk_clang_tidy

- name: Test
run: ctest --test-dir build -C Release --output-on-failure
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to this project are documented in this file.

## [2.0.1] - 2026-05-31

### Added

- **README** — v2 API docs (safe lookup, path API, throwing loads/dumps, typed accessors, `lua_val` factories); link to `CHANGELOG.md`; static analysis usage.
- **Static analysis** — `.clang-tidy`, `cmake/StaticAnalysis.cmake`, `cppcheck-suppressions.txt`, and Ubuntu CI job (cppcheck + clang-tidy).
- **Tests** — expanded `smallfolk_tests` / `smallfolk_schema_tests` coverage; fixed gvx/Lua Smallfolk wire interop fixtures in `test_lua_smallfolk_interop_wires()`.

### Fixed

- **Non-finite wire on glibc/libstdc++** — NaN/Inf values serialize as gvx tokens (`N`/`Q`/`I`/`i`) instead of libc `nan`/`inf` text that broke round-trip on Linux/macOS; parse `nan` literals and map `N`/`Q` via `std::nan`.
- **Schema depth-limit test** — avoid dangling `CompiledSchema` reference to a temporary nested schema (validation spuriously passed on GCC).

### Changed

- Restored pre-v2 API and serializer comments in `smallfolk.h` / `smallfolk.cpp`.
- README build instructions: fix `SMALLFOLK_BUILD_TESTS` cmake typo.

## [2.0.0] - 2026-05-31

### Added
Expand Down
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
cmake_minimum_required(VERSION 3.16)
project(smallfolk_cpp VERSION 2.0.0 LANGUAGES CXX)
project(smallfolk_cpp VERSION 2.0.1 LANGUAGES CXX)

option(SMALLFOLK_BUILD_TESTS "Build the test runner" ON)
option(SMALLFOLK_BUILD_BENCHMARK "Build the benchmark runner" ON)
option(SMALLFOLK_BUILD_EXAMPLES "Build the interactive demo example" ON)
option(SMALLFOLK_ENABLE_CLANG_TIDY "Attach clang-tidy to library targets when available" OFF)

include(cmake/StaticAnalysis.cmake)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_library(smallfolk smallfolk.cpp smallfolk_schema.cpp)
add_library(smallfolk_cpp::smallfolk ALIAS smallfolk)
smallfolk_enable_clang_tidy(smallfolk)
target_include_directories(smallfolk PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
"$<INSTALL_INTERFACE:include>"
Expand Down Expand Up @@ -80,3 +86,5 @@ install(EXPORT smallfolk_cppTargets
NAMESPACE smallfolk_cpp::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/smallfolk_cpp
)

smallfolk_add_static_analysis_targets()
55 changes: 52 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ You use, distribute and extend Smallfolk_cpp under the terms of the MIT license.

See [ASSUMPTIONS.md](ASSUMPTIONS.md) for documented behavioral assumptions (copy semantics, comparison, limits, and security).

See [CHANGELOG.md](CHANGELOG.md) for release history (current version **2.0.1**).

## Add to your project

**CMake (recommended)** — add this repository as a subdirectory or fetch it, then link the library target:
Expand All @@ -27,7 +29,7 @@ target_link_libraries(my_app PRIVATE smallfolk_cpp::smallfolk)
Build and test from the repository root:

```bash
cmake -B build -DSMALLFOLK_BUIlen()LD_TESTS=ON -DSMALLFOLK_BUILD_BENCHMARK=ON
cmake -B build -DSMALLFOLK_BUILD_TESTS=ON -DSMALLFOLK_BUILD_BENCHMARK=ON
cmake --build build
ctest --test-dir build --output-on-failure # if you enable CTest
./build/smallfolk_tests
Expand All @@ -36,7 +38,7 @@ ctest --test-dir build --output-on-failure # if you enable CTest
./build/smallfolk_benchmark
```

**Manual integration** — copy `smallfolk.h` and `smallfolk.cpp` into your tree and compile them as a static library or directly into your target. The library requires C++11 and has no other dependencies.
**Manual integration** — copy `smallfolk.h`, `smallfolk.cpp`, and (optionally) `smallfolk_schema.h`, `smallfolk_schema.cpp`, `smallfolk_convert.h` into your tree and compile them as a static library or directly into your target. The library requires C++11 and has no other dependencies.

**Install** — after building:

Expand Down Expand Up @@ -137,6 +139,15 @@ Tune limits for your deployment. See [ASSUMPTIONS.md](ASSUMPTIONS.md) for what i

Automated tests live in `tests/test_smallfolk.cpp` and `tests/test_schema.cpp`, run via the `smallfolk_tests` and `smallfolk_schema_tests` targets. The original interactive walkthrough from `main.cpp` now lives in `examples/demo.cpp` as the `smallfolk_demo` target (also run by CTest when `-DSMALLFOLK_BUILD_EXAMPLES=ON`, default). The demo uses a `DEMO_CHECK` macro instead of `assert()` so runtime verification still runs in Release/NDEBUG builds.

Static analysis (Linux CI and local when tools are installed):

```bash
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build --target smallfolk_static_analysis
```

This runs **cppcheck** and **clang-tidy** on the library sources when available. Optional: `-DSMALLFOLK_ENABLE_CLANG_TIDY=ON` attaches clang-tidy to normal library builds.

The code has also been in use with a server-client C++-Lua communication system called AIO through which the API has been made more usable and critical issues have been addressed.

- [https://github.com/Rochet2/AIO](https://github.com/Rochet2/AIO)
Expand Down Expand Up @@ -169,7 +180,9 @@ This function does not throw.
### deserializing

Deserializing happens by calling `static LuaVal LuaVal::loads(std::string const & string, std::string* errmsg = nullptr)` or the overload that accepts an explicit `LoadLimits` object. When an error occurs with the deserialization a LuaVal representing a nil is returned and if errmsg points to a string then it is filled with the error message.
This function does not throw.
This function does not throw. Use `LuaVal::loads_or_throw(...)` when you prefer exceptions on parse failure.

Serializing also has a throwing overload: `dumps_or_throw()`.

### LoadLimits

Expand Down Expand Up @@ -382,6 +395,42 @@ This operator does not throw unless you use it on non table objects or with nil
`luaval.has(key)` can be used to check if a value can be found in a table.
This function do not throw unless you use it on non table objects or with nil keys.

#### Safe lookup (no auto-vivification)

Prefer these when you do not want missing keys to create empty tables:

| Method | Missing key | Notes |
| --- | --- | --- |
| `try_get(key)` / `find(key)` | returns `nullptr` | safe optional access |
| `get(key)` | returns `LuaVal::nil` | const reference |
| `at(key)` | throws | mutable reference when present |
| `has(key)` | returns `false` | existence check |

Typed reads without exceptions: `try_as_number`, `try_as_string`, `try_as_bool`.

#### Path lookup and nested set/erase

Nested access uses the same tiers as single-key lookup. Paths never auto-vivify on read; `set_path` creates missing intermediate tables.

```C++
LuaVal doc = LuaVal::loads_or_throw("{stats:{hp:100}}");

if (LuaVal const * hp = doc.try_get_path("stats", "hp"))
std::cout << hp->num() << std::endl;

double hp = doc.get_path("stats", "hp").num(); // nil if missing
double hp2 = doc.at_path("stats", "hp").num(); // throws with $.stats.hp

doc.set_path({ "player", "name" }, std::string("Ada")); // auto-vivify intermediates
doc.erase_path("stats", "hp"); // no-op if path missing
```

Variadic segments (`try_get_path("a", "b", 1)`) and `std::initializer_list<LuaVal>` overloads are both available.

#### `lua_val` factories

`#include "smallfolk_convert.h"` for helpers such as `lua_val::map(...)`, `lua_val::array(...)`, `lua_val::string(...)`, and `lua_val::nil()`.

A method for erasing data with a key is `luaval.rem(key)` which also returns the accessed table.
This function do not throw unless you use it on non table objects or with nil keys.

Expand Down
66 changes: 66 additions & 0 deletions cmake/StaticAnalysis.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
option(SMALLFOLK_ENABLE_CLANG_TIDY "Attach clang-tidy to library targets when available" OFF)

function(smallfolk_enable_clang_tidy target)
if(NOT SMALLFOLK_ENABLE_CLANG_TIDY)
return()
endif()

find_program(SMALLFOLK_CLANG_TIDY_EXE NAMES clang-tidy clang-tidy-18 clang-tidy-17 clang-tidy-16)
if(NOT SMALLFOLK_CLANG_TIDY_EXE)
message(WARNING "SMALLFOLK_ENABLE_CLANG_TIDY is ON but clang-tidy was not found")
return()
endif()

set_property(
TARGET ${target}
PROPERTY CXX_CLANG_TIDY
"${SMALLFOLK_CLANG_TIDY_EXE};-warnings-as-errors=*"
)
endfunction()

function(smallfolk_add_static_analysis_targets)
find_program(SMALLFOLK_CPPCHECK_EXE NAMES cppcheck)
find_program(SMALLFOLK_CLANG_TIDY_EXE NAMES clang-tidy clang-tidy-18 clang-tidy-17 clang-tidy-16)

if(SMALLFOLK_CPPCHECK_EXE)
add_custom_target(smallfolk_cppcheck
COMMAND
${SMALLFOLK_CPPCHECK_EXE}
--enable=warning,performance,portability
--error-exitcode=1
--inline-suppr
--std=c++11
-I${CMAKE_SOURCE_DIR}
--suppressions-list=${CMAKE_SOURCE_DIR}/cppcheck-suppressions.txt
--quiet
${CMAKE_SOURCE_DIR}/smallfolk.cpp
${CMAKE_SOURCE_DIR}/smallfolk_schema.cpp
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running cppcheck on smallfolk sources"
VERBATIM
)
endif()

if(SMALLFOLK_CLANG_TIDY_EXE AND CMAKE_EXPORT_COMPILE_COMMANDS)
add_custom_target(smallfolk_clang_tidy
COMMAND
${SMALLFOLK_CLANG_TIDY_EXE}
-p ${CMAKE_BINARY_DIR}
${CMAKE_SOURCE_DIR}/smallfolk.cpp
${CMAKE_SOURCE_DIR}/smallfolk_schema.cpp
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running clang-tidy on library translation units"
VERBATIM
)
endif()

if(TARGET smallfolk_cppcheck OR TARGET smallfolk_clang_tidy)
add_custom_target(smallfolk_static_analysis)
if(TARGET smallfolk_cppcheck)
add_dependencies(smallfolk_static_analysis smallfolk_cppcheck)
endif()
if(TARGET smallfolk_clang_tidy)
add_dependencies(smallfolk_static_analysis smallfolk_clang_tidy)
endif()
endif()
endfunction()
12 changes: 12 additions & 0 deletions cppcheck-suppressions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Benign or intentional patterns in this C++11 header-library codebase.
missingIncludeSystem
unusedFunction
useStlAlgorithm
noExplicitConstructor
shadowFunction
knownConditionTrueFalse
unsignedLessThanZero
passedByValue
constVariableReference
duplicateExpression
normalCheckLevelMaxBranches
Loading
Loading