├── .gitignore ├── Makefile ├── README.md ├── brainfuck_machine.hpp ├── charlist.hpp ├── default.nix ├── main.cpp ├── nix ├── build.nix ├── ci.nix ├── sources.json └── sources.nix ├── tape.hpp ├── traits.hpp └── typelist.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CXXFLAGS = -std=c++11 -O2 3 | 4 | main: main.cpp 5 | $(CXX) $(CXXFLAGS) -o $@ $? 6 | 7 | clean: 8 | rm -f main 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A C++ Template Meta Brainfuck Interpreter 2 | 3 | For exercise reasons, i implemented this Brainfuck Interpreter on a long train ride. 4 | It works completely at compile time, as it is implemented in a purely functional way, expressed in C++ Template Language. 5 | 6 | The program and user input is provided via type lists, and the output is a type list again. 7 | The output string, as well as the Brainfuck Machine state can be printed as a compiler error message, or on the terminal when launching the program after compilation. 8 | 9 | ## Usage 10 | 11 | Program and user input are provided via preprocessor macros on the command line: 12 | ``` bash 13 | Jacek.Galowicz ~/src/tmp_brainfuck $ g++ -o main main.cpp -std=c++14 -DINPUT_STR=\"z\" -DPROGRAM_STR=\",[.-]\" 14 | Jacek.Galowicz ~/src/tmp_brainfuck $ ./main 15 | zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"! 16 | ``` 17 | 18 | The brainfuck program output can also be printed at compile time, by producing an error message with the helper type ```debug_t```: 19 | 20 | "*Hello World*" example, printed at compile time: 21 | ``` bash 22 | Jacek.Galowicz ~/src/tmp_brainfuck $ g++ -o main main.cpp -std=c++14 -DPROGRAM_STR='"++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.+++."' 23 | main.cpp:31:51: error: implicit instantiation of undefined template 24 | 'debug_t >' 25 | debug_t::list> t; 26 | ^ 27 | main.cpp:6:29: note: template is declared here 28 | template class debug_t; 29 | ^ 30 | 1 error generated. 31 | Makefile:3: recipe for target 'default' failed 32 | make: *** [default] Error 1 33 | ``` 34 | 35 | The "Hello World" example, printed at runtime: 36 | 37 | ``` bash 38 | Jacek.Galowicz ~/src/tmp_brainfuck $ g++ -o main main.cpp -std=c++14 -DPROGRAM_STR='"++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.+++."' 39 | Jacek.Galowicz ~/src/tmp_brainfuck $ ./main 40 | Hello World! 41 | ``` 42 | -------------------------------------------------------------------------------- /brainfuck_machine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "traits.hpp" 4 | #include "typelist.hpp" 5 | #include "tape.hpp" 6 | #include "charlist.hpp" 7 | 8 | namespace bfm { 9 | 10 | template using tt_move_left_t = ::tt::move_left_t; 11 | template using tt_move_right_t =::tt::move_right_t; 12 | template using tt_get_t = ::tt::get_t; 13 | template using tt_set_t = ::tt::set_t; 14 | template using tt_make_t = ::tt::make_t; 15 | 16 | template 17 | struct null_to_0; 18 | template 19 | struct null_to_0<::tt::tape> { 20 | using type = tt::tape, RList>; 21 | }; 22 | template struct null_to_0 { using type = Tape; }; 23 | template using null_to_0_t = typename null_to_0::type; 24 | 25 | template 26 | struct machine { 27 | using move_left = machine>>; 28 | using move_right = machine>>; 29 | 30 | using get = tt_get_t; 31 | template 32 | using set = machine>>; 33 | 34 | static const constexpr char value {get::value}; 35 | 36 | using increment = set; 37 | using decrement = set; 38 | }; 39 | 40 | template 41 | using move_left_t = typename Machine::move_left; 42 | template 43 | using move_right_t = typename Machine::move_right; 44 | 45 | template 46 | using get_t = typename Machine::get; 47 | template 48 | using set_t = typename Machine::template set; 49 | 50 | template 51 | using increment_t = typename Machine::increment; 52 | template 53 | using decrement_t = typename Machine::decrement; 54 | 55 | using make_t = machine>>; 56 | 57 | template 58 | struct io_bfm { 59 | using output = OutList; 60 | using state = BFM; 61 | }; 62 | 63 | 64 | template 65 | struct find_brace; 66 | 67 | template 68 | struct find_brace<::tl::tl<::char_t<']'>, InList>, OutList, 1> { 69 | using brace_block = OutList; 70 | using rest_prog = InList; 71 | }; 72 | template 73 | struct find_brace<::tl::tl<::char_t<']'>, InList>, OutList, N> 74 | : public find_brace>, N - 1> 75 | {}; 76 | template 77 | struct find_brace<::tl::tl<::char_t<'['>, InList>, OutList, N> 78 | : public find_brace>, N + 1> 79 | {}; 80 | template 81 | struct find_brace<::tl::tl<::char_t, InList>, OutList, N> 82 | : public find_brace>, N> 83 | {}; 84 | 85 | 86 | template 87 | struct interpret_step; 88 | 89 | template 90 | struct interpret_step, '.'> { 91 | using type = io_bfm>>; 92 | }; 93 | template 94 | struct interpret_step, ','> { 95 | using type = io_bfm::value>, tl::tail_t, OutList>; 96 | }; 97 | template 98 | struct interpret_step, '+'> { 99 | using type = io_bfm, InList, OutList>; 100 | }; 101 | template 102 | struct interpret_step, '-'> { 103 | using type = io_bfm, InList, OutList>; 104 | }; 105 | template 106 | struct interpret_step, '<'> { 107 | using type = io_bfm, InList, OutList>; 108 | }; 109 | template 110 | struct interpret_step, '>'> { 111 | using type = io_bfm, InList, OutList>; 112 | }; 113 | 114 | template 115 | using interpret_step_t = typename interpret_step::type; 116 | 117 | template 118 | struct run_tm; 119 | template 120 | struct run_tm, RestProg>> { 121 | static const constexpr bool loop_terminated {get_t::value == 0}; 122 | using blocks = find_brace; 123 | using type = typename if_else_t, 125 | run_tm< 126 | typename run_tm::type, 127 | ::tl::tl<::char_t<'['>, RestProg>> 128 | >::type; 129 | }; 130 | template 131 | struct run_tm, RestProg>> { 132 | using type = typename run_tm, RestProg>::type; 133 | }; 134 | template 135 | struct run_tm { 136 | using type = IOBFM; 137 | }; 138 | 139 | template 140 | using run_tm_t = typename run_tm::type; 141 | 142 | }; 143 | -------------------------------------------------------------------------------- /charlist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "typelist.hpp" 4 | 5 | template struct char_t { static const constexpr char value {val}; }; 6 | 7 | template 8 | struct char_tl; 9 | 10 | template 11 | struct char_tl { 12 | using type = tl::tl, typename char_tl::type>; 13 | }; 14 | template 15 | struct char_tl { 16 | using type = tl::tl, tl::null_t>; 17 | }; 18 | 19 | template 20 | using char_tl_t = typename char_tl::type; 21 | 22 | template 23 | struct string_list; 24 | 25 | template 26 | struct string_list { 27 | using next_piece = typename string_list< 28 | Str, 29 | Pos + 1, 30 | Str::str()[Pos + 1] 31 | >::type; 32 | using type = tl::tl, next_piece>; 33 | }; 34 | 35 | template 36 | struct string_list { 37 | using type = tl::null_t; 38 | }; 39 | 40 | template 41 | using string_list_t = typename string_list::type; 42 | 43 | template 44 | struct tl_to_varlist; 45 | 46 | template 47 | struct tl_to_varlist, restlist>, chars...> 48 | : public tl_to_varlist 49 | { }; 50 | 51 | template 52 | struct tl_to_varlist { 53 | using list = char_tl; 54 | 55 | static const char * const str() { 56 | static constexpr const char string[] = {chars..., '\0'}; 57 | return string; 58 | } 59 | }; 60 | 61 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ./nix/sources.nix {}; 3 | pkgs = import sources.nixpkgs {}; 4 | in 5 | 6 | pkgs.callPackage ./nix/build.nix {} 7 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "charlist.hpp" 4 | #include "brainfuck_machine.hpp" 5 | 6 | template class debug_t; 7 | 8 | #ifndef INPUT_STR 9 | #define INPUT_STR "" 10 | #endif 11 | 12 | #ifndef PROGRAM_STR 13 | // "Hello World" Program 14 | #define PROGRAM_STR "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++." \ 15 | ".+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.+++." 16 | #endif 17 | 18 | struct input_str { static constexpr const char * str() { return INPUT_STR; } }; 19 | struct program_str { static constexpr const char * str() { return PROGRAM_STR; } }; 20 | 21 | int main() 22 | { 23 | using input_list = string_list_t; 24 | using prog = string_list_t; 25 | 26 | using BFM = bfm::io_bfm; 27 | 28 | using output = bfm::run_tm_t::output; 29 | 30 | // Print output or state in a compiler error message 31 | //debug_t::list> t; 32 | 33 | puts(tl_to_varlist::str()); 34 | 35 | return 0; 36 | }; 37 | -------------------------------------------------------------------------------- /nix/build.nix: -------------------------------------------------------------------------------- 1 | { stdenv, lib }: 2 | 3 | stdenv.mkDerivation { 4 | name = "cpp_template_meta_brainfuck_interpreter"; 5 | 6 | src = lib.sourceByRegex ./.. [ 7 | "^.*\.cpp$" 8 | "^.*\.hpp$" 9 | "^Makefile$" 10 | ]; 11 | 12 | doCheck = true; 13 | checkPhase = '' 14 | output=$(./main) 15 | [[ "$output" =~ "Hello World!" ]] || echo "expected Hello World output" 16 | ''; 17 | 18 | installPhase = '' 19 | mkdir -p $out/bin 20 | install -m755 main $out/bin 21 | ''; 22 | } 23 | -------------------------------------------------------------------------------- /nix/ci.nix: -------------------------------------------------------------------------------- 1 | let 2 | sources = import ./sources.nix {}; 3 | pkgs = import sources.nixpkgs {}; 4 | 5 | pkg = pkgs.callPackage ./build.nix {}; 6 | in 7 | 8 | { 9 | with-gcc = pkg; 10 | with-clang = pkg.override { stdenv = pkgs.clangStdenv; }; 11 | } 12 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "nixpkgs": { 3 | "branch": "master", 4 | "description": "Nix Packages collection", 5 | "homepage": "", 6 | "owner": "NixOS", 7 | "repo": "nixpkgs", 8 | "rev": "8322b25a327d36e22e8fe5453601d5432d62877e", 9 | "sha256": "1cs20xaray598dvj11a9cjz0p0w37ychk3xsh20pah3b0cpjqk89", 10 | "type": "tarball", 11 | "url": "https://github.com/NixOS/nixpkgs/archive/8322b25a327d36e22e8fe5453601d5432d62877e.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else 102 | # this turns the string into an actual Nix path (for both absolute and 103 | # relative paths) 104 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 105 | 106 | # Ports of functions for older nix versions 107 | 108 | # a Nix version of mapAttrs if the built-in doesn't exist 109 | mapAttrs = builtins.mapAttrs or ( 110 | f: set: with builtins; 111 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 112 | ); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 115 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 118 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 119 | 120 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 121 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 122 | concatMapStrings = f: list: concatStrings (map f list); 123 | concatStrings = builtins.concatStringsSep ""; 124 | 125 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 126 | optionalAttrs = cond: as: if cond then as else {}; 127 | 128 | # fetchTarball version that is compatible between all the versions of Nix 129 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 130 | let 131 | inherit (builtins) lessThan nixVersion fetchTarball; 132 | in 133 | if lessThan nixVersion "1.12" then 134 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 135 | else 136 | fetchTarball attrs; 137 | 138 | # fetchurl version that is compatible between all the versions of Nix 139 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 140 | let 141 | inherit (builtins) lessThan nixVersion fetchurl; 142 | in 143 | if lessThan nixVersion "1.12" then 144 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 145 | else 146 | fetchurl attrs; 147 | 148 | # Create the final "sources" from the config 149 | mkSources = config: 150 | mapAttrs ( 151 | name: spec: 152 | if builtins.hasAttr "outPath" spec 153 | then abort 154 | "The values in sources.json should not have an 'outPath' attribute" 155 | else 156 | spec // { outPath = replace name (fetch config.pkgs name spec); } 157 | ) config.sources; 158 | 159 | # The "config" used by the fetchers 160 | mkConfig = 161 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 162 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 163 | , system ? builtins.currentSystem 164 | , pkgs ? mkPkgs sources system 165 | }: rec { 166 | # The sources, i.e. the attribute set of spec name to spec 167 | inherit sources; 168 | 169 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 170 | inherit pkgs; 171 | }; 172 | 173 | in 174 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 175 | -------------------------------------------------------------------------------- /tape.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "typelist.hpp" 4 | 5 | namespace tt { 6 | 7 | using ::tl::tl; 8 | using ::tl::null_t; 9 | 10 | template 11 | struct tape; 12 | 13 | template 14 | struct tape, Cursor, tl> { 15 | using get = Cursor; 16 | template 17 | using set = tape, T, tl>; 18 | 19 | using move_left = tape>>; 20 | using move_right = tape>, RHead, RTail>; 21 | }; 22 | 23 | template 24 | struct tape { 25 | using get = Cursor; 26 | template 27 | using set = tape; 28 | 29 | using move_left = tape>; 30 | using move_right = tape, null_t, null_t>; 31 | }; 32 | 33 | template 34 | struct tape> { 35 | using get = Cursor; 36 | template 37 | using set = tape>; 38 | 39 | using move_left = tape>>; 40 | using move_right = tape, RHead, RTail>; 41 | 42 | }; 43 | 44 | template 45 | struct tape, Cursor, null_t> { 46 | using get = Cursor; 47 | template 48 | using set = tape, T, null_t>; 49 | 50 | using move_left = tape>; 51 | using move_right = tape>, null_t, null_t>; 52 | }; 53 | 54 | template 55 | using get_t = typename Tape::get; 56 | 57 | template 58 | using set_t = typename Tape::template set; 59 | 60 | template 61 | using move_left_t = typename Tape::move_left; 62 | 63 | template 64 | using move_right_t = typename Tape::move_right; 65 | 66 | template 67 | using make_t = tape; 68 | 69 | } 70 | -------------------------------------------------------------------------------- /traits.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct if_else { using type = ELSE; }; 5 | template 6 | struct if_else { using type = THEN; }; 7 | 8 | template 9 | using if_else_t = typename if_else::type; 10 | 11 | -------------------------------------------------------------------------------- /typelist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace tl { 4 | 5 | struct null_t { 6 | using head = null_t; 7 | using tail = null_t; 8 | }; 9 | 10 | template 11 | struct tl 12 | { 13 | using head = T; 14 | using tail = U; 15 | }; 16 | 17 | template 18 | using head_t = typename TList::head; 19 | 20 | template 21 | using tail_t = typename TList::tail; 22 | 23 | 24 | template struct make; 25 | 26 | template 27 | struct make { using type = tl::type>; }; 28 | template <> 29 | struct make<> { using type = null_t; }; 30 | 31 | template 32 | using make_t = typename make::type; 33 | 34 | 35 | template 36 | struct append; 37 | template <> 38 | struct append { using type = null_t; }; 39 | template 40 | struct append { using type = make_t; }; 41 | template 42 | struct append> { using type = tl; }; 43 | template 44 | struct append, T> 45 | { using type = tl::type>; }; 46 | 47 | template 48 | using append_t = typename append::type; 49 | 50 | 51 | } // namespace tl 52 | --------------------------------------------------------------------------------