├── .clang-format ├── .github └── workflows │ └── c-cpp.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── .gitignore ├── CMakeLists.txt ├── airplane.c ├── default_impl.c ├── marker.c ├── opaque_type │ ├── CMakeLists.txt │ ├── croak.h │ ├── frog.c │ ├── frog.h │ └── main.c ├── read_write.c ├── read_write_both.c ├── shape.c └── tracing_vehicle.c ├── images └── suggestion.png ├── interface99.h ├── scripts ├── check-fmt.sh ├── fmt.sh ├── test-all.sh ├── test-examples.sh └── test.sh └── tests ├── .gitignore ├── CMakeLists.txt ├── basic_tests.c ├── common.c ├── common.h ├── decl_impl.c ├── default_impl.c ├── extern_impl ├── CMakeLists.txt ├── impl.c ├── test.c └── types.h ├── metalang99_compliant.c ├── superinterfaces.c ├── util.h ├── vcalls.c └── version.c /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: LLVM 3 | 4 | IndentWidth: 4 5 | ContinuationIndentWidth: 4 6 | ColumnLimit: 100 7 | 8 | AllowShortFunctionsOnASingleLine: Empty 9 | AllowAllArgumentsOnNextLine: false 10 | BinPackArguments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | BinPackParameters: true 13 | 14 | AlignConsecutiveMacros: true 15 | AlignAfterOpenBracket: AlwaysBreak 16 | 17 | StatementMacros: ["ML99_EVAL", "vfunc", "vfuncDefault"] 18 | -------------------------------------------------------------------------------- /.github/workflows/c-cpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest, windows-latest] 14 | include: 15 | - os: ubuntu-latest 16 | compiler: gcc 17 | - os: macos-latest 18 | compiler: clang 19 | - os: windows-latest 20 | compiler: msvc 21 | 22 | runs-on: ${{ matrix.os }} 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Test 28 | run: ./scripts/test-all.sh 29 | 30 | test-tcc: 31 | runs-on: ubuntu-latest 32 | env: 33 | CC: tcc 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | 38 | - name: Install TCC 39 | run: sudo apt install tcc 40 | 41 | - name: Test 42 | run: ./scripts/test-all.sh 43 | 44 | check-fmt: 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - uses: actions/checkout@v2 49 | 50 | - name: Download run-clang-format 51 | run: git submodule update --init run-clang-format 52 | 53 | - name: Check code formatting 54 | run: ./scripts/check-fmt.sh 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | # CMake build files 55 | build/ 56 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "run-clang-format"] 2 | path = run-clang-format 3 | url = https://github.com/Sarcasm/run-clang-format 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## unreleased 8 | 9 | ## 1.0.2 - 2025-03-17 10 | 11 | ### Fixed 12 | 13 | - Update the minimum required CMake version to 3.10.0 due to deprecation (see [metalang99/issues/33](https://github.com/hirrolot/metalang99/issues/33)). 14 | 15 | ## 1.0.1 - 2023-03-11 16 | 17 | ### Fixed 18 | 19 | - Fix the `DOWNLOAD_EXTRACT_TIMESTAMP` CMake warning (see [datatype99/issues/15](https://github.com/hirrolot/datatype99/issues/15)). 20 | 21 | ## 1.0.0 - 2022-05-15 22 | 23 | ### Added 24 | 25 | - `IFACE99_VERSION_COMPATIBLE` to check for a SemVer-compatible version. 26 | - `IFACE99_VERSION_EQ` to check for an exact version. 27 | 28 | ## 0.8.4 - 2022-03-17 29 | 30 | ### Added 31 | 32 | - `DYN_LIT` that constructs interface objects out of compound literals. 33 | 34 | ## 0.8.3 - 2021-12-09 35 | 36 | ### Fixed 37 | 38 | - Specify `C` as the project language in `CMakeLists.txt`. Previously, CMake detected C++ and required a C++ compiler to compile the project. 39 | - Require CMake v3.11.4. 40 | 41 | ## 0.8.2 - 2021-12-01 42 | 43 | ### Added 44 | 45 | - Add the root `CMakeLists.txt` to be able to use CMake with [`FetchContent`] or [`add_subdirectory`]. 46 | 47 | [`FetchContent`]: https://cmake.org/cmake/help/latest/module/FetchContent.html 48 | [`add_subdirectory`]: https://cmake.org/cmake/help/latest/command/add_subdirectory.html 49 | 50 | ## 0.8.1 - 2021-11-23 51 | 52 | ### Fixed 53 | 54 | - Programmatically require Metalang99 version compliant with 1.12.0. 55 | 56 | ## 0.8.0 - 2021-11-09 57 | 58 | ### Added 59 | 60 | - `VCALL`, `VCALL_OBJ`, `VCALL_SUPER`, `VCALL_SUPER_OBJ` as a convenient syntax sugar to call virtual functions. 61 | - `VSelf` and `VSELF(T)` to imitate typed `self` parameters. 62 | 63 | ### Changed 64 | 65 | - `iMethod` => `vfunc` (consistent with `VTABLE`, `vptr`) [**BC**]. 66 | - `defaultIMethod` => `vfuncDefault` [**BC**]. 67 | - `externImpl` => `implExtern` [**BC**]. 68 | - `externDeclImpl` => `declImplExtern` [**BC**]. 69 | - `self` (`VSelf`) parameters are now `restrict`. 70 | 71 | ## 0.7.0 - 2021-10-06 72 | 73 | ### Changed 74 | 75 | - `method` => `iMethod`, `defaultMethod` => `defaultIMethod` to avoid name clashes [**BC**]. 76 | 77 | ### Fixed 78 | 79 | - Previously, shortcuts referring to functional macros were object-like. To avoid nasty compilation errors, now they are function-like too: 80 | - `interface(iface)` 81 | - `impl(iface, implementer)` 82 | - `externImpl(iface, implementer)` 83 | - `declImpl(iface, implementer)` 84 | - `externDeclImpl(iface, implementer)` 85 | - `iMethod(ret_ty, name, ...)` 86 | - `defaultIMethod(ret_ty, name, ...)` 87 | - `DYN(implementer, iface, ...)` 88 | - `VTABLE(implementer, iface)` 89 | 90 | ## 0.6.0 - 2021-10-02 91 | 92 | ### Added 93 | 94 | - Default method implementations. 95 | 96 | ### Changed 97 | 98 | - Use `method` and `defaultMethod` instead of [X-Macro] for the sake of conciseness [**BC**]. 99 | - `_INTERFACE` => `_IFACE` [**BC**]. 100 | 101 | ## 0.5.0 - 2021-09-16 102 | 103 | ### Changed 104 | 105 | - Remove `implPrimary`, `externImplPrimary` [**BC**]. 106 | 107 | ## 0.4.0 - 2021-09-14 108 | 109 | ### Changed 110 | 111 | - Use a variation of the [X-Macro] pattern instead of `iFn`. This change makes the implementation easier to reason about [**BC**]. 112 | 113 | [X-Macro]: https://en.wikipedia.org/wiki/X_Macro 114 | 115 | ## 0.3.0 - 2021-08-13 116 | 117 | ### Changed 118 | 119 | - Generate virtual tables with static linkage in `impl(Primary)` & `declImpl` [**BC**]. 120 | - `dyn` => `DYN` to know where is a function and where is a macro. 121 | 122 | ### Added 123 | 124 | - Counterparts with external linkage: `externImpl(Primary)` & `externDeclImpl`. 125 | 126 | ## 0.2.0 - 2021-07-01 127 | 128 | ### Added 129 | 130 | - Generate `typedef struct VTable VTable;` and `typedef struct ;` prior to their definitions to allow accepting `` and `` as interface function parameters. 131 | 132 | ### Changed 133 | 134 | - Generate `char dummy;` only for an empty virtual table (i.e., a marker interface without superinterfaces) [**BC**]. 135 | 136 | ## 0.1.0 - 2021-06-23 137 | 138 | ### Added 139 | 140 | - This excellent project. 141 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.0) 2 | project(interface99 LANGUAGES C) 3 | 4 | # Fix the warnings about `DOWNLOAD_EXTRACT_TIMESTAMP` in newer CMake versions. 5 | if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") 6 | cmake_policy(SET CMP0135 NEW) 7 | endif() 8 | 9 | include(FetchContent) 10 | 11 | FetchContent_Declare( 12 | metalang99 13 | URL https://github.com/hirrolot/metalang99/archive/refs/tags/v1.13.5.tar.gz 14 | ) 15 | 16 | FetchContent_MakeAvailable(metalang99) 17 | 18 | add_library(${PROJECT_NAME} INTERFACE) 19 | target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) 20 | target_link_libraries(${PROJECT_NAME} INTERFACE metalang99) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2025 hirrolot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Interface99

3 | 4 | 5 | 6 | 7 | Full-featured interfaces inspired by Rust and Golang. Multiple inheritance, superinterfaces, and default implementations supported. No external tools required, pure C99. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 32 | 33 |
Shape
16 |
17 | 18 | ```c 19 | #include 20 | 21 | #include 22 | 23 | #define Shape_IFACE \ 24 | vfunc( int, perim, const VSelf) \ 25 | vfunc(void, scale, VSelf, int factor) 26 | 27 | interface(Shape); 28 | ``` 29 | 30 |
31 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 65 | 90 | 91 |
Rectangle
Triangle
42 |
43 | 44 | ```c 45 | typedef struct { 46 | int a, b; 47 | } Rectangle; 48 | 49 | int Rectangle_perim(const VSelf) { 50 | VSELF(const Rectangle); 51 | return (self->a + self->b) * 2; 52 | } 53 | 54 | void Rectangle_scale(VSelf, int factor) { 55 | VSELF(Rectangle); 56 | self->a *= factor; 57 | self->b *= factor; 58 | } 59 | 60 | impl(Shape, Rectangle); 61 | ``` 62 | 63 |
64 |
66 |
67 | 68 | ```c 69 | typedef struct { 70 | int a, b, c; 71 | } Triangle; 72 | 73 | int Triangle_perim(const VSelf) { 74 | VSELF(const Triangle); 75 | return self->a + self->b + self->c; 76 | } 77 | 78 | void Triangle_scale(VSelf, int factor) { 79 | VSELF(Triangle); 80 | self->a *= factor; 81 | self->b *= factor; 82 | self->c *= factor; 83 | } 84 | 85 | impl(Shape, Triangle); 86 | ``` 87 | 88 |
89 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 119 | 120 |
Test
99 |
100 | 101 | ```c 102 | void test(Shape shape) { 103 | printf("perim = %d\n", VCALL(shape, perim)); 104 | VCALL(shape, scale, 5); 105 | printf("perim = %d\n", VCALL(shape, perim)); 106 | } 107 | 108 | int main(void) { 109 | Shape r = DYN_LIT(Rectangle, Shape, {5, 7}); 110 | Shape t = DYN_LIT(Triangle, Shape, {10, 20, 30}); 111 | 112 | test(r); 113 | test(t); 114 | } 115 | ``` 116 | 117 |
118 |
121 | 122 |
123 | 124 | (Based on [`examples/shape.c`](examples/shape.c).) 125 | 126 |
127 | Output 128 | 129 | ``` 130 | perim = 24 131 | perim = 120 132 | perim = 60 133 | perim = 300 134 | ``` 135 | 136 |
137 | 138 | ## Highlights 139 | 140 | - **Minimum boilerplate.** Forget about maintaining virtual tables -- just write `impl(Shape, Rectangle)` and Interface99 will do it for you! 141 | 142 | - **Portable.** Everything you need is a standard-conforming C99 compiler; neither the standard library, nor compiler/platform-specific functionality or VLA are required. 143 | 144 | - **Predictable.** Interface99 comes with formal [code generation semantics], meaning that the generated data layout is guaranteed to always be the same. 145 | 146 | - **Comprehensible errors.** Interface99 is [resilient to bad code]. 147 | 148 | - **Battle-tested.** Interface99 is used at [OpenIPC] to develop real-time streaming software for IP cameras; this includes an [RTSP 1.0 implementation] along with ~50k lines of private code. 149 | 150 | [code generation semantics]: #semantics 151 | [resilient to bad code]: #q-what-about-compile-time-errors 152 | [OpenIPC]: https://openipc.org/ 153 | [RTSP 1.0 implementation]: https://github.com/OpenIPC/smolrtsp/ 154 | 155 | ## Features 156 | 157 | | Feature | Status | Description | 158 | |---------|--------|-------------| 159 | | [Multiple interface inheritance](examples/read_write.c) | ✅ | A type can inherit multiple interfaces at the same time. | 160 | | [Superinterfaces](examples/airplane.c) | ✅ | One interface can require a set of other interfaces to be implemented as well. | 161 | | [Marker interfaces](examples/marker.c) | ✅ | An interface with no functions. | 162 | | [Single/Dynamic dispatch](examples/shape.c) | ✅ | Determine a function to be called at runtime based on `self`. | 163 | | Multiple dispatch | ❌ | Determine a function to be called at runtime based on multiple arguments. Likely to never going to be implemented. | 164 | | [Dynamic objects of multiple interfaces](examples/read_write_both.c) | ✅ | Given interfaces `Foo` and `Bar`, you can construct an object of both interfaces, `FooBar obj`. | 165 | | [Default implementations](examples/default_impl.c) | ✅ | Some interface functions may be given default implementations. A default function can call other functions and vice versa. | 166 | | Data and implementation separation | ✅ | New interfaces can be implemented for existing types. | 167 | 168 | ## Installation 169 | 170 | Interface99 consists of one header file `interface99.h` and one dependency [Metalang99]. To use it in your project, you need to: 171 | 172 | [Metalang99]: https://github.com/hirrolot/metalang99 173 | 174 | 1. Add `interface99` and `metalang99/include` to your include directories. 175 | 2. Specify [`-ftrack-macro-expansion=0`] (GCC) or [`-fmacro-backtrace-limit=1`] (Clang) to avoid useless macro expansion errors. 176 | 177 | [`-ftrack-macro-expansion=0`]: https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html 178 | [`-fmacro-backtrace-limit=1`]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fmacro-backtrace-limit 179 | 180 | If you use CMake, the recommended way is [`FetchContent`]: 181 | 182 | [`FetchContent`]: https://cmake.org/cmake/help/latest/module/FetchContent.html 183 | 184 | ```cmake 185 | include(FetchContent) 186 | 187 | FetchContent_Declare( 188 | interface99 189 | URL https://github.com/hirrolot/interface99/archive/refs/tags/vx.y.z.tar.gz # vx.y.z 190 | ) 191 | 192 | FetchContent_MakeAvailable(interface99) 193 | 194 | target_link_libraries(MyProject interface99) 195 | 196 | # Disable full macro expansion backtraces for Metalang99. 197 | if(CMAKE_C_COMPILER_ID STREQUAL "Clang") 198 | target_compile_options(MyProject PRIVATE -fmacro-backtrace-limit=1) 199 | elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") 200 | target_compile_options(MyProject PRIVATE -ftrack-macro-expansion=0) 201 | endif() 202 | ``` 203 | 204 | (By default, `interface99/CMakeLists.txt` downloads Metalang99 [v1.13.5](https://github.com/hirrolot/metalang99/releases/tag/v1.13.5) from the GitHub releases; if you want to override this behaviour, you can do so by invoking [`FetchContent_Declare`] earlier.) 205 | 206 | [`FetchContent_Declare`]: https://cmake.org/cmake/help/latest/module/FetchContent.html#command:fetchcontent_declare 207 | 208 | Optionally, you can [precompile headers] in your project that rely on Interface99. This will decrease compilation time, because the headers will not be compiled each time they are included. 209 | 210 | [precompile headers]: https://en.wikipedia.org/wiki/Precompiled_header 211 | 212 | Happy hacking! 213 | 214 | ## Tutorial 215 | 216 | This section is based on a collection of well-documented [examples](examples/), each of which demonstrates one specific aspect of Interface99. 217 | 218 | ### Basic usage 219 | 220 | 1. **Interface definition.** 221 | 222 | Syntax: [`interface(Shape);`](#interface) 223 | 224 | An interface definition expands to a virtual table structure and a so-called _interface object type_. In the case of [`examples/shape.c`](examples/shape.c): 225 | 226 | ```c 227 | // interface(Shape); 228 | typedef struct ShapeVTable ShapeVTable; 229 | typedef struct Shape Shape; 230 | 231 | struct ShapeVTable { 232 | int (*perim)(const VSelf); 233 | void (*scale)(VSelf, int factor); 234 | }; 235 | 236 | struct Shape { 237 | void *self; 238 | const ShapeVTable *vptr; 239 | }; 240 | ``` 241 | 242 | Here, `Shape.self` is the pointer to an object whose type implements `Shape`, and `Shape.vptr` points to a corresponding virtual table instance. Inside `ShapeVTable`, you can observe the mysterious [`VSelf`](#vselfvself) bits -- they expand to parameters of type `void * restrict` (with extra `const` for `perim`); when calling these methods, Interface99 will substitute `Shape.self` for these parameters. 243 | 244 | Usually, interface definitions go in `*.h` files. 245 | 246 | 2. **Implementation definition.** 247 | 248 | | Linkage | Syntax | 249 | |---------|--------| 250 | | Internal | [`impl(Shape, Rectangle);`](#impl) | 251 | | External | [`implExtern(Shape, Rectangle);`](#implExtern) | 252 | 253 | An implementation definition expands to nothing but a virtual table instance of a particular implementer. In the case of `examples/shape.c`: 254 | 255 | ```c 256 | // impl(Shape, Rectangle); 257 | static const ShapeVTable Rectangle_Shape_impl = { 258 | .perim = Rectangle_perim, 259 | .scale = Rectangle_scale, 260 | }; 261 | ``` 262 | 263 | (If you were using [`implExtern`](#implExtern), this definition would be `extern` likewise.) 264 | 265 | Note that inside function implementations, we use [`VSELF`](#vselfvself), which simply casts the parameter introduced by `VSelf` to a user-defined type (`const Rectangle` or `Rectangle` in our case): 266 | 267 | ```c 268 | int Rectangle_perim(const VSelf) { 269 | VSELF(const Rectangle); 270 | return (self->a + self->b) * 2; 271 | } 272 | 273 | void Rectangle_scale(VSelf, int factor) { 274 | VSELF(Rectangle); 275 | self->a *= factor; 276 | self->b *= factor; 277 | } 278 | ``` 279 | 280 | 3. **Dynamic dispatch.** 281 | 282 | Once an interface and its implementations are both generated, it is time to instantiate an interface object and invoke some functions upon it. 283 | 284 | First of all, to instantiate `Shape`, use the [`DYN_LIT`](#DYN_LIT) macro: 285 | 286 | ```с 287 | Shape r = DYN_LIT(Rectangle, Shape, {5, 7}); 288 | test(r); 289 | ``` 290 | 291 | Here, `DYN_LIT(Rectangle, Shape, {5, 7})` creates `Shape` by assigning `Shape.self` to `&(Rectangle){5, 7}` and `Shape.vptr` to the aforementioned `&Rectangle_Shape_impl`. Eventually, you can accept `Shape` as a function parameter and perform dynamic dispatch through the [`VCALL`](#vcall_) macro: 292 | 293 | ```c 294 | void test(Shape shape) { 295 | printf("perim = %d\n", VCALL(shape, perim)); 296 | VCALL(shape, scale, 5); 297 | printf("perim = %d\n", VCALL(shape, perim)); 298 | } 299 | ``` 300 | 301 | Finally, just a few brief notes: 302 | 303 | - Besides `VCALL`, you also have `VCALL_OBJ`, `VCALL_SUPER`, and `VCALL_SUPER_OBJ`. They all serve a different purpose; for more information, please refer to [their documentation](#vcall_). 304 | - In practice, [`DYN`](#DYN) is used more often than [`DYN_LIT`](#DYN_LIT); it just accepts an ordinary pointer instead of an initialiser list, which means that you can `malloc` it beforehand. 305 | - If your virtual function does not accept `self`, you can invoke it as `obj.vptr->foo(...)`. 306 | - If you want to call an interface function on some concrete type, just write `VTABLE(T, Iface).foo(...)`. 307 | 308 | Congratulations, this is all you need to know to write most of the stuff! 309 | 310 | ### Superinterfaces 311 | 312 | Interface99 has the feature called superinterfaces, or interface requirements. [`examples/airplane.c`](examples/airplane.c) demonstrates how to extend interfaces with new functionality: 313 | 314 | ```c 315 | #define Vehicle_IFACE \ 316 | vfunc(void, move_forward, VSelf, int distance) \ 317 | vfunc(void, move_back, VSelf, int distance) 318 | 319 | interface(Vehicle); 320 | 321 | #define Airplane_IFACE \ 322 | vfunc(void, move_up, VSelf, int distance) \ 323 | vfunc(void, move_down, VSelf, int distance) 324 | 325 | #define Airplane_EXTENDS (Vehicle) 326 | 327 | interface(Airplane); 328 | ``` 329 | 330 | (Note that `#define Airplane_EXTENDS` must appear prior to `interface(Airplane);`.) 331 | 332 | Here, `Airplane` extends `Vehicle` with the new functions `move_up` and `move_down`. Everywhere you have `Airplane`, you can also operate `Vehicle`: 333 | 334 | ```c 335 | Airplane my_airplane = DYN_LIT(MyAirplane, Airplane, {.x = 0, .y = 0}); 336 | 337 | VCALL_SUPER(my_airplane, Vehicle, move_forward, 10); 338 | VCALL_SUPER(my_airplane, Vehicle, move_back, 3); 339 | ``` 340 | 341 | Internally, Interface99 embeds superinterfaces' virtual tables into those of subinterfaces, thereby forming a _virtual table hierarchy_. For example, you can specify `Repairable` and `Armoured` along with `Vehicle`, and they all will be included into `AirplaneVTable` like so: 342 | 343 | ```c 344 | // #define Airplane_EXTENDS (Vehicle, Repairable, Armoured) 345 | typedef struct AirplaneVTable { 346 | void (*move_up)(VSelf, int distance); 347 | void (*move_down)(VSelf, int distance); 348 | const VehicleVTable *Vehicle; 349 | const RepairableVTable *Repairable; 350 | const ArmouredVTable *Armoured; 351 | } AirplaneVTable; 352 | ``` 353 | 354 | ### Default implementations 355 | 356 | Sometimes we wish to define default behaviour for several implementers; this is supported by _default implementations_. 357 | 358 | Take a look at [`examples/default_impl.c`](examples/default_impl.c). In this example, we define the interface `Droid`: 359 | 360 | ```c 361 | #define Droid_IFACE \ 362 | vfunc(const char *, name, void) \ 363 | vfuncDefault(void, turn_on, Droid droid) 364 | 365 | interface(Droid); 366 | ``` 367 | 368 | The macro `vfuncDefault` tells Interface99 to use the default implementation for `turn_on` automatically. But where is it located? Here: 369 | 370 | ```c 371 | void Droid_turn_on(Droid droid) { 372 | printf("Turning on %s...\n", droid.vptr->name()); 373 | } 374 | ``` 375 | 376 | As you can see, default implementations follow a strict naming convention, `_` , which provides Interface99 with sufficient information to generate a virtual table. Additionally, as a developer, you can also rely on this convention and call a default function of a third-party interface. For `C_3PO`, we use the default implementation of `turn_on`, and the resulting virtual table would look like this: 377 | 378 | ```c 379 | static const DroidVTable C_3PO_Droid_impl = { 380 | .name = C_3PO_name, 381 | .turn_on = Droid_turn_on, 382 | }; 383 | ``` 384 | 385 | But for `R2_D2`, we use a custom implementation `R2_D2_turn_on`: 386 | 387 | ```c 388 | void R2_D2_turn_on(Droid droid) { 389 | Droid_turn_on(droid); 390 | puts("Waaaaoow!"); 391 | } 392 | 393 | #define R2_D2_turn_on_CUSTOM () 394 | impl(Droid, R2_D2); 395 | ``` 396 | 397 | (`R2_D2_turn_on_CUSTOM` tells Interface99 to use the custom implementation instead of the default one; this is because it is impossible to detect at compile-time whether a specific function is defined or not.) 398 | 399 | And the virtual table would be: 400 | 401 | ```c 402 | static const DroidVTable R2_D2_Droid_impl = { 403 | .name = R2_D2_name, 404 | .turn_on = R2_D2_turn_on, 405 | }; 406 | ``` 407 | 408 | Please, note that you have to specify `()` for the `*_CUSTOM` attribute; do not leave it empty. 409 | 410 | ## Syntax and semantics 411 | 412 | Having a well-defined semantics of the macros, you can write an FFI which is quite common in C. 413 | 414 | ### EBNF syntax 415 | 416 | ```ebnf 417 | ::= "interface(" ")" ; 418 | ::= ; 419 | 420 | ::= | ; 421 | ::= "vfunc(" "," "," ")" ; 422 | ::= "vfuncDefault(" "," "," ")" ; 423 | ::= ; 424 | ::= ; 425 | ::= ; 426 | 427 | ::= "impl(" "," ")" ; 428 | ::= "implExtern(" "," ")" ; 429 | ::= "declImpl(" "," ")" ; 430 | ::= "declImplExtern(" "," ")" ; 431 | ::= ; 432 | 433 | ::= "DYN(" "," "," ")" ; 434 | ::= "DYN_LIT(" "," "," "{" "}" ")" ; 435 | ::= "VTABLE(" "," ")" ; 436 | 437 | ::= "VSelf" ; 438 | ::= "VSELF(" ")" ; 439 | 440 | (* must be an expression of an interface object type. *) 441 | ::= "VCALL(" "," ")" ; 442 | ::= "VCALL_OBJ(" "," ")" ; 443 | ::= "VCALL_SUPER(" "," "," ")" ; 444 | ::= "VCALL_SUPER_OBJ(" "," "," ")" ; 445 | ::= [ "," ] ; 446 | 447 | ::= ; 448 | ``` 449 | 450 |
451 | Note: shortened vs. postfixed versions 452 | 453 | Each listed identifier in the above grammar corresponds to a macro name defined by default -- these are called _shortened versions_. On the other hand, there are also _postfixed versions_ (`interface99`, `impl99`, `vfunc99`, etc.), which are defined unconditionally. If you want to avoid name clashes caused by shortened versions, define `IFACE99_NO_ALIASES` before including `interface99.h`. Library headers are strongly advised to use the postfixed macros, but without resorting to `IFACE99_NO_ALIASES`. 454 |
455 | 456 | Notes: 457 | 458 | - For every interface ``, the macro `_IFACE` must expand to `{ }*`. 459 | - For any interface, a macro `_EXTENDS` can be defined, which must expand to `"(" { "," }* ")"`. 460 | - For any interface function implementation, a macro `__CUSTOM` can be defined, which must expand to `"()"`. 461 | 462 | [Clang-Format]: https://clang.llvm.org/docs/ClangFormatStyleOptions.html 463 | 464 | ### Semantics 465 | 466 | (It might be helpful to look at the [generated output](https://godbolt.org/z/Gr6f7TM83) of [`examples/shape.c`](examples/shape.c).) 467 | 468 | #### `interface` 469 | 470 | Expands to 471 | 472 | ``` 473 | typedef struct VTable VTable; 474 | typedef struct ; 475 | 476 | struct VTable { 477 | // Only if is a marker interface without superinterfaces: 478 | char dummy; 479 | 480 | 0 (*0)(0); 481 | ... 482 | N (*N)(N); 483 | 484 | const 0VTable *; 485 | ... 486 | const NVTable *; 487 | }; 488 | 489 | struct { 490 | void *self; 491 | const VTable *vptr; 492 | } 493 | ``` 494 | 495 | (`char dummy;` is needed for an empty `VTable` because a structure must have at least one member, according to C99.) 496 | 497 | I.e., this macro defines a virtual table structure for ``, as well as the structure `` that is polymorphic over `` implementers. This is generated in two steps: 498 | 499 | - **Function pointers**. For each `I` specified in the macro `_IFACE`, the corresponding function pointer is generated. 500 | - **Requirements obligation.** If the macro `_EXTENDS` is defined, then the listed requirements are generated to obligate `` implementers to satisfy them. 501 | 502 | #### `impl` 503 | 504 | Expands to 505 | 506 | ``` 507 | static const VTable VTABLE(, ) = { 508 | // Only if is a marker interface without superinterfaces: 509 | .dummy = '\0', 510 | 511 | 0 = either _0 or _0, 512 | ... 513 | N = either _N or _N, 514 | 515 | 0 = &VTABLE(, 0), 516 | ... 517 | N = &VTABLE(, N), 518 | } 519 | ``` 520 | 521 | I.e., this macro defines a virtual table instance of type `VTable` for ``. It is generated in two steps: 522 | 523 | - **Function implementations.** If `I` is defined via `vfuncDefault` and `_I_CUSTOM` is **not** defined, `_I` is generated (default implementation). Otherwise, `_I` is generated (custom implementation). 524 | - **Requirements satisfaction.** If the macro `_EXTENDS` is defined, then the listed requirements are generated to satisfy ``. 525 | 526 | #### `implExtern` 527 | 528 | The same as [`impl`](#impl) but generates an `extern` definition instead of `static`. 529 | 530 | #### `declImpl` 531 | 532 | Expands to `static const VTable VTABLE(, )`, i.e., it declares a virtual table instance of `` of type `VTable`. 533 | 534 | #### `declImplExtern` 535 | 536 | The same as [`declImpl`](#declImpl) but generates an `extern` declaration instead of `static`. 537 | 538 | #### `DYN` 539 | 540 | Expands to an expression of type ``, with `.self` initialised to `` and `.vptr` initialised to `&VTABLE(, )`. 541 | 542 | `` is guaranteed to be evaluated only once. 543 | 544 | #### `DYN_LIT` 545 | 546 | `DYN_LIT(, , ...)` expands to `DYN(, , &()...)`. The `...` must take the form of an initialiser list in [compound literals]. 547 | 548 | [compound literals]: https://en.cppreference.com/w/c/language/compound_literal 549 | 550 | #### `VTABLE` 551 | 552 | Expands to `__impl`, i.e., a virtual table instance of `` of type `VTable`. 553 | 554 | #### `VSelf`/`VSELF` 555 | 556 | `VSelf` is an object-like macro that expands to a function parameter of type `void * restrict`, with an implementation-defined name. In order to downcast this parameter to an implementer type, there exists a function-like macro `VSELF`. `VSELF(T)` which brings a variable `self` of type `T * restrict` into the scope, and initialises it to the `VSelf`-produced parameter name casted to `T * restrict`. 557 | 558 | `VSelf` can be used on any position for any virtual function, however, it only makes sense to use it as a first parameter. `VSELF(T)` can be used everywhere inside a function with the `VSelf` parameter. 559 | 560 | #### `VCALL_*` 561 | 562 | The `VCALL_*` macros are meant to **call** a **v**irtual method, which is a `vfunc`/`vfuncDefault` that accepts either `VSelf` or an interface object (of a containing interface type) as a first parameter. 563 | 564 | For methods accepting `VSelf`, there exist `VCALL` and `VCALL_SUPER`: 565 | 566 | - `VCALL(obj, func)` => `obj.vptr->func(obj.self)`. 567 | - `VCALL(obj, func, args...)` => `obj.vptr->func(obj.self, args...)`. 568 | - `VCALL_SUPER(obj, superiface, func)` => `obj.vptr->superiface->func(obj.self)`. 569 | - `VCALL_SUPER(obj, superiface, func, args...)` => `obj.vptr->superiface->func(obj.self, args...)`. 570 | 571 | For methods accepting an interface object, there are `VCALL_OBJ` and `VCALL_SUPER_OBJ`: 572 | 573 | - `VCALL_OBJ` is the same as `VCALL` except that it passes `obj` to `func` instead of `obj.self`. 574 | - `VCALL_SUPER_OBJ` is the same as `VCALL_SUPER` except that it passes `(superiface){obj.self, obj.vptr->superiface}` to `func` instead of `obj.self`. 575 | 576 | ## Miscellaneous 577 | 578 | - The macros `IFACE99_MAJOR`, `IFACE99_MINOR`, `IFACE99_PATCH`, `IFACE99_VERSION_COMPATIBLE(x, y, z)`, and `IFACE99_VERSION_EQ(x, y, z)` have the [same semantics as of Metalang99](https://metalang99.readthedocs.io/en/latest/#version-manipulation-macros). 579 | 580 | - For each macro using `ML99_EVAL`, Interface99 provides its [Metalang99-compliant](https://metalang99.readthedocs.io/en/latest/#definitions) counterpart which can be used inside derivers and other Metalang99-compliant macros: 581 | 582 | | Macro | Metalang99-compliant counterpart | 583 | |----------|----------| 584 | | `interface` | `IFACE99_interface` | 585 | | `impl` | `IFACE99_impl` | 586 | | `implExtern` | `IFACE99_implExtern` | 587 | 588 | (An [arity specifier] and [desugaring macro] are provided for each of the above macros.) 589 | 590 | [arity specifier]: https://hirrolot.gitbook.io/metalang99/partial-application 591 | [desugaring macro]: https://metalang99.readthedocs.io/en/latest/#definitions 592 | 593 | ## Guidelines 594 | 595 | - Write `impl(...)`/`implExtern(...)` right after all functions are implemented; do not gather all implementation definitions in a single place. 596 | - If you use [Clang-Format], it can be helpful to add `vfunc` and `vfuncDefault` to the `StatementMacros` vector (see [our `.clang-format`](.clang-format)). It will instruct the formatter to place them onto different lines. 597 | 598 | ## Pitfalls 599 | 600 | - Both interfaces that you implement for a single type can have a function with the same name, thus resulting in a name collision. However, you can elegantly workaround like this: 601 | 602 | ```c 603 | // `MyType_Iface1_foo` function definition here... 604 | 605 | #define Iface1_foo MyType_Iface1_foo 606 | impl(Iface1, MyType); 607 | #undef Iface1_foo 608 | 609 | // `MyType_Iface2_foo` function definition here... 610 | 611 | #define Iface2_foo MyType_Iface2_foo 612 | impl(Iface2, MyType); 613 | #undef Iface2_foo 614 | ``` 615 | 616 | The same holds for custom implementations: 617 | 618 | ```c 619 | // Use a custom implementation for `Iface1::bar`. 620 | #define MyType_bar_CUSTOM () 621 | impl(Iface1, MyType); 622 | #undef MyType_bar_CUSTOM 623 | 624 | // Use the default `Iface2::bar`. 625 | impl(Iface2, MyType); 626 | ``` 627 | 628 | ## Design choices 629 | 630 | The design of Interface99 may raise some questions. In this section, you may find answers why it was designed in this way. 631 | 632 | ### `VCALL_*` 633 | 634 | Instead of using the `VCALL_*` macros, we could instead generate functions that accept an interface object as a first parameter, with the rest of parameters being arguments to a particular method: 635 | 636 | ```c 637 | void Shape_scale(Shape shape, int factor) { 638 | shape.vptr->scale(shape.self, factor); 639 | } 640 | ``` 641 | 642 | But this approach does not work for superinterfaces' methods, as well as for methods accepting an interface object instead of `VSelf` or a combination thereof. For this reason, I decided to stick to more expressive `VCALL_*` macros, although at the cost of some IDE support. 643 | 644 | ### `self` type safety 645 | 646 | Since there can be many specific implementations of a virtual method (like `Rectangle_scale` or `Triangle_scale`), `self` **must** be of type `void *`. But the problem is that in concrete implementations, we still want `self` to be of some concrete type; and since `void *` and `T *` may be incompatible types, assigning a concrete method accepting `T *` to a virtual method field [results in UB](https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type). 647 | 648 | To solve the problem, we may want to generate untyped wrapper functions that accept `void *restrict self` and pass the downcasted version to the underlying method: 649 | 650 | ```c 651 | void Rectangle_scale_wrapper(void *restrict self, int factor) { 652 | Rectangle_scale((Rectangle * restrict)self, factor); 653 | } 654 | ``` 655 | 656 | But the reason we do **not** do this is that in C99, it is impossible to differentiate `void` from other types; if the return type is `void`, we must not emit `return` with an expression, otherwise, we **must**. We could come up with something like `vfuncVoid` and `vfuncDefaultVoid` but this would increase the learning curve and complicate the design and implementation of Interface99. 657 | 658 | However, casting untyped `self` to a particular type is still quite unpleasant. The best thing I came up with is the `VSelf` and `VSELF(T)` mechanism, which nonetheless works quite well. 659 | 660 | ## Credits 661 | 662 | Thanks to Rust and Golang for their implementations of traits/interfaces. 663 | 664 | ## Blog posts 665 | 666 | - [_Comparing Golang and Interface99_](https://www.reddit.com/r/C_Programming/comments/tgm5ft/comparing_golang_and_interface99/) 667 | - [_What’s the Point of the C Preprocessor, Actually?_](https://hirrolot.github.io/posts/whats-the-point-of-the-c-preprocessor-actually.html) 668 | - [_Macros on Steroids, Or: How Can Pure C Benefit From Metaprogramming_](https://hirrolot.github.io/posts/macros-on-steroids-or-how-can-pure-c-benefit-from-metaprogramming.html) 669 | - [_Extend Your Language, Don’t Alter It_](https://hirrolot.github.io/posts/extend-your-language-dont-alter-it.html) 670 | 671 | ## Release procedure 672 | 673 | 1. Update `IFACE99_MAJOR`, `IFACE99_MINOR`, and `IFACE99_PATCH` in `interface99.h`. 674 | 2. Update `CHANGELOG.md`. 675 | 3. Release the project in [GitHub Releases]. 676 | 677 | [GitHub Releases]: https://github.com/hirrolot/interface99/releases 678 | 679 | ## FAQ 680 | 681 | ### Q: Why use C instead of Rust/Zig/whatever else? 682 | 683 | A: See [Datatype99's README >>](https://github.com/hirrolot/datatype99#q-why-use-c-instead-of-rustzigwhatever-else). 684 | 685 | ### Q: Why not third-party code generators? 686 | 687 | A: See [Metalang99's README >>](https://github.com/hirrolot/metalang99#q-why-not-third-party-code-generators). 688 | 689 | ### Q: How does it work? 690 | 691 | A: Interface99 is implemented upon [Metalang99], a preprocessor metaprogramming library that allows enriching pure C with some custom syntax sugar. 692 | 693 | ### Q: Does it work on C++? 694 | 695 | A: Yes, C++11 and onwards is supported. 696 | 697 | ### Q: How Interface99 differs from similar projects? 698 | 699 | A: 700 | 701 | - **Less boilerplate.** In particular, Interface99 deduces function implementations from the context, thus improving code maintenance. To my knowledge, no other alternative can do this. 702 | 703 | - **Small.** Interface99 only features the software interface concept, no less and no more -- it does not bring all the other fancy OOP stuff, unlike [GObject] or [COS]. 704 | 705 | - **Depends on Metalang99.** Interface99 is built upon [Metalang99], the underlying metaprogramming framework. With Metalang99, you can also use [Datatype99]. 706 | 707 | Other worth-mentioning projects: 708 | 709 | - [typeclass-interface-pattern], though it is rather a general idea than a ready-to-use implementation. 710 | - [OOC] -- a book about OO programming in ANSI C. 711 | 712 | [`obj.h`]: https://github.com/small-c/obj.h 713 | [GObject]: https://developer.gnome.org/gobject/stable/ 714 | [COS]: http://ldeniau.web.cern.ch/ldeniau/cos.html 715 | [Datatype99]: https://github.com/hirrolot/datatype99 716 | [typeclass-interface-pattern]: https://github.com/TotallyNotChase/typeclass-interface-pattern 717 | [OOC]: https://www.cs.rit.edu/~ats/books/ooc.pdf 718 | 719 | ### Q: What about compile-time errors? 720 | 721 | #### Error: missing interface implementation 722 | 723 | [`playground.c`] 724 | ```c 725 | #define Foo_IFACE vfunc(void, foo, int x, int y) 726 | interface(Foo); 727 | 728 | typedef struct { 729 | char dummy; 730 | } MyFoo; 731 | 732 | // Missing `void MyFoo_foo(int x, int y)`. 733 | 734 | impl(Foo, MyFoo); 735 | ``` 736 | 737 | [`/bin/sh`] 738 | ``` 739 | playground.c:12:1: error: ‘MyFoo_foo’ undeclared here (not in a function) 740 | 12 | impl(Foo, MyFoo); 741 | | ^~~~ 742 | ``` 743 | 744 | ---------- 745 | 746 | #### Error: improperly typed interface implementation 747 | 748 | [`playground.c`] 749 | ```c 750 | #define Foo_IFACE vfunc(void, foo, int x, int y) 751 | interface(Foo); 752 | 753 | typedef struct { 754 | char dummy; 755 | } MyFoo; 756 | 757 | void MyFoo_foo(const char *str) {} 758 | 759 | impl(Foo, MyFoo); 760 | ``` 761 | 762 | [`/bin/sh`] 763 | ``` 764 | playground.c:12:1: warning: initialization of ‘void (*)(int, int)’ from incompatible pointer type ‘void (*)(const char *)’ [-Wincompatible-pointer-types] 765 | 12 | impl(Foo, MyFoo); 766 | | ^~~~ 767 | playground.c:12:1: note: (near initialization for ‘MyFoo_Foo_impl.foo’) 768 | ``` 769 | 770 | ---------- 771 | 772 | #### Error: unsatisfied interface requirement 773 | 774 | [`playground.c`] 775 | ```c 776 | #define Foo_IFACE vfunc(void, foo, int x, int y) 777 | interface(Foo); 778 | 779 | #define Bar_IFACE vfunc(void, bar, void) 780 | #define Bar_EXTENDS (Foo) 781 | 782 | interface(Bar); 783 | 784 | typedef struct { 785 | char dummy; 786 | } MyBar; 787 | 788 | void MyBar_bar(void) {} 789 | 790 | // Missing `impl(Foo, MyBar)`. 791 | 792 | impl(Bar, MyBar); 793 | ``` 794 | 795 | [`/bin/sh`] 796 | ``` 797 | playground.c:19:1: error: ‘MyBar_Foo_impl’ undeclared here (not in a function); did you mean ‘MyBar_Bar_impl’? 798 | 19 | impl(Bar, MyBar); 799 | | ^~~~ 800 | | MyBar_Bar_impl 801 | ``` 802 | 803 | ---------- 804 | 805 | #### Error: typo in `DYN` 806 | 807 | [`playground.c`] 808 | ```c 809 | #define Foo_IFACE vfunc(void, foo, void) 810 | interface(Foo); 811 | 812 | typedef struct { 813 | char dummy; 814 | } MyFoo; 815 | 816 | void MyFoo_foo(void) {} 817 | 818 | impl(Foo, MyFoo); 819 | 820 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } 821 | ``` 822 | 823 | [`/bin/sh`] 824 | ``` 825 | playground.c: In function ‘main’: 826 | playground.c:14:28: error: ‘Bar’ undeclared (first use in this function) 827 | 14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } 828 | | ^~~ 829 | playground.c:14:28: note: each undeclared identifier is reported only once for each function it appears in 830 | playground.c:14:31: error: expected ‘)’ before ‘{’ token 831 | 14 | int main(void) { Foo foo = DYN(MyFoo, /* Foo */ Bar, &(MyFoo){0}); } 832 | | ~~~^ 833 | | ) 834 | ``` 835 | 836 | ---------- 837 | 838 | #### Error: typo in `VTABLE` 839 | 840 | [`playground.c`] 841 | ```c 842 | #define Foo_IFACE vfunc(void, foo, void) 843 | interface(Foo); 844 | 845 | typedef struct { 846 | char dummy; 847 | } MyFoo; 848 | 849 | void MyFoo_foo(void) {} 850 | 851 | impl(Foo, MyFoo); 852 | 853 | int main(void) { FooVTable foo = VTABLE(/* MyFoo */ MyBar, Foo); } 854 | ``` 855 | 856 | [`/bin/sh`] 857 | ``` 858 | playground.c: In function ‘main’: 859 | playground.c:14:34: error: ‘MyBar_Foo_impl’ undeclared (first use in this function); did you mean ‘MyFoo_Foo_impl’? 860 | 14 | int main(void) { FooVTable foo = VTABLE(/* MyFoo */ MyBar, Foo); } 861 | | ^~~~~~ 862 | | MyFoo_Foo_impl 863 | ``` 864 | 865 | ---------- 866 | 867 | From my experience, nearly 95% of errors make sense. 868 | 869 | If an error is not comprehensible at all, try to look at generated code (`-E`). Hopefully, the [code generation semantics] is formally defined so normally you will not see something unexpected. 870 | 871 | ### Q: What about IDE support? 872 | 873 | ![Suggestion](images/suggestion.png) 874 | 875 | A: VS Code automatically enables suggestions of generated types but, of course, it does not support macro syntax highlighting. The sad part is that `VCALL` and its friends break go-to definitions and do not highlight function signatures, so we do intentionally [trade some IDE support for syntax conciseness](#vcall_-1). 876 | 877 | ### Q: Which compilers are tested? 878 | 879 | A: Interface99 is known to work on these compilers: 880 | 881 | - GCC 882 | - Clang 883 | - MSVC 884 | - TCC 885 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(examples LANGUAGES C) 3 | 4 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU") 5 | add_compile_options(-Wall -Wextra -pedantic -ftrack-macro-expansion=0 6 | -fsanitize=address) 7 | add_link_options(-fsanitize=address) 8 | elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") 9 | add_compile_options(-fmacro-backtrace-limit=1 -fsanitize=address) 10 | add_link_options(-fsanitize=address) 11 | elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 12 | # Enable a standard-conforming C99/C11 preprocessor. 13 | add_compile_options("/std:c11") 14 | elseif(CMAKE_C_COMPILER_ID STREQUAL "TinyCC") 15 | add_compile_definitions(ML99_ALLOW_POOR_DIAGNOSTICS) 16 | endif() 17 | 18 | add_subdirectory(opaque_type) 19 | 20 | add_executable(shape shape.c) 21 | add_executable(tracing_vehicle tracing_vehicle.c) 22 | add_executable(airplane airplane.c) 23 | add_executable(read_write read_write.c) 24 | add_executable(read_write_both read_write_both.c) 25 | add_executable(marker marker.c) 26 | add_executable(default_impl default_impl.c) 27 | 28 | add_subdirectory(.. build) 29 | 30 | get_property( 31 | EXAMPLES 32 | DIRECTORY . 33 | PROPERTY BUILDSYSTEM_TARGETS) 34 | 35 | get_property( 36 | OPAQUE_TYPE_EXAMPLE 37 | DIRECTORY opaque_type 38 | PROPERTY BUILDSYSTEM_TARGETS) 39 | 40 | foreach(TARGET ${EXAMPLES};${OPAQUE_TYPE_EXAMPLE}) 41 | target_link_libraries(${TARGET} interface99) 42 | set_target_properties(${TARGET} PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED 43 | ON) 44 | endforeach() 45 | -------------------------------------------------------------------------------- /examples/airplane.c: -------------------------------------------------------------------------------- 1 | // This example demonstrates the usage of superinterfaces/interface requirements. 2 | 3 | #include 4 | 5 | #include 6 | 7 | #define Vehicle_IFACE \ 8 | vfunc(void, move_forward, VSelf, int distance) \ 9 | vfunc(void, move_back, VSelf, int distance) 10 | 11 | interface(Vehicle); 12 | 13 | #define Airplane_IFACE \ 14 | vfunc(void, move_up, VSelf, int distance) \ 15 | vfunc(void, move_down, VSelf, int distance) 16 | 17 | #define Airplane_EXTENDS (Vehicle) 18 | 19 | interface(Airplane); 20 | 21 | typedef struct { 22 | int x, y; 23 | } MyAirplane; 24 | 25 | void MyAirplane_log(MyAirplane *self, const char *command, int distance) { 26 | printf("%s(%d): x = %d, y = %d\n", command, distance, self->x, self->y); 27 | } 28 | 29 | void MyAirplane_move_forward(VSelf, int distance) { 30 | VSELF(MyAirplane); 31 | self->x += distance; 32 | MyAirplane_log(self, "move_forward", distance); 33 | } 34 | 35 | void MyAirplane_move_back(VSelf, int distance) { 36 | VSELF(MyAirplane); 37 | self->x -= distance; 38 | MyAirplane_log(self, "move_back", distance); 39 | } 40 | 41 | impl(Vehicle, MyAirplane); 42 | 43 | void MyAirplane_move_up(VSelf, int distance) { 44 | VSELF(MyAirplane); 45 | self->y += distance; 46 | MyAirplane_log(self, "move_up", distance); 47 | } 48 | 49 | void MyAirplane_move_down(VSelf, int distance) { 50 | VSELF(MyAirplane); 51 | 52 | self->y -= distance; 53 | MyAirplane_log(self, "move_down", distance); 54 | } 55 | 56 | impl(Airplane, MyAirplane); 57 | 58 | /* 59 | * Output: 60 | * move_forward(10): x = 10, y = 0 61 | * move_back(3): x = 7, y = 0 62 | * move_up(7): x = 7, y = 7 63 | * move_down(11): x = 7, y = -4 64 | */ 65 | int main(void) { 66 | Airplane my_airplane = DYN_LIT(MyAirplane, Airplane, {.x = 0, .y = 0}); 67 | 68 | VCALL_SUPER(my_airplane, Vehicle, move_forward, 10); 69 | VCALL_SUPER(my_airplane, Vehicle, move_back, 3); 70 | 71 | VCALL(my_airplane, move_up, 7); 72 | VCALL(my_airplane, move_down, 11); 73 | 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /examples/default_impl.c: -------------------------------------------------------------------------------- 1 | // This examples demonstrates default method implementations. 2 | 3 | #include 4 | 5 | #include 6 | 7 | #define Droid_IFACE \ 8 | vfunc(const char *, name, void) \ 9 | vfuncDefault(void, turn_on, Droid droid) 10 | 11 | interface(Droid); 12 | 13 | // The default implementation of `Droid::turn_on`. 14 | void Droid_turn_on(Droid droid) { 15 | // A default implementation can access other methods through an interface object. 16 | printf("Turning on %s...\n", droid.vptr->name()); 17 | } 18 | 19 | // C-3PO implementation 20 | // ============================================================ 21 | 22 | typedef struct { 23 | char dummy; 24 | } C_3PO; 25 | 26 | const char *C_3PO_name(void) { 27 | return "C-3PO"; 28 | } 29 | 30 | // Use the default implementation of `Droid::turn_on` automatically. 31 | impl(Droid, C_3PO); 32 | 33 | // R2-D2 implementation 34 | // ============================================================ 35 | 36 | typedef struct { 37 | char dummy; 38 | } R2_D2; 39 | 40 | const char *R2_D2_name(void) { 41 | return "R2-D2"; 42 | } 43 | 44 | void R2_D2_turn_on(Droid droid) { 45 | // A default implementation can be called from a custom one. 46 | Droid_turn_on(droid); 47 | puts("Waaaaoow!"); 48 | } 49 | 50 | // Use a custom implementation for `Droid::turn_on`. 51 | #define R2_D2_turn_on_CUSTOM () 52 | impl(Droid, R2_D2); 53 | 54 | // Test 55 | // ============================================================ 56 | 57 | /* 58 | * Output: 59 | * Turning on C-3PO... 60 | * Turning on R2-D2... 61 | * Waaaaoow! 62 | */ 63 | int main(void) { 64 | Droid c_3po = DYN_LIT(C_3PO, Droid, {0}); 65 | VCALL_OBJ(c_3po, turn_on); 66 | 67 | Droid r2_d2 = DYN_LIT(R2_D2, Droid, {0}); 68 | VCALL_OBJ(r2_d2, turn_on); 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /examples/marker.c: -------------------------------------------------------------------------------- 1 | // This examples demonstrates marker interfaces. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // A marker interface whose implementers can be duplicated by simply copying bits. 11 | #define Copy_IFACE 12 | 13 | interface(Copy); 14 | 15 | typedef struct { 16 | unsigned health, victories, defeats; 17 | } PlayerStats; 18 | 19 | impl(Copy, PlayerStats); 20 | 21 | // This is not `Copy`able because of `.name`. 22 | typedef struct { 23 | const char *name; 24 | int id; 25 | PlayerStats stats; 26 | } Player; 27 | 28 | void test(Copy src, Copy dst, size_t size) { 29 | memcpy(src.self, dst.self, size); 30 | } 31 | 32 | int main(void) { 33 | const PlayerStats stats1 = {.health = 100, .victories = 5, .defeats = 2}; 34 | PlayerStats stats2; 35 | test(DYN(PlayerStats, Copy, &stats1), DYN(PlayerStats, Copy, &stats2), sizeof(PlayerStats)); 36 | 37 | assert(memcmp(&stats1, &stats2, sizeof(PlayerStats)) == 0); 38 | 39 | const Player p1 = {.name = "John", .id = 123, .stats = stats1}; 40 | Player p2; 41 | // COMPILE-TIME ERROR: 42 | // test(DYN(Player, Copy, &p1), DYN(Player, Copy, &p2), sizeof(Player)); 43 | 44 | (void)p1; 45 | (void)p2; 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /examples/opaque_type/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(frog main.c frog.h frog.c croak.h) 2 | -------------------------------------------------------------------------------- /examples/opaque_type/croak.h: -------------------------------------------------------------------------------- 1 | #ifndef OPAQUE_TYPE_CROAK_H 2 | #define OPAQUE_TYPE_CROAK_H 3 | 4 | #include 5 | 6 | #define Croak_IFACE vfunc(void, croak, VSelf) 7 | 8 | interface(Croak); 9 | 10 | #endif // OPAQUE_TYPE_CROAK_H 11 | -------------------------------------------------------------------------------- /examples/opaque_type/frog.c: -------------------------------------------------------------------------------- 1 | #include "frog.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | struct Frog { 8 | const char *name; 9 | int ncroaks; 10 | }; 11 | 12 | Frog *Frog_new(const char *name) { 13 | Frog *self = malloc(sizeof *self); 14 | assert(self); 15 | 16 | self->name = name; 17 | self->ncroaks = 0; 18 | 19 | return self; 20 | } 21 | 22 | void Frog_free(Frog *self) { 23 | assert(self); 24 | free(self); 25 | } 26 | 27 | static void Frog_croak(VSelf) { 28 | VSELF(Frog); 29 | printf("%s: croak!\n", self->name); 30 | self->ncroaks++; 31 | } 32 | 33 | implExtern(Croak, Frog); 34 | -------------------------------------------------------------------------------- /examples/opaque_type/frog.h: -------------------------------------------------------------------------------- 1 | #ifndef OPAQUE_TYPE_FROG_H 2 | #define OPAQUE_TYPE_FROG_H 3 | 4 | #include 5 | 6 | #include "croak.h" 7 | 8 | typedef struct Frog Frog; 9 | 10 | Frog *Frog_new(const char *name); 11 | void Frog_free(Frog *self); 12 | 13 | declImplExtern(Croak, Frog); 14 | 15 | #endif // OPAQUE_TYPE_FROG_H 16 | -------------------------------------------------------------------------------- /examples/opaque_type/main.c: -------------------------------------------------------------------------------- 1 | // This example demonstrates how to implement interfaces for opaque types. 2 | // Note: you can export interface implementations outside of a TU for non-opaque types in the same 3 | // way. 4 | 5 | #include "croak.h" 6 | #include "frog.h" 7 | 8 | /* 9 | * Output: 10 | * Paul: croak! 11 | * Steve: croak! 12 | */ 13 | int main(void) { 14 | Frog *paul = Frog_new("Paul"); 15 | Frog *steve = Frog_new("Steve"); 16 | 17 | VCALL(DYN(Frog, Croak, paul), croak); 18 | VCALL(DYN(Frog, Croak, steve), croak); 19 | 20 | Frog_free(paul); 21 | Frog_free(steve); 22 | 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /examples/read_write.c: -------------------------------------------------------------------------------- 1 | // This examples demonstrates multiple interface inheritance. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define Reader_IFACE vfunc(size_t, read, VSelf, char *dest, size_t bytes_to_read) 10 | interface(Reader); 11 | 12 | #define Writer_IFACE vfunc(size_t, write, VSelf, const char *src, size_t bytes_to_write) 13 | interface(Writer); 14 | 15 | typedef struct { 16 | FILE *fp; 17 | } File; 18 | 19 | size_t File_read(VSelf, char *dest, size_t bytes_to_read) { 20 | VSELF(File); 21 | return fread(dest, 1, bytes_to_read, self->fp); 22 | } 23 | 24 | impl(Reader, File); 25 | 26 | size_t File_write(VSelf, const char *src, size_t bytes_to_write) { 27 | VSELF(File); 28 | return fwrite(src, 1, bytes_to_write, self->fp); 29 | } 30 | 31 | impl(Writer, File); 32 | 33 | /* 34 | * Output: 35 | * We have read: 'hello world' 36 | */ 37 | int main(void) { 38 | FILE *fp = tmpfile(); 39 | assert(fp); 40 | 41 | Writer w = DYN_LIT(File, Writer, {fp}); 42 | VCALL(w, write, "hello world", strlen("hello world")); 43 | rewind(fp); 44 | 45 | Reader r = DYN_LIT(File, Reader, {fp}); 46 | char hello_world[16] = {0}; 47 | VCALL(r, read, hello_world, strlen("hello world")); 48 | 49 | printf("We have read: '%s'\n", hello_world); 50 | fclose(fp); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /examples/read_write_both.c: -------------------------------------------------------------------------------- 1 | // This examples demonstrates dynamic objects of multiple interfaces. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define Reader_IFACE vfunc(size_t, read, VSelf, char *dest, size_t bytes_to_read) 10 | interface(Reader); 11 | 12 | #define Writer_IFACE vfunc(size_t, write, VSelf, const char *src, size_t bytes_to_write) 13 | interface(Writer); 14 | 15 | #define ReadWriter_IFACE 16 | #define ReadWriter_EXTENDS (Reader, Writer) 17 | interface(ReadWriter); 18 | 19 | typedef struct { 20 | FILE *fp; 21 | } File; 22 | 23 | size_t File_read(VSelf, char *dest, size_t bytes_to_read) { 24 | VSELF(File); 25 | return fread(dest, 1, bytes_to_read, self->fp); 26 | } 27 | 28 | impl(Reader, File); 29 | 30 | size_t File_write(VSelf, const char *src, size_t bytes_to_write) { 31 | VSELF(File); 32 | return fwrite(src, 1, bytes_to_write, self->fp); 33 | } 34 | 35 | impl(Writer, File); 36 | 37 | // `Reader` and `Writer` are already implemented. 38 | impl(ReadWriter, File); 39 | 40 | /* 41 | * Output: 42 | * We have read: 'hello world' 43 | */ 44 | int main(void) { 45 | FILE *fp = tmpfile(); 46 | assert(fp); 47 | 48 | ReadWriter rw = DYN_LIT(File, ReadWriter, {fp}); 49 | 50 | VCALL_SUPER(rw, Writer, write, "hello world", strlen("hello world")); 51 | rewind(fp); 52 | 53 | char hello_world[16] = {0}; 54 | VCALL_SUPER(rw, Reader, read, hello_world, strlen("hello world")); 55 | 56 | printf("We have read: '%s'\n", hello_world); 57 | fclose(fp); 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /examples/shape.c: -------------------------------------------------------------------------------- 1 | // This examples demonstrates the basic usage of Interface99. 2 | 3 | #include 4 | 5 | #include 6 | 7 | // clang-format off 8 | #define Shape_IFACE \ 9 | vfunc( int, perim, const VSelf) \ 10 | vfunc(void, scale, VSelf, int factor) 11 | // clang-format on 12 | 13 | interface(Shape); 14 | 15 | // Rectangle implementation 16 | // ============================================================ 17 | 18 | typedef struct { 19 | int a, b; 20 | } Rectangle; 21 | 22 | int Rectangle_perim(const VSelf) { 23 | VSELF(const Rectangle); 24 | return (self->a + self->b) * 2; 25 | } 26 | 27 | void Rectangle_scale(VSelf, int factor) { 28 | VSELF(Rectangle); 29 | self->a *= factor; 30 | self->b *= factor; 31 | } 32 | 33 | impl(Shape, Rectangle); 34 | 35 | // Triangle implementation 36 | // ============================================================ 37 | 38 | typedef struct { 39 | int a, b, c; 40 | } Triangle; 41 | 42 | int Triangle_perim(const VSelf) { 43 | VSELF(const Triangle); 44 | return self->a + self->b + self->c; 45 | } 46 | 47 | void Triangle_scale(VSelf, int factor) { 48 | VSELF(Triangle); 49 | self->a *= factor; 50 | self->b *= factor; 51 | self->c *= factor; 52 | } 53 | 54 | impl(Shape, Triangle); 55 | 56 | // Test 57 | // ============================================================ 58 | 59 | void test(Shape shape) { 60 | printf("perim = %d\n", VCALL(shape, perim)); 61 | VCALL(shape, scale, 5); 62 | printf("perim = %d\n", VCALL(shape, perim)); 63 | } 64 | 65 | /* 66 | * Output: 67 | * perim = 24 68 | * perim = 120 69 | * perim = 60 70 | * perim = 300 71 | */ 72 | int main(void) { 73 | Shape r = DYN_LIT(Rectangle, Shape, {5, 7}); 74 | Shape t = DYN_LIT(Triangle, Shape, {10, 20, 30}); 75 | 76 | test(r); 77 | test(t); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /examples/tracing_vehicle.c: -------------------------------------------------------------------------------- 1 | // This example demonstrates how to write interface decorators. 2 | 3 | #include 4 | 5 | #include 6 | 7 | #define Vehicle_IFACE \ 8 | vfunc(void, refuel, VSelf, int fuel) \ 9 | vfunc(void, drive, VSelf) 10 | 11 | interface(Vehicle); 12 | 13 | // Car implementation 14 | // ============================================================ 15 | 16 | typedef struct { 17 | int fuel; 18 | } Car; 19 | 20 | void Car_refuel(VSelf, int fuel) { 21 | VSELF(Car); 22 | self->fuel += fuel; 23 | } 24 | 25 | void Car_drive(VSelf) { 26 | VSELF(Car); 27 | self->fuel--; 28 | } 29 | 30 | impl(Vehicle, Car); 31 | 32 | // TracingVehicle implementation 33 | // ============================================================ 34 | 35 | typedef struct { 36 | Vehicle inner; 37 | } TracingVehicle; 38 | 39 | void TracingVehicle_refuel(VSelf, int fuel) { 40 | VSELF(TracingVehicle); 41 | printf("Vehicle.refuel(%d)\n", fuel); 42 | VCALL(self->inner, refuel, fuel); 43 | } 44 | 45 | void TracingVehicle_drive(VSelf) { 46 | VSELF(TracingVehicle); 47 | printf("Vehicle.drive()\n"); 48 | VCALL(self->inner, drive); 49 | } 50 | 51 | impl(Vehicle, TracingVehicle); 52 | 53 | // Test 54 | // ============================================================ 55 | 56 | void test(Vehicle vehicle) { 57 | VCALL(vehicle, refuel, 15); 58 | VCALL(vehicle, drive); 59 | } 60 | 61 | /* 62 | * Output: 63 | * Vehicle.refuel(15) 64 | * Vehicle.drive() 65 | */ 66 | int main(void) { 67 | Vehicle car = DYN_LIT(Car, Vehicle, {.fuel = 0}); 68 | Vehicle tracing_car = DYN_LIT(TracingVehicle, Vehicle, {.inner = car}); 69 | 70 | test(tracing_car); 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /images/suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hirrolot/interface99/723bc751f13ef221e6adf285015737387010518b/images/suggestion.png -------------------------------------------------------------------------------- /interface99.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2021-2025 hirrolot 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | // The official repository: . 26 | 27 | #ifndef INTERFACE99_H 28 | #define INTERFACE99_H 29 | 30 | #include 31 | 32 | #if !ML99_VERSION_COMPATIBLE(1, 13, 2) 33 | #error Please, update Metalang99 to v1.13.2 or later. 34 | #endif 35 | 36 | #ifndef IFACE99_NO_ALIASES 37 | 38 | #define interface(iface) interface99(iface) 39 | #define impl(iface, implementer) impl99(iface, implementer) 40 | #define implExtern(iface, implementer) implExtern99(iface, implementer) 41 | #define declImpl(iface, implementer) declImpl99(iface, implementer) 42 | #define declImplExtern(iface, implementer) declImplExtern99(iface, implementer) 43 | #define vfunc(ret_ty, name, ...) vfunc99(ret_ty, name, __VA_ARGS__) 44 | #define vfuncDefault(ret_ty, name, ...) vfuncDefault99(ret_ty, name, __VA_ARGS__) 45 | 46 | #define VCALL(obj, ...) VCALL99(obj, __VA_ARGS__) 47 | #define VCALL_OBJ(obj, ...) VCALL_OBJ99(obj, __VA_ARGS__) 48 | #define VCALL_SUPER(obj, superiface, ...) VCALL_SUPER99(obj, superiface, __VA_ARGS__) 49 | #define VCALL_SUPER_OBJ(obj, superiface, ...) VCALL_SUPER_OBJ99(obj, superiface, __VA_ARGS__) 50 | 51 | #define DYN(implementer, iface, ...) DYN99(implementer, iface, __VA_ARGS__) 52 | #define DYN_LIT(implementer, iface, ...) DYN_LIT99(implementer, iface, __VA_ARGS__) 53 | #define VTABLE(implementer, iface) VTABLE99(implementer, iface) 54 | 55 | #define VSelf VSelf99 56 | #define VSELF(T) VSELF99(T) 57 | 58 | #endif // IFACE99_NO_ALIASES 59 | 60 | // Public stuff { 61 | 62 | // Metalang99-compliant macros { 63 | 64 | #define IFACE99_interface(iface) ML99_call(IFACE99_interface, iface) 65 | #define IFACE99_impl(iface, implementer) ML99_call(IFACE99_impl, iface, implementer) 66 | #define IFACE99_implExtern(iface, implementer) ML99_call(IFACE99_implExtern, iface, implementer) 67 | 68 | #define interface99(iface) ML99_EVAL(IFACE99_interface_IMPL(iface)) 69 | #define impl99(iface, implementer) ML99_EVAL(IFACE99_impl_IMPL(iface, implementer)) 70 | #define implExtern99(iface, implementer) ML99_EVAL(IFACE99_implExtern_IMPL(iface, implementer)) 71 | // } (Metalang99-compliant macros) 72 | 73 | #define vfunc99(ret_ty, name, ...) ML99_CHOICE(vfunc, ret_ty, name, __VA_ARGS__) 74 | #define vfuncDefault99(ret_ty, name, ...) ML99_CHOICE(vfuncDefault, ret_ty, name, __VA_ARGS__) 75 | 76 | #define DYN99(implementer, iface, ...) \ 77 | ((iface){.self = (void *)(__VA_ARGS__), .vptr = &VTABLE99(implementer, iface)}) 78 | #define DYN_LIT99(implementer, iface, ...) DYN99(implementer, iface, &(implementer)__VA_ARGS__) 79 | #define VTABLE99(implementer, iface) ML99_CAT4(implementer, _, iface, _impl) 80 | 81 | #define VSelf99 void *restrict iface99_self 82 | 83 | // clang-format off 84 | 85 | #define VSELF99(T) T *restrict self = (T *restrict)(iface99_self) 86 | // clang-format on 87 | 88 | #define IFACE99_MAJOR 1 89 | #define IFACE99_MINOR 0 90 | #define IFACE99_PATCH 2 91 | 92 | #define IFACE99_VERSION_COMPATIBLE(x, y, z) \ 93 | (IFACE99_MAJOR == (x) && \ 94 | ((IFACE99_MINOR == (y) && IFACE99_PATCH >= (z)) || (IFACE99_MINOR > (y)))) 95 | 96 | #define IFACE99_VERSION_EQ(x, y, z) \ 97 | (IFACE99_MAJOR == (x) && IFACE99_MINOR == (y) && IFACE99_PATCH == (z)) 98 | // } (Public stuff) 99 | 100 | // Interface generation { 101 | 102 | #define IFACE99_interface_IMPL(iface) \ 103 | ML99_TERMS( \ 104 | v(typedef struct iface##VTable iface##VTable;), \ 105 | v(typedef struct iface iface;), \ 106 | ML99_semicoloned(ML99_struct(v(iface##VTable), IFACE99_PRIV_genVTableFields(iface))), \ 107 | v(struct iface { \ 108 | void *self; \ 109 | const iface##VTable *vptr; \ 110 | })) 111 | 112 | /* 113 | * // Only if is a marker interface without superinterfaces: 114 | * char dummy; 115 | * 116 | * 0 (*0)(0); 117 | * ... 118 | * N (*N)(N); 119 | * 120 | * const 0VTable *; 121 | * ... 122 | * const NVTable *; 123 | */ 124 | #define IFACE99_PRIV_genVTableFields(iface) \ 125 | ML99_uncomma(ML99_QUOTE( \ 126 | IFACE99_PRIV_genDummy(iface), \ 127 | ML99_IF( \ 128 | IFACE99_PRIV_IS_MARKER_IFACE(iface), \ 129 | ML99_empty(), \ 130 | IFACE99_PRIV_genFuncPtrForEach(iface)), \ 131 | ML99_IF( \ 132 | IFACE99_PRIV_IS_SUB_IFACE(iface), \ 133 | IFACE99_PRIV_genRequirementForEach(iface), \ 134 | ML99_empty()))) 135 | 136 | #define IFACE99_PRIV_genDummy(iface) \ 137 | ML99_IF(IFACE99_PRIV_IS_EMPTY_VTABLE(iface), v(char dummy;), ML99_empty()) 138 | 139 | /* 140 | * 0 (*0)(0); 141 | * ... 142 | * N (*N)(N); 143 | */ 144 | #define IFACE99_PRIV_genFuncPtrForEach(iface) \ 145 | ML99_seqForEach(v(IFACE99_PRIV_genFuncPtr), v(iface##_IFACE)) 146 | 147 | #define IFACE99_PRIV_genFuncPtr_IMPL(_tag, ret_ty, name, ...) v(ret_ty (*name)(__VA_ARGS__);) 148 | 149 | /* 150 | * const 0VTable *; 151 | * ... 152 | * const NVTable *; 153 | */ 154 | #define IFACE99_PRIV_genRequirementForEach(iface) \ 155 | ML99_tupleForEach(v(IFACE99_PRIV_genRequirement), v(iface##_EXTENDS)) 156 | 157 | #define IFACE99_PRIV_genRequirement_IMPL(requirement) v(const requirement##VTable *requirement;) 158 | // } (Interface generation) 159 | 160 | // Interface implementation generation { 161 | 162 | #define IFACE99_impl_IMPL(iface, implementer) \ 163 | IFACE99_PRIV_implCommon(IFACE99_PRIV_STORAGE_CLASS_STATIC, iface, implementer) 164 | #define IFACE99_implExtern_IMPL(iface, implementer) \ 165 | IFACE99_PRIV_implCommon(IFACE99_PRIV_STORAGE_CLASS_EXTERN, iface, implementer) 166 | 167 | #define IFACE99_PRIV_STORAGE_CLASS_STATIC static 168 | #define IFACE99_PRIV_STORAGE_CLASS_EXTERN /* If no storage-class specifier is provided, the \ 169 | default for objects is `extern` (file scope) or \ 170 | `auto` (block scope). */ 171 | 172 | #define IFACE99_PRIV_implCommon(storage_class, iface, implementer) \ 173 | ML99_assignInitializerList( \ 174 | v(storage_class const iface##VTable VTABLE99(implementer, iface)), \ 175 | IFACE99_PRIV_genImplInitList(iface, implementer)) 176 | 177 | /* 178 | * // Only if is a marker interface without superinterfaces: 179 | * .dummy = '\0', 180 | * 181 | * 0 = either _0 or _0, 182 | * ... 183 | * N = either _N or _N, 184 | * 185 | * 0 = &VTABLE(, 0), 186 | * ... 187 | * N = &VTABLE(, N), 188 | */ 189 | #define IFACE99_PRIV_genImplInitList(iface, implementer) \ 190 | ML99_uncomma(ML99_QUOTE( \ 191 | IFACE99_PRIV_genDummyInit(iface), \ 192 | ML99_IF( \ 193 | IFACE99_PRIV_IS_MARKER_IFACE(iface), \ 194 | ML99_empty(), \ 195 | IFACE99_PRIV_genImplFuncNameForEach(iface, implementer)), \ 196 | ML99_IF( \ 197 | IFACE99_PRIV_IS_SUB_IFACE(iface), \ 198 | IFACE99_PRIV_genRequirementsImplForEach(iface, implementer), \ 199 | ML99_empty()))) 200 | 201 | #define IFACE99_PRIV_genDummyInit(iface) \ 202 | ML99_IF(IFACE99_PRIV_IS_EMPTY_VTABLE(iface), v(.dummy = '\0'), ML99_empty()) 203 | 204 | /* 205 | * 0 = either _0 or _0, 206 | * ... 207 | * N = either _N or _N, 208 | */ 209 | #define IFACE99_PRIV_genImplFuncNameForEach(iface, implementer) \ 210 | ML99_seqForEach( \ 211 | ML99_appl(v(IFACE99_PRIV_genImplFuncName), v(iface, implementer)), \ 212 | v(iface##_IFACE)) 213 | 214 | #define IFACE99_PRIV_genImplFuncName_IMPL(iface, implementer, tag, _ret_ty, name, ...) \ 215 | ML99_match(ML99_choice(v(tag), v(iface, implementer, name)), v(IFACE99_PRIV_genImpl_)) 216 | 217 | #define IFACE99_PRIV_genImpl_vfunc_IMPL(_iface, implementer, name) v(.name = implementer##_##name, ) 218 | #define IFACE99_PRIV_genImpl_vfuncDefault_IMPL(iface, implementer, name) \ 219 | ML99_IF( \ 220 | IFACE99_PRIV_IS_CUSTOM(implementer, name), \ 221 | IFACE99_PRIV_genImpl_vfunc_IMPL(~, implementer, name), \ 222 | v(.name = iface##_##name, )) 223 | 224 | /* 225 | * 0 = &VTABLE(, 0), 226 | * ... 227 | * N = &VTABLE(, N), 228 | */ 229 | #define IFACE99_PRIV_genRequirementsImplForEach(iface, implementer) \ 230 | ML99_tupleForEach( \ 231 | ML99_appl(v(IFACE99_PRIV_genRequirementImpl), v(implementer)), \ 232 | v(iface##_EXTENDS)) 233 | 234 | #define IFACE99_PRIV_genRequirementImpl_IMPL(implementer, requirement) \ 235 | v(.requirement = &VTABLE99(implementer, requirement), ) 236 | // } (Interface implementation generation) 237 | 238 | // Implementation declaration { 239 | 240 | #define declImpl99(iface, implementer) static IFACE99_PRIV_DECL_IMPL_COMMON(iface, implementer) 241 | #define declImplExtern99(iface, implementer) \ 242 | extern IFACE99_PRIV_DECL_IMPL_COMMON(iface, implementer) 243 | 244 | #define IFACE99_PRIV_DECL_IMPL_COMMON(iface, implementer) \ 245 | const ML99_CAT(iface, VTable) VTABLE99(implementer, iface) 246 | // } (Implementation declaration) 247 | 248 | // Virtual calls { 249 | 250 | #define VCALL99(obj, ...) \ 251 | ((obj).vptr->IFACE99_PRIV_VCALL_OVERLOAD(__VA_ARGS__)((obj).self, __VA_ARGS__)) 252 | #define VCALL_OBJ99(obj, ...) \ 253 | ((obj).vptr->IFACE99_PRIV_VCALL_OVERLOAD(__VA_ARGS__)((obj), __VA_ARGS__)) 254 | #define VCALL_SUPER99(obj, superiface, ...) \ 255 | ((obj).vptr->superiface->IFACE99_PRIV_VCALL_OVERLOAD(__VA_ARGS__)((obj).self, __VA_ARGS__)) 256 | #define VCALL_SUPER_OBJ99(obj, superiface, ...) \ 257 | ((obj).vptr->superiface->IFACE99_PRIV_VCALL_OVERLOAD(__VA_ARGS__)( \ 258 | ((superiface){ \ 259 | .self = (obj).self, \ 260 | .vptr = (obj).vptr->superiface, \ 261 | }), \ 262 | __VA_ARGS__)) 263 | 264 | #define IFACE99_PRIV_VCALL_OVERLOAD(...) \ 265 | ML99_CAT(IFACE99_PRIV_VCALL_, ML99_VARIADICS_IS_SINGLE(__VA_ARGS__)) 266 | #define IFACE99_PRIV_VCALL_1(obj, func_name) func_name(obj) 267 | #define IFACE99_PRIV_VCALL_0(obj, func_name, ...) func_name(obj, __VA_ARGS__) 268 | // } (Virtual calls) 269 | 270 | // Various predicates { 271 | 272 | #define IFACE99_PRIV_IS_EMPTY_VTABLE(iface) \ 273 | ML99_AND(IFACE99_PRIV_IS_MARKER_IFACE(iface), ML99_NOT(IFACE99_PRIV_IS_SUB_IFACE(iface))) 274 | #define IFACE99_PRIV_IS_MARKER_IFACE(iface) ML99_SEQ_IS_EMPTY(iface##_IFACE) 275 | #define IFACE99_PRIV_IS_SUB_IFACE(iface) ML99_IS_TUPLE(iface##_EXTENDS) 276 | 277 | #define IFACE99_PRIV_IS_CUSTOM(implementer, func_name) \ 278 | ML99_IS_TUPLE(implementer##_##func_name##_CUSTOM) 279 | // } (Various predicates) 280 | 281 | // Arity specifiers { 282 | 283 | #define IFACE99_PRIV_genRequirement_ARITY 1 284 | #define IFACE99_PRIV_genRequirementImpl_ARITY 2 285 | 286 | #define IFACE99_PRIV_genFuncPtr_ARITY 1 287 | #define IFACE99_PRIV_genImplFuncName_ARITY 2 288 | 289 | // Public: 290 | 291 | #define IFACE99_interface_ARITY 1 292 | #define IFACE99_impl_ARITY 2 293 | #define IFACE99_implExtern_ARITY 2 294 | // } (Arity specifiers) 295 | 296 | #endif // INTERFACE99_H 297 | -------------------------------------------------------------------------------- /scripts/check-fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./run-clang-format/run-clang-format.py \ 4 | --exclude examples/build \ 5 | --exclude tests/build \ 6 | -r interface99.h tests examples 7 | -------------------------------------------------------------------------------- /scripts/fmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | find tests examples \ 4 | \( -path examples/build -o -path tests/build \) -prune -false -o \ 5 | \( -iname "*.h" \) -or \( -iname "*.c" \) | xargs clang-format -i interface99.h 6 | -------------------------------------------------------------------------------- /scripts/test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ./scripts/test.sh 6 | ./scripts/test-examples.sh 7 | -------------------------------------------------------------------------------- /scripts/test-examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p examples/build 6 | cd examples/build 7 | cmake .. 8 | cmake --build . 9 | 10 | run_example() { 11 | echo "executing ./$1 ..." 12 | ./$1 13 | } 14 | 15 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 16 | run_example "shape" 17 | run_example "tracing_vehicle" 18 | run_example "airplane" 19 | run_example "read_write" 20 | run_example "read_write_both" 21 | run_example "marker" 22 | run_example "default_impl" 23 | 24 | run_example "opaque_type/frog" 25 | fi 26 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | mkdir -p tests/build 6 | cd tests/build 7 | cmake .. 8 | cmake --build . 9 | 10 | run_test() { 11 | echo "executing ./$1 ..." 12 | ./$1 13 | } 14 | 15 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 16 | run_test "basic_tests" 17 | run_test "decl_impl" 18 | run_test "default_impl" 19 | run_test "metalang99_compliant" 20 | run_test "superinterfaces" 21 | run_test "vcalls" 22 | run_test "version" 23 | run_test "extern_impl/test" 24 | fi 25 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(tests LANGUAGES C) 3 | 4 | if(CMAKE_C_COMPILER_ID STREQUAL "GNU") 5 | add_compile_options(-Wall -Wextra -pedantic -ftrack-macro-expansion=0 6 | -fsanitize=address) 7 | add_link_options(-fsanitize=address) 8 | elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang") 9 | add_compile_options(-fmacro-backtrace-limit=1 -fsanitize=address) 10 | add_link_options(-fsanitize=address) 11 | elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 12 | # Enable a standard-conforming C99/C11 preprocessor. 13 | add_compile_options("/std:c11") 14 | elseif(CMAKE_C_COMPILER_ID STREQUAL "TinyCC") 15 | add_compile_definitions(ML99_ALLOW_POOR_DIAGNOSTICS) 16 | endif() 17 | 18 | add_subdirectory(extern_impl) 19 | 20 | add_executable(basic_tests basic_tests.c common.h common.c util.h) 21 | add_executable(vcalls vcalls.c) 22 | add_executable(decl_impl decl_impl.c common.h common.c) 23 | add_executable(metalang99_compliant metalang99_compliant.c) 24 | add_executable(superinterfaces superinterfaces.c common.h common.c util.h) 25 | add_executable(default_impl default_impl.c) 26 | add_executable(version version.c) 27 | 28 | add_subdirectory(.. build) 29 | 30 | get_property( 31 | TESTS 32 | DIRECTORY . 33 | PROPERTY BUILDSYSTEM_TARGETS) 34 | 35 | get_property( 36 | EXTERN_IMPL_TESTS 37 | DIRECTORY extern_impl 38 | PROPERTY BUILDSYSTEM_TARGETS) 39 | 40 | foreach(TARGET ${TESTS};${EXTERN_IMPL_TESTS}) 41 | target_link_libraries(${TARGET} interface99) 42 | set_target_properties(${TARGET} PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED 43 | ON) 44 | endforeach() 45 | -------------------------------------------------------------------------------- /tests/basic_tests.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "util.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // Ensure that forward declarations are generated. 11 | #define TestForwardDecl_IFACE vfunc(void, abc, Foo self, FooVTable vtable) 12 | 13 | interface(TestForwardDecl); 14 | 15 | // Implementations { 16 | 17 | typedef struct { 18 | char dummy; 19 | } MarkerImpl; 20 | 21 | impl(Marker, MarkerImpl); 22 | 23 | typedef struct { 24 | char dummy; 25 | } FooImpl; 26 | 27 | #define FooImpl_foo foo1_impl 28 | impl(Foo, FooImpl); 29 | 30 | typedef struct { 31 | char dummy; 32 | } BarImpl1; 33 | 34 | #define BarImpl1_foo foo1_impl 35 | #define BarImpl1_bar bar1_impl 36 | impl(Bar, BarImpl1); 37 | 38 | // Ensure that an interface can be implemented by many types. 39 | typedef struct { 40 | char dummy; 41 | } BarImpl2; 42 | 43 | #define BarImpl2_foo foo2_impl 44 | #define BarImpl2_bar bar2_impl 45 | impl(Bar, BarImpl2); 46 | 47 | // Ensure that a type can implement multiple interfaces. 48 | typedef struct { 49 | char dummy; 50 | } FooBarImpl; 51 | 52 | #define FooBarImpl_foo foo1_impl 53 | impl(Foo, FooBarImpl); 54 | #undef FooBarImpl_foo 55 | 56 | #define FooBarImpl_foo foo1_impl 57 | #define FooBarImpl_bar bar1_impl 58 | impl(Bar, FooBarImpl); 59 | #undef FooBarImpl_foo 60 | #undef FooBarImpl_bar 61 | 62 | typedef struct { 63 | int x; 64 | long long d; 65 | const char *str; 66 | } TestCompoundLit; 67 | 68 | #define TestCompoundLit_foo foo1_impl 69 | impl(Foo, TestCompoundLit); 70 | // } (Implementations) 71 | 72 | int main(void) { 73 | // Ensure `interface`-generated data. 74 | { 75 | ENSURE_VTABLE_FIELD_TYPE(MarkerVTable, dummy, char); 76 | ENSURE_DYN_OBJ_TYPE(Marker); 77 | 78 | ENSURE_VTABLE_FIELD_TYPE(FooVTable, foo, FooOpType); 79 | ENSURE_DYN_OBJ_TYPE(Foo); 80 | 81 | ENSURE_VTABLE_FIELD_TYPE(BarVTable, foo, FooOpType); 82 | ENSURE_VTABLE_FIELD_TYPE(BarVTable, bar, BarOpType); 83 | ENSURE_DYN_OBJ_TYPE(Bar); 84 | } 85 | 86 | // Ensure `impl`-generated data. 87 | { 88 | assert(VTABLE(MarkerImpl, Marker).dummy == '\0'); 89 | 90 | assert(VTABLE(FooImpl, Foo).foo == foo1_impl); 91 | 92 | assert(VTABLE(BarImpl1, Bar).foo == foo1_impl); 93 | assert(VTABLE(BarImpl1, Bar).bar == bar1_impl); 94 | 95 | assert(VTABLE(BarImpl2, Bar).foo == foo2_impl); 96 | assert(VTABLE(BarImpl2, Bar).bar == bar2_impl); 97 | 98 | assert(VTABLE(FooBarImpl, Foo).foo == foo1_impl); 99 | assert(VTABLE(FooBarImpl, Bar).foo == foo1_impl); 100 | assert(VTABLE(FooBarImpl, Bar).bar == bar1_impl); 101 | } 102 | 103 | ENSURE_DYN_OBJ(MarkerImpl, Marker); 104 | ENSURE_DYN_OBJ(FooImpl, Foo); 105 | ENSURE_DYN_OBJ(BarImpl1, Bar); 106 | ENSURE_DYN_OBJ(BarImpl2, Bar); 107 | ENSURE_DYN_OBJ(FooBarImpl, Foo); 108 | ENSURE_DYN_OBJ(FooBarImpl, Bar); 109 | 110 | // Test compound literals with `DYN_LIT`. 111 | { 112 | Foo compound = DYN_LIT(TestCompoundLit, Foo, {.x = 123, .d = 15, .str = "abc"}); 113 | 114 | assert(compound.vptr == &VTABLE(TestCompoundLit, Foo)); 115 | assert(((TestCompoundLit *)compound.self)->x == 123); 116 | assert(((TestCompoundLit *)compound.self)->d == 15); 117 | assert(strcmp(((TestCompoundLit *)compound.self)->str, "abc") == 0); 118 | } 119 | 120 | return 0; 121 | } 122 | -------------------------------------------------------------------------------- /tests/common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | const char *foo1_impl(int x, double *restrict y) { 4 | (void)x; 5 | (void)y; 6 | return (const char *)NULL; 7 | } 8 | 9 | float bar1_impl(long long x) { 10 | (void)x; 11 | return 12.141f; 12 | } 13 | 14 | const char *foo2_impl(int x, double *restrict y) { 15 | return foo1_impl(x, y); 16 | } 17 | 18 | float bar2_impl(long long x) { 19 | return bar1_impl(x); 20 | } 21 | -------------------------------------------------------------------------------- /tests/common.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERFACE99_TESTS_COMMON_H 2 | #define INTERFACE99_TESTS_COMMON_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | // Interfaces { 9 | 10 | #define Marker_IFACE 11 | 12 | #define Foo_IFACE vfunc(const char *, foo, int x, double *restrict y) 13 | 14 | #define Bar_IFACE \ 15 | vfunc(const char *, foo, int x, double *restrict y) \ 16 | vfunc(float, bar, long long x) 17 | 18 | interface(Marker); 19 | interface(Foo); 20 | interface(Bar); 21 | // } (Interfaces) 22 | 23 | const char *foo1_impl(int x, double *restrict y); 24 | float bar1_impl(long long x); 25 | 26 | const char *foo2_impl(int x, double *restrict y); 27 | float bar2_impl(long long x); 28 | 29 | typedef const char *(*FooOpType)(int x, double *restrict y); 30 | typedef float (*BarOpType)(long long x); 31 | 32 | #endif // INTERFACE99_TESTS_COMMON_H 33 | -------------------------------------------------------------------------------- /tests/decl_impl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | 5 | #include 6 | 7 | typedef struct { 8 | char dummy; 9 | } BarImpl; 10 | 11 | declImpl(Bar, BarImpl); 12 | 13 | static void test_decl_impl(void) { 14 | assert(VTABLE(BarImpl, Bar).foo == foo1_impl); 15 | assert(VTABLE(BarImpl, Bar).bar == bar1_impl); 16 | } 17 | 18 | #define BarImpl_foo foo1_impl 19 | #define BarImpl_bar bar1_impl 20 | impl(Bar, BarImpl); 21 | 22 | // Multiple declarations should work fine. 23 | declImpl(Bar, BarImpl); 24 | declImpl(Bar, BarImpl); 25 | 26 | int main(void) { 27 | test_decl_impl(); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /tests/default_impl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define TestDefault_IFACE vfuncDefault(void, default_op, VSelf, int x) 6 | 7 | interface(TestDefault); 8 | 9 | static void TestDefault_default_op(VSelf, int x) { 10 | VSELF(void); 11 | (void)self; 12 | (void)x; 13 | } 14 | 15 | static void custom_impl(VSelf, int x) { 16 | VSELF(void); 17 | (void)self; 18 | (void)x; 19 | } 20 | 21 | typedef struct { 22 | char dummy; 23 | } A; 24 | 25 | impl(TestDefault, A); 26 | 27 | typedef struct { 28 | char dummy; 29 | } B; 30 | 31 | #define B_default_op_CUSTOM () 32 | #define B_default_op custom_impl 33 | impl(TestDefault, B); 34 | 35 | #define TestNoOpCustom_IFACE vfunc(void, custom_op, VSelf, int x) 36 | 37 | interface(TestNoOpCustom); 38 | 39 | typedef struct { 40 | char dummy; 41 | } C; 42 | 43 | // This `*_CUSTOM` attribute must be no-op. 44 | #define C_custom_op_CUSTOM () 45 | #define C_custom_op custom_impl 46 | impl(TestNoOpCustom, C); 47 | 48 | int main(void) { 49 | assert(VTABLE(A, TestDefault).default_op == TestDefault_default_op); 50 | assert(VTABLE(B, TestDefault).default_op == custom_impl); 51 | assert(VTABLE(C, TestNoOpCustom).custom_op == custom_impl); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /tests/extern_impl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test test.c impl.c types.h ../common.h ../common.c ../util.h) 2 | -------------------------------------------------------------------------------- /tests/extern_impl/impl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../common.h" 4 | #include "types.h" 5 | 6 | #define FooImpl_foo foo1_impl 7 | implExtern(Foo, FooImpl); 8 | 9 | #define BarImpl_foo foo1_impl 10 | #define BarImpl_bar bar1_impl 11 | implExtern(Bar, BarImpl); 12 | -------------------------------------------------------------------------------- /tests/extern_impl/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../common.h" 4 | #include "../util.h" 5 | #include "types.h" 6 | 7 | #include 8 | 9 | declImplExtern(Foo, FooImpl); 10 | 11 | // Multiple declarations shoud work fine. 12 | declImplExtern(Bar, BarImpl); 13 | declImplExtern(Bar, BarImpl); 14 | 15 | int main(void) { 16 | // Ensure `impl`-generated data. 17 | { 18 | assert(VTABLE(FooImpl, Foo).foo == foo1_impl); 19 | assert(VTABLE(BarImpl, Bar).foo == foo1_impl); 20 | } 21 | 22 | ENSURE_DYN_OBJ(FooImpl, Foo); 23 | ENSURE_DYN_OBJ(BarImpl, Bar); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/extern_impl/types.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERFACE99_TESTS_EXTERN_IMPL_TYPES_H 2 | #define INTERFACE99_TESTS_EXTERN_IMPL_TYPES_H 3 | 4 | typedef struct { 5 | char dummy; 6 | } FooImpl; 7 | 8 | typedef struct { 9 | char dummy; 10 | } BarImpl; 11 | 12 | #endif // INTERFACE99_TESTS_EXTERN_IMPL_TYPES_H 13 | -------------------------------------------------------------------------------- /tests/metalang99_compliant.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "util.h" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | ML99_ASSERT_UNEVAL(IFACE99_interface_ARITY == 1); 11 | ML99_ASSERT_UNEVAL(IFACE99_impl_ARITY == 2); 12 | ML99_ASSERT_UNEVAL(IFACE99_implExtern_ARITY == 2); 13 | 14 | #define Foo_IFACE vfunc(const char *, foo, int x, double *restrict y) 15 | 16 | ML99_EVAL(IFACE99_interface(v(Foo))); 17 | 18 | // Implementations { 19 | 20 | static const char *foo_impl(int x, double *restrict y) { 21 | (void)x; 22 | (void)y; 23 | return (const char *)NULL; 24 | } 25 | 26 | typedef struct { 27 | char dummy; 28 | } A; 29 | 30 | #define A_foo foo_impl 31 | ML99_EVAL(IFACE99_impl(v(Foo), v(A))); 32 | 33 | typedef struct { 34 | char dummy; 35 | } B; 36 | 37 | #define B_foo foo_impl 38 | ML99_EVAL(IFACE99_implExtern(v(Foo), v(B))); 39 | // } (Implementations) 40 | 41 | int main(void) { 42 | // Ensure `impl`-generated data. 43 | { 44 | assert(VTABLE(A, Foo).foo == foo_impl); 45 | assert(VTABLE(B, Foo).foo == foo_impl); 46 | } 47 | 48 | ENSURE_DYN_OBJ(A, Foo); 49 | ENSURE_DYN_OBJ(B, Foo); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /tests/superinterfaces.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "common.h" 4 | #include "util.h" 5 | 6 | #include 7 | 8 | // Interfaces { 9 | 10 | #define FooExtendsMarker_IFACE Foo_IFACE 11 | #define FooExtendsMarker_EXTENDS (Marker) 12 | 13 | #define FooExtendsMany_IFACE Foo_IFACE 14 | #define FooExtendsMany_EXTENDS (Foo, Bar) 15 | 16 | #define MarkerExtendsMarker_IFACE Marker_IFACE 17 | #define MarkerExtendsMarker_EXTENDS (Marker) 18 | 19 | #define MarkerExtendsMany_IFACE Marker_IFACE 20 | #define MarkerExtendsMany_EXTENDS (Foo, Bar) 21 | 22 | interface(FooExtendsMarker); 23 | interface(FooExtendsMany); 24 | 25 | interface(MarkerExtendsMarker); 26 | interface(MarkerExtendsMany); 27 | // } (Interfaces) 28 | 29 | // FooExtendsMarker { 30 | 31 | typedef struct { 32 | char dummy; 33 | } A; 34 | 35 | impl(Marker, A); 36 | 37 | #define A_foo foo1_impl 38 | impl(FooExtendsMarker, A); 39 | // } (FooExtendsMarker) 40 | 41 | // FooExtendsMany { 42 | 43 | typedef struct { 44 | char dummy; 45 | } B; 46 | 47 | #define B_foo foo1_impl 48 | impl(Foo, B); 49 | #undef B_foo 50 | 51 | #define B_foo foo1_impl 52 | #define B_bar bar1_impl 53 | impl(Bar, B); 54 | #undef B_foo 55 | #undef B_bar 56 | 57 | #define B_foo foo1_impl 58 | impl(FooExtendsMany, B); 59 | #undef B_foo 60 | // } (FooExtendsMany) 61 | 62 | // MarkerExtendsMarker { 63 | 64 | typedef struct { 65 | char dummy; 66 | } C; 67 | 68 | impl(Marker, C); 69 | impl(MarkerExtendsMarker, C); 70 | // } (MarkerExtendsMarker) 71 | 72 | // MarkerExtendsMany { 73 | 74 | typedef struct { 75 | char dummy; 76 | } D; 77 | 78 | #define D_foo foo1_impl 79 | impl(Foo, D); 80 | #undef D_foo 81 | 82 | #define D_foo foo1_impl 83 | #define D_bar bar1_impl 84 | impl(Bar, D); 85 | #undef D_foo 86 | #undef D_bar 87 | 88 | impl(MarkerExtendsMany, D); 89 | // } (MarkerExtendsMany) 90 | 91 | int main(void) { 92 | // Ensure `interface`-generated data. 93 | { 94 | ENSURE_VTABLE_FIELD_TYPE(FooExtendsMarkerVTable, foo, FooOpType); 95 | ENSURE_DYN_OBJ_TYPE(FooExtendsMarker); 96 | 97 | ENSURE_VTABLE_FIELD_TYPE(FooExtendsManyVTable, foo, FooOpType); 98 | ENSURE_DYN_OBJ_TYPE(FooExtendsMany); 99 | 100 | ENSURE_DYN_OBJ_TYPE(MarkerExtendsMarker); 101 | 102 | ENSURE_DYN_OBJ_TYPE(MarkerExtendsMany); 103 | } 104 | 105 | // Ensure `impl`-generated data. 106 | { 107 | assert(VTABLE(A, FooExtendsMarker).Marker == &VTABLE(A, Marker)); 108 | 109 | assert(VTABLE(B, FooExtendsMany).Foo == &VTABLE(B, Foo)); 110 | assert(VTABLE(B, FooExtendsMany).Bar == &VTABLE(B, Bar)); 111 | 112 | assert(VTABLE(C, MarkerExtendsMarker).Marker == &VTABLE(C, Marker)); 113 | 114 | assert(VTABLE(D, MarkerExtendsMany).Foo == &VTABLE(D, Foo)); 115 | assert(VTABLE(D, MarkerExtendsMany).Bar == &VTABLE(D, Bar)); 116 | } 117 | 118 | ENSURE_DYN_OBJ(A, FooExtendsMarker); 119 | ENSURE_DYN_OBJ(B, FooExtendsMany); 120 | ENSURE_DYN_OBJ(C, MarkerExtendsMarker); 121 | ENSURE_DYN_OBJ(D, MarkerExtendsMany); 122 | 123 | // Ensure that dynamic objects of superinterfaces still work. 124 | { 125 | ENSURE_DYN_OBJ(A, Marker); 126 | 127 | ENSURE_DYN_OBJ(B, Foo); 128 | ENSURE_DYN_OBJ(B, Bar); 129 | 130 | ENSURE_DYN_OBJ(C, Marker); 131 | 132 | ENSURE_DYN_OBJ(D, Foo); 133 | ENSURE_DYN_OBJ(D, Bar); 134 | } 135 | 136 | return 0; 137 | } 138 | -------------------------------------------------------------------------------- /tests/util.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERFACE99_TESTS_UTIL_H 2 | #define INTERFACE99_TESTS_UTIL_H 3 | 4 | #define ENSURE_EXPR_TYPE(expr, type) \ 5 | do { \ 6 | type x = (expr); \ 7 | (void)x; \ 8 | } while (0) 9 | 10 | #define ENSURE_VTABLE_FIELD_TYPE(vtable_type, field, type) \ 11 | do { \ 12 | ENSURE_FIELD_TYPE(vtable_type, field, type); \ 13 | ENSURE_FIELD_TYPE(struct vtable_type, field, type); \ 14 | } while (0) 15 | #define ENSURE_FIELD_TYPE(outer_type, field, type) ENSURE_EXPR_TYPE(((outer_type){0}).field, type) 16 | 17 | #define ENSURE_DYN_OBJ_TYPE(iface) \ 18 | do { \ 19 | ENSURE_FIELD_TYPE(iface, self, void *); \ 20 | ENSURE_FIELD_TYPE(iface, vptr, const iface##VTable *); \ 21 | \ 22 | ENSURE_FIELD_TYPE(struct iface, self, void *); \ 23 | ENSURE_FIELD_TYPE(struct iface, vptr, const iface##VTable *); \ 24 | } while (0) 25 | 26 | #define ENSURE_DYN_OBJ(implementer, iface) \ 27 | do { \ 28 | implementer x = {0}; \ 29 | iface x_dyn = DYN(implementer, iface, &x); \ 30 | assert(x_dyn.self == &x); \ 31 | assert(x_dyn.vptr == &VTABLE(implementer, iface)); \ 32 | } while (0) 33 | 34 | #endif // INTERFACE99_TESTS_UTIL_H 35 | -------------------------------------------------------------------------------- /tests/vcalls.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define METHODS(prefix, T) \ 6 | vfunc(int, prefix##_vcall, VSelf) \ 7 | vfunc(int, prefix##_vcall_args, const VSelf, int x, const char *y) \ 8 | \ 9 | vfunc(int, prefix##_vcall_obj, T self) \ 10 | vfunc(int, prefix##_vcall_obj_args, T self, int x, const char *y) 11 | 12 | #define TestSuper_IFACE METHODS(test_super, TestSuper) 13 | #define Test_IFACE METHODS(test, Test) 14 | #define Test_EXTENDS (TestSuper) 15 | 16 | interface(TestSuper); 17 | interface(Test); 18 | 19 | static const int n = 7; 20 | static const char *str = "abc"; 21 | 22 | typedef enum { 23 | TEST_VCALL, 24 | TEST_VCALL_ARGS, 25 | TEST_VCALL_OBJ, 26 | TEST_VCALL_OBJ_ARGS, 27 | } RetVal; 28 | 29 | typedef struct { 30 | char dummy; 31 | } Impl; 32 | 33 | static Impl obj = {0}; 34 | 35 | #define IMPL_METHODS(prefix, T) \ 36 | static int Impl_##prefix##_vcall(VSelf) { \ 37 | VSELF(Impl); \ 38 | assert(&obj == self); \ 39 | \ 40 | return TEST_VCALL; \ 41 | } \ 42 | \ 43 | static int Impl_##prefix##_vcall_args(const VSelf, int x, const char *y) { \ 44 | VSELF(const Impl); \ 45 | assert(&obj == self); \ 46 | assert(n == x); \ 47 | assert(str == y); \ 48 | \ 49 | return TEST_VCALL_ARGS; \ 50 | } \ 51 | \ 52 | static int Impl_##prefix##_vcall_obj(T test) { \ 53 | assert(&obj == test.self); \ 54 | assert(&VTABLE(Impl, T) == test.vptr); \ 55 | \ 56 | return TEST_VCALL_OBJ; \ 57 | } \ 58 | \ 59 | static int Impl_##prefix##_vcall_obj_args(T test, int x, const char *y) { \ 60 | assert(&obj == test.self); \ 61 | assert(&VTABLE(Impl, T) == test.vptr); \ 62 | assert(n == x); \ 63 | assert(str == y); \ 64 | \ 65 | return TEST_VCALL_OBJ_ARGS; \ 66 | } 67 | 68 | declImpl(TestSuper, Impl); 69 | declImpl(Test, Impl); 70 | 71 | // clang-format off 72 | IMPL_METHODS(test_super, TestSuper) 73 | IMPL_METHODS(test, Test) 74 | // clang-format on 75 | 76 | impl(TestSuper, Impl); 77 | impl(Test, Impl); 78 | 79 | int main(void) { 80 | #define RVALUE DYN(Impl, Test, &obj) 81 | 82 | // Test that VCALL_* can accept lvalues. 83 | { 84 | Test lvalue = RVALUE; 85 | 86 | assert(TEST_VCALL == VCALL(lvalue, test_vcall)); 87 | assert(TEST_VCALL_ARGS == VCALL(lvalue, test_vcall_args, n, str)); 88 | assert(TEST_VCALL_OBJ == VCALL_OBJ(lvalue, test_vcall_obj)); 89 | assert(TEST_VCALL_OBJ_ARGS == VCALL_OBJ(lvalue, test_vcall_obj_args, n, str)); 90 | 91 | assert(TEST_VCALL == VCALL_SUPER(lvalue, TestSuper, test_super_vcall)); 92 | assert(TEST_VCALL_ARGS == VCALL_SUPER(lvalue, TestSuper, test_super_vcall_args, n, str)); 93 | assert(TEST_VCALL_OBJ == VCALL_SUPER_OBJ(lvalue, TestSuper, test_super_vcall_obj)); 94 | assert( 95 | TEST_VCALL_OBJ_ARGS == 96 | VCALL_SUPER_OBJ(lvalue, TestSuper, test_super_vcall_obj_args, n, str)); 97 | } 98 | 99 | // Test rvalues. 100 | { 101 | (void)VCALL(RVALUE, test_vcall); 102 | (void)VCALL_OBJ(RVALUE, test_vcall_obj); 103 | (void)VCALL_SUPER(RVALUE, TestSuper, test_super_vcall); 104 | (void)VCALL_SUPER_OBJ(RVALUE, TestSuper, test_super_vcall_obj); 105 | } 106 | 107 | #undef RVALUE 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /tests/version.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | 6 | #undef IFACE99_MAJOR 7 | #undef IFACE99_MINOR 8 | #undef IFACE99_PATCH 9 | 10 | #define IFACE99_MAJOR 1 11 | #define IFACE99_MINOR 2 12 | #define IFACE99_PATCH 3 13 | 14 | // IFACE99_VERSION_COMPATIBLE 15 | { 16 | 17 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 0, 0)); 18 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 1, 0)); 19 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 1, 1)); 20 | 21 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 2, 0)); 22 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 2, 1)); 23 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 2, 2)); 24 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_COMPATIBLE(1, 2, 3)); 25 | 26 | // Major-incompatible. 27 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(2, 0, 0)); 28 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(7, 1, 2)); 29 | 30 | // Minor-incompatible. 31 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(1, 3, 0)); 32 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(1, 4, 9)); 33 | 34 | // Patch-incompatible. 35 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(1, 2, 4)); 36 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_COMPATIBLE(1, 2, 5)); 37 | } 38 | 39 | // IFACE99_VERSION_EQ 40 | { 41 | ML99_ASSERT_UNEVAL(IFACE99_VERSION_EQ(1, 2, 3)); 42 | 43 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_EQ(1, 2, 7)); 44 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_EQ(1, 7, 3)); 45 | ML99_ASSERT_UNEVAL(!IFACE99_VERSION_EQ(7, 2, 3)); 46 | } 47 | 48 | #undef IFACE99_MAJOR 49 | #undef IFACE99_MINOR 50 | #undef IFACE99_PATCH 51 | 52 | return 0; 53 | } 54 | --------------------------------------------------------------------------------