├── .gitignore ├── Earthfile ├── LICENSE.md ├── Makefile ├── README.md ├── bpt.yaml ├── dag.py ├── docs ├── .gitignore ├── background │ ├── compile-fast.rst │ ├── context.rst │ ├── evaluator.rst │ ├── everything-else.rst │ ├── index.rst │ ├── invoke.rst │ ├── lang.rst │ ├── missing.rst │ ├── parser.rst │ ├── stdlib.rst │ ├── the-name.rst │ ├── tokenizer.rst │ └── why.rst ├── conf.py ├── docutils.conf ├── index.rst ├── prolog.rst ├── ref-in │ ├── concepts.yaml │ ├── const.yaml │ ├── cx_str.yaml │ ├── error.yaml │ ├── index.yaml │ ├── invoke.yaml │ ├── lex.yaml │ ├── schema.yaml │ └── string.yaml ├── ref │ ├── index.rst │ └── lang │ │ ├── grammar.rst │ │ └── index.rst ├── refpages.py └── static │ ├── BQN386.woff2 │ ├── scripting.png │ └── styles.css ├── poetry.lock ├── pyproject.toml ├── src ├── example1.main.cpp └── lmno │ ├── ast.hpp │ ├── ast.test.cpp │ ├── concepts │ ├── stateless.hpp │ ├── stateless.test.cpp │ ├── structural.hpp │ ├── structural.test.cpp │ └── typed_constant.hpp │ ├── const.hpp │ ├── const.test.cpp │ ├── context.hpp │ ├── context.test.cpp │ ├── define.hpp │ ├── error.hpp │ ├── eval.hpp │ ├── eval.test.cpp │ ├── func_wrap.hpp │ ├── invoke.hpp │ ├── invoke.test.cpp │ ├── lex.hpp │ ├── lex.test.cpp │ ├── md │ ├── aplib.hpp │ ├── cursor.hpp │ ├── cursor.test.cpp │ ├── kokkos-mdspan.hpp │ ├── mdspan.hpp │ ├── range.hpp │ ├── range.test.cpp │ ├── shape.hpp │ └── shape.test.cpp │ ├── meta.hpp │ ├── meta.test.cpp │ ├── parse.hpp │ ├── parse.test.cpp │ ├── rational.hpp │ ├── render.hpp │ ├── render.test.cpp │ ├── stdlib.hpp │ ├── stdlib │ ├── algorithm.hpp │ ├── arithmetic.hpp │ ├── comb.hpp │ ├── constants.hpp │ ├── logic.hpp │ ├── numeric.hpp │ ├── ranges.hpp │ └── valences.hpp │ ├── strand.hpp │ ├── strand.test.cpp │ ├── string.hpp │ └── string.test.cpp └── tools ├── clang-16.yaml ├── earthly.sh ├── gcc-11.yaml └── unmangle.py /.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | .vscode/ 3 | __pycache__/ 4 | .dagon.db 5 | -------------------------------------------------------------------------------- /Earthfile: -------------------------------------------------------------------------------- 1 | VERSION 0.6 2 | 3 | bpt-linux: 4 | FROM alpine:3.16 5 | RUN apk add curl && \ 6 | curl -sL https://github.com/vector-of-bool/bpt/releases/download/1.0.0-beta.1/bpt-linux-x64 \ 7 | -o bpt && \ 8 | chmod +x bpt 9 | SAVE ARTIFACT bpt 10 | 11 | BUILD: 12 | COMMAND 13 | COPY --dir src/ tools/ bpt.yaml /s 14 | COPY +bpt-linux/bpt /usr/local/bin/bpt 15 | ARG compiler_id=gnu 16 | ARG compiler="g++" 17 | ARG cxx_flags 18 | WORKDIR /s 19 | RUN jq -n \ 20 | --arg compiler_id "$compiler_id" \ 21 | --arg compiler "$compiler" \ 22 | --arg cxx_flags "$cxx_flags" \ 23 | '{ \ 24 | compiler_id: $compiler_id, \ 25 | cxx_compiler: $compiler, \ 26 | cxx_version: "c++20", \ 27 | cxx_flags: $cxx_flags, \ 28 | debug: true, \ 29 | runtime: {debug: true}, \ 30 | warning_flags: ["-Werror", "-Wsign-compare"], \ 31 | compiler_launcher: "python3 -u tools/unmangle.py ccache", \ 32 | }' \ 33 | | tee toolchain.yaml 34 | RUN --mount=type=cache,target=/root/.ccache \ 35 | bpt build --toolchain=toolchain.yaml --tweaks-dir=conf 36 | 37 | build-alpine-gcc-11.2: 38 | FROM alpine:3.16 39 | RUN apk add jq gcc "g++" musl-dev ccache python3 40 | DO +BUILD 41 | 42 | build-alpine-gcc-12.2: 43 | FROM alpine:3.17 44 | RUN apk add jq gcc "g++" musl-dev ccache python3 45 | DO +BUILD 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This project and all of its code, with the exception of the file 2 | `src/lmno/md/kokkos-mdspan.hpp`, is licensed according to the Creative Commons 3 | CC0 1.0 Universal License, the text of which can be found under the "CC 0 4 | License" heading below. 5 | 6 | **The file `src/lmno/md/kokkos-mdspan.hpp` is not covered by this license, and 7 | is licensed separately as Apache 2.0 with LLVM Exceptions.** 8 | 9 | # CC 0 License 10 | 11 | Creative Commons Legal Code 12 | 13 | CC0 1.0 Universal 14 | 15 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 16 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 17 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 18 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 19 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 20 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 21 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 22 | HEREUNDER. 23 | 24 | Statement of Purpose 25 | 26 | The laws of most jurisdictions throughout the world automatically confer 27 | exclusive Copyright and Related Rights (defined below) upon the creator 28 | and subsequent owner(s) (each and all, an "owner") of an original work of 29 | authorship and/or a database (each, a "Work"). 30 | 31 | Certain owners wish to permanently relinquish those rights to a Work for 32 | the purpose of contributing to a commons of creative, cultural and 33 | scientific works ("Commons") that the public can reliably and without fear 34 | of later claims of infringement build upon, modify, incorporate in other 35 | works, reuse and redistribute as freely as possible in any form whatsoever 36 | and for any purposes, including without limitation commercial purposes. 37 | These owners may contribute to the Commons to promote the ideal of a free 38 | culture and the further production of creative, cultural and scientific 39 | works, or to gain reputation or greater distribution for their Work in 40 | part through the use and efforts of others. 41 | 42 | For these and/or other purposes and motivations, and without any 43 | expectation of additional consideration or compensation, the person 44 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 45 | is an owner of Copyright and Related Rights in the Work, voluntarily 46 | elects to apply CC0 to the Work and publicly distribute the Work under its 47 | terms, with knowledge of his or her Copyright and Related Rights in the 48 | Work and the meaning and intended legal effect of CC0 on those rights. 49 | 50 | 1. Copyright and Related Rights. A Work made available under CC0 may be 51 | protected by copyright and related or neighboring rights ("Copyright and 52 | Related Rights"). Copyright and Related Rights include, but are not 53 | limited to, the following: 54 | 55 | i. the right to reproduce, adapt, distribute, perform, display, 56 | communicate, and translate a Work; 57 | ii. moral rights retained by the original author(s) and/or performer(s); 58 | iii. publicity and privacy rights pertaining to a person's image or 59 | likeness depicted in a Work; 60 | iv. rights protecting against unfair competition in regards to a Work, 61 | subject to the limitations in paragraph 4(a), below; 62 | v. rights protecting the extraction, dissemination, use and reuse of data 63 | in a Work; 64 | vi. database rights (such as those arising under Directive 96/9/EC of the 65 | European Parliament and of the Council of 11 March 1996 on the legal 66 | protection of databases, and under any national implementation 67 | thereof, including any amended or successor version of such 68 | directive); and 69 | vii. other similar, equivalent or corresponding rights throughout the 70 | world based on applicable law or treaty, and any national 71 | implementations thereof. 72 | 73 | 2. Waiver. To the greatest extent permitted by, but not in contravention 74 | of, applicable law, Affirmer hereby overtly, fully, permanently, 75 | irrevocably and unconditionally waives, abandons, and surrenders all of 76 | Affirmer's Copyright and Related Rights and associated claims and causes 77 | of action, whether now known or unknown (including existing as well as 78 | future claims and causes of action), in the Work (i) in all territories 79 | worldwide, (ii) for the maximum duration provided by applicable law or 80 | treaty (including future time extensions), (iii) in any current or future 81 | medium and for any number of copies, and (iv) for any purpose whatsoever, 82 | including without limitation commercial, advertising or promotional 83 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 84 | member of the public at large and to the detriment of Affirmer's heirs and 85 | successors, fully intending that such Waiver shall not be subject to 86 | revocation, rescission, cancellation, termination, or any other legal or 87 | equitable action to disrupt the quiet enjoyment of the Work by the public 88 | as contemplated by Affirmer's express Statement of Purpose. 89 | 90 | 3. Public License Fallback. Should any part of the Waiver for any reason 91 | be judged legally invalid or ineffective under applicable law, then the 92 | Waiver shall be preserved to the maximum extent permitted taking into 93 | account Affirmer's express Statement of Purpose. In addition, to the 94 | extent the Waiver is so judged Affirmer hereby grants to each affected 95 | person a royalty-free, non transferable, non sublicensable, non exclusive, 96 | irrevocable and unconditional license to exercise Affirmer's Copyright and 97 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 98 | maximum duration provided by applicable law or treaty (including future 99 | time extensions), (iii) in any current or future medium and for any number 100 | of copies, and (iv) for any purpose whatsoever, including without 101 | limitation commercial, advertising or promotional purposes (the 102 | "License"). The License shall be deemed effective as of the date CC0 was 103 | applied by Affirmer to the Work. Should any part of the License for any 104 | reason be judged legally invalid or ineffective under applicable law, such 105 | partial invalidity or ineffectiveness shall not invalidate the remainder 106 | of the License, and in such case Affirmer hereby affirms that he or she 107 | will not (i) exercise any of his or her remaining Copyright and Related 108 | Rights in the Work or (ii) assert any associated claims and causes of 109 | action with respect to the Work, in either case contrary to Affirmer's 110 | express Statement of Purpose. 111 | 112 | 4. Limitations and Disclaimers. 113 | 114 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 115 | surrendered, licensed or otherwise affected by this document. 116 | b. Affirmer offers the Work as-is and makes no representations or 117 | warranties of any kind concerning the Work, express, implied, 118 | statutory or otherwise, including without limitation warranties of 119 | title, merchantability, fitness for a particular purpose, non 120 | infringement, or the absence of latent or other defects, accuracy, or 121 | the present or absence of errors, whether or not discoverable, all to 122 | the greatest extent permissible under applicable law. 123 | c. Affirmer disclaims responsibility for clearing rights of other persons 124 | that may apply to the Work or any use thereof, including without 125 | limitation any person's Copyright and Related Rights in the Work. 126 | Further, Affirmer disclaims responsibility for obtaining any necessary 127 | consents, permissions or other rights required for any use of the 128 | Work. 129 | d. Affirmer understands and acknowledges that Creative Commons is not a 130 | party to this document and has no duty or obligation with respect to 131 | this CC0 or use of the Work. 132 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SILENT: 2 | .PHONY: build 3 | 4 | .PHONY: poetry-install 5 | poetry-install: 6 | echo "Installing Python tooling..." 7 | poetry -q install 8 | 9 | .PHONY: build 10 | build: poetry-install 11 | poetry run dagon build 12 | 13 | .PHONY: docs 14 | docs: poetry-install 15 | poetry run dagon docs 16 | 17 | .PHONY: docs-server 18 | docs-server: poetry-install 19 | poetry run \ 20 | sphinx-autobuild docs/ _build/docs \ 21 | --re-ignore "docs/_build" \ 22 | --re-ignore docs/ref/api \ 23 | -j auto 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `lmno` an Embedded Programming Language for C++ 2 | 3 | `lmno` (pronounced "elemenoh") is a programming language presented as a generic 4 | C++ library. The core language itself is symbolic, following in the path of many 5 | array programming languages such as APL, J, and BQN. The `lmno` language borrows 6 | many symbols and concepts from APL and BQN, but has several important 7 | differences. 8 | 9 | # IMPORTANT! 10 | 11 | This repository is not a supported project of the author! This started as a 12 | personal experiment that exploded into something viable as a real tool set. 13 | 14 | This GitHub repository does not accept issues or PRs, but you are encouraged to 15 | look through, inspect, download, build, and experiment with the work yourself. 16 | If you want to fork the project and continue it, you have my blessing and 17 | eternal gratitude! 18 | 19 | For more thorough (yet still very incomplete) documentation, the `docs` 20 | directory contains a Sphinx project and rST files. 21 | 22 | 23 | # Building 24 | 25 | Before building anything, you will need to have 26 | [Poetry](https://python-poetry.org) installed! 27 | 28 | Once you have Poetry, you can run the Make targets in the root repo: 29 | 30 | ```shell 31 | ## Build the project and run the tests 32 | $ make build 33 | ## Build the documentation 34 | $ make docs 35 | ## Run a documentation server 36 | $ make docs-server 37 | ``` 38 | 39 | ## Note on compiler support 40 | 41 | Currently, the only toolchains tested with this repository are GCC 11 and Clang 42 | 16 . This repository features very heavy use of template metaprogramming and 43 | C++20 concepts. It is very likely that any compiler outside of those tested will 44 | reject the code, either due to bugs in *your* compilers, or bugs in *my* 45 | compilers that allowed malformed code to pass through. 46 | -------------------------------------------------------------------------------- /bpt.yaml: -------------------------------------------------------------------------------- 1 | name: lmno 2 | version: 0.1.0 3 | 4 | dependencies: 5 | - neo-fun@0.13.0 6 | - nameof@0.10.2 7 | - boost.pfr@2.0.2 8 | 9 | test-dependencies: 10 | - catch2@2.13.9 using main 11 | 12 | readme: README.md 13 | description: | 14 | The is the lmno project. Hello! 15 | 16 | ## Point to the project's homepage on the web 17 | # homepage: example.com 18 | 19 | ## The source-control repository of the project 20 | # repository: example.com/lmno/source 21 | 22 | ## A URL to the documentation for the project 23 | # documentation: example.com/lmno/docs 24 | 25 | ## Specify an SPX License ID for the project 26 | # license: 27 | 28 | # List the authors of the project 29 | authors: 30 | - Colby Pike 31 | -------------------------------------------------------------------------------- /dag.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | from pathlib import Path 5 | 6 | from dagon import fs, http, option, proc, task, ui 7 | 8 | HERE = Path(__file__).parent.resolve() 9 | 10 | USE_BPT = option.add( 11 | "bpt", Path, default=None, doc="Use this path to an existing bpt executable instead of a downloaded version" 12 | ) 13 | BPT_VERSION = option.add("bpt.version", str, default="1.0.0-beta.1", doc="The version of bpt to download") 14 | TOOLCHAIN = option.add( 15 | "toolchain", Path, default_factory=lambda: _get_default_toolchain(), doc="The toolchain to use for the build" 16 | ) 17 | BUILD_DIR = option.add( 18 | "build-dir", 19 | Path, 20 | default=HERE.parent.joinpath("_build/_dagon"), 21 | doc="The directory in which build results will be written", 22 | ) 23 | 24 | 25 | def _get_default_toolchain() -> Path: 26 | if os.name == "posix": 27 | return HERE / "tools/gcc-11.yaml" 28 | raise RuntimeError( 29 | f"No default toolchain is defined for this platform. " 30 | f'Please define one and pass the "--option=toolchain="' 31 | ) 32 | 33 | 34 | def _get_bpt_url() -> str: 35 | use = USE_BPT.get() 36 | if use is not None: 37 | return use.as_uri() 38 | filename = { 39 | "win32": "bpt-win-x64.exe", 40 | "linux": "bpt-linux-x64", 41 | "darwin": "bpt-macos-x64", 42 | }.get(sys.platform) 43 | if filename is None: 44 | raise RuntimeError( 45 | f'We do not have a pre-built bpt to download for "{sys.platform}". Obtain a ' 46 | "suitable bpt executable and specify it using `--option=bpt=` on the Dagon command line." 47 | ) 48 | url = f"https://github.com/vector-of-bool/bpt/releases/download/1.0.0-beta.1/{filename}" 49 | return url 50 | 51 | 52 | __bpt_dl = http.download_task(".bpt.dl", _get_bpt_url, doc="Downloads a BPT for the current system") 53 | 54 | clean = task.fn_task("clean", lambda: fs.remove(BUILD_DIR.get(), recurse=True, absent_ok=True)) 55 | 56 | 57 | @task.define(depends=[__bpt_dl]) 58 | async def __bpt_exe() -> Path: 59 | p = await task.result_of(__bpt_dl) 60 | os.chmod(p, 0o755) 61 | return p 62 | 63 | 64 | def _progress(e: proc.ProcessOutputItem): 65 | line = e.out.decode() 66 | ui.print(line.rstrip()) 67 | mat = re.search(r"\[\s*(\d+)/(\d+)\]", line) 68 | if not mat: 69 | return 70 | cur, total = mat.groups() 71 | ui.progress(int(cur) / int(total)) 72 | 73 | 74 | @task.define(depends=[__bpt_exe], order_only_depends=[clean]) 75 | async def build() -> None: 76 | ui.status("Compiling, linking, and running tests") 77 | await proc.run( 78 | [ 79 | await task.result_of(__bpt_exe), 80 | "build", 81 | ("--out", BUILD_DIR.get()), 82 | ("--toolchain", TOOLCHAIN.get()), 83 | ], 84 | on_output=_progress, 85 | ) 86 | ui.print("All build and unit tests ran successfully", type=ui.MessageType.Information) 87 | 88 | 89 | @task.define() 90 | async def docs() -> None: 91 | await proc.run(["sphinx-build", HERE / "docs", HERE / "_build/docs", "-jauto", "-Wa"], on_output="status") 92 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # The API reference pages are generated dynamically 2 | ref/api/ -------------------------------------------------------------------------------- /docs/background/context.rst: -------------------------------------------------------------------------------- 1 | The Context (Name Lookup) 2 | ######################### 3 | 4 | .. default-role:: cpp 5 | .. highlight:: cpp 6 | .. default-domain:: cpp 7 | .. namespace:: lmno 8 | 9 | The default :doc:`evaluator ` accepts a context as input, and only 10 | uses two methods on that context: `bind` and `get`. 11 | 12 | `bind()` 13 | - Accepts a name-value pair object and returns a new context that has that 14 | name bound to the value. 15 | 16 | `get()` 17 | - Accepts a name token (as a *template* argument) and returns a reference to 18 | the value (or an error object if the name is not found). 19 | 20 | 21 | The `default_context` 22 | ********************* 23 | 24 | |lmno| comes with a simple `default_context`, which implemented using 25 | `lmno::scope`. `lmno::scope` is simply a tuple of named values, and it starts 26 | out empty. 27 | 28 | Getting a Name 29 | ============== 30 | 31 | `default_context::get()` performs two lookups:: 32 | 33 | template 34 | constexpr decltype(auto) get() const noexcept { 35 | if constexpr (Scope::template has_name_v) { 36 | return _scope.template get(); 37 | } else { 38 | return lmno::define; 39 | } 40 | } 41 | 42 | First, it introspects the `Scope` template parameter to see if it has a value 43 | bound to `Name`. If it does, we call `.get()` on that scope object to resolve 44 | the name. (Names are compile-time constants, but they may refer to runtime 45 | values.) 46 | 47 | If the scope does not contain the `Name`, then we return `lmno::define`, 48 | which is a variable template that allows names to be defined globally. This is 49 | how the |lmno| standard library is defined. If there is no specialization of 50 | `define` for `Name`, then the default value will be an error object. 51 | 52 | .. seealso:: 53 | 54 | :doc:`stdlib` for information on `lmno::define` and the standard library. 55 | 56 | 57 | The `scope` Template 58 | ******************** 59 | 60 | The `scope` class template implements the core of name storage and retrieval. 61 | It is a variadic template with only a partial specialization defined:: 62 | 63 | template 64 | struct scope...> { 65 | using tuple = detail::name_value_pairs...>; 66 | 67 | tuple _values; 68 | // … 69 | }; 70 | 71 | The variadic pack `Names` allows us to implement `has_name_v` as a simple pack expansion expression:: 72 | 73 | template 74 | constexpr static bool has_name_v = ((Name == Names) or ...); 75 | 76 | The `name_value_pairs` class template uses variadic inheritance to create a 77 | tuple of each value. It inherits from each `named_value` that is given as a 78 | template argument. 79 | 80 | 81 | Getting a Name 82 | ============== 83 | 84 | `scope::get` is implemented using a special tuple retrieval:: 85 | 86 | template 87 | requires has_name_v 88 | constexpr named_t get() const noexcept { 89 | return detail::get_named(_values).get(); 90 | } 91 | 92 | The `get_named` function appears as:: 93 | 94 | template 95 | constexpr named_value const& 96 | get_named(const named_value& p) noexcept { 97 | return p; 98 | } 99 | 100 | Importantly, we provide only the `Name` argument explicitly, and leave `T` to be 101 | deduced. The compiler must now match the `name_value_pairs` class against this 102 | argument set. It scans through all the base classes to find a match. Because 103 | there is only one base class that has the correct `Name`, there is only one 104 | acceptable candidate for the conversion. The compile is then able to infer the 105 | argument `T` and bind to the correct subobject of the tuple. 106 | 107 | The `.get()` function on the pair will simply return the value that is enclosed. 108 | 109 | 110 | Binding Names 111 | ============= 112 | 113 | The `bind` function on `scope` needs to create a new `scope` type that contains 114 | the same values as the current scope, plus new names being bound, minus the 115 | names that are being shadowed. 116 | 117 | For `scope`, this is simply a sequence of metafunctions:: 118 | 119 | template 120 | constexpr auto bind(Pairs&&... ps) const noexcept { 121 | using to_add = meta::list; 122 | using current = meta::rebind; 123 | using replace = detail::replace_named; 124 | using new_list = replace::type; 125 | using keep_list = replace::keep; 126 | return this->_bind_copy(static_cast(nullptr), 127 | static_cast(nullptr), 128 | FWD(ps)...); 129 | } 130 | 131 | It works as: 132 | 133 | 1. Convert the variadic pack into a list `to_add` 134 | 2. Convert our existing names (`tuple`) back into a list 135 | 3. Call the "replace named" metafunction to remove items from `current` that 136 | exist in `to_add`, and appends them to the current list. 137 | 4. `new_list` is the new list, including the names that are being bound. 138 | 5. `keep_list` is the list from `current` of names that *are not* in `new_list`. 139 | 140 | The `_bind_copy` function simply constructs the new scope:: 141 | 142 | template 146 | constexpr auto _bind_copy(meta::list...>*, 147 | meta::list*, 148 | AddPairs&&... ps) const noexcept { 149 | auto tup = detail::name_value_pairs{{this->template get()}..., 150 | {FWD(ps)}...}; 151 | return scope(std::move(tup)); 152 | } 153 | 154 | List initialization is used to construct each base class of `name_value_pairs`, 155 | using a pack expansion of `Keep` to obtain the current values from our own 156 | tuple. 157 | -------------------------------------------------------------------------------- /docs/background/everything-else.rst: -------------------------------------------------------------------------------- 1 | Everything Else 2 | ############### 3 | 4 | .. default-domain:: cpp 5 | .. default-role:: cpp 6 | .. highlight:: cpp 7 | .. namespace:: lmno 8 | 9 | This documentation is incomplete, and there are several aspects that haven't 10 | been covered here. 11 | 12 | Rather than leave things unmentioned, it'll be best to let you, dear reader, 13 | *know what you don't know* than to let you blindly stumble upon them. Here's a 14 | brief summary: 15 | 16 | 1. The machinery underlying :func:`lmno::invoke`, which was very carefully 17 | optimized for compile-time speed and pretty error messages. 18 | 2. `error.hpp` and error generation with :func:`lmno::invoke`. The standard 19 | library functions implement an interface `T::error()`, which should 20 | return an error object representing an explanation on why `T` is not callable 21 | with `Args`. This is used to generate error messages, rather than relying on 22 | the C++ compiler to generate enormous nasty template backtraces (I've seen 23 | them, and they're *bad*, and should not be shown to the user, if at all 24 | possible). 25 | 3. The implementation of "strand", represented by the underty ":lmno:`‿`". It's 26 | a range-ish thing that's also a :concept:`typed_constant` sometimes. 27 | 4. Compile-time string formatting with :class:`lmno::cx_str` and 28 | :var:`lmno::cx_fmt_v`. 29 | 5. Compile-time type and constexpr value rendering, implemented in 30 | ``. Most types provide speicalizations of the `render_v` and 31 | `render_type_v` variable templates that will emit `cx_str` instances. These 32 | are used in error message generation. 33 | 6. The `rational` abstraction. Because `float` is just awful at compile-time. 34 | -------------------------------------------------------------------------------- /docs/background/index.rst: -------------------------------------------------------------------------------- 1 | Backround, Design, and Rationale 2 | ################################ 3 | 4 | This section will explain the design and background of |lmno|. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents 9 | 10 | why 11 | the-name 12 | lang 13 | 14 | compile-fast 15 | 16 | tokenizer 17 | parser 18 | evaluator 19 | context 20 | stdlib 21 | 22 | invoke 23 | 24 | everything-else 25 | missing 26 | -------------------------------------------------------------------------------- /docs/background/invoke.rst: -------------------------------------------------------------------------------- 1 | Rich Invocation with :func:`lmno::invoke` 2 | ######################################### 3 | 4 | .. namespace:: lmno 5 | 6 | .. |invoke| replace:: :func:`invoke` 7 | .. |Const| replace:: :class:`Const` 8 | .. |stateless| replace:: :concept:`stateless` 9 | 10 | |invoke| is one of the core powerful APIs of |lmno|. It provides 11 | machinery to encode and transfer compile-time constants, as well as generate 12 | "pretty" error messages in the case of constraint failure. 13 | 14 | At its core, |invoke| serves the same purpose of `std::invoke`, and has the same 15 | API-shape. The first argument must be an *invocable* object, and the subsequent 16 | arguments will be the arguments to pass through to the underlying invocable:: 17 | 18 | static_assert(lmno::invoke(std::plus<>{}, 4, 3) == 7); 19 | 20 | But it has a few important differences. 21 | 22 | 23 | Error Handling 24 | ************** 25 | 26 | :: 27 | 28 | // Compiles?! 29 | lmno::invoke(std::plus<>{}, 4, std::string("cat")); 30 | 31 | The above code *will* compile, but will also generate a compile-time warning. 32 | Here, |invoke| detects that `std::plus<>` cannot be invoked with arguments of 33 | type `int` and `std::string`, and will modify its return type to be a special 34 | error-type that indicates this error. The error-type is marked as 35 | :cpp:`[[nodiscard]]`, so dropping it (as above) will produce a compiler warning. 36 | If the compiler's diagnostic is formatted properly, the error message content of 37 | the error-type will be printed in the compiler output. 38 | 39 | Beyond :cpp:`[[nodiscard]]`, the error-type value is not usable for most 40 | purposes. Attempting to access members or use it in other expressions will cause 41 | a hard compile-error that will also include the error-type's diagnostic. 42 | 43 | |lmno| also includes a concept, :concept:`err::non_error`, whose only purpose is 44 | to reject the error-type returned by |invoke|. One can use this as a placeholder 45 | for a variable or return type to hard-error immediately when an error-type is 46 | generated:: 47 | 48 | // Does not compile: lmno::non_error is not satisfied 49 | lmno::non_error auto sum = lmno::invoke(std::plus<>{}, 4, std::string("cat")); 50 | 51 | 52 | Constant Propagation 53 | ******************** 54 | 55 | |invoke| propagates compile-time constants up and down the call stack. The class 56 | template :class:`Const` is the primary representation of compile-time constants 57 | in |lmno|. 58 | 59 | If an argument of type |Const| is passed to |invoke| as a function argument, 60 | |invoke| will attempt to invoke the underlying function using that |Const| 61 | object. If the invocable does not accept the |Const| object directly, |invoke| 62 | will retry the invocation with the `Const::value` instead. Thus |invoke| will 63 | automatically "unwrap" |Const| values if the underlying callable object does not 64 | handle them directly:: 65 | 66 | constexpr int seven = invoke(std::plus<>{}, 4, Const<3>{}); 67 | static_assert(seven == 7); 68 | 69 | In the above, `std::plus` will reject the `Const<3>` as an argument, since 70 | `4 + Const<3>{}` is ill-formed (|Const| does not provide a :cpp:`+` operator). 71 | |invoke| will then fall-back on unwrapping the `Const<3>` and invoke `std::plus` 72 | with two regular `int`\ s. 73 | 74 | The auto-|Const| goes in both directions: If |invoke| detects that an invocation 75 | will generate a stateful structural constant expression, it will wrap the 76 | returned value in |Const|:: 77 | 78 | std::same_as> auto seven 79 | = invoke(std::plus<>{}, Const<4>{}, Const<3>{}); 80 | 81 | This means that |invoke| with |Const| can be used to pass/return 82 | ":cpp:`constexpr` arguments" to/from functions. 83 | 84 | This has a downside: The return type of |invoke| may be wrapped in |Const| 85 | automatically in generic contexts. For this reason, generic code must be ready 86 | to handle |Const| values. 87 | 88 | 89 | When Does `Const`-return "kick-in"? 90 | =================================== 91 | 92 | |invoke| will wrap the return value in |Const| if-and-only-if the following 93 | conditions are met: 94 | 95 | 1. The invocable :cpp:`remove_cvref_t` as well as each argument 96 | :cpp:`remove_cvref_t...` must be |stateless| types [#const_stateless]_. 97 | 2. The invocation must not be ill-formed nor produce an 98 | :concept:`~err::any_error` type. 99 | 3. The return type of the invocation must **not** be |stateless|. 100 | [#ret_not_stateless]_ 101 | 4. The return type is :concept:`structural`. 102 | 5. The invocation itself must be a valid constant expression. 103 | 104 | If all of the above conditions are satisfied, |invoke| will return a |Const| 105 | object encoding the result of the invocation. 106 | 107 | 108 | .. [#const_stateless] 109 | 110 | Note that |Const| is always a |stateless| type, since it has no 111 | non-static data members, and therefore encodes no runtime state. 112 | 113 | .. [#ret_not_stateless] 114 | 115 | The return-type of |invoke| will not be wrapped in |Const| if the return type 116 | is |stateless|, because such a wrapping would be redundant: There is no value 117 | to encode within a stateless type, and so there is nothing that needs to be 118 | saved into a |Const|. Since the type is stateless, it will already be valid as 119 | a subsequent argument to produce other |Const| values. 120 | -------------------------------------------------------------------------------- /docs/background/missing.rst: -------------------------------------------------------------------------------- 1 | Missing: Arrays 2 | ############### 3 | 4 | .. default-domain:: cpp 5 | .. default-role:: cpp 6 | .. highlight:: cpp 7 | .. namespace:: lmno 8 | 9 | Those familiar with APL, BQN, and just about any other symbolic array language 10 | will notice the strong lack of "arrays" in |lmno|. 11 | 12 | What is an array language without arrays!? 13 | 14 | Well to that I say: |lmno| is not an array language. 15 | 16 | And maybe that's a disappointing answer. I agree! 17 | 18 | C++ is not an array language, and it is surprisingly difficult to shoe-horn an 19 | array language into it, but not for lack of trying! 20 | 21 | I made several attempts to fit multi-dimensional arrays into |lmno|, but I found 22 | all approaches to be lacking. Indeed, I even published a Tweet demonstrating 23 | array features, but if you look through the code, you won't find that feature! 24 | 25 | The issue lies in an impedence mismatch between C++ *ranges* and BQN/APL-style 26 | *arrays*. A brief list of questions that I *couldn't quite answer* in my 27 | attempts: 28 | 29 | 1. APL-style arrays have *arbitrary* but *finite* dimensions (AKA the "shape" of 30 | the arrays). C++ ranges are limited to a single dimension, which may be of a 31 | compile-time fixed size, a runtime-known size, a runtime-unknown size (i.e. 32 | `forward_range` is not a `sized_range`), or even *infinite* ranges (e.g. 33 | `iota()`). 34 | 2. C++ has multi-dimensional arrays, but these are very limited, but a possible 35 | rough starting point. 36 | 3. `mdspan` is coming and provides a way to view a range as a multi-dimensional 37 | array. Several early attempts in |lmno| were based on using `mdspan`. 38 | 4. We need *more* than `mdspan`: We need `mdarray` or `mdvector`. What about 39 | multi-dimensional arrays that are potentially infinite in one or more 40 | extents? Do we need an "`mdrange`" concept? What would that look like? (I 41 | tried to define such a thing, but I am unsure of my atttempts.) 42 | 5. If we have an `mdrange` with any infinite extents, what would it mean to 43 | "deshape" :lmno:`⥊` such a thing? 44 | 6. We can define lazy ranges, such as filters and transforms. How would one 45 | define a lazy `mdrange`? 46 | 7. How would one define a multi-dimensional `mdrange` with a dynamic rank? 47 | `mdspan` won't help you there. 48 | 8. An iterator is defined with a sentinel to tell it when to stop. What would a 49 | multi-dimensional sentinel look like? Is this even a non-sense question? 50 | 51 | My attempts at defining an `mdrange` are in the repository in a set of unused 52 | files (In ``src/lmno/md/``), for any curious readers that may want to inspect my 53 | attempts and attempt to salvage anything useful. 54 | -------------------------------------------------------------------------------- /docs/background/stdlib.rst: -------------------------------------------------------------------------------- 1 | The Standard Library 2 | #################### 3 | 4 | .. default-domain:: cpp 5 | .. default-role:: cpp 6 | .. highlight:: cpp 7 | .. namespace:: lmno 8 | 9 | By default, |lmno| contains no functions or operators. Even something as simple 10 | as :lmno:`4 + 7` will not compile, because we don't have a name ":lmno:`+`" 11 | defined. 12 | 13 | 14 | The `define` Variable Templates 15 | ******************************* 16 | 17 | The default name lookup context will fall back to `lmno::define` if `Name` 18 | is not defined within the bound scope. 19 | 20 | `define<>` is a variable template that is parameterized on a 21 | :class:`lmno::lex::token`:: 22 | 23 | template 24 | constexpr auto define = /* … */; 25 | 26 | (The default value simply generates an unspecified error object.) 27 | 28 | In order to evaluate any useful code, one must include the `` 29 | header within the translation unit. This defines specializations of `define` for 30 | all standard library names. 31 | 32 | For example, the plus ":lmno:`+`" function is defined as an instance of a 33 | callable object:: 34 | 35 | namespace stdlib { 36 | struct plus { 37 | template 38 | constexpr auto operator()(W&& w, addable auto&& x) const 39 | NEO_RETURNS(w + x); 40 | }; 41 | } 42 | 43 | template <> 44 | constexpr inline auto define<"+"> = stdlib::plus{}; 45 | 46 | Thus, when `default_context` looks for `define<"+">`, it will find this instance 47 | of `stdlib::plus`. 48 | 49 | All standard library components are defined in a similar manner. 50 | 51 | 52 | Combinators 53 | *********** 54 | 55 | The most complicated standard library definitions are the combinators. Almost 56 | all of them are functions-returning-functions, and are very dense with 57 | convenience macros to reduce reduntancy and boilerplate. 58 | 59 | The definition of :lmno:`∘` "atop" is not the implementation of atop, but rather 60 | a lambda expression that returns the atop closure object:: 61 | 62 | template <> 63 | constexpr inline auto define<"∘"> = 64 | [](auto&& f, auto&& g) NEO_RETURNS_L(stdlib::atop{NEO_FWD(f), NEO_FWD(g)}); 65 | 66 | The `stdlib::atop` class template implements the actual semantics of :lmno:`∘`:: 67 | 68 | template 69 | struct atop { 70 | NEO_NO_UNIQUE_ADDRESS F _f; 71 | NEO_NO_UNIQUE_ADDRESS G _g; 72 | 73 | LMNO_INDIRECT_INVOCABLE(atop); 74 | 75 | constexpr auto call(auto&& x) 76 | NEO_RETURNS(invoke(_f, invoke(_g, NEO_FWD(x)))); 77 | constexpr auto call(auto&& x) const 78 | NEO_RETURNS(invoke(_f, invoke(_g, NEO_FWD(x)))); 79 | 80 | constexpr auto call(auto&& w, auto&& x) 81 | NEO_RETURNS(invoke(_f, invoke(_g, NEO_FWD(w), NEO_FWD(x)))); 82 | constexpr auto call(auto&& w, auto&& x) const 83 | NEO_RETURNS(invoke(_f, invoke(_g, NEO_FWD(w), NEO_FWD(x)))); 84 | }; 85 | 86 | .. note:: 87 | 88 | The `LMNO_INDIRECT_INVOCABLE` macro defines `operator()` in terms of 89 | :func:`lmno::invoke` and the class's `call()` methods, and could be made far 90 | simple with C++23 *explicit object parameters* as a base class call calls 91 | `self.call()`. The :func:`lmno::invoke` function is used to generate better 92 | error messages in case of malformed calls. 93 | 94 | The *explicit object parameter* would also alleviate the redundancy in 95 | implementing `call()` twice for each `const`\ -ness. 96 | 97 | -------------------------------------------------------------------------------- /docs/background/the-name.rst: -------------------------------------------------------------------------------- 1 | What does the name "|lmno|" mean? 2 | ################################# 3 | 4 | The language itself is inspired by the style, grammar, and idioms of array 5 | programming languages such as APL, BQN, J, etc. In fact, a huge number of 6 | symbolic languages all have names that are single letters or initialisms. 7 | 8 | So, what kind of letter/initialism could I come up with? 9 | 10 | Obviously: Everyone's favorite "letter" in the English mnemonic alphabet song: 11 | *Elemenoh*, or "L-M-N-O". 12 | 13 | What does |lmno| "stand for"? 14 | ***************************** 15 | 16 | That is an excellent question. 17 | -------------------------------------------------------------------------------- /docs/background/tokenizer.rst: -------------------------------------------------------------------------------- 1 | The Tokenizer 2 | ############# 3 | 4 | .. default-role:: cpp 5 | .. highlight:: cpp 6 | .. cpp:namespace:: lmno 7 | 8 | This page will detail the implementation and design of the |lmno| tokenizer. The 9 | tokenizer is by far the most "wow" feature from the outside perspective, since 10 | it has been difficult to process strings at compile time for most of C++'s 11 | history. However, the tokenizer itself is probably the simplest component in the 12 | stack. Once tokenization is complete, the rest of |lmno| operates using 13 | mostly-familiar C++ template metaprogramming. 14 | 15 | 16 | Accepting a String Input 17 | ************************ 18 | 19 | The primary input for the tokenizer is a compile-time constant array of `char`. 20 | Within |lmno|, the default compile-time string type is 21 | :type:`lmno::cx_str\ `, where `N` is the number of `char`\ s 22 | in the array (not including the nul-terminator). 23 | 24 | The tokenizer entrypoint is a regular alias template:: 25 | 26 | template 27 | using tokenize_t = decltype(tokenization_impl(); 28 | 29 | If invoked with a character array (including a string literal), |CTAD| will take 30 | care of deducing the `N` for :type:`cx_str`. 31 | 32 | We next pass that string to the internal entrypoint of the tokenizer, which is 33 | itself a function-template:: 34 | 35 | template 36 | auto tokenization_impl() { 37 | ... 38 | } 39 | 40 | .. note:: 41 | 42 | The names here are only expository. The actual names in code may vary and are 43 | not part of the public API. 44 | 45 | 46 | Preparing an Array 47 | ****************** 48 | 49 | Since we will be accumulating tokens into an array, it may be tempting to use 50 | `std::vector`, which is `constexpr` in C++20. However: This is of little help to 51 | us here, since such a `vector` cannot be used as an argument to a template. 52 | 53 | Instead, we can make the observation that we *cannot* generate more tokens than 54 | there are `char` in the string, so we can instead just use a `std::array`:: 55 | 56 | template 57 | auto tokenization_impl() { 58 | array tokens = {}; 59 | ... 60 | } 61 | 62 | 63 | Defining the Intermediate Type 64 | ****************************** 65 | 66 | But what would the element type be? A token can be variable-length, so we can't 67 | put a `std::string` inside (even if it is `constexpr`-capable, we still run into 68 | the same trouble as `std::vector`). Maybe we can use `std::string_view`, since 69 | it's non-allocating and cheap to copy? Again: This doesn't work. 70 | `std::string_view` is not valid as a non-type template parameter. 71 | 72 | The answer is pretty simple: Just a pair of *position*\ +\ *length* values that 73 | refer to subranges of the string:: 74 | 75 | struct token_range { 76 | int pos; 77 | int len; 78 | }; 79 | 80 | :: 81 | 82 | template 83 | auto tokenization_impl() { 84 | array tokens = {}; 85 | // ... 86 | } 87 | 88 | 89 | Filling and Using the Array 90 | *************************** 91 | 92 | This specialization of `std::array` is valid as a non-type template parameter, 93 | so we're good to use this. After we have filled in the `tokens` array, we just 94 | need to pass it to a template metafunction to turn the array of ranges back into 95 | a :cpp:type:`~lmno::lex::token_list`:: 96 | 97 | // Writes token ranges into 'out', returns the number of tokens. 98 | constexpr int do_tokenize(token_range* out, const char* string); 99 | 100 | template 101 | auto tokenization_impl() { 102 | array tokens = {}; 103 | do_tokenize(tokens.data(), S.data()); 104 | return finalize_tokens_somehow_v; 105 | } 106 | 107 | The above code looks promising, but it doesn't work: The `tokens` array needs to 108 | be `constexpr` in order to be used as a template argument, but if we declare it 109 | `constexpr`, we won't be able to modify it in `do_tokenize`! 110 | 111 | 112 | More Indirection 113 | **************** 114 | 115 | As always, every problem can be solved with another level of indirection:: 116 | 117 | constexpr int do_tokenize(token_range* out, const char* string); 118 | 119 | template 120 | constexpr auto tokenization_impl_inner() { 121 | array tokens = {}; 122 | int num_tokens = do_tokenize(tokens.data(), S.data()); 123 | return make_pair(num_tokens, tokens); 124 | } 125 | 126 | template 127 | auto tokenization_impl() { 128 | constexpr auto pair = tokenization_impl_inner(); 129 | constexpr int num_tokens = pair.first; 130 | constexpr auto tokens = pair.second; 131 | // Finalize the tokens: 132 | return finalize_tokens_v>; 133 | } 134 | 135 | 136 | Finalization 137 | ************ 138 | 139 | The last step, `finalize_tokens_v`, accepts the string, array of `token_range`\ 140 | s, and an index sequence based on the number of tokens in the we found. We just 141 | define a partial specialization of the variable template `finalize_tokens_v` to 142 | unpack the sequence and use it to rebind into a :type:`~lmno::lex::token_list`:: 143 | 144 | template 145 | auto finalize_tokens_v = delete; 146 | 147 | template 148 | auto finalize_tokens_v> 151 | = token_list{}; 152 | 153 | `fin_1_token` is a very simple function that accepts a pointer to the beginning 154 | of the source string and a `token_range`, and returns a 155 | :class:`~lmno::lex::token` corresponding to the range. 156 | -------------------------------------------------------------------------------- /docs/background/why.rst: -------------------------------------------------------------------------------- 1 | Why did you make this? 2 | ###################### 3 | 4 | Excellent question. 5 | 6 | 7 | Beginnings 8 | ********** 9 | 10 | In October of 2021, I published `a blog post entitled "Stringy Templates"`__, 11 | which outlined the new C++20 features that now afforded developers the ability 12 | to parameterize templates on strings |--| something which used to be very 13 | difficult and un-ergonomic. 14 | 15 | Earlier that year, I had also tweeted screenshots of an "embedded scripting 16 | language" I had hacked together during my Summer break: 17 | 18 | __ https://vector-of-bool.github.io/2021/10/22/string-templates.html 19 | 20 | .. image:: /static/scripting.png 21 | :align: center 22 | :alt: Screenshot of an embedded scripting language, different from lmno, 23 | but also using string literals as template arguments. 24 | 25 | The language itself was very ad-hoc and not particularly interesting. It was 26 | more of an excercise in "*is* this possible" rather than "*what* is possible." 27 | 28 | At the end of the *Stringy Templates* blog post, I hinted that one could "take 29 | the idea further", and that there would be an eventual follow-up. 30 | 31 | 32 | Ruminating 33 | ********** 34 | 35 | This idea sat in the back of my mind for a long time, and yet I wasn't really 36 | sure what I could make from it. The language that I had hacked together was very 37 | "C-like", and it wasn't something that one would find compelling, as anything 38 | you could write in it wasn't significantly different from what one could write 39 | in regular C++. 40 | 41 | I wanted an idea for an embeddable language that was *significantly* different 42 | from the existing C++ paradigms. I have a particular love of C++ and Python both 43 | for their flexibility and support of multi-paradigm programming. Being able to 44 | pull out the right tool for the task at hand *without* needing to call out to a 45 | separate API or foreign function is something I appreciate greatly. One might 46 | refer to it as *in-situ paradigm shifting*. 47 | 48 | 49 | Ideas 50 | ***** 51 | 52 | I cannot quite recall what about it drew my attention, but the array programming 53 | languages gave me the "lightbulb moment" of discovery. Not specifically for 54 | their array-handling features, but for their *symbolic representation*, and the 55 | terseness of expressing and composing fundamental algorithms. 56 | 57 | I enjoy the C++20 :cpp:`std::ranges` library additions. I enjoy the expressivity 58 | and ease with which one can compose small building blocks to build larger 59 | machinery. :cpp:`std::ranges` does come with downsides, however: It can be slow 60 | to compile, difficult to read, and the error messages can be ghastly. I would 61 | like a more compact and terse way to compose the same components. That's a good 62 | candidate for an embedded language. 63 | 64 | My interest was also piqued by developments in heterogeneous computing, the 65 | integration story of which is fairly cumbersome. For most of computing, programs 66 | for external compute devices need be written out-of-line and given to a 67 | dedicated toolchain for preparation. Some efforts are underway to allow the 68 | device programs to be written within the C++ sources which use them, but this 69 | still requires language-external toolchain support to intercept the C++ sources 70 | and emit the intermediate representation that will later be dispatched to 71 | compute devices. What if we could use the host C++ compiler itself to generate 72 | the IR from an embedded language *within the very same translation unit*? 73 | 74 | .. code-block:: c++ 75 | 76 | using code = parse_t; 79 | device.exec(param_a, param_b, param_c); 80 | 81 | 82 | Experimenting 83 | ************* 84 | 85 | In late 2022, I began prodding at the idea of another embedded language. This 86 | time, I :strike:`plagarized` *took inspiration from* array languages such as 87 | APL__ and BQN__, opting for a fully-symbolic language. The grammar of APL is 88 | very simple to understand, provided you have some understanding of the 89 | symbology. This started as just an experiment to see "what is possible" with 90 | compile-time parsing. 91 | 92 | __ https://aplwiki.com/ 93 | __ https://aplwiki.com/wiki/BQN 94 | 95 | After poking around and expanding on the idea further and further, I found 96 | myself subject to a kind of *snowball effect*, where I began to take the idea 97 | more seriously and consider that maybe I had something that people might 98 | actually find *useful* rather than it just be a novelty. 99 | 100 | And thus, |lmno| was born. 101 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | from pathlib import Path 10 | from sphinx.application import Sphinx 11 | import sys 12 | 13 | HERE = Path(__file__).parent.resolve() 14 | sys.path.append(str(HERE)) 15 | import refpages 16 | 17 | project = "lmno" 18 | copyright = "2023, vector-of-bool" 19 | author = "vector-of-bool" 20 | release = "0.0.0" 21 | 22 | # -- General configuration --------------------------------------------------- 23 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 24 | 25 | # extensions = ['sphinx_math_dollar'] 26 | 27 | templates_path = [] 28 | exclude_patterns = ["Thumbs.db", ".DS_Store", "prolog.rst", "ref-in/*.rst"] 29 | nitpicky = True 30 | # cpp_debug_lookup = True 31 | # cpp_debug_show_tree = True 32 | 33 | refpages.generate(HERE / "ref-in", HERE / "ref/api") 34 | 35 | # -- Options for HTML output ------------------------------------------------- 36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 37 | 38 | html_theme = "furo" 39 | html_static_path = ["static"] 40 | highlight_language = "cpp" 41 | primary_domain = "cpp" 42 | default_role = "cpp:expr" 43 | html_theme_options = { 44 | # 'logo': { 45 | # 'image_light': 'light.png', 46 | # 'image_dark': 'dark.png', 47 | # } 48 | } 49 | 50 | rst_prolog = r""" 51 | .. include:: /prolog.rst 52 | """ 53 | 54 | mathjax3_config = { 55 | "chtml": { 56 | "displayAlign": "left", 57 | }, 58 | "tex": { 59 | "inlineMath": [["$", "$"], ["\\(", "\\)"]], 60 | }, 61 | } 62 | 63 | from pygments.lexers.apl import APLLexer 64 | from pygments import token 65 | 66 | 67 | class LMNOLexer(APLLexer): 68 | name = "lmno" 69 | aliases = ["lmno"] 70 | 71 | tokens = APLLexer.tokens | { 72 | "root": [ 73 | (r"\s+", token.Whitespace), 74 | (r"\(:.*?:\)", token.Comment), 75 | (r"[\]\[()·;{}$.:]", token.Punctuation), 76 | (r"[‿˘˜¬¨˙⁼⌜´`/\\∘○⚇⎉⌾⊸⟜φ]", token.Name.Attribute), 77 | (r"[-+×√÷⍳↕⋄≠=#⌊⌈∨∧⌽~⥊]", token.Operator), 78 | (r"¯?\d+", token.Number), 79 | (r"[π∞]", token.Name.Constant), 80 | (r"[αω∞]", token.Name.Constant), 81 | (r"[←]", token.Keyword.Declaration), 82 | (r"[A-Za-z_]\w*", token.Name.Variable), 83 | ] 84 | } 85 | 86 | 87 | from docutils.parsers.rst.directives import tables 88 | from sphinx.highlighting import lexers 89 | 90 | 91 | class SummaryTable(tables.CSVTable): 92 | def run(self): 93 | els = super().run() 94 | tbl = els[0] 95 | tbl["classes"] += ["summary-table", "mono-links"] 96 | return els 97 | 98 | 99 | lexers["lmno"] = LMNOLexer() 100 | 101 | 102 | def setup(app: Sphinx): 103 | app.add_css_file("styles.css") 104 | app.add_directive("summary-table", SummaryTable) 105 | -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [restructuredtext parser] 2 | ; Allow highlight of inline snippets 3 | syntax_highlight = short 4 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ``lmno`` - An Embedded Programming Language 2 | =========================================== 3 | 4 | |lmno| is an (experimental) programming language. It is implemented as an 5 | C++ library, and can be compiled and executed by a modern C++20 compiler. 6 | 7 | .. note:: 8 | 9 | These docs, like |lmno| itself, are "experimental", and woefully incomplete. 10 | They will cover some aspects of |lmno| and the C++ library, but a lot remains 11 | undocumented. 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: Contents: 16 | 17 | background/index 18 | 19 | ref/index 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/prolog.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | .. role:: lmno-name(literal) 4 | :class: lmno-name 5 | 6 | .. role:: lmno(code) 7 | :language: lmno 8 | :class: highlight 9 | 10 | .. role:: cpp(code) 11 | :language: cpp 12 | :class: highlight 13 | 14 | .. role:: apl(code) 15 | :language: apl 16 | :class: highlight 17 | 18 | .. role:: strike 19 | :class: stricken 20 | 21 | .. |lmno| replace:: :lmno-name:`lmno` 22 | 23 | .. |--| unicode:: — 24 | 25 | .. |BQN| replace:: BQN_ 26 | 27 | .. _BQN: https://mlochbaum.github.io/BQN 28 | 29 | .. |backtick| raw:: html 30 | 31 | ` 32 | 33 | .. |CTAD| replace:: :abbr:`CTAD (class-template argument deduction)` 34 | 35 | .. Common doc links: 36 | 37 | 38 | .. f |cx_str| replace:: :doc:`cx_str ` 39 | 40 | .. |pub-mem-fns| replace:: Public Member Functions 41 | .. |ctors| replace:: Constructors 42 | 43 | .. |∘| replace:: :math:`∘` 44 | 45 | .. |Phi| replace:: :math:`Φ` 46 | .. |phi| replace:: :math:`φ` 47 | 48 | .. |constexpr| replace:: :cpp:`constexpr` 49 | .. |concept| replace:: :cpp:`concept` 50 | 51 | .. |true| replace:: :cpp:expr:`true` 52 | .. |false| replace:: :cpp:expr:`false` 53 | -------------------------------------------------------------------------------- /docs/ref-in/concepts.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | $schema: file://schema.yaml 3 | title: "Header: ````" 4 | intro: | 5 | The ```` header defines C++ |concept|\ s that are used by the library 6 | 7 | ns: lmno 8 | contents: 9 | Concepts: 10 | - name: stateless 11 | desc: >- 12 | A type that has no runtime state. (also: `enable_stateless_v`) 13 | page: 14 | title: "Concept: ``lmno::stateless``" 15 | outro: | 16 | .. rubric:: Footnotes 17 | .. [#stateless-aggr] 18 | To detect whether a non-empty aggregate type is stateless, one must 19 | check that all of its non-static data members are also stateless. 20 | This is currently implemented using Boost.PFR__, which is imperfect 21 | in this detection. 22 | 23 | __ https://github.com/boostorg/pfr 24 | 25 | In particular: If we request `stateless` for a type ``T`` which is a 26 | non-empty aggregate with any base classes, this will generate a 27 | hard-error at compile-time, since PFR cannot handle this case in an 28 | SFINAE-friendly manner. 29 | 30 | If such a case is encountered, it is recommended to ensure the type 31 | is empty (using ``[[no_unique_address]]``) if applicable, or to 32 | explicitly tell |lmno| that the type is/isn't stateless by 33 | specializing :var:`lmno::enable_stateless_v` for that type. 34 | 35 | entities: 36 | - kind: concept 37 | template: 38 | name: stateless 39 | intro: | 40 | `T` is *stateless* if-and-only-if the following conditions are met: 41 | 42 | 1. `enable_stateless_v` is `true` 43 | 2. OR: 44 | 45 | 1. `enable_stateless_v` is *not* `false` for `T`, 46 | 2. The type `T` is trivially-destructible, 47 | 3. The type `T` is default-initializable, 48 | 4. The expression `T{}` is valid in a constant-expression, 49 | 5. And either: 50 | 51 | 1. The type `T` is empty (according to `std::is_empty_v`) 52 | 2. Or: The type `T` is an aggregate type *and* all non-static data 53 | members are also of `stateless` types [#stateless-aggr]_. 54 | 55 | - kind: const 56 | name: enable_stateless_v 57 | template: 58 | type: constexpr auto 59 | main: | 60 | A customizable type-trait variable template that can be used to force a 61 | given type `T` to be accepted/rejected as `stateless`. The default 62 | value is unspecified, which allows `stateless` to do deeper inspection 63 | on the given `T`. 64 | 65 | - If `enable_stateless_v` is `true`, then `stateless` will *always* 66 | be satisfied. 67 | 68 | - If `enable_stateless_v` is `false`, then `stateless` will *never* be 69 | satisified. 70 | - name: structural 71 | desc: A type that is valid as a non-type template parameter 72 | ent-page: 73 | kind: concept 74 | name: structural 75 | template: 76 | main: | 77 | A type `T` is *structural* if it is valid as a non-type template parameter. 78 | 79 | *Most* fundamental types are structural, including integers, floating-point types, 80 | and arrays thereof. Additionally, class types that meet certain requirements 81 | are also considered *structural*. 82 | 83 | .. seealso:: 84 | 85 | For more information, refer to `the cppreference page about non-type template parameters`__ 86 | 87 | __ https://en.cppreference.com/w/cpp/language/template_parameters#Non-type_template_parameter 88 | 89 | - name: typed_constant 90 | desc: |- 91 | A type that encodes a constant value (also: `typed_constant_base`, `enable_typed_constant_v`) 92 | page: 93 | title: "Concept: ``lmno::typed_constant``" 94 | entities: 95 | - kind: concept 96 | name: typed_constant 97 | template: 98 | main: | 99 | Detects that the given type `T` represents a compile-time constant 100 | value. 101 | 102 | Is satisfied if-and-only-if: 103 | 104 | 1. `T` is derived from `typed_constant_base` OR `enable_typed_constant_v` is `true`, 105 | 2. `stateless` is `true`, 106 | 3. `T::type` names a type, 107 | 4. `T::type` satisfies `structural`, 108 | 5. `T::value` is an expression of type `T::type const&`, 109 | 6. and an instance of `T` can be explicitly-converted to `T::type`. 110 | 111 | - kind: struct 112 | name: typed_constant_base 113 | main: A simple empty type that can be used to tag a class as a typed constant 114 | - kind: const 115 | name: enable_typed_constant_v 116 | template: 117 | type: constexpr bool 118 | main: | 119 | If the expression `T::enabled_typed_constant` is a valid expression and is 120 | contextually convertible to a `bool` value $B$, has the value $B$. Otherwise, `false`. 121 | 122 | Alias Templates: 123 | - name: unconst_t 124 | desc: Strip away a `typed_constant` type 125 | ent-page: 126 | kind: type 127 | template: > 128 | name: unconst_t 129 | main: | 130 | With `Ot` being defined as `std::remove_cvref_t`, if the type `Ot` 131 | satisifes `typed_constant`, resolves to the type `Ot::type const&`. 132 | Otherwise, resolves to `T`. 133 | 134 | .. seealso:: `unconst` 135 | 136 | Functions: 137 | - name: unconst() 138 | slug: unconst 139 | desc: Strip away a `typed_constant` value 140 | ent-page: 141 | kind: fn 142 | name: unconst 143 | sigs: 144 | - sig: auto unconst(T&& x) -> unconst_t 145 | template: <__deduced_t T> 146 | main: | 147 | `unconst` is an invocable object that accepts a single parameter. The 148 | template parameter `T` cannot be provided explicitly. 149 | 150 | :returns: `static_cast&&>(x)` 151 | -------------------------------------------------------------------------------- /docs/ref-in/const.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | title: "Header: ````" 3 | intro: | 4 | The ```` header defines a type for encoding compile-time constant 5 | values as distinct types. 6 | ns: lmno 7 | contents: 8 | Class Templates: 9 | - name: Const 10 | desc: Encode a compile-time constant as a type 11 | ent-page: 12 | name: Const 13 | template: 14 | kind: struct 15 | intro: | 16 | The class-template `Const` is used to generate a unique type for any 17 | given value `V`. 18 | 19 | `V` may be a value of any type suitable for use as a non-type template parameter. 20 | (That is: `V` must be `structural`.) 21 | 22 | :tparam V: The value that is encoded in the new typ e 23 | :tparam Type: The deduced type of `V`. **DO NOT** provide this argument 24 | explicitly! Always allow it to be inferred from the type of `V`. 25 | contents: 26 | Constructors: 27 | - name: Const() = default 28 | slug: Const 29 | desc: Construct an instance of `Const`. 30 | ent-page: 31 | name: Const 32 | kind: ctor 33 | sigs: [constexpr Const() noexcept = default] 34 | intro: 35 | Construct a new instance of the `Const` type. For any given value 36 | parameter `V`, all instances of `Const` are equivalent. 37 | Member Types: 38 | - name: type 39 | desc: The type of the constant value 40 | ent-page: 41 | name: type 42 | kind: type 43 | intro: "`type` is the unqualified type of the template parameter `V`" 44 | 45 | Static Member Variables: 46 | - name: value 47 | desc: The value encoded by this type 48 | ent-page: 49 | name: value 50 | kind: const 51 | type: static constexpr const type& 52 | intro: A reference-to-const referring to `V`. 53 | 54 | Operators: 55 | - name: operator ⟨type⟩ 56 | slug: convert 57 | desc: Convert the constant to another type 58 | page: 59 | title: Conversion Operators 60 | entities: 61 | - kind: fn 62 | name: operator other 63 | sigs: 64 | - sig: explicit operator type const&() const noexcept 65 | desc: Bind to a reference to the associated type (Returns `value`) 66 | - sig: explicit operator Other() const 67 | template: Other> 68 | desc: | 69 | Explicitly convert the wrapped `value` to an instance of 70 | `Other`. `Other` must be constructible from `const type&`. 71 | - name: operator== 72 | desc: Equality-compare constants 73 | ent-page: 74 | kind: fn 75 | name: operator== 76 | sigs: 77 | - sig: bool operator==(const type& t) const noexcept 78 | desc: Equality-compare against a runtime value. Compares ``t`` with `value`. 79 | - sig: bool operator==(Const) const 80 | template: >- 81 | U, U Uv> 82 | desc: | 83 | Compare-for-equality with another arbitrary `Const` object which may have 84 | a different value or type. Only applicable if the other type is 85 | equality-comparable with `type`. 86 | - name: operator<=> 87 | desc: Order-compare constants 88 | ent-page: 89 | kind: fn 90 | name: operator<=> 91 | sigs: 92 | - sig: auto operator<=>(const type& t) const noexcept -> __deduced_t 93 | desc: 94 | Compare with a runtime value of `type`. Compares `value` with ``t``. 95 | The comparison category result is deduced. 96 | - sig: auto operator<=>(Const) const noexcept -> __deduced_t 97 | template: U, U Uv> 98 | desc: Compare `value` with `Uv`. Requires that `U` is totally ordered with `type`. 99 | 100 | Alias Templates: 101 | - name: ConstInt64 102 | desc: Generates a `Const` with type `std::int64_t` 103 | ent-page: 104 | kind: type 105 | name: ConstInt64 106 | template: 107 | main: | 108 | This is a convenience type-alias that generates a `Const` type. 109 | 110 | By default, `Const` deduces the type of its template argument. This 111 | can lead to "unexpected" results, as `Const<0>` is not the same type 112 | as `Const<0ull>`. While they will *compare* as equivalent according 113 | to `Const::operator==`, the compiler will treat them as different 114 | types for the purpose of overload resolution. 115 | 116 | |lmno| defaults to using `std::int64_t` for its integers, so `ConstInt64` 117 | is defined as a convenience to synthesize a `Const` of the appropriate 118 | type to match those that |lmno| would generate. 119 | -------------------------------------------------------------------------------- /docs/ref-in/cx_str.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml# 2 | kind: class 3 | name: cx_str 4 | template: &N-template 5 | intro: | 6 | 7 | The `cx_str` class template represents a array of `char` of fixed-length `N` 8 | (`+1` for a null terminator). 9 | 10 | The type is *structural*, *literal*, *regular*, fully :cpp:`constexpr`, and 11 | valid as a non-type template parameter. 12 | 13 | There is a (non-:cpp:`explicit`) |CTAD| deduction guide for a 14 | reference-to-array-of-`char`, where `N` is deduced to be one less than the 15 | length of the character array argument. 16 | 17 | contents: 18 | Constructors: 19 | - name: cx_str 20 | desc: Construct a new `cx_str` 21 | ent-page: 22 | kind: ctor 23 | name: cx_str 24 | intro: | 25 | All constructors are :cpp:`constexpr` 26 | sigs: 27 | - sig: cx_str() = default 28 | desc: Default-construct a ``cx_str``. The string will be filled with `char{0}`. 29 | - sig: cx_str(const char (&arr)[N+1]) noexcept 30 | desc: 31 | Construct a ``cx_str`` by copying from the given array of `char`. 32 | This will only match arrays of the appropriate length (`N+1`) 33 | - sig: cx_str(std::string_view sv) noexcept 34 | desc: | 35 | Copy `N` characters from the string-view `sv`. 36 | 37 | .. note:: **Note**: `sv` must have ``size()`` of length `N`. 38 | 39 | This is checked with an :cpp:`assert()` 40 | 41 | Accessors: 42 | - name: data 43 | desc: Access the pointer to the underlying array 44 | ent-page: 45 | kind: fn 46 | name: data 47 | intro: Obtain a pointer to the beginning of the string's array of `char`. 48 | sigs: 49 | - data() noexcept -> char* 50 | - data() const noexcept -> const char* 51 | - name: size 52 | desc: Get the size of the string 53 | ent-page: 54 | kind: fn 55 | name: size 56 | sigs: [size() const noexcept -> std::size_t] 57 | intro: | 58 | Always returns `N`. This function is defined to match other 59 | string types and to satisfy `std::ranges::sized_range` 60 | 61 | - name: range-access 62 | desc: Access the begin/end of the string as a range 63 | page: 64 | title: Range Access 65 | entities: 66 | - kind: fn 67 | name: range-access 68 | sigs: 69 | - begin() 70 | - begin() const 71 | - end() 72 | - end() const 73 | intro: | 74 | Access the `cx_str` as a proper C++ range. 75 | 76 | `cx_str` satisfies `std::ranges::contiguous_range`. 77 | -------------------------------------------------------------------------------- /docs/ref-in/error.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | $schema: file://schema.yaml 3 | title: "Header: ````" 4 | intro: | 5 | ```` defines types, concepts, and utilities for defining and 6 | handling errors 7 | ns: lmno::err 8 | contents: 9 | Classes and Class Templates: 10 | - name: error_base 11 | desc: Base class for any error type 12 | ent-page: 13 | kind: class 14 | name: error_base 15 | main: | 16 | An empty class intended for use in defining new error types. All 17 | error types should publicly derived from `error_base`. 18 | - name: error_type 19 | desc: Class template of compile-time errors 20 | ent-page: 21 | kind: struct 22 | name: error_type 23 | template: 24 | intro: | 25 | :Inherits from: `error_base` 26 | entities: 27 | - kind: type 28 | name: child 29 | is: Child 30 | main: | 31 | The type given for the `Child` template parameter. 32 | - kind: const 33 | name: message 34 | type: static constexpr const auto& 35 | is: Message 36 | main: | 37 | The error message string given by `Message` 38 | main: | 39 | `error_type` represents a compile-time error. The error message is 40 | given by the `Message` template parameter. An error may have a "child" 41 | error given by the `Child` parameter (default is `void`). 42 | 43 | .. seealso:: 44 | 45 | - `fmt_error_t`: Generate an `error_type` using a format-string. 46 | 47 | Type Aliases: 48 | - name: fmt_error_t 49 | desc: Generate an `error_type` from a compile-time format string 50 | ent-page: 51 | kind: type 52 | name: fmt_error_t 53 | template: 54 | is: error_type> 55 | - name: fmt_errorex_t 56 | desc: Generate an `error_type` with a child error and a compile-time format string 57 | ent-page: 58 | kind: type 59 | name: fmt_errorex_t 60 | template: 61 | is: error_type, Child> 62 | 63 | Concepts: 64 | - name: any_error 65 | desc: Match any type that is an error 66 | ent-page: 67 | kind: concept 68 | name: any_error 69 | template: 70 | is: std::derived_from, error_base> 71 | main: | 72 | Match any (cvr-qualified) type that derives from `error_base`. These 73 | error types indicate compile-time errors, not runtime errors. 74 | - name: non_error 75 | desc: Match any type that is not an error 76 | ent-page: 77 | kind: concept 78 | name: non_error 79 | template: 80 | is: not any_error 81 | main: | 82 | Match non-error types (any type that does not derive from `error_base`) 83 | -------------------------------------------------------------------------------- /docs/ref-in/index.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | $schema: file://schema.yaml 3 | page: 4 | title: C++ API Reference 5 | contents: 6 | Headers: 7 | - name: 8 | slug: lex 9 | desc: Lexing and Tokenization 10 | page: !include lex.yaml 11 | 12 | - name: 13 | slug: const 14 | desc: Types constants 15 | page: !include const.yaml 16 | 17 | - name: 18 | slug: string 19 | desc: Compile-time strings and string handling 20 | page: !include string.yaml 21 | 22 | - name: 23 | slug: concepts 24 | desc: "|lmno| Concepts" 25 | page: !include concepts.yaml 26 | 27 | - name: 28 | slug: invoke 29 | desc: Function application and invocation utilities 30 | page: !include invoke.yaml 31 | 32 | - name: 33 | slug: error 34 | desc: Error types and error handling 35 | page: !include error.yaml 36 | 37 | Other: 38 | - name: Exposition 39 | desc: Constructs that are only used for documentation purposes 40 | slug: exposition 41 | page: 42 | title: Exposition-only Components 43 | intro: 44 | The entities described on this page are not actual, and are only used 45 | in the documentation of other entities. 46 | entities: 47 | - name: __deduced_t 48 | kind: type 49 | is: __deduced_t 50 | intro: | 51 | This is a placeholder type within this reference material that 52 | represents a type which is deduced from surrounding context. The 53 | exact definition of this type that appears in a program should not 54 | be relied on unless specified. It is rendered as `__deduced_t`. 55 | - name: __impldef_t 56 | kind: type 57 | is: __impldef_t 58 | intro: | 59 | This is a docuemtnation placeholder type that represents a type 60 | which is defined by the implementation, but has no reliable value 61 | other than the name through which it was obtained. It is rendered 62 | as `__impldef_t` 63 | - name: __exposition_t 64 | kind: type 65 | is: __exposition_t 66 | main: | 67 | This type annotates an exposition-only type, and is not actually 68 | present in the API, but is useful for defining other terms within 69 | the API in which it appears. This construct is rendered as 70 | `__exposition_t`. 71 | - kind: fn 72 | sigs: 73 | - FWD(...) 74 | main: | 75 | This exposition-only function is equivalent to ``std::forward(X)`` 76 | for a given argument ``X`` 77 | -------------------------------------------------------------------------------- /docs/ref-in/invoke.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | $schema: file://schema.yaml 3 | title: "Header: ````" 4 | intro: The ```` header provides utilities to invoke functions 5 | and other invocable objects 6 | ns: lmno 7 | contents: 8 | Functions: 9 | - name: invoke() 10 | slug: invoke 11 | desc: Invoke an invocable object with a set of arguments 12 | ent-page: 13 | kind: fn 14 | name: invoke 15 | sigs: 16 | - sig: constexpr auto invoke(F&& fn, Args&&... args) noexcept(__deduced_t) -> __deduced_t 17 | template: >- 18 | <__deduced_t F, 19 | __deduced_t... Args, 20 | __exposition_t... NormArgs> 21 | main: | 22 | Invokes an invocable object `fn` with the given arguments `args...`. 23 | 24 | `invoke` is a function-like invocable object, and is not a function 25 | template itself. The template parameters `F` and `Args` cannot be 26 | provided explicitly. 27 | 28 | `invoke` is `noexcept(true)` if a similar `std::invoke` call would be 29 | well-formed and `noexcept(true)` 30 | 31 | `invoke()` performs the following operations: 32 | 33 | .. |StdInvoke| replace:: `std::invoke(unconst(FWD(fn)), static_cast(args)...)` 34 | 35 | 1. Find the argument set `NormArgs...` for which 36 | `std::invocable` is satisified: 37 | 38 | 1. If `std::invocable, Args...>` is `true`, `NormArgs` 39 | is `Args`. 40 | 2. Otherwise, attempt to apply `unconst_t` to each arg within 41 | `Args`. Find the proper subset of `Args` with `unconst_t` 42 | applied that results in `std::invocable` being satisifed for 43 | `unconst_t` with those modified arguments. If found, 44 | `NormArgs` is that modified version of `Args`. 45 | 3. Otherwise, if `std::invocable, unconst_t...>` 46 | is `true`, `NormArgs` is `unconst_t...` 47 | 4. Otherwise, if `invoke_error_t` names a type, return 48 | a default-constructed instance of that type. 49 | 5. Otherwise, return an unspecified error type. 50 | 51 | 2. Let $R$ be the type of |StdInvoke|. 52 | 3. If `not stateless` **or** `not (stateless && ...)` **or** 53 | $R$ satisifes `stateless` **or** $R$ is not `structural` **or** 54 | |StdInvoke| is *not* valid as a constant-expression, return 55 | |StdInvoke|. 56 | 4. Otherwise: Return 57 | `Const(args)...)>{}`. 58 | 59 | .. seealso:: 60 | 61 | The background-discussion page :doc:`/background/invoke` explains 62 | more about `invoke()` and why it behaves as it does. 63 | 64 | Alias Templates: 65 | - name: invoke_t 66 | desc: Get the result type of a call to `invoke` 67 | ent-page: 68 | kind: type 69 | name: invoke_t 70 | template: 71 | is: decltype(invoke(std::declval(), std::declval()...)) 72 | main: | 73 | Resolves to the type that would be returned by a call to `invoke` with 74 | arguments of type `F` and `Args...`. 75 | 76 | .. note:: 77 | 78 | This will *always* resolve to a type, even if the direct 79 | `std::invoke_result_t` would be ill-formed. Refer to 80 | :func:`invoke`. 81 | - name: invoke_error_t 82 | desc: Get the error-type for the given malformed invocation 83 | ent-page: 84 | kind: type 85 | name: invoke_error_t 86 | template: 87 | is: decltype(std::remove_cvref_t>::template error()) 88 | Concepts: 89 | - name: invocable 90 | desc: Match an invocation that will not generate an error with `invoke` 91 | ent-page: 92 | kind: concept 93 | name: invocable 94 | template: 95 | main: | 96 | `invocable` is a concept similar to `std::invocable`. It will evaluate 97 | to `true` if-and-only-if `invoke_t` satisfies :concept:`err::non_error`. 98 | -------------------------------------------------------------------------------- /docs/ref-in/lex.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | title: "Header: ````" 3 | intro: | 4 | .. namespace:: lmno::lex 5 | 6 | The |lmno| tokenization API can be used to split a string into |lmno| tokens and 7 | inspect those tokens. The `token` type is used throughout the codebase to refer 8 | to names entities. 9 | 10 | All entities within this subsection are defined within the :cpp:`lmno::lex` 11 | namespace. 12 | ns: lmno::lex 13 | contents: 14 | Classes and Class Templates: 15 | - name: token 16 | desc: A string-like type that stores the contents of an |lmno| token 17 | ent-page: 18 | kind: struct 19 | name: token 20 | intro: | 21 | A single token, represented as a string. Valid as a non-type template 22 | parameter. Fully :cpp:`constexpr`. The contained string is immutable. 23 | 24 | The token owns its string contents, but does not allocate. Each token 25 | contains a `char` array with a maximum size of `max_token_length`. 26 | contents: 27 | Constructors: 28 | - name: token 29 | desc: Construct a new token 30 | ent-page: 31 | kind: ctor 32 | name: token 33 | sigs: 34 | - sig: constexpr token() noexcept = default 35 | desc: | 36 | Default-construct an empty :class:`token`. 37 | The token will have a `size()` of `0`. 38 | - sig: constexpr token(const char* s) noexcept 39 | desc: | 40 | Construct a :class:`token` that copies charactes from a 41 | null-terminated array of `char` that begins at `s`. 42 | The pointed-to array must have fewer than `max_token_length` 43 | `char`\ s 44 | 45 | Accessors: 46 | - name: data 47 | desc: Get a pointer to the token's string 48 | ent-page: 49 | name: data 50 | kind: fn 51 | sigs: [data() const noexcept -> const char*] 52 | intro: | 53 | Get the pointer to the beginning of the underlying 54 | `char` array. 55 | 56 | .. note:: Never returns `nullptr` 57 | 58 | - name: size 59 | desc: Get the size of the token string 60 | page: 61 | title: "``token::size``" 62 | intro: ".. namespace:: lmno::lex::token" 63 | entities: 64 | - kind: fn 65 | sigs: [size() const noexcept -> std::size_t] 66 | intro: | 67 | Get the size of the token (in UTF-8 code units) 68 | 69 | - name: operator[] 70 | desc: Get the Nth code-unit of the token string 71 | ent-page: 72 | name: operator[] 73 | kind: fn 74 | sigs: 75 | - operator[](std::size_t n) const noexcept -> char 76 | intro: | 77 | Access the `n`\ th `char` within the token string. 78 | 79 | - name: operator std::string_view 80 | desc: Access the token as a `std::string_view` 81 | ent-page: 82 | name: operator std::string_view 83 | kind: fn 84 | sigs: 85 | - operator std::string_view() const noexcept 86 | intro: | 87 | Implicit conversion to `std::string_view` 88 | 89 | Associated Constants: 90 | - name: max_token_length 91 | desc: The maximum length of a token 92 | ent-page: 93 | kind: const 94 | ns: "lmno::lex" 95 | type: constexpr std::size_t 96 | name: max_token_length 97 | intro: | 98 | The maximum number of UTF-8 code units that can be placed 99 | in a `token`. 100 | - name: token_list 101 | desc: An incomplete class template that encodes a sequence of `token`\ s 102 | ent-page: 103 | kind: class 104 | name: token_list 105 | template: 106 | main: | 107 | A type representing the value of a list of `token`\ s. 108 | 109 | .. note:: 110 | 111 | This template has no definition, so it cannot be instantiated 112 | (only specialized). 113 | 114 | .. seealso:: 115 | 116 | A `token_list` is generated by passing a string to `tokenize_t`. 117 | 118 | Alias Templates: 119 | - name: tokenize_t 120 | desc: Generate a `token_list` for the given ``cx_str`` string 121 | ent-page: 122 | kind: type 123 | name: tokenize_t 124 | template: 125 | is: token_list<__deduced_t> 126 | intro: | 127 | Generate a `token_list` from the given |lmno| ``cx_str`` string, tokenized according to 128 | the |lmno| grammar:: 129 | 130 | static_assert( 131 | std::same_as< 132 | tokenize_t, 137 | token_list 143 | > 144 | ); 145 | 146 | Functions: 147 | - name: is_digit, is_alpha, is_ident 148 | slug: classify 149 | desc: "`char` Classifier functions" 150 | page: 151 | title: "`char` Classifier Functions" 152 | ns: lmno::lex 153 | 154 | entities: 155 | - kind: fn 156 | intro: | 157 | The functions `is_digit`, `is_alpha`, and `is_ident` are used to classify values 158 | of `char` according to the |lmno| grammar. 159 | sigs: 160 | - sig: is_digit(char c) -> bool 161 | desc: Return `true` if `c` is an **ASCII digit**. 162 | - sig: is_alpha(char c) -> bool 163 | desc: Return `true` if `c` is an **ASCII alphabetic** letter. 164 | - sig: is_ident(char c) -> bool 165 | desc: Return `true` if `c` is an **ASCII digit**, 166 | an **ASCII alphabetic** letter, or an **underscore**. 167 | -------------------------------------------------------------------------------- /docs/ref-in/schema.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://json-schema.org/draft/2020-12/schema 2 | $id: reflink-schema 3 | $schema: https://json-schema.org/draft/2020-12/schema 4 | title: Refpages Document 5 | description: A page for API references 6 | $defs: 7 | GenericPage: 8 | required: [page] 9 | description: A generic page 10 | properties: 11 | page: 12 | allOf: 13 | - $ref: "#/$defs/PageCommon" 14 | - required: [title] 15 | properties: 16 | title: &title 17 | type: string 18 | description: The title of the page 19 | 20 | EntityPage: 21 | required: [ent-page] 22 | properties: 23 | ent-page: 24 | allOf: 25 | - $ref: "#/$defs/PageCommon" 26 | - $ref: "#/$defs/Entity" 27 | 28 | PageCommon: 29 | properties: 30 | ns: &ns 31 | type: string 32 | description: The namespace of the page/entity. If unspecified, it is inherited from the parent 33 | intro: &intro 34 | type: string 35 | description: | 36 | An introductory paragraph for the page or entity. Inserted before the 37 | contents-table or entities listing 38 | main: 39 | type: string 40 | description: | 41 | The main body content of the page/entity. Shown below the contents, 42 | but before the entities listing. 43 | contents: 44 | type: object 45 | description: | 46 | A grouped set of sub-entities and sub-pages related to this object. 47 | additionalProperties: 48 | type: array 49 | items: { $ref: "#/$defs/ContentItem" } 50 | entities: 51 | type: array 52 | items: { $ref: "#/$defs/Entity" } 53 | 54 | Entity: 55 | required: [kind] 56 | properties: 57 | intro: *intro 58 | oneOf: 59 | - $ref: "#/$defs/StructEntity" 60 | - $ref: "#/$defs/TypeEntity" 61 | - $ref: "#/$defs/ClassEntity" 62 | - $ref: "#/$defs/FuncEntity" 63 | - $ref: "#/$defs/ConstructorEntity" 64 | - $ref: "#/$defs/ConstEntity" 65 | - $ref: "#/$defs/VarEntity" 66 | - $ref: "#/$defs/ConceptEntity" 67 | 68 | NamedEntity: 69 | required: [name] 70 | properties: 71 | name: 72 | type: string 73 | description: The name of the entity within its namespace 74 | 75 | StructEntity: 76 | $ref: "#/$defs/NamedEntity" 77 | properties: 78 | template: &template 79 | type: string 80 | description: The template signature (if this is a templated entity) 81 | kind: 82 | const: struct 83 | 84 | TypeEntity: 85 | $ref: "#/$defs/NamedEntity" 86 | properties: 87 | kind: 88 | const: type 89 | template: *template 90 | is: &ent-is 91 | type: string 92 | description: Declare the value of this entity 93 | 94 | ClassEntity: 95 | $ref: "#/$defs/NamedEntity" 96 | properties: 97 | kind: 98 | const: class 99 | template: *template 100 | 101 | FuncEntity: 102 | required: [sigs] 103 | properties: 104 | kind: 105 | const: fn 106 | sigs: &fn-sigs 107 | kind: array 108 | items: { $ref: "#/$defs/FuncSignature" } 109 | 110 | ConstructorEntity: 111 | required: [sigs] 112 | properties: 113 | kind: 114 | const: ctor 115 | sigs: *fn-sigs 116 | template: *template 117 | 118 | ConstEntity: 119 | $ref: "#/$defs/NamedEntity" 120 | properties: 121 | kind: 122 | const: const 123 | is: *ent-is 124 | template: *template 125 | 126 | VarEntity: 127 | $ref: "#/$defs/NamedEntity" 128 | properties: 129 | kind: 130 | const: var 131 | is: *ent-is 132 | template: *template 133 | 134 | ConceptEntity: 135 | $ref: "#/$defs/NamedEntity" 136 | required: [template] 137 | properties: 138 | kind: 139 | const: concept 140 | is: *ent-is 141 | template: *template 142 | 143 | FuncSignature: 144 | anyOf: 145 | - type: string 146 | - type: object 147 | required: [sig] 148 | additionalProperties: false 149 | properties: 150 | sig: 151 | type: string 152 | description: The function signature (not including a template-head) 153 | template: *template 154 | desc: 155 | type: string 156 | description: A description of this particular signature's behavior 157 | 158 | ContentItem: 159 | type: object 160 | required: [name, desc] 161 | description: A contents-table entry 162 | properties: 163 | name: 164 | type: string 165 | description: The primary link text for this page/entity 166 | desc: 167 | type: string 168 | description: A longer explanation of the linked page/entity 169 | slug: 170 | type: string 171 | description: The spelling of the URL slug. Default is to reuse the name. 172 | oneOf: 173 | - $ref: "#/$defs/GenericPage" 174 | - $ref: "#/$defs/EntityPage" 175 | 176 | oneOf: 177 | - $ref: "#/$defs/GenericPage" 178 | - $ref: "#/$defs/EntityPage" 179 | - allOf: 180 | - $ref: "#/$defs/PageCommon" 181 | - required: [title] 182 | properties: 183 | title: *title 184 | - allOf: 185 | - $ref: "#/$defs/Entity" 186 | - $ref: "#/$defs/PageCommon" 187 | -------------------------------------------------------------------------------- /docs/ref-in/string.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=schema.yaml 2 | title: "Header: ````" 3 | intro: | 4 | The ```` header defines compile-time strings and string 5 | handline utilities 6 | ns: lmno 7 | contents: 8 | Classes: 9 | - name: cx_str 10 | desc: Fixed-length string, usable as a non-type template parameter 11 | ent-page: !include cx_str.yaml 12 | 13 | Variable Templates: 14 | - name: cx_fmt_v 15 | desc: Generate a compile-time formatted `cx_str` string 16 | ent-page: 17 | name: cx_fmt_v 18 | kind: const 19 | type: cx_str 20 | template: 21 | main: | 22 | Generate a `cx_str` using a format-specifiying string `Format` to 23 | interpolate `Items`. 24 | 25 | :tparam Format: 26 | The format-specifier string for the string interpolation. 27 | :tparam Items: 28 | The strings that will be inserted into the placeholders found 29 | in `Format`. 30 | 31 | The format-string `Format` is like that in `std::format`, but with a very reduced 32 | featureset. The following format-specifiers may appear in `Format`: 33 | 34 | - `"{}"` - Interpolate an item 35 | - `"{:}"` - Interpolate an item. Equivalent to `"{}"`. 36 | - `"{:'}"` - Interpolate the item, but wrapped it in single-quotes: ``‘ ’`` 37 | 38 | No other format-specifier syntax is supported. 39 | 40 | - name: cx_str_join_v 41 | desc: Join `cx_str`\ s using a joining infix string 42 | ent-page: 43 | name: cx_str_join_v 44 | kind: const 45 | type: cx_str 46 | template: 47 | main: | 48 | Create a new `cx_str` that is the concatenation of all of `Items`, with the 49 | `Joiner` string inserted between them:: 50 | 51 | static_assert( 52 | cx_str_join_v<" : ", "foo", "bar", "baz"> == "foo : bar : baz"); 53 | 54 | If `Items` is empty, Evaluates to an empty string `cx_str{""}`. If `Items` 55 | is a single string $S$, evaluates to $S$. 56 | 57 | - name: cx_str_replace_v 58 | desc: Replace occurances of a compile-time substring 59 | ent-page: 60 | name: cx_str_replace_v 61 | kind: const 62 | type: cx_str 63 | template: 64 | main: | 65 | :tparam S: The input string to inspect 66 | :tparam Find: The substring to search for within `S` 67 | :tparam Replace: The string to splice into `S` in place of `Find` 68 | 69 | Replace every instance of `Find` in `S` with `Replace`:: 70 | 71 | static_assert( 72 | cx_str_replace_v<"foobarbaz", "bar", ", "> == "foo, baz"); 73 | 74 | Macros: 75 | - name: LMNO_CX_STR 76 | desc: Generate a `cx_str` from a compile-time `std::string`-like value 77 | page: 78 | title: "Macro: ``LMNO_CX_STR``" 79 | ns: lmno 80 | main: | 81 | .. c:macro:: LMNO_CX_STR(S) 82 | 83 | :param S: 84 | A constant expression with a string-like interface. Must have a |constexpr| ``.size()``, 85 | and a |constexpr| ``.data()`` 86 | 87 | This macro is useful to generate a `cx_str` from a |constexpr| string 88 | where the |CTAD| guide of `cx_str` is not of any help:: 89 | 90 | constexpr std::string_view sv = calculate_string(); 91 | cx_str s = LMNO_CX_STR(sv); 92 | // Expands to: 93 | cx_str s = (::lmno::cx_str<(sv).size()>{(sv)}); 94 | 95 | .. note:: Beware that the ``S`` expression will be evaluated twice! 96 | -------------------------------------------------------------------------------- /docs/ref/index.rst: -------------------------------------------------------------------------------- 1 | ######### 2 | Reference 3 | ######### 4 | 5 | .. toctree:: 6 | :caption: Contents 7 | :maxdepth: 2 8 | 9 | api/index 10 | lang/index 11 | -------------------------------------------------------------------------------- /docs/ref/lang/grammar.rst: -------------------------------------------------------------------------------- 1 | The |lmno| Grammar 2 | ################## 3 | 4 | The tokenization rules for |lmno| are intentionally simplistic and easy to 5 | understand. 6 | 7 | An |lmno| program is a sequence of Unicode codepoints. Larger Unicode structures 8 | are not considered. 9 | 10 | .. default-role:: token 11 | 12 | Token Kinds 13 | *********** 14 | 15 | The following productions are whitespace-sensitive: 16 | 17 | .. container:: grammar 18 | 19 | .. productionlist:: 20 | ID_START : ALPHA | "_" 21 | ID_CONT : `ID_START` | DIGIT 22 | IDENT : `ID_START` `ID_CONT`* 23 | INTEGER : ["¯"] DIGIT+ 24 | WHITESPACE : " " | "\n" | "\t" 25 | SPECIAL : ";" | ":" | "." | "(" | ")" 26 | : "{" | "}" | "←" | "‿" | "¯" 27 | : "[" | "]" | " " | "·" | 28 | : `WHITESPACE` | DIGIT | ALPHA 29 | NAME : IDENT | NON-`SPECIAL` 30 | COMMENT : "(:" ... ":)" 31 | 32 | The psuedo-token "``NON-SPECIAL``" refers to any single Unicode codepoint that 33 | is not `SPECIAL`. This forms the basis of the `NAME` token. 34 | 35 | .. default-role:: lmno 36 | .. note:: 37 | 38 | :token:`IDENT` includes any C-like identifier. An identifier begins with any 39 | ASCII-equivalent letter or an underscore, and is followed by zero or more 40 | ASCII letters, digits, or underscores. `foo`, `bar`, `foo_bar`, and `_bar9` 41 | are all identifiers, but `9foo` and `foo-bar` are *not* identifiers. 42 | 43 | .. note:: 44 | 45 | The ASCI hyphen "`-`" is *not* part of :token:`INTEGER`, but is actually 46 | parsed as a stand-alone :token:`NAME`. Negative :token:`INTEGER` literals are 47 | represented using an APL high-bar codepoint "``¯``". The high-bar *must* be 48 | attached to the digits, otherwise it is an invalid syntax: `9`, `30`, `42`, 49 | `¯7` are all numeric literals. `-9` is *not* a numeric literal, but is two 50 | separate tokens. The string "``¯ 4``" is *not* a numeric literal, and is 51 | actually invalid (The high-bar ``¯`` must be attached to the digits). 52 | 53 | .. note:: 54 | 55 | Other ``NON-``\ :token:`SPECIAL` codepoints do not merge together like the 56 | characters within an identifier. Instead, each codepoint stands as a lone 57 | name. e.g. The source string `⌽÷` is composed of *two* :token:`NAME`\ s: `⌽` 58 | and `÷`. 59 | 60 | 61 | Grammar 62 | ******* 63 | 64 | .. default-role:: token 65 | 66 | The following productions are whitespace-insensitive (`WHITESPACE` and 67 | `COMMENT`\ s between tokens are discarded). 68 | 69 | .. container:: grammar 70 | 71 | .. productionlist:: 72 | program : `expr_seq` 73 | expr_seq : `expr_assign` (";" `expr_assign`)* 74 | expr_assign : `expr_dollar` ["←" `expr_dollar`] 75 | expr_dollar : `expr_dollar_prefix` | `expr_dollar_infix` | `expr_main` 76 | expr_dollar_prefix : `expr_main` "$" `expr_dollar_infix` 77 | expr_dollar_infix : `expr_main` "$" `expr_main` "$" (`expr_dollar_infix` | `expr_main`) 78 | expr_main : `expr_prefix` | `expr_infix` | `expr_colon` 79 | expr_prefix : `expr_colon` `expr_infix` 80 | expr_infix : `expr_colon` `expr_colon` (`expr_infix` | `expr_colon`) 81 | expr_colon : `expr_strand` (":" `expr_strand`)* 82 | expr_strand : `expr_primary` ("‿" `expr_primary`)* 83 | expr_primary : `NAME` | `INTEGER` | `expr_group` | `expr_block` 84 | expr_group : "(" `expr_seq` ")" 85 | expr_block : "{" `expr_seq` "}" 86 | 87 | .. note:: 88 | 89 | The above productions `expr_dollar` and `expr_main` require indefinite 90 | lookahead to determine whether to accept the ``prefix`` or ``infix`` variant. 91 | For simplicity and performance of implementation, it is easier to parse a 92 | flat sequence of operand expressions and then regroup them as appropriate. 93 | -------------------------------------------------------------------------------- /docs/ref/lang/index.rst: -------------------------------------------------------------------------------- 1 | The |lmno| Language 2 | ################### 3 | 4 | This subsection details the |lmno| language itself. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | :caption: Contents 9 | 10 | grammar 11 | -------------------------------------------------------------------------------- /docs/static/BQN386.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector-of-bool/lmno/16b41c8f35db414ec2398bab42afb2a39d1c6295/docs/static/BQN386.woff2 -------------------------------------------------------------------------------- /docs/static/scripting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vector-of-bool/lmno/16b41c8f35db414ec2398bab42afb2a39d1c6295/docs/static/scripting.png -------------------------------------------------------------------------------- /docs/static/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: BQN386 Unicode; 3 | src: url(BQN386.woff2); 4 | } 5 | 6 | :root { 7 | --mono-font: Consolas, Menlo, DejaVu Sans Mono, BQN386 Unicode, monospace; 8 | } 9 | 10 | code.code.highlight, 11 | div>div.highlight>pre, 12 | .sig.sig-inline { 13 | font-family: var(--mono-font); 14 | font-size: 0.8em; 15 | } 16 | 17 | body div.body { 18 | font-size: 0.9em; 19 | } 20 | 21 | body div.body code { 22 | font-size: 1.1em; 23 | } 24 | 25 | div.related code { 26 | color: white; 27 | } 28 | 29 | code.lmno-name.literal>span.pre { 30 | font-weight: bold; 31 | } 32 | 33 | p>code.lmno-name.literal>span.pre { 34 | font-size: 13pt; 35 | } 36 | 37 | :not(div.grammar>pre>a)>code.literal:not(.lmno-name), 38 | .sig.sig-inline { 39 | border: 1px solid #00000016; 40 | padding: 1px 4px; 41 | border-radius: 5px; 42 | /* The background-border color comes from Furo styles */ 43 | background-color: var(--color-code-background); 44 | /* color-mix(in srgb, 90%, transparent 10%); */ 45 | white-space: nowrap; 46 | } 47 | 48 | table.summary-table.mono-links tbody tr>:first-child p { 49 | font-family: var(--mono-font); 50 | } 51 | 52 | :is(div, article, .body)[role="main"] section img { 53 | border-radius: 5px; 54 | box-shadow: 0 2px 3px #000e, 0 2px 10px #0005; 55 | } 56 | 57 | span.stricken { 58 | text-decoration: line-through; 59 | } 60 | 61 | section dt.sig { 62 | padding: 2px 6px; 63 | border-radius: 3px; 64 | } 65 | 66 | section dt.sig:not(:target) { 67 | background-color: #0001; 68 | } 69 | 70 | section dt.sig:not(:first-child) { 71 | border-top-left-radius: 0; 72 | border-top-right-radius: 0; 73 | } 74 | 75 | section dt.sig:not(:last-of-type) { 76 | border-bottom-left-radius: 0; 77 | border-bottom-right-radius: 0; 78 | border-bottom: 1px solid #0002; 79 | } 80 | 81 | .table-wrapper.summary-table table.summary-table tr>*:first-child { 82 | /* justify-self: end; */ 83 | text-align: right; 84 | } 85 | 86 | .table-wrapper.summary-table table.summary-table { 87 | display: grid; 88 | grid: auto / 1fr 2fr; 89 | } 90 | 91 | .table-wrapper.summary-table table.summary-table :is(thead, tbody, tr) { 92 | display: contents; 93 | } 94 | 95 | .table-wrapper.summary-table table.summary-table { 96 | margin-top: 10px; 97 | } 98 | 99 | .table-wrapper.summary-table table.summary-table tr :is(td, th) { 100 | border-bottom: unset; 101 | align-self: center; 102 | } 103 | 104 | a.reference.internal:is([title="__deduced_t"], [title="__impldef_t"], [title="__exposition_t"])>span.n { 105 | display: none; 106 | } 107 | 108 | a.reference.internal:is([title="__deduced_t"], [title="__impldef_t"], [title="__exposition_t"])::before { 109 | font-style: italic; 110 | color: #777; 111 | font-weight: bold; 112 | font-family: Arial; 113 | } 114 | 115 | a.reference.internal[title="__deduced_t"]::before { 116 | content: '⟨deduced⟩'; 117 | } 118 | 119 | a.reference.internal[title="__impldef_t"]::before { 120 | content: '⟨implementation-defined⟩'; 121 | } 122 | 123 | a.reference.internal[title="__exposition_t"]::before { 124 | content: '⟨exposition-only⟩'; 125 | } 126 | 127 | dl.cpp.function>dt.sig-object:not(:first-child) { 128 | margin-top: 5px; 129 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "lmno-dev" 3 | version = "0.0.0" 4 | description = "Development project for LMNO" 5 | authors = ["vector-of-bool "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | 10 | [tool.poetry.dev-dependencies] 11 | dagon = {extras = ["http"], version = "^0.10.1"} 12 | yapf = "^0.32.0" 13 | toml = "^0.10.2" 14 | pyright = "^1.1.290" 15 | 16 | [tool.poetry.group.dev.dependencies] 17 | sphinx-autobuild = "^2021.3.14" 18 | sphinx-math-dollar = "^1.2.1" 19 | pydata-sphinx-theme = "^0.12.0" 20 | 21 | [tool.yapf] 22 | based_on_style = "pep8" 23 | column_limit = 120 24 | 25 | [tool.pyright] 26 | typeCheckingMode = "strict" 27 | useLibraryCodeForTypes = true 28 | reportMissingTypeStubs = false 29 | reportMissingImports = true 30 | -------------------------------------------------------------------------------- /src/example1.main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // The main header with lmno::eval 4 | #include 5 | #include 6 | 7 | int main() { 8 | // Invoke the result 9 | auto result = lmno::unconst(lmno::eval<"2+3">()); 10 | std::cout << "The sum of 2 and 3 is " << result << '\n'; 11 | } 12 | -------------------------------------------------------------------------------- /src/lmno/ast.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./lex.hpp" 4 | #include "./render.hpp" 5 | 6 | #include 7 | 8 | namespace lmno::ast { 9 | 10 | using lex::token; 11 | 12 | /** 13 | * @brief An infix application expression 14 | * 15 | * @tparam W The left-hand operand 16 | * @tparam F The invocable in the middle 17 | * @tparam X The right-hand operand 18 | */ 19 | template 20 | struct dyad {}; 21 | 22 | /** 23 | * @brief A prefix application 24 | * 25 | * @tparam F The invocable on the left 26 | * @tparam X The operand 27 | */ 28 | template 29 | struct monad {}; 30 | 31 | /** 32 | * @brief A name 33 | */ 34 | template 35 | struct name {}; 36 | 37 | /** 38 | * @brief The special 'nothing' node 39 | */ 40 | struct nothing {}; 41 | 42 | /** 43 | * @brief A block-expression (function) 44 | * 45 | * @tparam Inner The code in between the braces 46 | */ 47 | template 48 | struct block {}; 49 | 50 | /** 51 | * @brief An assignment expression 52 | * 53 | * @tparam Name The name being assigned to 54 | * @tparam Expr The expression of the assignment 55 | */ 56 | template 57 | struct assignment {}; 58 | 59 | /** 60 | * @brief A strand‿expression 61 | * 62 | * @tparam Elems The elements being stranded together 63 | */ 64 | template 65 | struct strand {}; 66 | 67 | /** 68 | * @brief A sequence of expressions separated by semicolon ";" 69 | * 70 | * @tparam Stmts The individual nodes 71 | */ 72 | template 73 | struct stmt_seq {}; 74 | 75 | template 76 | struct render_not_implemented {}; 77 | 78 | /** 79 | * @brief Render an AST element type 80 | */ 81 | template 82 | constexpr auto render_v = render::type_v>; 83 | 84 | template 85 | constexpr auto render_value_v = render::value_of_type_v; 86 | 87 | template 88 | constexpr auto render_value_v = render::integer_v; 89 | 90 | namespace detail { 91 | 92 | template 93 | constexpr auto render_operand_v = cx_fmt_v<"({})", render_v>; 94 | 95 | template 96 | constexpr auto render_operand_v> = cx_str{N.data()}; 97 | 98 | template 99 | constexpr auto render_operand_v = render_v; 100 | 101 | template <> 102 | constexpr auto render_operand_v = cx_str{"·"}; 103 | 104 | template 105 | constexpr auto render_monad_rhs_v = render_operand_v; 106 | 107 | template 108 | constexpr auto render_monad_rhs_v> = render_v>; 109 | 110 | template 111 | constexpr auto render_dyad_rhs_v = render_v; 112 | 113 | template 114 | constexpr auto render_dyad_rhs_v> = render_operand_v>; 115 | 116 | constexpr std::size_t n_chars_required(auto v) { 117 | if (v == 0) { 118 | return 1; 119 | } 120 | if (v < 0) { 121 | return 2 + n_chars_required(-v); 122 | } 123 | std::size_t r = 0; 124 | while (v) { 125 | ++r; 126 | v /= 10; 127 | } 128 | return r; 129 | } 130 | 131 | template 132 | constexpr auto render_integral(Const) noexcept { 133 | constexpr auto NumDigits = n_chars_required(Value); 134 | cx_str string; 135 | auto out = string.data() + NumDigits - 1; 136 | if (Value == 0) { 137 | *out = '0'; 138 | } 139 | for (auto val = Value < 0 ? -Value : Value; val; val /= 10, --out) { 140 | auto mod = val % 10; 141 | *out = char('0' + mod); 142 | } 143 | if (Value < 0) { 144 | *out-- = char(0xaf); 145 | *out = char(0xc2); 146 | } 147 | return string; 148 | } 149 | 150 | template 151 | constexpr auto render_template() { 152 | constexpr std::string_view t = NAMEOF_TYPE(L); 153 | constexpr auto angle_pos = t.find("<"); 154 | return cx_str(t.substr(0, angle_pos)); 155 | } 156 | 157 | } // namespace detail 158 | 159 | template 160 | constexpr auto render_v> = cx_str(N.data()); 161 | 162 | template 163 | constexpr auto render_constant_value_v = nullptr; 164 | 165 | template 166 | constexpr auto render_constant_value_v> = render::integer_v; 167 | 168 | template <> 169 | constexpr auto render_v = cx_str{"·"}; 170 | 171 | template 172 | constexpr auto render_v> 173 | = cx_fmt_v<"{} {}", detail::render_operand_v, detail::render_monad_rhs_v>; 174 | 175 | template 176 | constexpr auto render_v> = cx_fmt_v<"{} {} {}", 177 | detail::render_operand_v, 178 | detail::render_operand_v, 179 | detail::render_dyad_rhs_v>; 180 | 181 | template 182 | constexpr auto render_v> = cx_str_join_v<" ; ", render_v...>; 183 | 184 | template 185 | constexpr auto render_v> = render_v; 186 | 187 | template 188 | constexpr auto render_v> = cx_fmt_v<"{} ← {}", render_v, render_v>; 189 | 190 | template 191 | constexpr auto render_v> = cx_fmt_v<"{{{}}}", render_v>; 192 | 193 | template 194 | constexpr auto render_v> = render::integer_v; 195 | 196 | template 197 | constexpr auto render_v> = cx_str_join_v<"‿", detail::render_operand_v...>; 198 | 199 | template 200 | constexpr auto render() noexcept { 201 | return render_v; 202 | } 203 | 204 | template 205 | requires requires { F::dyadic_name(); } 206 | constexpr auto dyadic_name() { 207 | constexpr auto n = F::dyadic_name(); 208 | return cx_str(n); 209 | } 210 | 211 | template 212 | constexpr auto dyadic_name() { 213 | return render::type_v; 214 | } 215 | 216 | template 217 | requires requires { F::monadic_name(); } 218 | constexpr auto monadic_name() { 219 | constexpr auto n = F::monadic_name(); 220 | return cx_str(n); 221 | } 222 | 223 | template 224 | constexpr auto monadic_name() { 225 | return render::type_v; 226 | } 227 | 228 | } // namespace lmno::ast 229 | 230 | namespace lmno::render { 231 | 232 | template 233 | constexpr auto type_v> 234 | = cx_fmt_v<"", ast::render_v>>; 235 | 236 | template 237 | constexpr auto type_v> 238 | = cx_fmt_v<"", ast::render_v>>; 239 | 240 | } // namespace lmno::render 241 | -------------------------------------------------------------------------------- /src/lmno/ast.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./ast.hpp" 2 | #include "./parse.hpp" 3 | 4 | namespace ast = lmno::ast; 5 | using ast::name; 6 | using ast::render_v; 7 | 8 | namespace { 9 | 10 | static_assert(render_v> == "foo"); 11 | 12 | static_assert(render_v> == "⍳"); 13 | 14 | static_assert(render_v, name<"bar">>> == "foo bar"); 15 | 16 | static_assert(render_v> == "foo bar baz"); 17 | static_assert(render_v> == "foo bar baz egg"); 18 | static_assert(render_v> == "foo bar baz quux another"); 19 | static_assert(render_v> 20 | == "foo bar baz · quux another"); 21 | 22 | static_assert(render_v> 23 | == "foo bar baz · quux another"); 24 | 25 | static_assert(render_v> == "foo (bar baz)"); 26 | 27 | static_assert(render_v> == "foo (bar baz) quux"); 28 | static_assert(render_v> == "foo bar (baz quux)"); 29 | 30 | static_assert(render_v> == "foo ; bar"); 31 | static_assert(render_v> == "foo ← two ; bar"); 32 | static_assert(render_v> == "foo ← {5 ⊸ +} ; bar"); 33 | static_assert(ast::render_value_v == "12"); 34 | static_assert(ast::render_value_v == "0"); 35 | static_assert(ast::render_value_v == "¯210"); 36 | 37 | static_assert(render_v> == "⍳ 5"); 38 | static_assert(render_v> == "- 5"); 39 | 40 | static_assert(render_v> == "+ (φ =) ×"); 41 | 42 | static_assert(render_v> == "1‿2‿2‿Q‿1"); 43 | 44 | } // namespace 45 | -------------------------------------------------------------------------------- /src/lmno/concepts/stateless.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace lmno { 9 | 10 | /** 11 | * @brief Specialize for a type to force LMNO to consider it "stateless" 12 | */ 13 | template 14 | constexpr int enable_stateless_v = 0; 15 | 16 | namespace detail { 17 | 18 | template , 20 | typename Seq = std::make_index_sequence> 21 | constexpr bool all_elems_stateless_v = false; 22 | 23 | } // namespace detail 24 | 25 | /** 26 | * @brief Match a type that we can prove has no state. 27 | * 28 | * @tparam T 29 | */ 30 | template 31 | concept stateless = // 32 | enable_stateless_v == true or // 33 | requires { 34 | requires neo::weak_same_as), const int>; 35 | requires enable_stateless_v == 0; 36 | // We can't use "is_literal", but being trivially destructible 37 | // is a reasonable substitute in most cases. 38 | requires neo::trivially_destructible; 39 | requires neo::default_initializable; 40 | // Check for constexpr-constructor-ness 41 | requires(static_cast(T{}), true); 42 | // Finally, it must be either empty: 43 | requires std::is_empty_v // 44 | or requires { 45 | // Or all of its public elements must be empty: 46 | requires std::is_aggregate_v; 47 | requires detail::all_elems_stateless_v; 48 | }; 49 | }; 50 | 51 | template 52 | constexpr bool detail::all_elems_stateless_v> = // 53 | requires { 54 | requires(stateless>> and ...); 55 | }; 56 | 57 | // Enable for tuples that are themselves stateless 58 | template 59 | constexpr bool enable_stateless_v> = (stateless and ...); 60 | 61 | } // namespace lmno 62 | -------------------------------------------------------------------------------- /src/lmno/concepts/stateless.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./stateless.hpp" 2 | 3 | #include 4 | 5 | struct empty {}; 6 | 7 | template 8 | struct aggregate { 9 | Elem p; 10 | }; 11 | 12 | struct inherits : empty { 13 | empty e; 14 | }; 15 | 16 | template <> 17 | constexpr bool lmno::enable_stateless_v = false; 18 | 19 | static_assert(lmno::stateless>); 20 | static_assert(not lmno::stateless); 21 | static_assert(lmno::stateless>); 22 | static_assert(not lmno::stateless>); 23 | static_assert(not lmno::stateless); 24 | -------------------------------------------------------------------------------- /src/lmno/concepts/structural.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace lmno { 6 | 7 | /** 8 | * @brief Match a type that is "structural". Such a type can be used as a non-type template 9 | * argument. 10 | */ 11 | template 12 | concept structural = requires { [](std::integral_constant*) {}; }; 13 | 14 | } // namespace lmno 15 | -------------------------------------------------------------------------------- /src/lmno/concepts/structural.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./structural.hpp" 2 | 3 | struct empty {}; 4 | 5 | struct simple { 6 | int a; 7 | int b; 8 | 9 | constexpr simple(); 10 | }; 11 | 12 | struct non_structural { 13 | private: 14 | int e; 15 | 16 | public: 17 | constexpr non_structural(); 18 | int a; 19 | }; 20 | 21 | static_assert(lmno::structural); 22 | static_assert(lmno::structural); 23 | static_assert(not lmno::structural); 24 | -------------------------------------------------------------------------------- /src/lmno/concepts/typed_constant.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./stateless.hpp" 4 | #include "./structural.hpp" 5 | 6 | #include 7 | 8 | namespace lmno { 9 | 10 | struct typed_constant_base {}; 11 | 12 | template 13 | constexpr bool enable_typed_constant_v 14 | = requires { requires(T::enable_typed_constant ? true : false); }; 15 | 16 | /** 17 | * @brief Match any object which declares itself to be a typed constant and 18 | * satisfies certain constraints 19 | * 20 | * A typed-consteant must inherit from typed_constant_base, or specialize 21 | * enable_typed_constant_v to `true`. It must also expose a static member variable 22 | * `::value` and a member type `type`. The nested `::type` must be a structural type. 23 | * 24 | * The type itself must be stateless. 25 | */ 26 | template 27 | concept typed_constant = // 28 | (neo::derived_from or enable_typed_constant_v) // 29 | and stateless // 30 | and requires(T c) { 31 | // It must declare its type 32 | typename T::type; 33 | // That type must be valid as a non-type template parameter 34 | requires structural; 35 | // ::value must actually be of that type 36 | { T::value } -> neo::weak_same_as; 37 | // We should be able to static_cast to the underlying type: 38 | static_cast(c); 39 | }; 40 | 41 | /** 42 | * @brief Match any type that isn't a typed_constant 43 | * 44 | * @tparam T 45 | */ 46 | template 47 | concept variate = (not typed_constant); 48 | 49 | namespace detail { 50 | 51 | template 52 | struct unconst { 53 | template 54 | using f = T; 55 | }; 56 | 57 | template <> 58 | struct unconst { 59 | template 60 | using f = neo::remove_cvref_t::type const&; 61 | }; 62 | 63 | } // namespace detail 64 | 65 | template 66 | using unconst_t = detail::unconst>>::template f; 67 | 68 | constexpr inline auto unconst 69 | = [](T&& arg) -> unconst_t { return static_cast>(arg); }; 70 | 71 | /** 72 | * @brief Match a typed_constant type that has an integral value 73 | */ 74 | template 75 | concept integral_typed_constant = typed_constant and neo::integral; 76 | 77 | } // namespace lmno 78 | -------------------------------------------------------------------------------- /src/lmno/const.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./concepts/typed_constant.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace lmno { 14 | 15 | using neo::remove_cvref_t; 16 | 17 | template > 18 | struct Const : typed_constant_base { 19 | /** 20 | * @brief The actual value that is bound within the type 21 | */ 22 | static constexpr const Type& value = V; 23 | 24 | /** 25 | * @brief The type of the value of this constant. 26 | */ 27 | using type = remove_cvref_t; 28 | 29 | static_assert(neo::same_as, "Do not specify a different type for Const<>"); 30 | 31 | // An explicit conversion to the underlying type is allowed: 32 | constexpr explicit operator type const&() const noexcept { return value; } 33 | 34 | template 35 | requires neo::constructible_from 36 | constexpr explicit operator Other() const noexcept { 37 | return static_cast(value); 38 | } 39 | 40 | template U, U Vu> 41 | [[nodiscard]] constexpr bool operator==(Const) const noexcept { 42 | return V == Vu; 43 | } 44 | template U, U Vu> 45 | [[nodiscard]] constexpr auto operator<=>(Const) const noexcept { 46 | return V <=> Vu; 47 | } 48 | 49 | [[nodiscard]] constexpr bool operator==(const type& t) const noexcept { return value == t; } 50 | [[nodiscard]] constexpr auto operator<=>(const type& t) const noexcept { return value <=> t; } 51 | 52 | #define DECL_WRAPPER(Func) \ 53 | [[nodiscard]] constexpr decltype(auto) Func(auto&&... args) const noexcept \ 54 | requires requires { value.Func(NEO_FWD(args)...); } \ 55 | { \ 56 | return value.Func(NEO_FWD(args)...); \ 57 | } 58 | 59 | DECL_WRAPPER(size); 60 | DECL_WRAPPER(data); 61 | DECL_WRAPPER(begin); 62 | DECL_WRAPPER(end); 63 | DECL_WRAPPER(cbegin); 64 | DECL_WRAPPER(cend); 65 | DECL_WRAPPER(front); 66 | DECL_WRAPPER(back); 67 | 68 | #undef DECL_WRAPPER 69 | }; 70 | 71 | template 72 | using ConstInt64 = Const; 73 | 74 | template 75 | constexpr bool enable_stateless_v> = true; 76 | 77 | } // namespace lmno 78 | -------------------------------------------------------------------------------- /src/lmno/const.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./const.hpp" 2 | #include "./rational.hpp" 3 | 4 | using namespace lmno; 5 | 6 | static_assert(typed_constant>); 7 | static_assert(structural); 8 | static_assert(typed_constant>); 9 | static_assert(typed_constant>); 10 | 11 | static_assert(std::equality_comparable>); 12 | static_assert(std::totally_ordered>); 13 | 14 | static_assert(Const<12>{} == Const<12>{}); 15 | static_assert(Const<42>{} != Const<12>{}); 16 | static_assert(Const<42>{} != Const<12u>{}); 17 | static_assert(Const<42>{} > Const<12>{}); 18 | 19 | static_assert(structural); 20 | -------------------------------------------------------------------------------- /src/lmno/context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./concepts/stateless.hpp" 4 | #include "./define.hpp" 5 | #include "./lex.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace lmno { 11 | 12 | /** 13 | * @brief A scope of names and their associated values 14 | * 15 | * @tparam Pairs The name-value pairs in scope 16 | */ 17 | template 18 | struct scope; 19 | 20 | /** 21 | * @brief A single name-value pair 22 | */ 23 | template 24 | struct named_value { 25 | NEO_NO_UNIQUE_ADDRESS T _value; 26 | 27 | constexpr static const auto& name = Name; 28 | 29 | constexpr T& get() noexcept { return _value; } 30 | constexpr const T& get() const noexcept { return _value; } 31 | }; 32 | 33 | template 34 | constexpr named_value make_named(T&& item) noexcept { 35 | return named_value{NEO_FWD(item)}; 36 | } 37 | 38 | /** 39 | * @brief A default name lookup context. Contains a scope. When a name lookup fails, 40 | * falls back to lmno::define<> to find the name. 41 | * 42 | * @tparam Scope 43 | */ 44 | template > 45 | struct default_context { 46 | NEO_NO_UNIQUE_ADDRESS Scope _scope; 47 | 48 | /** 49 | * @brief Obtain the definition of the given name 50 | * 51 | * @tparam Name The name being looked up 52 | * @return The value bound to that name, or a sentinel representing its absence. 53 | */ 54 | template 55 | constexpr decltype(auto) get() const noexcept { 56 | if constexpr (Scope::template has_name_v) { 57 | return _scope.template get(); 58 | } else { 59 | return lmno::define; 60 | } 61 | } 62 | 63 | /** 64 | * @brief Create a new context based on the current context, with new names 65 | * bound to the scope. 66 | * 67 | * @tparam Items The new name-value pairs to add. 68 | * @param items 69 | * @return constexpr auto 70 | */ 71 | template 72 | constexpr auto bind(Items&&... items) const noexcept { 73 | auto new_scope = _scope.bind(NEO_FWD(items)...); 74 | return lmno::default_context{NEO_MOVE(new_scope)}; 75 | } 76 | }; 77 | LMNO_AUTO_CTAD_GUIDE(default_context); 78 | 79 | namespace detail { 80 | 81 | template 82 | struct nv_pairs_base { 83 | NEO_NO_UNIQUE_ADDRESS Type value; 84 | }; 85 | 86 | template 87 | constexpr named_value const& get_named(const named_value& p) noexcept { 88 | return p; 89 | } 90 | 91 | template 92 | constexpr named_value& get_named(named_value& p) noexcept { 93 | return p; 94 | } 95 | 96 | using meta::list; 97 | 98 | template 99 | struct name_value_pairs; 100 | 101 | template 102 | struct name_value_pairs...> : named_value... {}; 103 | 104 | template > 105 | struct replace_named; 106 | 107 | template 108 | struct replace_named, list, list> 109 | : replace_named, list, list> {}; 110 | 111 | template 112 | requires((News::name == Old1::name) or ...) 113 | struct replace_named, list, list> 114 | : replace_named, list, list> {}; 115 | 116 | template 117 | struct replace_named, list<>, list> { 118 | using keep = list; 119 | using type = list; 120 | }; 121 | 122 | } // namespace detail 123 | 124 | template 125 | struct scope...> { 126 | 127 | using tuple = detail::name_value_pairs...>; 128 | 129 | NEO_NO_UNIQUE_ADDRESS tuple _values; 130 | 131 | scope() = default; 132 | 133 | constexpr scope(tuple&& t) noexcept 134 | : _values(NEO_MOVE(t)) {} 135 | 136 | template 137 | constexpr static bool has_name_v = ((N == Names) or ...); 138 | 139 | template 140 | using named_t = decltype(detail::get_named(NEO_DECLVAL(tuple)).get()); 141 | 142 | template 143 | requires has_name_v 144 | constexpr named_t get() const noexcept { 145 | return detail::get_named(_values).get(); 146 | } 147 | 148 | template 149 | constexpr auto bind(Pairs&&... ps) const noexcept { 150 | using to_add = meta::list; 151 | using current = meta::rebind; 152 | using replace = detail::replace_named; 153 | using new_list = replace::type; 154 | using keep_list = replace::keep; 155 | return this->_bind_copy(static_cast(nullptr), 156 | static_cast(nullptr), 157 | NEO_FWD(ps)...); 158 | } 159 | 160 | template 161 | constexpr auto _bind_copy(meta::list...>*, 162 | meta::list*, 163 | AddPairs&&... ps) const noexcept { 164 | auto tup = detail::name_value_pairs{{this->template get()}..., 165 | {NEO_FWD(ps)}...}; 166 | return scope(NEO_MOVE(tup)); 167 | } 168 | }; 169 | 170 | template 171 | constexpr bool enable_stateless_v...>> = (stateless and ...); 172 | 173 | template 174 | constexpr bool enable_stateless_v...>>> 175 | = (stateless and ...); 176 | 177 | } // namespace lmno 178 | -------------------------------------------------------------------------------- /src/lmno/context.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./context.hpp" 2 | 3 | #include 4 | 5 | using namespace lmno; 6 | 7 | namespace { 8 | 9 | constexpr scope<> s1 = {}; 10 | 11 | static_assert(not s1.has_name_v<"dog">); 12 | 13 | constexpr auto s2 = s1.bind(make_named<"dog">(12)); 14 | 15 | static_assert(s2.has_name_v<"dog">); 16 | 17 | static_assert(s2.get<"dog">() == 12); 18 | 19 | constexpr auto s3 = s2.bind(make_named<"cat">(std::string_view("I am a string"))); 20 | 21 | static_assert(s3.get<"cat">() == "I am a string"); 22 | 23 | constexpr auto s4 = s3.bind(make_named<"dog">('a')); 24 | static_assert(s4.get<"dog">() == 'a'); 25 | static_assert(s4.get<"cat">() == "I am a string"); 26 | 27 | struct nonempty { 28 | std::plus<> p; 29 | }; 30 | 31 | static_assert(stateless>>); 32 | 33 | } // namespace 34 | -------------------------------------------------------------------------------- /src/lmno/define.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./error.hpp" 4 | #include "./lex.hpp" 5 | 6 | namespace lmno { 7 | 8 | template 9 | constexpr auto make_undefined_name() { 10 | return err::make_error<"The name {:'} is not defined", Name>(); 11 | } 12 | 13 | template 14 | constexpr auto define = make_undefined_name{std::string_view(Name)}>(); 15 | 16 | } // namespace lmno 17 | -------------------------------------------------------------------------------- /src/lmno/error.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./string.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace lmno::err { 11 | 12 | /// Base class of all errors, used to detect whether a type is an error 13 | struct error_base {}; 14 | 15 | /** 16 | * @brief Class template that encloses an error message, as well as an optional 17 | * child error 18 | * 19 | * @tparam Message The message string that represents the error 20 | * @tparam Child A child error that provides additional context 21 | */ 22 | template 23 | struct [[nodiscard]] error_type : error_base { 24 | // The child type associated with this error, or void 25 | using child = Child; 26 | 27 | // The error message string associated with this error 28 | static constexpr const auto& message = Message; 29 | }; 30 | 31 | /** 32 | * @brief Construct an error object using a string format message 33 | */ 34 | template 35 | requires(lmno::cx_sized_string and ...) 36 | constexpr auto make_error() { 37 | constexpr auto s = lmno::cx_fmt_v; 38 | return error_type{}; 39 | } 40 | 41 | /** 42 | * @brief Construct an error object using a string format message, with the given 43 | * child error type 44 | */ 45 | template 46 | requires(lmno::cx_sized_string and ...) 47 | constexpr auto make_error() { 48 | constexpr auto s = lmno::cx_fmt_v; 49 | return error_type{}; 50 | } 51 | 52 | template 53 | requires(lmno::cx_sized_string and ...) 54 | using fmt_error_t = decltype(make_error()); 55 | 56 | template 57 | requires(lmno::cx_sized_string and ...) 58 | using fmt_errorex_t = decltype(make_error()); 59 | 60 | } // namespace lmno::err 61 | 62 | namespace lmno { 63 | 64 | /** 65 | * @brief Match any cvr-qualified type that represents a compile-time error 66 | */ 67 | template 68 | concept any_error = neo::derived_from, err::error_base>; 69 | 70 | /** 71 | * @brief Determinate whether the given named value designates an error 72 | */ 73 | #define LMNO_IS_ERROR(...) (::lmno::any_error) 74 | 75 | /** 76 | * @brief Match any type that is not a cvr-qualified compile-time error 77 | */ 78 | template 79 | concept non_error = (not any_error); 80 | 81 | } // namespace lmno 82 | -------------------------------------------------------------------------------- /src/lmno/eval.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./eval.hpp" 2 | 3 | #include "./stdlib.hpp" 4 | 5 | #include 6 | 7 | using lmno::Const; 8 | using lmno::ConstInt64; 9 | using lmno::eval; 10 | using lmno::eval_v; 11 | using lmno::rational; 12 | 13 | static_assert(eval<"4">() == 4); 14 | static_assert(eval<"4">() == 4u); 15 | static_assert(eval<"17">() == Const<17>{}); 16 | static_assert(eval<"174">() == Const<174>{}); 17 | static_assert(eval<"¯174">() == Const<-174>{}); 18 | 19 | static_assert(eval_v<"3"> == 3); 20 | static_assert(eval_v<"3+4"> == 7); 21 | 22 | static_assert(eval<"4+3">() == 7); 23 | static_assert(eval<"- 4+3">() == -7); 24 | 25 | static_assert(eval<"4+3">() == 7); 26 | static_assert(eval<"4×3">() == 12); 27 | 28 | static_assert(eval<"5 - 3">() == 2); 29 | 30 | static_assert(eval<"-4">() == -4); 31 | 32 | static_assert(eval<"4÷3">() == rational{4, 3}); 33 | 34 | static_assert(std::same_as, ConstInt64<7>>); 35 | static_assert(std::same_as, Const>); 36 | 37 | // Hi-dot "const" operator 38 | static_assert(eval<"˙5">()(4) == 5); 39 | // Just-left 40 | static_assert(eval<"⊣">()(4) == 4); 41 | static_assert(eval<"⊣">()(4, 5) == 4); 42 | // Just-right 43 | static_assert(eval<"⊢">()(4) == 4); 44 | static_assert(eval<"⊢">()(4, 8) == 8); 45 | // minus-atop-minus 46 | static_assert(eval<"-∘-">()(4) == 4); 47 | // minus atop 2× 48 | static_assert(eval<"(2⊸×)∘-">()(4) == -8); 49 | static_assert(eval<"(×⟜2)∘-">()(4) == -8); 50 | // φ combinator over ⌈ 51 | static_assert(eval<"-φ:⌈+">()(8, -12) == 20); 52 | // Swap ˜ 53 | static_assert(eval<"-φ:⌈˜:-">()(8, 12) == 4); 54 | static_assert(eval<"-φ:⌈˜:-">()(12, 8) == 4); 55 | // Fold 56 | static_assert(eval<"/:+∘⍳">()(6) == 15); 57 | static_assert(eval<"/:+∘⍳">()(Const<6>{}) == 15); 58 | static_assert(eval<"3⊸÷">()(4, 10) == rational{3, 10}); 59 | 60 | // Concept checks 61 | static_assert(lmno::stateless>); 62 | static_assert(lmno::stateless>); 63 | static_assert(lmno::stateless>); 64 | static_assert(lmno::stateless>); 65 | static_assert(lmno::stateless>); 66 | 67 | static_assert(lmno::stateless); 68 | static_assert(lmno::stateless>); 69 | static_assert(lmno::stateless>); 70 | static_assert( 71 | lmno::stateless, 72 | lmno::named_value<"ω", lmno::Const<0>>>>>); 73 | static_assert( 74 | lmno::stateless< 75 | lmno::closure, 76 | lmno::default_sema, 77 | lmno::default_context, 78 | lmno::named_value<"ω", lmno::Const<0>>>>>>); 79 | 80 | static_assert( 81 | not lmno::stateless< 82 | lmno::closure, 83 | lmno::default_sema, 84 | lmno::default_context, 85 | lmno::named_value<"ω", lmno::Const<0>>>>>>); 86 | 87 | // Simply requires that its argument be a typed-constant of value V 88 | template 89 | requires(V == U) 90 | constexpr void is_constant(Const) {} 91 | 92 | // An operator func that is purposefully non-constexpr: 93 | struct not_constexpr { 94 | auto operator()(int a) const NEO_RETURNS(a * 2); 95 | }; 96 | 97 | TEST_CASE("Cases") { 98 | constexpr auto pow2 = eval<"2⊸^">(); 99 | static_assert(pow2(8) == 256); 100 | 101 | constexpr auto drop = eval<"2↓·⍳5">(); 102 | CHECK(drop.size() == 3); 103 | 104 | // Simple runtime closure: 105 | constexpr auto f2 = eval<"{2}">(); 106 | static_assert(f2(1) == 2); 107 | 108 | // Closures must support both Const<> and runtime values: 109 | constexpr auto f3 = eval<"{2+ω}">(); 110 | ConstInt64<4> four [[maybe_unused]] = f3(Const<2>{}); 111 | static_assert(f3(4) == 6); 112 | 113 | // Miscellaneous: 114 | is_constant<6>(eval<"3+3">()); 115 | is_constant<6>(eval<"{3+ω}">()(Const<3>{})); 116 | is_constant<6>(eval<"/+$⍳4">()); 117 | is_constant<4>(eval<"{0+ω}">()(Const<4>{})); 118 | is_constant<4>(eval<"{ω}">()(Const<4>{})); 119 | is_constant<2>(eval<"{{ω}2}0">()); 120 | is_constant<2>(eval<"{{ω}2}">()(Const<0>{})); 121 | is_constant<6>(eval<"/:+∘⍳">()(Const<4>{})); 122 | is_constant<6>(eval<"{/+$⍳ω}">()(Const<4>{})); 123 | is_constant<6>(eval<"{0⊸·/+$⍳ω}">()(Const<4>{})); 124 | is_constant<6>(eval<"{0⊸·/{α+ω}$⍳ω}">()(Const<4>{})); 125 | 126 | // Pass a non-constexpr, then call it: 127 | lmno::non_error auto v = eval<"⊢">()(not_constexpr{})(7); 128 | CHECK(v == 14); 129 | 130 | // Create a closure returning a function: 131 | constexpr auto f5 = eval<"{+⟜ω}">(); 132 | constexpr auto add5 = f5(5); 133 | static_assert(add5(4) == 9); 134 | 135 | // Sequence expressions: 136 | static_assert(eval<"foo ← 5 ; foo + 2">() == 7); 137 | 138 | // Assignment as the only statement: The name goes nowhere, but doesn't matter 139 | static_assert(eval<"foo ← 8">() == 8); 140 | 141 | // Name reassignment: Just overrides the prior name 142 | static_assert(eval<"foo ← 5; foo ← 42; foo">() == 42); 143 | 144 | // Compute the Nth inverse of a power of two: 145 | constexpr lmno::non_error auto fn [[maybe_unused]] = eval<"1⊸·/+⟜÷∘2⊸^ $ ∘ $ 1φ:↓⍳">(); 146 | constexpr lmno::non_error auto quo = fn(63); 147 | static_assert(quo == rational{9'223'372'036'854'775'807, 4'611'686'018'427'387'904}); 148 | // Written using closures: 149 | constexpr auto f4 = eval<"{ 0 /:{α + 1÷2^ω} ·⍳ ω }">(); 150 | is_constant(f4(Const<63>{})); 151 | 152 | // Imperative style: 153 | constexpr auto imper = eval(); 158 | 159 | static_assert(f4(63) == fn(63)); 160 | static_assert(f4(63) == imper(63)); 161 | 162 | // Even more imperative: 163 | constexpr auto imper2 = eval(); 170 | 171 | static_assert( 172 | std::same_as{})), 173 | Const>); 174 | 175 | // Create some errors: 176 | lmno::any_error auto e [[maybe_unused]] = eval<"/:+⟜⍳7">(); 177 | lmno::any_error auto e1 [[maybe_unused]] = eval<"÷">()(std::string("yo")); 178 | lmno::any_error auto e2 [[maybe_unused]] = eval<"2⊸÷">()(std::string("yo")); 179 | 180 | // Mapping: 181 | constexpr lmno::non_error auto add_four = eval<"¨{4+ω}">(); 182 | constexpr auto nums = std::array{2, 1, 4, -3, 1, -44}; 183 | lmno::non_error auto added = add_four(nums); 184 | CHECK(*added.as_range().begin() == 6); 185 | 186 | using namespace std::literals; 187 | 188 | // Attempt to add a number to a string is invalid: 189 | // A function that adds 2 to every element in a range 190 | auto add_two_to_each = eval<"¨2⊸+">(); 191 | // An array of strings 192 | auto string_array = {"foo"s, "bar"s, "baz"s}; 193 | // That's nonsense. 194 | lmno::any_error auto result [[maybe_unused]] = add_two_to_each(string_array); 195 | // Attempt to "use" `result` to see the error message: 196 | // result.foo; 197 | } 198 | -------------------------------------------------------------------------------- /src/lmno/func_wrap.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace lmno { 4 | 5 | template 6 | struct func_wrap : decltype(Func) { 7 | using decltype(Func)::operator(); 8 | }; 9 | 10 | } // namespace lmno 11 | -------------------------------------------------------------------------------- /src/lmno/lex.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./meta.hpp" 4 | #include "./string.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace lmno::lex { 12 | 13 | constexpr static std::size_t max_token_length = 23; 14 | 15 | /** 16 | * @brief Represents a LMNO token. 17 | * 18 | * Structural and valid as a non-type template parameter 19 | */ 20 | struct token { 21 | char _chars[max_token_length + 1] = {}; 22 | 23 | constexpr token() = default; 24 | 25 | constexpr token(const char* s) { 26 | auto dst = _chars; 27 | std::size_t n = 0; 28 | while (*s) { 29 | ++n; 30 | assert(n <= max_token_length); 31 | *dst++ = *s++; 32 | } 33 | } 34 | 35 | constexpr operator std::string_view() const noexcept { return std::string_view(_chars); } 36 | 37 | constexpr const char* data() const noexcept { return _chars; } 38 | constexpr std::size_t size() const noexcept { return std::char_traits::length(_chars); } 39 | constexpr char operator[](std::size_t n) const noexcept { 40 | assert(n < max_token_length); 41 | return _chars[n]; 42 | } 43 | 44 | bool operator==(const token&) const = default; 45 | }; 46 | 47 | template 48 | struct token_list; 49 | 50 | constexpr bool is_digit(char c) { return c <= '9' and c >= '0'; } 51 | constexpr bool is_alpha(char c) { return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z'); } 52 | constexpr bool is_ident(char c) { return is_alpha(c) or is_digit(c); } 53 | 54 | namespace detail { 55 | 56 | using meta::list; 57 | 58 | constexpr auto fin_token(const char* s, std::size_t len) { 59 | token ret; 60 | for (auto i = 0u; i < len; ++i) { 61 | ret._chars[i] = s[i]; 62 | } 63 | return ret; 64 | } 65 | 66 | /** 67 | * @brief Represents the position of a token within a larger string 68 | * 69 | */ 70 | struct token_range { 71 | /// The beginning offset of the token 72 | std::uint32_t pos; 73 | /// The length of the token (in char) 74 | std::uint8_t len; 75 | }; 76 | 77 | /** 78 | * @brief Obtain the next token_range from the given string at the specified offset 79 | * 80 | * @param begin A pointer to the very beginning of the source string 81 | * @param begin_pos The offset within the string to begin searching for another token 82 | * @return constexpr token_range 83 | */ 84 | constexpr token_range next_token(const char* const begin, std::uint32_t start_pos) { 85 | auto it = begin + start_pos; 86 | // The beginning index of the token within the string view: 87 | std::uint32_t pos = start_pos; 88 | // Skip leading whitespace 89 | while (*it == ' ' or *it == '\n') { 90 | ++it; 91 | ++pos; 92 | } 93 | 94 | const char first = *it; 95 | if (first & 0b1000'0000) { 96 | if (*it) { 97 | auto b = static_cast(first); 98 | switch ((b >> 4) & 0xf) { 99 | case 0b1100: 100 | if (it[1] == char(0xaf) and it[0] == char(0xc2)) { 101 | // A leading hi-bar '¯', which introduces a negative literal 102 | token_range peek = next_token(begin, pos + 2); 103 | peek.pos = pos; 104 | peek.len += 2; 105 | return peek; 106 | } 107 | [[fallthrough]]; 108 | case 0b1101: 109 | return {pos, 2}; 110 | case 0b1110: 111 | return {pos, 3}; 112 | default: 113 | return {pos, 4}; 114 | } 115 | } 116 | } 117 | 118 | // Regular ASCII char 119 | if (first >= 'A') { 120 | if (is_ident(first)) { 121 | // Ident 122 | ++it; 123 | std::uint8_t len = 1; 124 | while (is_ident(*it)) { 125 | ++it; 126 | ++len; 127 | } 128 | return {pos, len}; 129 | } else { 130 | return {pos, 1}; 131 | } 132 | } else { 133 | if (is_digit(first)) { 134 | auto num_begin = it; 135 | ++it; 136 | while (is_digit(*it)) { 137 | ++it; 138 | } 139 | auto len = static_cast(it - num_begin); 140 | return {pos, len}; 141 | } else { 142 | // End case: Return an EOF 143 | if (not *it) { 144 | return {pos, 0}; 145 | } 146 | if (first == '(' and it[1] == ':') { 147 | // We're in a comment 148 | it += 2; 149 | pos += 2; 150 | while (*it and (it[0] != ':' or it[1] != ')')) { 151 | ++it; 152 | ++pos; 153 | } 154 | if (not *it) { 155 | throw "Unterminated comment in source string"; 156 | } 157 | it += 2; 158 | pos += 2; 159 | return next_token(begin, pos); 160 | } 161 | // Other: 162 | return {pos, 1}; 163 | } 164 | } 165 | } 166 | 167 | template 168 | struct tokenize_result { 169 | std::array tokens; 170 | int num_tokens; 171 | }; 172 | 173 | template 174 | constexpr tokenize_result tokenize(const char* _chars) { 175 | tokenize_result ret = {}; 176 | ret.num_tokens = tokenize(ret.tokens.data(), _chars); 177 | return ret; 178 | } 179 | 180 | constexpr int tokenize(token_range* into, char const* const cptr) { 181 | int n_tokens = 0; 182 | std::uint32_t off = 0; 183 | while (1) { 184 | auto& tr = *into++ = detail::next_token(cptr, off); 185 | if (tr.len == 0) { 186 | break; 187 | } 188 | ++n_tokens; 189 | off = tr.pos + tr.len; 190 | } 191 | return n_tokens; 192 | } 193 | 194 | constexpr token take_token(const char* str_begin, token_range const& r) { 195 | return detail::fin_token(str_begin + r.pos, r.len); 196 | } 197 | 198 | template 199 | auto prune_f(std::index_sequence*) 200 | -> token_list; 201 | 202 | template 203 | auto tokenize() { 204 | constexpr auto res = tokenize(String.data()); 205 | return meta::ptr( 206 | (std::make_index_sequence*)(nullptr)))>{}; 207 | } 208 | 209 | } // namespace detail 210 | 211 | template 212 | using tokenize_t = neo::remove_pointer_t())>; 213 | 214 | } // namespace lmno::lex 215 | -------------------------------------------------------------------------------- /src/lmno/md/aplib.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./range.hpp" 4 | #include "cursor.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace lmno::md { 10 | 11 | // clang-format off 12 | template 13 | concept mdrange_adaptable_container = 14 | std::ranges::forward_range 15 | and std::ranges::sized_range 16 | and std::ranges::output_range> 17 | and requires(C& mref, 18 | const std::remove_reference_t& cref, 19 | std::ranges::range_size_t size, 20 | std::ranges::range_difference_t offset, 21 | std::ranges::range_reference_t ref, 22 | std::ranges::range_value_t const& value) { 23 | // clang-format on 24 | { mref.resize(size) }; 25 | { mref.resize(size, value) }; 26 | // clang-format off 27 | }; 28 | // clang-format on 29 | 30 | template 31 | class mdarray_adaptor { 32 | public: 33 | using container_type = Container; 34 | using extents_type = Extents; 35 | using reference = std::ranges::range_reference_t; 36 | using const_reference = std::ranges::range_reference_t; 37 | using index_type = std::array; 38 | 39 | private: 40 | NEO_NO_UNIQUE_ADDRESS extents_type _shape = extents_type(); 41 | NEO_NO_UNIQUE_ADDRESS container_type _container = container_type(); 42 | 43 | public: 44 | constexpr mdarray_adaptor() { _container.resize(md::bounds(_shape)); } 45 | 46 | template 47 | requires std::constructible_from 48 | explicit(not std::convertible_to) // 49 | constexpr mdarray_adaptor(Shape&& shape) 50 | : _shape(NEO_FWD(shape)) { 51 | _container.resize(md::bounds(shape)); 52 | } 53 | constexpr const extents_type& extents() const noexcept { return _shape; } 54 | 55 | constexpr reference operator[](index_type const& idx) noexcept { 56 | const auto span = md::mdspan_for_range(_container, _shape); 57 | reference el = span[idx]; 58 | return el; 59 | } 60 | 61 | constexpr const_reference operator[](index_type const& idx) const noexcept { 62 | const auto span = md::mdspan_for_range(_container, _shape); 63 | const_reference el = span[idx]; 64 | return el; 65 | } 66 | 67 | template 68 | requires std::constructible_from 69 | constexpr void reshape(Shape&& ext) noexcept { 70 | reshape(extents_type(ext)); 71 | } 72 | 73 | constexpr void reshape(index_type new_shape) noexcept { 74 | this->reshape(extents_type(new_shape)); 75 | } 76 | 77 | constexpr void reshape(extents_type new_shape) noexcept { 78 | md::reshape(_container, extents(), new_shape); 79 | _shape = new_shape; 80 | } 81 | 82 | constexpr auto zero_cells() noexcept { return std::ranges::subrange(_container); } 83 | constexpr auto zero_cells() const noexcept { return std::ranges::subrange(_container); } 84 | 85 | // TODO: Define an origin() and a cursor type for this adaptor. 86 | //* almost: ... 87 | // constexpr auto origin() noexcept { 88 | // return md::origin(md::mdspan_for_range(_container, _shape)); 89 | // } 90 | // constexpr auto origin() const noexcept { 91 | // return md::origin(md::mdspan_for_range(_container, _shape)); 92 | // } 93 | //* (but not quite...) 94 | }; 95 | 96 | template 97 | using mdvector = mdarray_adaptor, Shape>; 98 | 99 | template 100 | using mdvector_of_rank = mdvector>; 101 | 102 | } // namespace lmno::md 103 | -------------------------------------------------------------------------------- /src/lmno/md/cursor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "./shape.hpp" 8 | 9 | namespace lmno::md { 10 | 11 | namespace detail { 12 | 13 | template 14 | requires requires(void (&fn)(const Off&)) { 15 | // Check: We can construct it using N offsets 16 | Off{Ns...}; 17 | fn({Ns...}); 18 | // Check: Default-construction is equivalent to initializing it with all zero 19 | requires(Off{} == Off{(Ns - Ns)...}); 20 | } 21 | void check_cursor_construct(std::integer_sequence); 22 | 23 | } // namespace detail 24 | 25 | /** 26 | * @brief A type that can be used as the offset for a multi-dimensional cursor. 27 | * 28 | * Requires a static rank(), and must be constructible using rank()-count of std::ptrdiff_t, or 29 | * default-constructed.. 30 | * 31 | * @tparam T 32 | */ 33 | template 34 | concept offset = neo::regular // 35 | and requires(T off, std::ptrdiff_t pos) { 36 | requires(T::rank() >= 0); 37 | off[1] = pos; 38 | /** 39 | * Require that, for a pack Ns... of rank() ptrdiff_t: 40 | * • Valid: T{Ns...} 41 | * • Valid: T{} 42 | * • Valid: T o = {Ns...}; 43 | * • Require: T{} == T{(Ns-Ns)...} 44 | */ 45 | detail::check_cursor_construct( 46 | std::make_integer_sequence{}); 47 | }; 48 | 49 | template 50 | using cursor_offset_t = decltype(NEO_DECLVAL(neo::const_reference_t) 51 | .difference(NEO_DECLVAL(neo::const_reference_t))); 52 | 53 | template 54 | using cursor_reference_t = decltype(NEO_DECLVAL(neo::const_reference_t).get()); 55 | 56 | // clang-format off 57 | template 58 | concept cursor = 59 | neo::semiregular 60 | and requires { 61 | typename cursor_offset_t; 62 | requires offset>; 63 | } 64 | and requires(neo::const_reference_t c, cursor_offset_t off) { 65 | { c.adjust(off) } -> neo::weak_same_as; 66 | requires neo_is_referencable(cursor_reference_t); 67 | }; 68 | 69 | template 70 | concept output_cursor = 71 | cursor 72 | and neo::assignable_from, T>; 73 | // clang-format on 74 | 75 | template 76 | struct basic_offset { 77 | std::ptrdiff_t _coords[R] = {}; 78 | 79 | static constexpr std::size_t rank() noexcept { return R; } 80 | 81 | inline constexpr std::ptrdiff_t const& operator[](std::size_t nth) const noexcept { 82 | return _coords[nth]; 83 | } 84 | 85 | inline constexpr std::ptrdiff_t& operator[](std::size_t nth) noexcept { return _coords[nth]; } 86 | 87 | bool operator==(const basic_offset&) const = default; 88 | }; 89 | 90 | template 91 | explicit basic_offset(Is...) -> basic_offset; 92 | 93 | template 94 | struct range_cursor { 95 | using _iterator = std::ranges::iterator_t; 96 | 97 | NEO_NO_UNIQUE_ADDRESS _iterator _iter; 98 | 99 | range_cursor() = default; 100 | 101 | constexpr explicit range_cursor(T view) noexcept 102 | : _iter(std::ranges::begin(view)) {} 103 | 104 | constexpr explicit range_cursor(_iterator i) noexcept 105 | : _iter(i) {} 106 | 107 | constexpr decltype(auto) get() const noexcept { return *_iter; } 108 | 109 | constexpr range_cursor adjust(basic_offset<1> off) const noexcept { 110 | return range_cursor{_iter + off[0]}; 111 | } 112 | 113 | constexpr basic_offset<1> difference(range_cursor other) const noexcept { 114 | return basic_offset<1>{other._iter - _iter}; 115 | } 116 | }; 117 | 118 | template 119 | explicit range_cursor(V) -> range_cursor; 120 | 121 | template 122 | class c_array_cursor { 123 | public: 124 | static constexpr std::size_t rank() noexcept { return std::rank_v; } 125 | 126 | private: 127 | using _offset = basic_offset; 128 | using _iseq = std::make_index_sequence*; 129 | using _ref = std::remove_all_extents_t&; 130 | 131 | public: 132 | c_array_cursor() = default; 133 | 134 | constexpr explicit c_array_cursor(Array& arr) noexcept 135 | : _ptr(arr) {} 136 | 137 | constexpr _offset difference(c_array_cursor const& other) const noexcept { 138 | return _make_diff(_pos, other._pos, _iseq{}); 139 | } 140 | 141 | constexpr _ref get() const noexcept { return _get(_ptr, _pos, _iseq{}); } 142 | 143 | constexpr c_array_cursor adjust(_offset by) const noexcept { return _adjust(by, _iseq{}); } 144 | 145 | private: 146 | using _pointer = neo::decay_t; 147 | 148 | constexpr explicit c_array_cursor(_pointer p, _offset o) noexcept 149 | : _ptr(p) 150 | , _pos(o) {} 151 | 152 | template 153 | constexpr static _offset 154 | _make_diff(_offset const& self, _offset const& other, std::index_sequence*) noexcept { 155 | return _offset{(self[Idx] - other[Idx])...}; 156 | } 157 | 158 | template 159 | constexpr static _ref _get(_pointer ptr, _offset pos, std::index_sequence*) noexcept { 160 | return _deref(ptr, pos[Idx]...); 161 | } 162 | 163 | template 164 | constexpr c_array_cursor _adjust(_offset by, std::index_sequence*) const noexcept { 165 | return c_array_cursor{_ptr, _offset{_pos[Idx] + by[Idx]...}}; 166 | } 167 | 168 | constexpr static _ref _deref(auto ptr, auto off, auto... more) noexcept { 169 | return _deref(ptr[off], more...); 170 | } 171 | 172 | constexpr static _ref _deref(_ref r) noexcept { return r; } 173 | 174 | public: // Public allows us to be a structural type 175 | _pointer _ptr = nullptr; 176 | _offset _pos; 177 | }; 178 | 179 | template 180 | requires detail::bounded_md_carray_v 181 | explicit c_array_cursor(const Array&) -> c_array_cursor; 182 | 183 | /** 184 | * @brief Wraps a cursor object with a convenience API 185 | * 186 | * @tparam T A cursor type to wrap 187 | */ 188 | template 189 | struct augmented_cursor { 190 | using offset_type = cursor_offset_t; 191 | 192 | T _cursor; 193 | 194 | /** 195 | * @brief Obtain the element referenced by the cursor's current location 196 | * 197 | * @return constexpr decltype(auto) 198 | */ 199 | inline constexpr cursor_reference_t operator*() const noexcept { return _cursor.get(); } 200 | 201 | /** 202 | * @brief Obtain a new augmented cursor adjusted by the given offset 203 | */ 204 | inline constexpr augmented_cursor operator+(offset_type off) const noexcept { 205 | return augmented_cursor{_cursor.adjust(off)}; 206 | } 207 | 208 | /** 209 | * @brief Adjust the cursor with the given offset 210 | */ 211 | inline constexpr augmented_cursor& operator+=(offset_type off) const noexcept { 212 | return *this = *this + off; 213 | } 214 | 215 | inline constexpr cursor_reference_t operator[](offset_type off) const noexcept { 216 | return *(*this + off); 217 | } 218 | }; 219 | 220 | template 221 | explicit augmented_cursor(T) -> augmented_cursor; 222 | 223 | } // namespace lmno::md 224 | -------------------------------------------------------------------------------- /src/lmno/md/cursor.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./cursor.hpp" 2 | 3 | #include 4 | 5 | namespace md = lmno::md; 6 | 7 | int main() {} 8 | 9 | static_assert(md::cursor&>>>); 10 | -------------------------------------------------------------------------------- /src/lmno/md/mdspan.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "./kokkos-mdspan.hpp" 4 | 5 | namespace std::experimental { 6 | 7 | // Kokkos does not include a deduction guide, but we want that 8 | template 9 | explicit extents(Is...) -> extents; 10 | 11 | } // namespace std::experimental 12 | 13 | namespace lmno::md { 14 | 15 | using std::experimental::dextents; 16 | using std::experimental::dynamic_extent; 17 | using std::experimental::extents; 18 | using std::experimental::full_extent; 19 | using std::experimental::full_extent_t; 20 | using std::experimental::layout_left; 21 | using std::experimental::layout_right; 22 | using std::experimental::layout_stride; 23 | using std::experimental::mdspan; 24 | using std::experimental::submdspan; 25 | 26 | template 27 | using uz_extents = extents; 28 | 29 | template 30 | using uz_dextents = dextents; 31 | 32 | } // namespace lmno::md 33 | 34 | namespace lmno { 35 | 36 | using lmno::md::mdspan; 37 | using lmno::md::submdspan; 38 | 39 | } // namespace lmno 40 | -------------------------------------------------------------------------------- /src/lmno/md/range.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "./cursor.hpp" 9 | #include "./shape.hpp" 10 | 11 | #include 12 | 13 | namespace lmno::md { 14 | 15 | /** 16 | * @brief Obtain a cursor pointing to the origin of a multidimensional range. 17 | */ 18 | inline constexpr struct origin_fn { 19 | template 20 | [[nodiscard]] constexpr auto operator()(A& a) const noexcept 21 | requires detail::non_array_range 22 | or detail::bounded_md_carray_v> // 23 | or requires { 24 | { a.origin() } -> cursor; 25 | } 26 | { 27 | if constexpr (detail::non_array_range) { 28 | auto view = std::views::all(a); 29 | return range_cursor(view); 30 | } else if constexpr (detail::bounded_md_carray_v>) { 31 | return c_array_cursor(a); 32 | } else { 33 | return a.origin(); 34 | } 35 | } 36 | } origin; 37 | 38 | /** 39 | * @brief Obtain the cursor type for the given mdrange 40 | */ 41 | template 42 | using cursor_t = decltype(origin(NEO_DECLVAL(A&))); 43 | 44 | /** 45 | * @brief Obtain the rank of the given mdrange 46 | */ 47 | template 48 | constexpr std::size_t rank_v = shape_t::rank(); 49 | 50 | /** 51 | * @brief Obtain the offset type used for the given mdrange's cursor 52 | */ 53 | template 54 | using offset_t = cursor_offset_t>; 55 | 56 | /** 57 | * @brief Get the reference-type returned by the given mdrange's cursor 58 | */ 59 | template 60 | using reference_t = cursor_reference_t>; 61 | 62 | template 63 | concept mdrange = // 64 | requires(A& a) { 65 | md::origin(a); 66 | md::shapeof(a); 67 | }; 68 | 69 | /** 70 | * @brief Match an mdrange that has a compiled-time fixed shape 71 | */ 72 | template 73 | concept fixed_shape_mdrange = mdrange and fixed_shape>; 74 | 75 | /** 76 | * @brief Compute the bounds of the given mdrange, i.e. the total number of 0-cells in the array 77 | */ 78 | inline constexpr struct bounds_fn { 79 | [[nodiscard]] constexpr auto operator()(shape auto const& e) const noexcept { 80 | std::size_t ret = 1; 81 | auto r = static_cast(e.rank()); 82 | for (auto n : std::views::iota(r - r, r)) { 83 | auto x = static_cast(e.extent(n)); 84 | ret = ret * x; 85 | } 86 | return ret; 87 | } 88 | [[nodiscard]] constexpr auto operator()(mdrange auto const& r) const noexcept { 89 | return (*this)(md::shapeof(r)); 90 | } 91 | } bounds; 92 | 93 | /** 94 | * @brief Obtain the rank of the given mdrange, i.e. the number of dimensions in the array. 95 | */ 96 | inline constexpr struct rank_fn { 97 | [[nodiscard]] constexpr auto operator()(shape auto const& e) const noexcept { return e.rank(); } 98 | [[nodiscard]] constexpr auto operator()(mdrange auto const& r) const noexcept { 99 | return md::shapeof(r).rank(); 100 | } 101 | } rank; 102 | 103 | template 104 | class range_md_accessor { 105 | public: 106 | using offset_policy = range_md_accessor; 107 | using element_type = std::ranges::range_value_t; 108 | using reference = std::ranges::range_reference_t; 109 | using pointer = std::ranges::iterator_t; 110 | 111 | // nonstd: Used by Kokkos instead of 'pointer' 112 | using data_handle_type = pointer; 113 | 114 | public: 115 | range_md_accessor() = default; 116 | 117 | constexpr pointer offset(pointer from, std::ptrdiff_t off) const noexcept { 118 | pointer to = std::ranges::next(from, off); 119 | return to; 120 | } 121 | 122 | constexpr reference access(pointer p, std::size_t nth) const noexcept { 123 | pointer adjust = this->offset(p, static_cast(nth)); 124 | reference v = *adjust; 125 | return v; 126 | } 127 | }; 128 | 129 | template 130 | requires std::ranges::viewable_range 131 | constexpr auto mdspan_for_range(R&& r, E shp) noexcept { 132 | auto iter = std::ranges::begin(r); 133 | auto mapping = std::experimental::layout_right::mapping(shp); 134 | auto access = range_md_accessor(); 135 | return mdspan(iter, mapping, access); 136 | } 137 | 138 | template 139 | requires std::ranges::viewable_range 140 | constexpr auto flat_mdspan_for_range(R&& r) noexcept { 141 | using shape_type = uz_dextents<1>; 142 | shape_type shp(std::ranges::distance(r)); 143 | return mdspan_for_range(NEO_FWD(r), shp); 144 | } 145 | 146 | inline constexpr struct view_extents_fn { 147 | constexpr auto operator()(md::shape auto shp) const noexcept { 148 | auto is = std::views::iota(0, shp.rank()); 149 | return std::views::transform(is, [shp](auto ax) { return shp.extent(ax); }); 150 | } 151 | } view_extents; 152 | 153 | namespace detail { 154 | 155 | namespace _sr = std::ranges; 156 | namespace _sv = std::views; 157 | 158 | constexpr void reshape_move_items(bool reverse, 159 | auto& vec, 160 | auto map_from, 161 | auto map_to, 162 | std::same_as auto... idx) { 163 | if constexpr (sizeof...(idx) == decltype(map_to)::extents_type::rank()) { 164 | // We're acting on the 0-cells 165 | // Get the previous index of the item and its new destination index 166 | const std::size_t from_idx = map_from(idx...); 167 | const std::size_t to_idx = map_to(idx...); 168 | // The iterators for those positions: 169 | const auto it_from = _sr::next(_sr::begin(vec), from_idx); 170 | const auto it_to = _sr::next(_sr::begin(vec), to_idx); 171 | // Do the move: 172 | auto& dest = *it_to; 173 | auto&& value = _sr::iter_move(it_from); 174 | dest = static_cast(value); 175 | } else { 176 | // Iterate over the K-cells, recusing into the inner cells 177 | const std::size_t tox = map_to.extents().extent(sizeof...(idx)); 178 | const std::size_t fromx = map_from.extents().extent(sizeof...(idx)); 179 | // Don't iterate over K-cells that weren't in the prior shape, nor 180 | // K-cells that won't be present in the new shape: 181 | const std::size_t len = (std::min)(tox, fromx); 182 | for (std::size_t nth : _sv::iota(0u, len)) { 183 | if (reverse) { 184 | // Caller wants us to reverse the iteration order 185 | nth = len - nth - 1; 186 | } 187 | reshape_move_items(reverse, vec, map_from, map_to, idx..., nth); 188 | } 189 | } 190 | } 191 | 192 | } // namespace detail 193 | 194 | namespace _sr = std::ranges; 195 | 196 | template 197 | requires _sr::forward_range // 198 | and _sr::output_range> // 199 | and requires(C& c) { c.resize(42); } 200 | constexpr void reshape(C& vec, From from_shape, To to_shape) noexcept { 201 | const std::size_t old_bounds = md::bounds(from_shape); 202 | const std::size_t new_bounds = md::bounds(to_shape); 203 | if (old_bounds > new_bounds) { 204 | // We need to move elements around before resizing, since items will be dropped 205 | detail::reshape_move_items(false, 206 | vec, 207 | layout_right::mapping(from_shape), 208 | layout_right::mapping(to_shape)); 209 | vec.resize(new_bounds); 210 | } else { 211 | // We added more room, so now we shift *after* resizing 212 | vec.resize(new_bounds); 213 | detail::reshape_move_items(true, 214 | vec, 215 | layout_right::mapping(from_shape), 216 | layout_right::mapping(to_shape)); 217 | } 218 | } 219 | 220 | } // namespace lmno::md 221 | -------------------------------------------------------------------------------- /src/lmno/md/range.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./range.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace md = lmno::md; 8 | 9 | static_assert(md::mdrange>); 10 | static_assert(md::mdrange); 11 | static_assert(md::fixed_shape_mdrange); 12 | static_assert(md::mdrange); 13 | static_assert(md::mdrange); 14 | static_assert(not md::mdrange); 15 | 16 | static_assert(md::rank_v == 1); 17 | static_assert(md::rank_v == 2); 18 | static_assert(md::rank_v> == 1); 19 | 20 | TEST_CASE("Scan a range") { 21 | std::vector vec = {1, 2, 3}; 22 | auto cur = md::augmented_cursor{md::origin(vec)}; 23 | CHECK(*cur == 1); 24 | cur = cur + md::basic_offset{1}; 25 | CHECK(*cur == 2); 26 | } 27 | 28 | TEST_CASE("Scan a multidimentional array") { 29 | int arr[2][3] = {{1, 2, 3}, {4, 5, 6}}; 30 | md::cursor auto orig = md::origin(arr); 31 | auto cur = md::augmented_cursor{orig}; 32 | auto six = cur[{1, 2}]; 33 | CHECK(*cur == 1); 34 | CHECK(six == 6); 35 | CHECK(md::bounds(arr) == 6); 36 | CHECK(md::rank(arr) == 2); 37 | } 38 | -------------------------------------------------------------------------------- /src/lmno/md/shape.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../concepts/stateless.hpp" 4 | #include "./mdspan.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace lmno::md { 10 | 11 | /** 12 | * @brief The rank_type of the given shape type 13 | */ 14 | template 15 | using shape_rank_t = neo::remove_reference_t::rank_type; 16 | 17 | /** 18 | * @brief The size_type of the given shape type 19 | */ 20 | template 21 | using shape_size_t = neo::remove_reference_t::size_type; 22 | 23 | // clang-format off 24 | /** 25 | * @brief Match a shape type that has a rank which can be determined at runtime 26 | */ 27 | template 28 | concept weak_shape = 29 | neo::regular 30 | and requires(const S& s) { 31 | { s.rank() } noexcept; 32 | { s.rank_dynamic() } noexcept; 33 | typename shape_rank_t; 34 | typename shape_size_t; 35 | requires neo::integral>; 36 | requires neo::integral>; 37 | } 38 | and requires(const S& s, shape_rank_t const rank) { 39 | { s.extent(rank) } noexcept -> neo::convertible_to>; 40 | }; 41 | 42 | /** 43 | * @brief Match a shape type that has a compile-time fixed rank, and can be queried 44 | * for its static-length axes 45 | */ 46 | template 47 | concept shape = 48 | weak_shape 49 | and requires(const shape_rank_t rank) { 50 | // rank, static_extent, and rank_dynamic must all be static constexpr 51 | requires S::rank() >= 0; 52 | requires S::rank_dynamic() >= 0; 53 | { S::static_extent(rank) } -> neo::weak_same_as; 54 | }; 55 | 56 | /** 57 | * @brief Check that shape `S` is has a static extent on the Nth axis 58 | */ 59 | template N> 60 | concept static_on_axis = 61 | shape 62 | and neo::remove_reference_t::static_extent(N) < ~(std::size_t(0)); 63 | 64 | /** 65 | * @brief Match a shape type that has no dynamic extents, and has a compile-time 66 | * fixed shape. 67 | */ 68 | template 69 | concept fixed_shape = 70 | shape 71 | and requires { 72 | // There must be zero dynamic extents: 73 | requires S::rank_dynamic() == 0; 74 | requires stateless>; 75 | }; 76 | 77 | template 78 | concept unit_shape = fixed_shape and S::rank() == 0; 79 | // clang-format on 80 | 81 | namespace detail { 82 | 83 | template 84 | concept non_array_range = // 85 | std::ranges::forward_range and (not neo::array_type>); 86 | 87 | template 88 | constexpr bool bounded_md_carray_v = false; 89 | 90 | template 91 | constexpr bool bounded_md_carray_v = (not neo::array_type) or bounded_md_carray_v; 92 | 93 | // clang-format off 94 | template 95 | concept has_shape = 96 | non_array_range 97 | or bounded_md_carray_v> 98 | or requires(T const& a) { { a.extents() } -> shape; } 99 | ; 100 | // clang-format on 101 | 102 | template , std::size_t... Sizes> 103 | constexpr auto carray_shape_v = nullptr; 104 | 105 | template 106 | constexpr auto carray_shape_v = carray_shape_v; 107 | 108 | template 109 | constexpr auto carray_shape_v = md::uz_extents{}; 110 | 111 | } // namespace detail 112 | 113 | /** 114 | * @brief Obtain the shape of an mdrange 115 | */ 116 | inline constexpr struct shapeof_fn { 117 | template 118 | [[nodiscard]] constexpr decltype(auto) operator()(A&& arr) const noexcept { 119 | if constexpr (detail::non_array_range) { 120 | return md::uz_dextents<1>{std::ranges::distance(arr)}; 121 | } else if constexpr (detail::bounded_md_carray_v>) { 122 | // It's a bounded multidim C-array 123 | return detail::carray_shape_v>; 124 | } else { 125 | return arr.extents(); 126 | } 127 | } 128 | } shapeof; 129 | 130 | template 131 | using shape_t = neo::remove_cvref_t; 132 | 133 | template 134 | using rank_t = shape_rank_t>; 135 | 136 | } // namespace lmno::md 137 | -------------------------------------------------------------------------------- /src/lmno/md/shape.test.cpp: -------------------------------------------------------------------------------- 1 | #include "./shape.hpp" 2 | 3 | #include "./kokkos-mdspan.hpp" 4 | 5 | int main() {} 6 | 7 | static_assert(lmno::md::weak_shape>); 8 | static_assert(lmno::md::shape>); 9 | static_assert(lmno::md::fixed_shape>); 10 | static_assert(lmno::md::shape>); 11 | static_assert( 12 | not lmno::md::fixed_shape>); 13 | -------------------------------------------------------------------------------- /src/lmno/meta.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace lmno::meta { 7 | 8 | /** 9 | * @brief A list of values 10 | * 11 | * @tparam Vs 12 | */ 13 | template 14 | struct vlist; 15 | 16 | using neo::meta::append; 17 | using neo::meta::head; 18 | using neo::meta::list; 19 | using neo::meta::rebind; 20 | using neo::meta::second; 21 | using neo::meta::tail; 22 | 23 | template