{"id":15047428,"url":"https://github.com/hirrolot/metalang99","last_synced_at":"2025-05-14T19:09:28.291Z","repository":{"id":38198983,"uuid":"296621386","full_name":"hirrolot/metalang99","owner":"hirrolot","description":"Full-blown preprocessor metaprogramming","archived":false,"fork":false,"pushed_at":"2025-03-17T16:55:19.000Z","size":12504,"stargazers_count":918,"open_issues_count":1,"forks_count":26,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-04-13T22:27:34.468Z","etag":null,"topics":["c","c99","cpp","cpp11","embedded-language","fp","functional-language","functional-programming","header-only","interpreter","language","macros","metalang99","metaprogramming","programming-language"],"latest_commit_sha":null,"homepage":"https://metalang99.readthedocs.io/en/latest/","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hirrolot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"buy_me_a_coffee":"hirrolot"}},"created_at":"2020-09-18T12:56:52.000Z","updated_at":"2025-04-12T08:03:03.000Z","dependencies_parsed_at":"2022-07-16T23:46:00.675Z","dependency_job_id":"61142a5d-75a4-4e9a-93d3-d9fb41f678ea","html_url":"https://github.com/hirrolot/metalang99","commit_stats":{"total_commits":1641,"total_committers":4,"mean_commits":410.25,"dds":0.001828153564899404,"last_synced_commit":"5e6b1b0800b1290885a89cf2f43cdc48e4b810d4"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fmetalang99","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fmetalang99/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fmetalang99/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hirrolot%2Fmetalang99/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hirrolot","download_url":"https://codeload.github.com/hirrolot/metalang99/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254209859,"owners_count":22032897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","c99","cpp","cpp11","embedded-language","fp","functional-language","functional-programming","header-only","interpreter","language","macros","metalang99","metaprogramming","programming-language"],"created_at":"2024-09-24T20:58:09.769Z","updated_at":"2025-05-14T19:09:24.931Z","avatar_url":"https://github.com/hirrolot.png","language":"C","funding_links":["https://buymeacoffee.com/hirrolot"],"categories":["Metaprogramming frameworks"],"sub_categories":[],"readme":"# Metalang99\n\n[![CI](https://github.com/hirrolot/metalang99/workflows/C/C++%20CI/badge.svg)](https://github.com/hirrolot/metalang99/actions)\n[![docs](https://img.shields.io/badge/docs-readthedocs.io-blue)](https://metalang99.readthedocs.io/en/latest/)\n[![book](https://img.shields.io/badge/book-gitbook.io-pink)](https://hirrolot.gitbook.io/metalang99/)\n[![specification](https://img.shields.io/badge/specification-PDF-aa44d6)](https://github.com/hirrolot/metalang99/blob/master/spec/spec.pdf)\n\n\u003e The dark side of the force is a pathway to many abilities, some considered to be unnatural.\u003cbr\u003e\u0026emsp; \u0026emsp; \u003cb\u003e-- Darth Sidious\u003c/b\u003e\n\nBased on [`examples/demo.c`](examples/demo.c):\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003e\u003cb\u003eCompile-time list manipulation\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e\n\n```c\n// 3, 3, 3, 3, 3\nstatic int five_threes[] = {\n    ML99_LIST_EVAL_COMMA_SEP(ML99_listReplicate(v(5), v(3))),\n};\n\n// 5, 4, 3, 2, 1\nstatic int from_5_to_1[] = {\n    ML99_LIST_EVAL_COMMA_SEP(ML99_listReverse(ML99_list(v(1, 2, 3, 4, 5)))),\n};\n\n// 9, 2, 5\nstatic int lesser_than_10[] = {\n    ML99_LIST_EVAL_COMMA_SEP(\n        ML99_listFilter(ML99_appl(v(ML99_greater), v(10)), ML99_list(v(9, 2, 11, 13, 5)))),\n};\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003e\u003cb\u003eMacro recursion\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e\n\n```c\n#define factorial(n)          ML99_natMatch(n, v(factorial_))\n#define factorial_Z_IMPL(...) v(1)\n#define factorial_S_IMPL(n)   ML99_mul(ML99_inc(v(n)), factorial(v(n)))\n\nML99_ASSERT_EQ(factorial(v(4)), v(24));\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003e\u003cb\u003eOverloading on a number of arguments\u003c/b\u003e\u003c/td\u003e\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e\n\n```c\ntypedef struct {\n    double width, height;\n} Rect;\n\n#define Rect_new(...) ML99_OVERLOAD(Rect_new_, __VA_ARGS__)\n#define Rect_new_1(x)                                                                              \\\n    { x, x }\n#define Rect_new_2(x, y)                                                                           \\\n    { x, y }\n\nstatic Rect _7x8 = Rect_new(7, 8), _10x10 = Rect_new(10);\n\n// ... and more!\n\nint main(void) {\n    // Yeah. All is done at compile time.\n}\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n(Hint: `v(something)` evaluates to `something`.)\n\nMetalang99 is a firm foundation for writing reliable and maintainable metaprograms in pure C99. It is implemented as an interpreted FP language atop of preprocessor macros: just `#include \u003cmetalang99.h\u003e` and you are ready to go. Metalang99 features algebraic data types, pattern matching, recursion, currying, and collections; in addition, it provides means for compile-time error reporting and debugging. With our [built-in syntax checker], macro errors should be perfectly comprehensible, enabling you for convenient development.\n\n[built-in syntax checker]: #q-what-about-compile-time-errors\n\nCurrently, Metalang99 is used at [OpenIPC] as an indirect dependency of [Datatype99] and [Interface99]; this includes an [RTSP 1.0 implementation] along with ~50k lines of private code.\n\n[OpenIPC]: https://openipc.org/\n[RTSP 1.0 implementation]: https://github.com/OpenIPC/smolrtsp/\n\n[Datatype99]: https://github.com/hirrolot/Datatype99\n[Interface99]: https://github.com/hirrolot/Interface99\n\n## Motivation\n\nMacros facilitate code re-use, macros are the building material that lets you shape the language to suit the problem being solved, leading to more clean and concise code. However, metaprogramming in C is utterly castrated: we cannot even operate with control flow, integers, unbounded sequences, and compound data structures, thereby throwing a lot of hypothetically useful metaprograms out of scope.\n\nTo solve the problem, I have implemented Metalang99. Having its functionality at our disposal, it becomes possible to develop even fairly non-trivial metaprograms, such as [Datatype99]:\n\n```c\n#include \u003cdatatype99.h\u003e\n\ndatatype(\n    BinaryTree,\n    (Leaf, int),\n    (Node, BinaryTree *, int, BinaryTree *)\n);\n\nint sum(const BinaryTree *tree) {\n    match(*tree) {\n        of(Leaf, x) return *x;\n        of(Node, lhs, x, rhs) return sum(*lhs) + *x + sum(*rhs);\n    }\n\n    return -1;\n}\n```\n\nOr [Interface99]:\n\n```c\n#include \u003cinterface99.h\u003e\n\n#include \u003cstdio.h\u003e\n\n#define Shape_IFACE                      \\\n    vfunc( int, perim, const VSelf)      \\\n    vfunc(void, scale, VSelf, int factor)\n\ninterface(Shape);\n\ntypedef struct {\n    int a, b;\n} Rectangle;\n\nint  Rectangle_perim(const VSelf) { /* ... */ }\nvoid Rectangle_scale(VSelf, int factor) { /* ... */ }\n\nimpl(Shape, Rectangle);\n\ntypedef struct {\n    int a, b, c;\n} Triangle;\n\nint  Triangle_perim(const VSelf) { /* ... */ }\nvoid Triangle_scale(VSelf, int factor) { /* ... */ }\n\nimpl(Shape, Triangle);\n\nvoid test(Shape shape) {\n    printf(\"perim = %d\\n\", VCALL(shape, perim));\n    VCALL(shape, scale, 5);\n    printf(\"perim = %d\\n\", VCALL(shape, perim));\n}\n```\n\nUnlike the vague techniques, such as [tagged unions] or [virtual method tables], the above metaprograms leverage type safety, syntax conciseness, and maintain the exact memory layout of generated code.\n\nLooks interesting? Check out the [motivational post] for more information.\n\n[tagged unions]: https://en.wikipedia.org/wiki/Tagged_union\n[virtual method tables]: https://en.wikipedia.org/wiki/Virtual_method_table\n[motivational post]: https://hirrolot.github.io/posts/macros-on-steroids-or-how-can-pure-c-benefit-from-metaprogramming.html\n\n## Getting started\n\nMetalang99 is just a set of header files and nothing else. To use it as a dependency, you need to:\n\n 1. Add `metalang99/include` to include directories.\n 2. Specify [`-ftrack-macro-expansion=0`] (GCC) or [`-fmacro-backtrace-limit=1`] (Clang) to avoid useless macro expansion errors.\n\n[`-ftrack-macro-expansion=0`]: https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html\n[`-fmacro-backtrace-limit=1`]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fmacro-backtrace-limit\n\nIf you use CMake, the recommended way is [`FetchContent`]:\n\n[`FetchContent`]: https://cmake.org/cmake/help/latest/module/FetchContent.html\n\n```cmake\ninclude(FetchContent)\n\nFetchContent_Declare(\n    metalang99\n    URL https://github.com/hirrolot/metalang99/archive/refs/tags/vx.y.z.tar.gz # vx.y.z\n)\n\nFetchContent_MakeAvailable(metalang99)\n\ntarget_link_libraries(MyProject metalang99)\n\n# Disable full macro expansion backtraces for Metalang99.\nif(CMAKE_C_COMPILER_ID STREQUAL \"Clang\")\n  target_compile_options(MyProject PRIVATE -fmacro-backtrace-limit=1)\nelseif(CMAKE_C_COMPILER_ID STREQUAL \"GNU\")\n  target_compile_options(MyProject PRIVATE -ftrack-macro-expansion=0)\nendif()\n```\n\nOptionally, you can [precompile headers] in your project that rely on Metalang99. This will decrease compilation time because the headers will not be compiled each time they are included.\n\n[precompile headers]: https://en.wikipedia.org/wiki/Precompiled_header\n\n[Tutorial](https://hirrolot.gitbook.io/metalang99/) | [Examples](examples/) | [User documentation](https://metalang99.readthedocs.io/en/latest/)\n\nHappy hacking!\n\n## Highlights\n\n - **Macro recursion.** Recursive calls behave as expected. In particular, to implement recursion, [Boost/Preprocessor] just copy-pastes all recursive functions up to a certain limit and forces to either keep track of recursion depth or rely on their built-in deduction. Being an interpreter, Metalang99 is free from such drawbacks.\n\n - **Almost the same syntax.** Metalang99 does not look too alien in comparison with [Order PP] because the syntax differs insignificantly from usual preprocessor code.\n\n - **Partial application.** Instead of tracking auxiliary arguments here and there (as it is done in Boost/Preprocessor), Metalang99's partial application allows to capture an environment by applying constant values first. Besides that, partial application facilitates better reuse of metafunctions; see `ML99_const`, `ML99_compose`, etc.\n\n - **Debugging and error reporting.** You can conveniently debug your macros with `ML99_abort` and report unrecoverable errors with `ML99_fatal`. The interpreter will immediately halt and do the trick. To the best of our knowledge, no other macro framework provides such a mechanism for debugging and error reporting.\n\n[Boost/Preprocessor]: http://boost.org/libs/preprocessor\n[Order PP]: https://github.com/rofl0r/order-pp\n\n## Philosophy and origins\n\nMy work on [Poica], a research programming language implemented upon [Boost/Preprocessor], has left me unsatisfied with the result. The fundamental limitations of Boost/Preprocessor have made the codebase simply unmaintainable; these include recursive macro calls (blocked by the preprocessor), which have made debugging a complete nightmare, the absence of partial application that has made context passing utterly awkward, and every single mistake that resulted in megabytes of compiler error messages.\n\nOnly then I have understood that instead of enriching the preprocessor with various ad-hoc mechanisms, we should really establish a clear paradigm in which to structure metaprograms. With these thoughts in mind, I started to implement Metalang99...\n\nLong story short, it took half of a year of hard work to release v0.1.0 and almost a year to make it stable. As a real-world application of Metalang99, I created [Datatype99] exactly of the same form I wanted it to be: the implementation is highly declarative, the syntax is nifty, and the semantics is well-defined.\n\nFinally, I want to say that Metalang99 is only about syntax transformations and not about CPU-bound tasks; the preprocessor is just too slow and limited for such kind of abuse.\n\n[Poica]: https://github.com/hirrolot/poica\n\n## Guidelines\n\n - If possible, assert macro parameters for well-formedness using `ML99_assertIsTuple`, `ML99_assertIsNat`, etc. for better diagnostic messages.\n - Prefer the `##` token-pasting operator inside [Metalang99-compliant macros] instead of `ML99_cat` or its friends, because arguments will nevertheless be fully expanded.\n - Use [`ML99_todo` and its friends] to indicate unimplemented functionality.\n\n[Metalang99-compliant macros]: https://metalang99.readthedocs.io/en/latest/#definitions\n[`ML99_todo` and its friends]: https://metalang99.readthedocs.io/en/latest/util.html#c.ML99_todo\n\n## Blog posts\n\n - [_Pretty-Printable Enumerations in Pure C_](https://hirrolot.github.io/posts/pretty-printable-enumerations-in-pure-c.html)\n - [_What’s the Point of the C Preprocessor, Actually?_]\n - [_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)\n - [_Extend Your Language, Don’t Alter It_](https://hirrolot.github.io/posts/extend-your-language-dont-alter-it.html)\n\n[_What’s the Point of the C Preprocessor, Actually?_]: https://hirrolot.github.io/posts/whats-the-point-of-the-c-preprocessor-actually.html\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md).\n\n## Architecture\n\nSee [`ARCHITECTURE.md`](ARCHITECTURE.md).\n\n## Idioms\n\nSee [`idioms.md`](idioms.md).\n\n## Optimization tips\n\nSee [`optimization_tips.md`](optimization_tips.md).\n\n## Release procedure\n\n 1. Update the `PROJECT_NUMBER` field in `Doxyfile`.\n 2. Update the `release` field in `docs/conf.py`.\n 3. Update `ML99_MAJOR`, `ML99_MINOR`, and `ML99_PATCH` in `include/metalang99.h`.\n 4. Update the version number in `spec/spec.tex` \u0026 `spec/spec.pdf`.\n 5. Update `CHANGELOG.md`.\n 6. Release the project in [GitHub Releases].\n\n[GitHub Releases]: https://github.com/hirrolot/metalang99/releases\n\n## FAQ\n\n### Q: What about compile-time errors?\n\nA: Metalang99 is a big step towards understandable compiler diagnostics. It has a built-in syntax checker that tests all incoming terms for validity:\n\n[`playground.c`]\n```c\nML99_EVAL(123)\nML99_EVAL(x, y, z)\nML99_EVAL(v(Billie) v(Jean))\n```\n\n[`/bin/sh`]\n```\n$ gcc playground.c -Imetalang99/include -ftrack-macro-expansion=0\nplayground.c:3:1: error: static assertion failed: \"invalid term `123`\"\n    3 | ML99_EVAL(123)\n      | ^~~~~~~~~\nplayground.c:4:1: error: static assertion failed: \"invalid term `x`\"\n    4 | ML99_EVAL(x, y, z)\n      | ^~~~~~~~~\nplayground.c:5:1: error: static assertion failed: \"invalid term `(0v, Billie) (0v, Jean)`, did you miss a comma?\"\n    5 | ML99_EVAL(v(Billie) v(Jean))\n      | ^~~~~~~~~\n```\n\nMetalang99 can even check for macro preconditions and report an error:\n\n[`playground.c`]\n```c\nML99_EVAL(ML99_listHead(ML99_nil()))\nML99_EVAL(ML99_unwrapLeft(ML99_right(v(123))))\nML99_EVAL(ML99_div(v(18), v(4)))\n```\n\n[`/bin/sh`]\n```\n$ gcc playground.c -Imetalang99/include -ftrack-macro-expansion=0\nplayground.c:3:1: error: static assertion failed: \"ML99_listHead: expected a non-empty list\"\n    3 | ML99_EVAL(ML99_listHead(ML99_nil()))\n      | ^~~~~~~~~\nplayground.c:4:1: error: static assertion failed: \"ML99_unwrapLeft: expected ML99_left but found ML99_right\"\n    4 | ML99_EVAL(ML99_unwrapLeft(ML99_right(v(123))))\n      | ^~~~~~~~~\nplayground.c:5:1: error: static assertion failed: \"ML99_div: 18 is not divisible by 4\"\n    5 | ML99_EVAL(ML99_div(v(18), v(4)))\n      | ^~~~~~~~~\n```\n\nHowever, if you do something awkward, compile-time errors can become quite obscured:\n\n```c\n// ML99_PRIV_REC_NEXT_ML99_PRIV_IF_0 blah(ML99_PRIV_SYNTAX_CHECKER_EMIT_ERROR, ML99_PRIV_TERM_MATCH) ((~, ~, ~) blah, ML99_PRIV_EVAL_)(ML99_PRIV_REC_STOP, (~), 0fspace, (, ), ((0end, ~), ~), ~, ~ blah)(0)()\nML99_EVAL((~, ~, ~) blah)\n```\n\nIn either case, you can try to [iteratively debug your metaprogram](https://hirrolot.gitbook.io/metalang99/testing-debugging-and-error-reporting). From my experience, 95% of errors are comprehensible -- Metalang99 is built for humans, not for macro monsters.\n\n### Q: What about debugging?\n\nA: See the chapter [_\"Testing, debugging, and error reporting\"_](https://hirrolot.gitbook.io/metalang99/testing-debugging-and-error-reporting).\n\n### Q: What about IDE support?\n\nA: I use VS Code for development. It enables pop-up suggestments of macro-generated constructions but, of course, it does not support macro syntax highlighting.\n\n### Q: Compilation times?\n\nA: To run the benchmarks, execute `./scripts/bench.sh` from the root directory.\n\n### Q: How does it work?\n\nA:\n\n 1. Because macro recursion is prohibited, there is an ad-hoc [recursion engine] which works by deferring macro expansions and passing continuations here and there.\n 2. Upon it, the [continuation-passing style] [interpreter] reduces language expressions into final results.\n 3. The standard library is nothing but a set of metafunctions implemented using the core metalanguage, i.e. they are to be evaluated by the interpreter.\n\n[recursion engine]: include/metalang99/eval/rec.h\n[interpreter]: include/metalang99/eval/eval.h\n[continuation-passing style]: https://en.wikipedia.org/wiki/Continuation-passing_style\n\n### Q: Why not third-party code generators?\n\nA: See the blog post [_\"What’s the Point of the C Preprocessor, Actually?\"_](https://hirrolot.github.io/posts/whats-the-point-of-the-c-preprocessor-actually.html)\n\n### Q: Is it Turing-complete?\n\nA: The C/C++ preprocessor is capable to iterate only [up to a certain limit](https://stackoverflow.com/questions/3136686/is-the-c99-preprocessor-turing-complete). For Metalang99, this limit is defined in terms of reductions steps: once a fixed amount of reduction steps is exhausted, your metaprogram will not be able to execute anymore.\n\n### Q: Why macros if we have templates?\n\nA: Metalang99 is primarily targeted at pure C, and C lacks templates. But anyway, you can find the argumentation for C++ at the website of [Boost/Preprocessor].\n\n### Q: Which standards are supported?\n\nA: C99/C++11 and onwards.\n\n### Q: Which compilers are tested?\n\nA: Metalang99 is known to work on these compilers:\n\n - GCC\n - Clang\n - MSVC\n - TCC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhirrolot%2Fmetalang99","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhirrolot%2Fmetalang99","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhirrolot%2Fmetalang99/lists"}