├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── csv ├── bills.csv ├── copy.csv ├── cycle.csv ├── foo.csv ├── stress-copy.csv └── sum.csv ├── nobuild.c ├── nobuild.h └── src ├── main.c └── sv.h /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build-linux-gcc: 9 | runs-on: ubuntu-18.04 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: install 3rd party things 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install valgrind 16 | - name: build everything 17 | run: | 18 | $CC -o nobuild nobuild.c 19 | ./nobuild valgrind 20 | env: 21 | CC: gcc 22 | CXX: g++ 23 | build-linux-clang: 24 | runs-on: ubuntu-18.04 25 | steps: 26 | - uses: actions/checkout@v1 27 | - name: install 3rd party things 28 | run: | 29 | sudo apt-get update 30 | sudo apt-get install valgrind 31 | - name: build everything 32 | run: | 33 | $CC -o nobuild nobuild.c 34 | ./nobuild valgrind 35 | env: 36 | CC: clang 37 | CXX: clang++ 38 | build-macos: 39 | runs-on: macOS-latest 40 | steps: 41 | - uses: actions/checkout@v1 42 | - name: build everything 43 | run: | 44 | $CC -o nobuild nobuild.c 45 | ./nobuild run 46 | env: 47 | CC: clang 48 | CXX: clang++ 49 | build-windows-msvc: 50 | runs-on: windows-2019 51 | steps: 52 | - name: force LF 53 | shell: cmd 54 | run: | 55 | git config --global core.autocrlf input 56 | - uses: actions/checkout@v1 57 | # this runs vcvarsall for us, so we get the MSVC toolchain in PATH. 58 | - uses: seanmiddleditch/gha-setup-vsdevenv@master 59 | - name: build everything 60 | shell: cmd 61 | # this replaces default PowerShell, which can't fail the build 62 | run: | 63 | cl.exe nobuild.c 64 | .\nobuild.exe run 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | minicel 2 | nobuild 3 | nobuild.old -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minicel 2 | 3 | The idea is to implement a batch program that can accept a CSV file that looks like this: 4 | 5 | ```csv 6 | A | B 7 | 1 | 2 8 | 3 | 4 9 | =A1+B1 | =A2+B2 10 | ``` 11 | 12 | And outputs: 13 | 14 | ```csv 15 | A | B 16 | 1 | 2 17 | 3 | 4 18 | 3 | 7 19 | ``` 20 | 21 | Basically a simple Excel engine without any UI. 22 | 23 | ## Quick Start 24 | 25 | The project is using [nobuild](https://github.com/tsoding/nobuild) build system. 26 | 27 | ```console 28 | $ cc -o nobuild nobuild.c 29 | $ ./nobuild 30 | $ ./minicel csv/sum.csv 31 | ``` 32 | 33 | ## Syntax 34 | 35 | ### Types of Cells 36 | 37 | | Type | Description | Examples | 38 | | --- | --- | --- | 39 | | Text | Just a human readiable text. | `A`, `B`, `C`, etc | 40 | | Number | Anything that can be parsed as a double by [strtod](https://en.cppreference.com/w/c/string/byte/strtof) | `1`, `2.0`, `1e-6`, etc | 41 | | Expression | Always starts with `=`. Excel style math expression that involves numbers and other cells. | `=A1+B1`, `=69+420`, `=A1+69` etc | 42 | | Clone | Always starts with `:`. Clones a neighbor cell in a particular direction denoted by characters `<`, `>`, `v`, `^`. | `:<`, `:>`, `:v`, `:^` | 43 | 44 | -------------------------------------------------------------------------------- /csv/bills.csv: -------------------------------------------------------------------------------- 1 | Date |Amount of A |Price of A|Sum |Total | 2 | 17.07.2021|69.420 | 2.50 |=B1 * C1|=D1 | 3 | 18.07.2021|70.24 | :^ | :^ |=E1+D2| 4 | 19.07.2021|3893.2 | :^ | :^ |:^ | 5 | 20.07.2021|38.2 | :^ | :^ |:^ | 6 | 21.07.2021|69.420 | :^ | :^ |:^ | 7 | 22.07.2021|1.0 | :^ | :^ |:^ | 8 | 23.07.2021|2.0 | :^ | :^ |:^ | 9 | -------------------------------------------------------------------------------- /csv/copy.csv: -------------------------------------------------------------------------------- 1 | A |B 2 | 1 |=A1+1 3 | =A1+1|:^ 4 | :^ |:^ 5 | :^ |:^ 6 | :^ |:^ 7 | :^ |:^ 8 | :^ |:^ 9 | :^ |:^ -------------------------------------------------------------------------------- /csv/cycle.csv: -------------------------------------------------------------------------------- 1 | 6| 2 | :^| 3 | 5| -------------------------------------------------------------------------------- /csv/foo.csv: -------------------------------------------------------------------------------- 1 | =2*2+1/69 |69 2 | A | 3 | A | 4 | A | 5 | A | 6 | :> |=420+B1-1*2+3 7 | -------------------------------------------------------------------------------- /csv/stress-copy.csv: -------------------------------------------------------------------------------- 1 | 1 |=A0+1 |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 2 | =A0+1 |=B0+A1|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 3 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 4 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 5 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 6 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 7 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 8 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 9 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 10 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 11 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 12 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 13 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 14 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 15 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 16 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 17 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 18 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 19 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 20 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 21 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 22 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 23 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 24 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 25 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 26 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 27 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 28 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 29 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 30 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 31 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 32 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 33 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 34 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 35 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 36 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 37 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 38 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 39 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 40 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 41 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 42 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 43 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 44 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 45 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< 46 | :^ |:^ |:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:<|:< -------------------------------------------------------------------------------- /csv/sum.csv: -------------------------------------------------------------------------------- 1 | A | B 2 | 1 | 2 3 | 3 | 4 4 | =A1+B1 | =A2+B2 5 | -------------------------------------------------------------------------------- /nobuild.c: -------------------------------------------------------------------------------- 1 | #define NOBUILD_IMPLEMENTATION 2 | #include "./nobuild.h" 3 | 4 | #define CFLAGS "-Wall", "-Wextra", "-Wswitch-enum", "-std=c11", "-pedantic", "-ggdb" 5 | #define CSV_FILE_PATH "./csv/stress-copy.csv" 6 | // #define CSV_FILE_PATH "./csv/sum.csv" 7 | // #define CSV_FILE_PATH "./csv/foo.csv" 8 | // #define CSV_FILE_PATH "./csv/bills.csv" 9 | 10 | const char *cc(void) 11 | { 12 | const char *result = getenv("CC"); 13 | return result ? result : "cc"; 14 | } 15 | 16 | int posix_main(int argc, char **argv) 17 | { 18 | CMD(cc(), CFLAGS, "-o", "minicel", "src/main.c"); 19 | 20 | if (argc > 1) { 21 | if (strcmp(argv[1], "run") == 0) { 22 | CMD("./minicel", CSV_FILE_PATH); 23 | } else if (strcmp(argv[1], "gdb") == 0) { 24 | CMD("gdb", "./minicel"); 25 | } else if (strcmp(argv[1], "valgrind") == 0) { 26 | CMD("valgrind", "--error-exitcode=1", "./minicel", CSV_FILE_PATH); 27 | } else { 28 | PANIC("%s is unknown subcommand", argv[1]); 29 | } 30 | } 31 | 32 | return 0; 33 | } 34 | 35 | int msvc_main(int argc, char **argv) 36 | { 37 | CMD("cl.exe", "/Feminicel", "src/main.c"); 38 | if (argc > 1) { 39 | if (strcmp(argv[1], "run") == 0) { 40 | CMD(".\\minicel.exe", CSV_FILE_PATH); 41 | } else { 42 | PANIC("%s is unknown subcommand", argv[1]); 43 | } 44 | } 45 | return 0; 46 | } 47 | 48 | int main(int argc, char **argv) 49 | { 50 | GO_REBUILD_URSELF(argc, argv); 51 | 52 | #ifndef _WIN32 53 | return posix_main(argc, argv); 54 | #else 55 | return msvc_main(argc, argv); 56 | #endif 57 | } 58 | -------------------------------------------------------------------------------- /nobuild.h: -------------------------------------------------------------------------------- 1 | #ifndef NOBUILD_H_ 2 | #define NOBUILD_H_ 3 | 4 | #ifndef _WIN32 5 | # define _POSIX_C_SOURCE 200809L 6 | # include 7 | # include 8 | # include 9 | # include 10 | # include 11 | # include 12 | # define PATH_SEP "/" 13 | typedef pid_t Pid; 14 | typedef int Fd; 15 | #else 16 | # define WIN32_MEAN_AND_LEAN 17 | # include "windows.h" 18 | # include 19 | # define PATH_SEP "\\" 20 | typedef HANDLE Pid; 21 | typedef HANDLE Fd; 22 | // minirent.h HEADER BEGIN //////////////////////////////////////// 23 | // Copyright 2021 Alexey Kutepov 24 | // 25 | // Permission is hereby granted, free of charge, to any person obtaining 26 | // a copy of this software and associated documentation files (the 27 | // "Software"), to deal in the Software without restriction, including 28 | // without limitation the rights to use, copy, modify, merge, publish, 29 | // distribute, sublicense, and/or sell copies of the Software, and to 30 | // permit persons to whom the Software is furnished to do so, subject to 31 | // the following conditions: 32 | // 33 | // The above copyright notice and this permission notice shall be 34 | // included in all copies or substantial portions of the Software. 35 | // 36 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 38 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 40 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 41 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 42 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 | // 44 | // ============================================================ 45 | // 46 | // minirent — 0.0.1 — A subset of dirent interface for Windows. 47 | // 48 | // https://github.com/tsoding/minirent 49 | // 50 | // ============================================================ 51 | // 52 | // ChangeLog (https://semver.org/ is implied) 53 | // 54 | // 0.0.1 First Official Release 55 | 56 | #ifndef MINIRENT_H_ 57 | #define MINIRENT_H_ 58 | 59 | #define WIN32_LEAN_AND_MEAN 60 | #include "windows.h" 61 | 62 | struct dirent { 63 | char d_name[MAX_PATH+1]; 64 | }; 65 | 66 | typedef struct DIR DIR; 67 | 68 | DIR *opendir(const char *dirpath); 69 | struct dirent *readdir(DIR *dirp); 70 | int closedir(DIR *dirp); 71 | 72 | #endif // MINIRENT_H_ 73 | // minirent.h HEADER END //////////////////////////////////////// 74 | 75 | LPSTR GetLastErrorAsString(void); 76 | 77 | #endif // _WIN32 78 | 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | #include 85 | 86 | #define FOREACH_ARRAY(type, elem, array, body) \ 87 | for (size_t elem_##index = 0; \ 88 | elem_##index < array.count; \ 89 | ++elem_##index) \ 90 | { \ 91 | type *elem = &array.elems[elem_##index]; \ 92 | body; \ 93 | } 94 | 95 | typedef const char * Cstr; 96 | 97 | int cstr_ends_with(Cstr cstr, Cstr postfix); 98 | #define ENDS_WITH(cstr, postfix) cstr_ends_with(cstr, postfix) 99 | 100 | Cstr cstr_no_ext(Cstr path); 101 | #define NOEXT(path) cstr_no_ext(path) 102 | 103 | typedef struct { 104 | Cstr *elems; 105 | size_t count; 106 | } Cstr_Array; 107 | 108 | Cstr_Array cstr_array_make(Cstr first, ...); 109 | Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr); 110 | Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs); 111 | 112 | #define JOIN(sep, ...) cstr_array_join(sep, cstr_array_make(__VA_ARGS__, NULL)) 113 | #define CONCAT(...) JOIN("", __VA_ARGS__) 114 | #define PATH(...) JOIN(PATH_SEP, __VA_ARGS__) 115 | 116 | typedef struct { 117 | Fd read; 118 | Fd write; 119 | } Pipe; 120 | 121 | Pipe pipe_make(void); 122 | 123 | typedef struct { 124 | Cstr_Array line; 125 | } Cmd; 126 | 127 | Fd fd_open_for_read(Cstr path); 128 | Fd fd_open_for_write(Cstr path); 129 | void fd_close(Fd fd); 130 | void pid_wait(Pid pid); 131 | Cstr cmd_show(Cmd cmd); 132 | Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout); 133 | void cmd_run_sync(Cmd cmd); 134 | 135 | typedef struct { 136 | Cmd *elems; 137 | size_t count; 138 | } Cmd_Array; 139 | 140 | #define CMD(...) \ 141 | do { \ 142 | Cmd cmd = { \ 143 | .line = cstr_array_make(__VA_ARGS__, NULL) \ 144 | }; \ 145 | INFO("CMD: %s", cmd_show(cmd)); \ 146 | cmd_run_sync(cmd); \ 147 | } while (0) 148 | 149 | typedef enum { 150 | CHAIN_TOKEN_END = 0, 151 | CHAIN_TOKEN_IN, 152 | CHAIN_TOKEN_OUT, 153 | CHAIN_TOKEN_CMD 154 | } Chain_Token_Type; 155 | 156 | // A single token for the CHAIN(...) DSL syntax 157 | typedef struct { 158 | Chain_Token_Type type; 159 | Cstr_Array args; 160 | } Chain_Token; 161 | 162 | #define IN(path) \ 163 | (Chain_Token) { \ 164 | .type = CHAIN_TOKEN_IN, \ 165 | .args = cstr_array_make(path, NULL) \ 166 | } 167 | 168 | #define OUT(path) \ 169 | (Chain_Token) { \ 170 | .type = CHAIN_TOKEN_OUT, \ 171 | .args = cstr_array_make(path, NULL) \ 172 | } 173 | 174 | #define CHAIN_CMD(...) \ 175 | (Chain_Token) { \ 176 | .type = CHAIN_TOKEN_CMD, \ 177 | .args = cstr_array_make(__VA_ARGS__, NULL) \ 178 | } 179 | 180 | typedef struct { 181 | Cstr input_filepath; 182 | Cmd_Array cmds; 183 | Cstr output_filepath; 184 | } Chain; 185 | 186 | Chain chain_build_from_tokens(Chain_Token first, ...); 187 | void chain_run_sync(Chain chain); 188 | void chain_echo(Chain chain); 189 | 190 | #define CHAIN(...) \ 191 | do { \ 192 | Chain chain = chain_build_from_tokens(__VA_ARGS__, (Chain_Token) {0}); \ 193 | chain_echo(chain); \ 194 | chain_run_sync(chain); \ 195 | } while(0) 196 | 197 | #ifndef REBUILD_URSELF 198 | # if _WIN32 199 | # define REBUILD_URSELF(binary_path, source_path) CMD("cl.exe", source_path) 200 | # else 201 | # define REBUILD_URSELF(binary_path, source_path) CMD("cc", "-o", binary_path, source_path) 202 | # endif 203 | #endif 204 | 205 | // NOTE: The implementation idea is stolen from https://github.com/zhiayang/nabs 206 | #define GO_REBUILD_URSELF(argc, argv) \ 207 | do { \ 208 | const char *source_path = __FILE__; \ 209 | assert(argc >= 1); \ 210 | const char *binary_path = argv[0]; \ 211 | \ 212 | if (is_path1_modified_after_path2(source_path, binary_path)) { \ 213 | RENAME(binary_path, CONCAT(binary_path, ".old")); \ 214 | REBUILD_URSELF(binary_path, source_path); \ 215 | Cmd cmd = { \ 216 | .line = { \ 217 | .elems = (Cstr*) argv, \ 218 | .count = argc, \ 219 | }, \ 220 | }; \ 221 | INFO("CMD: %s", cmd_show(cmd)); \ 222 | cmd_run_sync(cmd); \ 223 | exit(0); \ 224 | } \ 225 | } while(0) 226 | 227 | void rebuild_urself(const char *binary_path, const char *source_path); 228 | 229 | int path_is_dir(Cstr path); 230 | #define IS_DIR(path) path_is_dir(path) 231 | 232 | int path_exists(Cstr path); 233 | #define PATH_EXISTS(path) path_exists(path) 234 | 235 | void path_mkdirs(Cstr_Array path); 236 | #define MKDIRS(...) \ 237 | do { \ 238 | Cstr_Array path = cstr_array_make(__VA_ARGS__, NULL); \ 239 | INFO("MKDIRS: %s", cstr_array_join(PATH_SEP, path)); \ 240 | path_mkdirs(path); \ 241 | } while (0) 242 | 243 | void path_rename(Cstr old_path, Cstr new_path); 244 | #define RENAME(old_path, new_path) \ 245 | do { \ 246 | INFO("RENAME: %s -> %s", old_path, new_path); \ 247 | path_rename(old_path, new_path); \ 248 | } while (0) 249 | 250 | void path_rm(Cstr path); 251 | #define RM(path) \ 252 | do { \ 253 | INFO("RM: %s", path); \ 254 | path_rm(path); \ 255 | } while(0) 256 | 257 | #define FOREACH_FILE_IN_DIR(file, dirpath, body) \ 258 | do { \ 259 | struct dirent *dp = NULL; \ 260 | DIR *dir = opendir(dirpath); \ 261 | if (dir == NULL) { \ 262 | PANIC("could not open directory %s: %s", \ 263 | dirpath, strerror(errno)); \ 264 | } \ 265 | errno = 0; \ 266 | while ((dp = readdir(dir))) { \ 267 | const char *file = dp->d_name; \ 268 | body; \ 269 | } \ 270 | \ 271 | if (errno > 0) { \ 272 | PANIC("could not read directory %s: %s", \ 273 | dirpath, strerror(errno)); \ 274 | } \ 275 | \ 276 | closedir(dir); \ 277 | } while(0) 278 | 279 | #if defined(__GNUC__) || defined(__clang__) 280 | // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html 281 | #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) 282 | #else 283 | #define NOBUILD_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) 284 | #endif 285 | 286 | void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args); 287 | void INFO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 288 | void WARN(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 289 | void ERRO(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 290 | void PANIC(Cstr fmt, ...) NOBUILD_PRINTF_FORMAT(1, 2); 291 | 292 | char *shift_args(int *argc, char ***argv); 293 | 294 | #endif // NOBUILD_H_ 295 | 296 | //////////////////////////////////////////////////////////////////////////////// 297 | 298 | #ifdef NOBUILD_IMPLEMENTATION 299 | 300 | #ifdef _WIN32 301 | LPSTR GetLastErrorAsString(void) 302 | { 303 | // https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror 304 | 305 | DWORD errorMessageId = GetLastError(); 306 | assert(errorMessageId != 0); 307 | 308 | LPSTR messageBuffer = NULL; 309 | 310 | DWORD size = 311 | FormatMessage( 312 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // DWORD dwFlags, 313 | NULL, // LPCVOID lpSource, 314 | errorMessageId, // DWORD dwMessageId, 315 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // DWORD dwLanguageId, 316 | (LPSTR) &messageBuffer, // LPTSTR lpBuffer, 317 | 0, // DWORD nSize, 318 | NULL // va_list *Arguments 319 | ); 320 | 321 | return messageBuffer; 322 | } 323 | 324 | // minirent.h IMPLEMENTATION BEGIN //////////////////////////////////////// 325 | struct DIR { 326 | HANDLE hFind; 327 | WIN32_FIND_DATA data; 328 | struct dirent *dirent; 329 | }; 330 | 331 | DIR *opendir(const char *dirpath) 332 | { 333 | assert(dirpath); 334 | 335 | char buffer[MAX_PATH]; 336 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 337 | 338 | DIR *dir = (DIR*)calloc(1, sizeof(DIR)); 339 | 340 | dir->hFind = FindFirstFile(buffer, &dir->data); 341 | if (dir->hFind == INVALID_HANDLE_VALUE) { 342 | errno = ENOSYS; 343 | goto fail; 344 | } 345 | 346 | return dir; 347 | 348 | fail: 349 | if (dir) { 350 | free(dir); 351 | } 352 | 353 | return NULL; 354 | } 355 | 356 | struct dirent *readdir(DIR *dirp) 357 | { 358 | assert(dirp); 359 | 360 | if (dirp->dirent == NULL) { 361 | dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); 362 | } else { 363 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 364 | if (GetLastError() != ERROR_NO_MORE_FILES) { 365 | errno = ENOSYS; 366 | } 367 | 368 | return NULL; 369 | } 370 | } 371 | 372 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 373 | 374 | strncpy( 375 | dirp->dirent->d_name, 376 | dirp->data.cFileName, 377 | sizeof(dirp->dirent->d_name) - 1); 378 | 379 | return dirp->dirent; 380 | } 381 | 382 | int closedir(DIR *dirp) 383 | { 384 | assert(dirp); 385 | 386 | if(!FindClose(dirp->hFind)) { 387 | errno = ENOSYS; 388 | return -1; 389 | } 390 | 391 | if (dirp->dirent) { 392 | free(dirp->dirent); 393 | } 394 | free(dirp); 395 | 396 | return 0; 397 | } 398 | // minirent.h IMPLEMENTATION END //////////////////////////////////////// 399 | #endif // _WIN32 400 | 401 | Cstr_Array cstr_array_append(Cstr_Array cstrs, Cstr cstr) 402 | { 403 | Cstr_Array result = { 404 | .count = cstrs.count + 1 405 | }; 406 | result.elems = malloc(sizeof(result.elems[0]) * result.count); 407 | memcpy(result.elems, cstrs.elems, cstrs.count * sizeof(result.elems[0])); 408 | result.elems[cstrs.count] = cstr; 409 | return result; 410 | } 411 | 412 | int cstr_ends_with(Cstr cstr, Cstr postfix) 413 | { 414 | const size_t cstr_len = strlen(cstr); 415 | const size_t postfix_len = strlen(postfix); 416 | return postfix_len <= cstr_len 417 | && strcmp(cstr + cstr_len - postfix_len, postfix) == 0; 418 | } 419 | 420 | Cstr cstr_no_ext(Cstr path) 421 | { 422 | size_t n = strlen(path); 423 | while (n > 0 && path[n - 1] != '.') { 424 | n -= 1; 425 | } 426 | 427 | if (n > 0) { 428 | char *result = malloc(n); 429 | memcpy(result, path, n); 430 | result[n - 1] = '\0'; 431 | 432 | return result; 433 | } else { 434 | return path; 435 | } 436 | } 437 | 438 | Cstr_Array cstr_array_make(Cstr first, ...) 439 | { 440 | Cstr_Array result = {0}; 441 | 442 | if (first == NULL) { 443 | return result; 444 | } 445 | 446 | result.count += 1; 447 | 448 | va_list args; 449 | va_start(args, first); 450 | for (Cstr next = va_arg(args, Cstr); 451 | next != NULL; 452 | next = va_arg(args, Cstr)) { 453 | result.count += 1; 454 | } 455 | va_end(args); 456 | 457 | result.elems = malloc(sizeof(result.elems[0]) * result.count); 458 | if (result.elems == NULL) { 459 | PANIC("could not allocate memory: %s", strerror(errno)); 460 | } 461 | result.count = 0; 462 | 463 | result.elems[result.count++] = first; 464 | 465 | va_start(args, first); 466 | for (Cstr next = va_arg(args, Cstr); 467 | next != NULL; 468 | next = va_arg(args, Cstr)) { 469 | result.elems[result.count++] = next; 470 | } 471 | va_end(args); 472 | 473 | return result; 474 | } 475 | 476 | Cstr cstr_array_join(Cstr sep, Cstr_Array cstrs) 477 | { 478 | if (cstrs.count == 0) { 479 | return ""; 480 | } 481 | 482 | const size_t sep_len = strlen(sep); 483 | size_t len = 0; 484 | for (size_t i = 0; i < cstrs.count; ++i) { 485 | len += strlen(cstrs.elems[i]); 486 | } 487 | 488 | const size_t result_len = (cstrs.count - 1) * sep_len + len + 1; 489 | char *result = malloc(sizeof(char) * result_len); 490 | if (result == NULL) { 491 | PANIC("could not allocate memory: %s", strerror(errno)); 492 | } 493 | 494 | len = 0; 495 | for (size_t i = 0; i < cstrs.count; ++i) { 496 | if (i > 0) { 497 | memcpy(result + len, sep, sep_len); 498 | len += sep_len; 499 | } 500 | 501 | size_t elem_len = strlen(cstrs.elems[i]); 502 | memcpy(result + len, cstrs.elems[i], elem_len); 503 | len += elem_len; 504 | } 505 | result[len] = '\0'; 506 | 507 | return result; 508 | } 509 | 510 | Pipe pipe_make(void) 511 | { 512 | Pipe pip = {0}; 513 | 514 | #ifdef _WIN32 515 | // https://docs.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output 516 | 517 | SECURITY_ATTRIBUTES saAttr = {0}; 518 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 519 | saAttr.bInheritHandle = TRUE; 520 | 521 | if (!CreatePipe(&pip.read, &pip.write, &saAttr, 0)) { 522 | PANIC("Could not create pipe: %s", GetLastErrorAsString()); 523 | } 524 | #else 525 | Fd pipefd[2]; 526 | if (pipe(pipefd) < 0) { 527 | PANIC("Could not create pipe: %s", strerror(errno)); 528 | } 529 | 530 | pip.read = pipefd[0]; 531 | pip.write = pipefd[1]; 532 | #endif // _WIN32 533 | 534 | return pip; 535 | } 536 | 537 | Fd fd_open_for_read(Cstr path) 538 | { 539 | #ifndef _WIN32 540 | Fd result = open(path, O_RDONLY); 541 | if (result < 0) { 542 | PANIC("Could not open file %s: %s", path, strerror(errno)); 543 | } 544 | return result; 545 | #else 546 | // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing 547 | SECURITY_ATTRIBUTES saAttr = {0}; 548 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 549 | saAttr.bInheritHandle = TRUE; 550 | 551 | Fd result = CreateFile( 552 | path, 553 | GENERIC_READ, 554 | 0, 555 | &saAttr, 556 | OPEN_EXISTING, 557 | FILE_ATTRIBUTE_READONLY, 558 | NULL); 559 | 560 | if (result == INVALID_HANDLE_VALUE) { 561 | PANIC("Could not open file %s", path); 562 | } 563 | 564 | return result; 565 | #endif // _WIN32 566 | } 567 | 568 | Fd fd_open_for_write(Cstr path) 569 | { 570 | #ifndef _WIN32 571 | Fd result = open(path, 572 | O_WRONLY | O_CREAT | O_TRUNC, 573 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 574 | if (result < 0) { 575 | PANIC("could not open file %s: %s", path, strerror(errno)); 576 | } 577 | return result; 578 | #else 579 | SECURITY_ATTRIBUTES saAttr = {0}; 580 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 581 | saAttr.bInheritHandle = TRUE; 582 | 583 | Fd result = CreateFile( 584 | path, // name of the write 585 | GENERIC_WRITE, // open for writing 586 | 0, // do not share 587 | &saAttr, // default security 588 | CREATE_NEW, // create new file only 589 | FILE_ATTRIBUTE_NORMAL, // normal file 590 | NULL // no attr. template 591 | ); 592 | 593 | if (result == INVALID_HANDLE_VALUE) { 594 | PANIC("Could not open file %s: %s", path, GetLastErrorAsString()); 595 | } 596 | 597 | return result; 598 | #endif // _WIN32 599 | } 600 | 601 | void fd_close(Fd fd) 602 | { 603 | #ifdef _WIN32 604 | CloseHandle(fd); 605 | #else 606 | close(fd); 607 | #endif // _WIN32 608 | } 609 | 610 | void pid_wait(Pid pid) 611 | { 612 | #ifdef _WIN32 613 | DWORD result = WaitForSingleObject( 614 | pid, // HANDLE hHandle, 615 | INFINITE // DWORD dwMilliseconds 616 | ); 617 | 618 | if (result == WAIT_FAILED) { 619 | PANIC("could not wait on child process: %s", GetLastErrorAsString()); 620 | } 621 | 622 | DWORD exit_status; 623 | if (GetExitCodeProcess(pid, &exit_status) == 0) { 624 | PANIC("could not get process exit code: %lu", GetLastError()); 625 | } 626 | 627 | if (exit_status != 0) { 628 | PANIC("command exited with exit code %lu", exit_status); 629 | } 630 | 631 | CloseHandle(pid); 632 | #else 633 | for (;;) { 634 | int wstatus = 0; 635 | if (waitpid(pid, &wstatus, 0) < 0) { 636 | PANIC("could not wait on command (pid %d): %s", pid, strerror(errno)); 637 | } 638 | 639 | if (WIFEXITED(wstatus)) { 640 | int exit_status = WEXITSTATUS(wstatus); 641 | if (exit_status != 0) { 642 | PANIC("command exited with exit code %d", exit_status); 643 | } 644 | 645 | break; 646 | } 647 | 648 | if (WIFSIGNALED(wstatus)) { 649 | PANIC("command process was terminated by %s", strsignal(WTERMSIG(wstatus))); 650 | } 651 | } 652 | 653 | #endif // _WIN32 654 | } 655 | 656 | Cstr cmd_show(Cmd cmd) 657 | { 658 | return cstr_array_join(" ", cmd.line); 659 | } 660 | 661 | Pid cmd_run_async(Cmd cmd, Fd *fdin, Fd *fdout) 662 | { 663 | #ifdef _WIN32 664 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 665 | 666 | STARTUPINFO siStartInfo; 667 | ZeroMemory(&siStartInfo, sizeof(siStartInfo)); 668 | siStartInfo.cb = sizeof(STARTUPINFO); 669 | // NOTE: theoretically setting NULL to std handles should not be a problem 670 | // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior 671 | siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 672 | siStartInfo.hStdOutput = fdout ? *fdout : GetStdHandle(STD_OUTPUT_HANDLE); 673 | siStartInfo.hStdInput = fdin ? *fdin : GetStdHandle(STD_INPUT_HANDLE); 674 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 675 | 676 | PROCESS_INFORMATION piProcInfo; 677 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 678 | 679 | BOOL bSuccess = 680 | CreateProcess( 681 | NULL, 682 | cstr_array_join(" ", cmd.line), 683 | NULL, 684 | NULL, 685 | TRUE, 686 | 0, 687 | NULL, 688 | NULL, 689 | &siStartInfo, 690 | &piProcInfo 691 | ); 692 | 693 | if (!bSuccess) { 694 | PANIC("Could not create child process %s: %s\n", 695 | cmd_show(cmd), GetLastErrorAsString()); 696 | } 697 | 698 | CloseHandle(piProcInfo.hThread); 699 | 700 | return piProcInfo.hProcess; 701 | #else 702 | pid_t cpid = fork(); 703 | if (cpid < 0) { 704 | PANIC("Could not fork child process: %s: %s", 705 | cmd_show(cmd), strerror(errno)); 706 | } 707 | 708 | if (cpid == 0) { 709 | Cstr_Array args = cstr_array_append(cmd.line, NULL); 710 | 711 | if (fdin) { 712 | if (dup2(*fdin, STDIN_FILENO) < 0) { 713 | PANIC("Could not setup stdin for child process: %s", strerror(errno)); 714 | } 715 | } 716 | 717 | if (fdout) { 718 | if (dup2(*fdout, STDOUT_FILENO) < 0) { 719 | PANIC("Could not setup stdout for child process: %s", strerror(errno)); 720 | } 721 | } 722 | 723 | if (execvp(args.elems[0], (char * const*) args.elems) < 0) { 724 | PANIC("Could not exec child process: %s: %s", 725 | cmd_show(cmd), strerror(errno)); 726 | } 727 | } 728 | 729 | return cpid; 730 | #endif // _WIN32 731 | } 732 | 733 | void cmd_run_sync(Cmd cmd) 734 | { 735 | pid_wait(cmd_run_async(cmd, NULL, NULL)); 736 | } 737 | 738 | static void chain_set_input_output_files_or_count_cmds(Chain *chain, Chain_Token token) 739 | { 740 | switch (token.type) { 741 | case CHAIN_TOKEN_CMD: { 742 | chain->cmds.count += 1; 743 | } 744 | break; 745 | 746 | case CHAIN_TOKEN_IN: { 747 | if (chain->input_filepath) { 748 | PANIC("Input file path was already set"); 749 | } 750 | 751 | chain->input_filepath = token.args.elems[0]; 752 | } 753 | break; 754 | 755 | case CHAIN_TOKEN_OUT: { 756 | if (chain->output_filepath) { 757 | PANIC("Output file path was already set"); 758 | } 759 | 760 | chain->output_filepath = token.args.elems[0]; 761 | } 762 | break; 763 | 764 | case CHAIN_TOKEN_END: 765 | default: { 766 | assert(0 && "unreachable"); 767 | exit(1); 768 | } 769 | } 770 | } 771 | 772 | static void chain_push_cmd(Chain *chain, Chain_Token token) 773 | { 774 | if (token.type == CHAIN_TOKEN_CMD) { 775 | chain->cmds.elems[chain->cmds.count++] = (Cmd) { 776 | .line = token.args 777 | }; 778 | } 779 | } 780 | 781 | Chain chain_build_from_tokens(Chain_Token first, ...) 782 | { 783 | Chain result = {0}; 784 | 785 | chain_set_input_output_files_or_count_cmds(&result, first); 786 | va_list args; 787 | va_start(args, first); 788 | Chain_Token next = va_arg(args, Chain_Token); 789 | while (next.type != CHAIN_TOKEN_END) { 790 | chain_set_input_output_files_or_count_cmds(&result, next); 791 | next = va_arg(args, Chain_Token); 792 | } 793 | va_end(args); 794 | 795 | result.cmds.elems = malloc(sizeof(result.cmds.elems[0]) * result.cmds.count); 796 | if (result.cmds.elems == NULL) { 797 | PANIC("could not allocate memory: %s", strerror(errno)); 798 | } 799 | result.cmds.count = 0; 800 | 801 | chain_push_cmd(&result, first); 802 | 803 | va_start(args, first); 804 | next = va_arg(args, Chain_Token); 805 | while (next.type != CHAIN_TOKEN_END) { 806 | chain_push_cmd(&result, next); 807 | next = va_arg(args, Chain_Token); 808 | } 809 | va_end(args); 810 | 811 | return result; 812 | } 813 | 814 | void chain_run_sync(Chain chain) 815 | { 816 | if (chain.cmds.count == 0) { 817 | return; 818 | } 819 | 820 | Pid *cpids = malloc(sizeof(Pid) * chain.cmds.count); 821 | 822 | Pipe pip = {0}; 823 | Fd fdin = 0; 824 | Fd *fdprev = NULL; 825 | 826 | if (chain.input_filepath) { 827 | fdin = fd_open_for_read(chain.input_filepath); 828 | if (fdin < 0) { 829 | PANIC("could not open file %s: %s", chain.input_filepath, strerror(errno)); 830 | } 831 | fdprev = &fdin; 832 | } 833 | 834 | for (size_t i = 0; i < chain.cmds.count - 1; ++i) { 835 | pip = pipe_make(); 836 | 837 | cpids[i] = cmd_run_async( 838 | chain.cmds.elems[i], 839 | fdprev, 840 | &pip.write); 841 | 842 | if (fdprev) fd_close(*fdprev); 843 | fd_close(pip.write); 844 | fdprev = &fdin; 845 | fdin = pip.read; 846 | } 847 | 848 | { 849 | Fd fdout = 0; 850 | Fd *fdnext = NULL; 851 | 852 | if (chain.output_filepath) { 853 | fdout = fd_open_for_write(chain.output_filepath); 854 | if (fdout < 0) { 855 | PANIC("could not open file %s: %s", 856 | chain.output_filepath, 857 | strerror(errno)); 858 | } 859 | fdnext = &fdout; 860 | } 861 | 862 | const size_t last = chain.cmds.count - 1; 863 | cpids[last] = 864 | cmd_run_async( 865 | chain.cmds.elems[last], 866 | fdprev, 867 | fdnext); 868 | 869 | if (fdprev) fd_close(*fdprev); 870 | if (fdnext) fd_close(*fdnext); 871 | } 872 | 873 | for (size_t i = 0; i < chain.cmds.count; ++i) { 874 | pid_wait(cpids[i]); 875 | } 876 | } 877 | 878 | void chain_echo(Chain chain) 879 | { 880 | printf("[INFO] CHAIN:"); 881 | if (chain.input_filepath) { 882 | printf(" %s", chain.input_filepath); 883 | } 884 | 885 | FOREACH_ARRAY(Cmd, cmd, chain.cmds, { 886 | printf(" |> %s", cmd_show(*cmd)); 887 | }); 888 | 889 | if (chain.output_filepath) { 890 | printf(" |> %s", chain.output_filepath); 891 | } 892 | 893 | printf("\n"); 894 | } 895 | 896 | int path_exists(Cstr path) 897 | { 898 | #ifdef _WIN32 899 | DWORD dwAttrib = GetFileAttributes(path); 900 | return (dwAttrib != INVALID_FILE_ATTRIBUTES); 901 | #else 902 | struct stat statbuf = {0}; 903 | if (stat(path, &statbuf) < 0) { 904 | if (errno == ENOENT) { 905 | return 0; 906 | } 907 | 908 | PANIC("could not retrieve information about file %s: %s", 909 | path, strerror(errno)); 910 | } 911 | 912 | return 1; 913 | #endif 914 | } 915 | 916 | int path_is_dir(Cstr path) 917 | { 918 | #ifdef _WIN32 919 | DWORD dwAttrib = GetFileAttributes(path); 920 | 921 | return (dwAttrib != INVALID_FILE_ATTRIBUTES && 922 | (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); 923 | #else 924 | struct stat statbuf = {0}; 925 | if (stat(path, &statbuf) < 0) { 926 | if (errno == ENOENT) { 927 | return 0; 928 | } 929 | 930 | PANIC("could not retrieve information about file %s: %s", 931 | path, strerror(errno)); 932 | } 933 | 934 | return S_ISDIR(statbuf.st_mode); 935 | #endif // _WIN32 936 | } 937 | 938 | void path_rename(const char *old_path, const char *new_path) 939 | { 940 | #ifdef _WIN32 941 | if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { 942 | PANIC("could not rename %s to %s: %s", old_path, new_path, 943 | GetLastErrorAsString()); 944 | } 945 | #else 946 | if (rename(old_path, new_path) < 0) { 947 | PANIC("could not rename %s to %s: %s", old_path, new_path, 948 | strerror(errno)); 949 | } 950 | #endif // _WIN32 951 | } 952 | 953 | void path_mkdirs(Cstr_Array path) 954 | { 955 | if (path.count == 0) { 956 | return; 957 | } 958 | 959 | size_t len = 0; 960 | for (size_t i = 0; i < path.count; ++i) { 961 | len += strlen(path.elems[i]); 962 | } 963 | 964 | size_t seps_count = path.count - 1; 965 | const size_t sep_len = strlen(PATH_SEP); 966 | 967 | char *result = malloc(len + seps_count * sep_len + 1); 968 | 969 | len = 0; 970 | for (size_t i = 0; i < path.count; ++i) { 971 | size_t n = strlen(path.elems[i]); 972 | memcpy(result + len, path.elems[i], n); 973 | len += n; 974 | 975 | if (seps_count > 0) { 976 | memcpy(result + len, PATH_SEP, sep_len); 977 | len += sep_len; 978 | seps_count -= 1; 979 | } 980 | 981 | result[len] = '\0'; 982 | 983 | if (mkdir(result, 0755) < 0) { 984 | if (errno == EEXIST) { 985 | WARN("directory %s already exists", result); 986 | } else { 987 | PANIC("could not create directory %s: %s", result, strerror(errno)); 988 | } 989 | } 990 | } 991 | } 992 | 993 | void path_rm(Cstr path) 994 | { 995 | if (IS_DIR(path)) { 996 | FOREACH_FILE_IN_DIR(file, path, { 997 | if (strcmp(file, ".") != 0 && strcmp(file, "..") != 0) 998 | { 999 | path_rm(PATH(path, file)); 1000 | } 1001 | }); 1002 | 1003 | if (rmdir(path) < 0) { 1004 | if (errno == ENOENT) { 1005 | WARN("directory %s does not exist", path); 1006 | } else { 1007 | PANIC("could not remove directory %s: %s", path, strerror(errno)); 1008 | } 1009 | } 1010 | } else { 1011 | if (unlink(path) < 0) { 1012 | if (errno == ENOENT) { 1013 | WARN("file %s does not exist", path); 1014 | } else { 1015 | PANIC("could not remove file %s: %s", path, strerror(errno)); 1016 | } 1017 | } 1018 | } 1019 | } 1020 | 1021 | int is_path1_modified_after_path2(const char *path1, const char *path2) 1022 | { 1023 | #ifdef _WIN32 1024 | FILETIME path1_time, path2_time; 1025 | 1026 | Fd path1_fd = fd_open_for_read(path1); 1027 | if (!GetFileTime(path1_fd, NULL, NULL, &path1_time)) { 1028 | PANIC("could not get time of %s: %s", path1, GetLastErrorAsString()); 1029 | } 1030 | fd_close(path1_fd); 1031 | 1032 | Fd path2_fd = fd_open_for_read(path2); 1033 | if (!GetFileTime(path2_fd, NULL, NULL, &path2_time)) { 1034 | PANIC("could not get time of %s: %s", path2, GetLastErrorAsString()); 1035 | } 1036 | fd_close(path2_fd); 1037 | 1038 | return CompareFileTime(&path1_time, &path2_time) == 1; 1039 | #else 1040 | struct stat statbuf = {0}; 1041 | 1042 | if (stat(path1, &statbuf) < 0) { 1043 | PANIC("could not stat %s: %s\n", path1, strerror(errno)); 1044 | } 1045 | int path1_time = statbuf.st_mtime; 1046 | 1047 | if (stat(path2, &statbuf) < 0) { 1048 | PANIC("could not stat %s: %s\n", path2, strerror(errno)); 1049 | } 1050 | int path2_time = statbuf.st_mtime; 1051 | 1052 | return path1_time > path2_time; 1053 | #endif 1054 | } 1055 | 1056 | void VLOG(FILE *stream, Cstr tag, Cstr fmt, va_list args) 1057 | { 1058 | fprintf(stream, "[%s] ", tag); 1059 | vfprintf(stream, fmt, args); 1060 | fprintf(stream, "\n"); 1061 | } 1062 | 1063 | void INFO(Cstr fmt, ...) 1064 | { 1065 | va_list args; 1066 | va_start(args, fmt); 1067 | VLOG(stdout, "INFO", fmt, args); 1068 | va_end(args); 1069 | } 1070 | 1071 | void WARN(Cstr fmt, ...) 1072 | { 1073 | va_list args; 1074 | va_start(args, fmt); 1075 | VLOG(stderr, "WARN", fmt, args); 1076 | va_end(args); 1077 | } 1078 | 1079 | void ERRO(Cstr fmt, ...) 1080 | { 1081 | va_list args; 1082 | va_start(args, fmt); 1083 | VLOG(stderr, "ERRO", fmt, args); 1084 | va_end(args); 1085 | } 1086 | 1087 | void PANIC(Cstr fmt, ...) 1088 | { 1089 | va_list args; 1090 | va_start(args, fmt); 1091 | VLOG(stderr, "ERRO", fmt, args); 1092 | va_end(args); 1093 | exit(1); 1094 | } 1095 | 1096 | char *shift_args(int *argc, char ***argv) 1097 | { 1098 | assert(*argc > 0); 1099 | char *result = **argv; 1100 | *argc -= 1; 1101 | *argv += 1; 1102 | return result; 1103 | } 1104 | 1105 | #endif // NOBUILD_IMPLEMENTATION 1106 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define SV_IMPLEMENTATION 8 | #include "./sv.h" 9 | 10 | #define UNREACHABLE(message) \ 11 | do { \ 12 | fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", \ 13 | __FILE__, __LINE__, message); \ 14 | exit(69); \ 15 | } while(0) 16 | 17 | typedef struct Expr Expr; 18 | typedef size_t Expr_Index; 19 | 20 | typedef enum { 21 | EXPR_KIND_NUMBER = 0, 22 | EXPR_KIND_CELL, 23 | EXPR_KIND_BOP, 24 | EXPR_KIND_UOP, 25 | } Expr_Kind; 26 | 27 | typedef enum { 28 | BOP_KIND_PLUS = 0, 29 | BOP_KIND_MINUS, 30 | BOP_KIND_MULT, 31 | BOP_KIND_DIV, 32 | COUNT_BOP_KINDS, 33 | } Bop_Kind; 34 | 35 | typedef struct { 36 | Bop_Kind kind; 37 | String_View token; 38 | size_t precedence; 39 | } Bop_Def; 40 | 41 | typedef enum { 42 | BOP_PRECEDENCE0 = 0, 43 | BOP_PRECEDENCE1, 44 | COUNT_BOP_PRECEDENCE 45 | } Bop_Precedence; 46 | 47 | static_assert(COUNT_BOP_KINDS == 4, "The amount of Binary Operators has changed. Please adjust the definition table accordingly"); 48 | static const Bop_Def bop_defs[COUNT_BOP_KINDS] = { 49 | [BOP_KIND_PLUS] = { 50 | .kind = BOP_KIND_PLUS, 51 | .token = SV_STATIC("+"), 52 | .precedence = BOP_PRECEDENCE0, 53 | }, 54 | [BOP_KIND_MINUS] = { 55 | .kind = BOP_KIND_MINUS, 56 | .token = SV_STATIC("-"), 57 | .precedence = BOP_PRECEDENCE0, 58 | }, 59 | [BOP_KIND_MULT] = { 60 | .kind = BOP_KIND_MULT, 61 | .token = SV_STATIC("*"), 62 | .precedence = BOP_PRECEDENCE1, 63 | }, 64 | [BOP_KIND_DIV] = { 65 | .kind = BOP_KIND_DIV, 66 | .token = SV_STATIC("/"), 67 | .precedence = BOP_PRECEDENCE1, 68 | }, 69 | }; 70 | 71 | const Bop_Def *bop_def_by_token(String_View token) 72 | { 73 | for (Bop_Kind kind = 0; kind < COUNT_BOP_KINDS; ++kind) { 74 | if (sv_eq(bop_defs[kind].token, token)) { 75 | return &bop_defs[kind]; 76 | } 77 | } 78 | 79 | return NULL; 80 | } 81 | 82 | typedef struct { 83 | Bop_Kind kind; 84 | Expr_Index lhs; 85 | Expr_Index rhs; 86 | } Expr_Bop; 87 | 88 | typedef enum { 89 | UOP_KIND_MINUS 90 | } Uop_Kind; 91 | 92 | typedef struct { 93 | Uop_Kind kind; 94 | Expr_Index param; 95 | } Expr_Uop; 96 | 97 | typedef struct { 98 | size_t row; 99 | size_t col; 100 | } Cell_Index; 101 | 102 | typedef union { 103 | double number; 104 | Cell_Index cell; 105 | Expr_Bop bop; 106 | Expr_Uop uop; 107 | } Expr_As; 108 | 109 | struct Expr { 110 | Expr_Kind kind; 111 | Expr_As as; 112 | const char *file_path; 113 | size_t file_row; 114 | size_t file_col; 115 | }; 116 | 117 | typedef struct { 118 | size_t count; 119 | size_t capacity; 120 | Expr *items; 121 | } Expr_Buffer; 122 | 123 | Expr_Index expr_buffer_alloc(Expr_Buffer *eb) 124 | { 125 | if (eb->count >= eb->capacity) { 126 | if (eb->capacity == 0) { 127 | assert(eb->items == NULL); 128 | eb->capacity = 128; 129 | } else { 130 | eb->capacity *= 2; 131 | } 132 | 133 | eb->items = realloc(eb->items, sizeof(Expr) * eb->capacity); 134 | } 135 | 136 | memset(&eb->items[eb->count], 0, sizeof(Expr)); 137 | 138 | return eb->count++; 139 | } 140 | 141 | Expr *expr_buffer_at(Expr_Buffer *eb, Expr_Index index) 142 | { 143 | assert(index < eb->count); 144 | return &eb->items[index]; 145 | } 146 | 147 | typedef enum { 148 | DIR_LEFT = 0, 149 | DIR_RIGHT, 150 | DIR_UP, 151 | DIR_DOWN, 152 | } Dir; 153 | 154 | typedef enum { 155 | CELL_KIND_TEXT = 0, 156 | CELL_KIND_NUMBER, 157 | CELL_KIND_EXPR, 158 | CELL_KIND_CLONE, 159 | } Cell_Kind; 160 | 161 | const char *cell_kind_as_cstr(Cell_Kind kind) 162 | { 163 | switch (kind) { 164 | case CELL_KIND_TEXT: 165 | return "TEXT"; 166 | case CELL_KIND_NUMBER: 167 | return "NUMBER"; 168 | case CELL_KIND_EXPR: 169 | return "EXPR"; 170 | case CELL_KIND_CLONE: 171 | return "CLONE"; 172 | default: 173 | UNREACHABLE("unknown Cell Kind"); 174 | } 175 | } 176 | 177 | typedef enum { 178 | UNEVALUATED = 0, 179 | INPROGRESS, 180 | EVALUATED, 181 | } Eval_Status; 182 | 183 | typedef struct { 184 | Expr_Index index; 185 | double value; 186 | } Cell_Expr; 187 | 188 | typedef union { 189 | String_View text; 190 | double number; 191 | Cell_Expr expr; 192 | Dir clone; 193 | } Cell_As; 194 | 195 | typedef struct { 196 | Cell_Kind kind; 197 | Cell_As as; 198 | Eval_Status status; 199 | 200 | size_t file_row; 201 | size_t file_col; 202 | } Cell; 203 | 204 | typedef struct { 205 | Cell *cells; 206 | size_t rows; 207 | size_t cols; 208 | const char *file_path; 209 | } Table; 210 | 211 | bool is_name(char c) 212 | { 213 | return isalnum(c) || c == '_'; 214 | } 215 | 216 | typedef struct { 217 | String_View text; 218 | const char *file_path; 219 | size_t file_row; 220 | size_t file_col; 221 | } Token; 222 | 223 | typedef struct { 224 | String_View source; 225 | const char *file_path; 226 | size_t file_row; 227 | const char *line_start; 228 | } Lexer; 229 | 230 | size_t lexer_file_col(const Lexer *lexer) 231 | { 232 | return lexer->source.data - lexer->line_start + 1; 233 | } 234 | 235 | void lexer_print_loc(const Lexer *lexer, FILE *stream) 236 | { 237 | fprintf(stream, "%s:%zu:%zu: ", 238 | lexer->file_path, 239 | lexer->file_row, 240 | lexer_file_col(lexer)); 241 | } 242 | 243 | Token lexer_peek_token(Lexer *lexer) 244 | { 245 | lexer->source = sv_trim(lexer->source); 246 | 247 | Token token; 248 | memset(&token, 0, sizeof(token)); 249 | token.file_path = lexer->file_path; 250 | token.file_row = lexer->file_row; 251 | token.file_col = lexer_file_col(lexer); 252 | 253 | if (lexer->source.count == 0) { 254 | return token; 255 | } 256 | 257 | if (*lexer->source.data == '+' || 258 | *lexer->source.data == '-' || 259 | *lexer->source.data == '*' || 260 | *lexer->source.data == '/' || 261 | *lexer->source.data == '(' || 262 | *lexer->source.data == ')') { 263 | token.text = (String_View) { 264 | .count = 1, 265 | .data = lexer->source.data 266 | }; 267 | return token; 268 | } 269 | 270 | if (is_name(*lexer->source.data)) { 271 | token.text = sv_take_left_while(lexer->source, is_name); 272 | return token; 273 | } 274 | 275 | lexer_print_loc(lexer, stderr); 276 | fprintf(stderr, "ERROR: unknown token starts with `%c`\n", *lexer->source.data); 277 | exit(1); 278 | } 279 | 280 | Token lexer_next_token(Lexer *lexer) 281 | { 282 | Token token = lexer_peek_token(lexer); 283 | sv_chop_left(&lexer->source, token.text.count); 284 | return token; 285 | } 286 | 287 | void lexer_expect_no_tokens(Lexer *lexer) 288 | { 289 | Token token = lexer_next_token(lexer); 290 | if (token.text.data != NULL) { 291 | fprintf(stderr, "%s:%zu:%zu: ERROR: unexpected token `"SV_Fmt"`\n", 292 | token.file_path, 293 | token.file_row, 294 | token.file_col, 295 | SV_Arg(token.text)); 296 | exit(1); 297 | } 298 | } 299 | 300 | typedef struct { 301 | size_t capacity; 302 | char *cstr; 303 | } Tmp_Cstr; 304 | 305 | char *tmp_cstr_fill(Tmp_Cstr *tc, const char *data, size_t data_size) 306 | { 307 | if (data_size + 1 >= tc->capacity) { 308 | tc->capacity = data_size + 1; 309 | tc->cstr = realloc(tc->cstr, tc->capacity); 310 | } 311 | 312 | memcpy(tc->cstr, data, data_size); 313 | tc->cstr[data_size] = '\0'; 314 | return tc->cstr; 315 | } 316 | 317 | bool sv_strtod(String_View sv, Tmp_Cstr *tc, double *out) 318 | { 319 | char *ptr = tmp_cstr_fill(tc, sv.data, sv.count); 320 | char *endptr = NULL; 321 | double result = strtod(ptr, &endptr); 322 | if (out) *out = result; 323 | return endptr != ptr && *endptr == '\0'; 324 | } 325 | 326 | bool sv_strtol(String_View sv, Tmp_Cstr *tc, long int *out) 327 | { 328 | char *ptr = tmp_cstr_fill(tc, sv.data, sv.count); 329 | char *endptr = NULL; 330 | long int result = strtol(ptr, &endptr, 10); 331 | if (out) *out = result; 332 | return endptr != ptr && *endptr == '\0'; 333 | } 334 | 335 | Expr_Index parse_expr(Lexer *lexer, Tmp_Cstr *tc, Expr_Buffer *eb); 336 | 337 | Expr_Index parse_primary_expr(Lexer *lexer, Tmp_Cstr *tc, Expr_Buffer *eb) 338 | { 339 | Token token = lexer_next_token(lexer); 340 | 341 | if (token.text.count == 0) { 342 | lexer_print_loc(lexer, stderr); 343 | fprintf(stderr, "ERROR: expected primary expression token, but got end of input\n"); 344 | exit(1); 345 | } 346 | 347 | double number = 0.0; 348 | if (sv_strtod(token.text, tc, &number)) { 349 | Expr_Index expr_index = expr_buffer_alloc(eb); 350 | Expr *expr = expr_buffer_at(eb, expr_index); 351 | expr->kind = EXPR_KIND_NUMBER; 352 | expr->as.number = number; 353 | expr->file_path = token.file_path; 354 | expr->file_row = token.file_row; 355 | expr->file_col = token.file_col; 356 | return expr_index; 357 | } else if (sv_eq(token.text, SV("("))) { 358 | Expr_Index expr_index = parse_expr(lexer, tc, eb); 359 | token = lexer_next_token(lexer); 360 | if (!sv_eq(token.text, SV(")"))) { 361 | fprintf(stderr, "%s:%zu:%zu: Expected token `)` but got `"SV_Fmt"`\n", token.file_path, token.file_row, token.file_col, SV_Arg(token.text)); 362 | exit(1); 363 | } 364 | return expr_index; 365 | } else if (sv_eq(token.text, SV("-"))) { 366 | Expr_Index param_index = parse_expr(lexer, tc, eb); 367 | Expr_Index expr_index = expr_buffer_alloc(eb); 368 | { 369 | Expr *expr = expr_buffer_at(eb, expr_index); 370 | expr->kind = EXPR_KIND_UOP; 371 | expr->as.uop.kind = UOP_KIND_MINUS; 372 | expr->as.uop.param = param_index; 373 | expr->file_path = token.file_path; 374 | expr->file_row = token.file_row; 375 | expr->file_col = token.file_col; 376 | } 377 | return expr_index; 378 | } else { 379 | Expr_Index expr_index = expr_buffer_alloc(eb); 380 | Expr *expr = expr_buffer_at(eb, expr_index); 381 | expr->file_path = token.file_path; 382 | expr->file_row = token.file_row; 383 | expr->file_col = token.file_col; 384 | expr->kind = EXPR_KIND_CELL; 385 | 386 | if (!isupper(*token.text.data)) { 387 | lexer_print_loc(lexer, stderr); 388 | fprintf(stderr, "ERROR: cell reference must start with capital letter\n"); 389 | exit(1); 390 | } 391 | 392 | expr->as.cell.col = *token.text.data - 'A'; 393 | 394 | sv_chop_left(&token.text, 1); 395 | 396 | long int row = 0; 397 | if (!sv_strtol(token.text, tc, &row)) { 398 | lexer_print_loc(lexer, stderr); 399 | fprintf(stderr, "ERROR: cell reference must have an integer as the row number\n"); 400 | exit(1); 401 | } 402 | 403 | expr->as.cell.row = (size_t) row; 404 | return expr_index; 405 | } 406 | } 407 | 408 | Expr_Index parse_bop_expr(Lexer *lexer, Tmp_Cstr *tc, Expr_Buffer *eb, size_t precedence) 409 | { 410 | if (precedence >= COUNT_BOP_PRECEDENCE) { 411 | return parse_primary_expr(lexer, tc, eb); 412 | } 413 | 414 | Expr_Index lhs_index = parse_bop_expr(lexer, tc, eb, precedence + 1); 415 | 416 | Token token = lexer_peek_token(lexer); 417 | const Bop_Def *def = bop_def_by_token(token.text); 418 | 419 | if (def != NULL && def->precedence == precedence) { 420 | token = lexer_next_token(lexer); 421 | Expr_Index rhs_index = parse_bop_expr(lexer, tc, eb, precedence); 422 | 423 | Expr_Index expr_index = expr_buffer_alloc(eb); 424 | { 425 | Expr *expr = expr_buffer_at(eb, expr_index); 426 | expr->kind = EXPR_KIND_BOP; 427 | expr->as.bop.kind = def->kind; 428 | expr->as.bop.lhs = lhs_index; 429 | expr->as.bop.rhs = rhs_index; 430 | expr->file_path = token.file_path; 431 | expr->file_row = token.file_row; 432 | expr->file_col = token.file_col; 433 | } 434 | 435 | return expr_index; 436 | } 437 | 438 | return lhs_index; 439 | } 440 | 441 | Cell *table_cell_at(Table *table, Cell_Index index) 442 | { 443 | assert(index.row < table->rows); 444 | assert(index.col < table->cols); 445 | return &table->cells[index.row * table->cols + index.col]; 446 | } 447 | 448 | void dump_table(FILE *stream, Table *table) 449 | { 450 | for (size_t row = 0; row < table->rows; ++row) { 451 | for (size_t col = 0; col < table->cols; ++col) { 452 | Cell_Index cell_index = { 453 | .row = row, 454 | .col = col, 455 | }; 456 | Cell *cell = table_cell_at(table, cell_index); 457 | 458 | fprintf(stream, "%s:%zu:%zu: %s\n", table->file_path, cell->file_row, cell->file_col, cell_kind_as_cstr(cell->kind)); 459 | } 460 | } 461 | } 462 | 463 | void dump_expr(FILE *stream, Expr_Buffer *eb, Expr_Index expr_index, int level) 464 | { 465 | fprintf(stream, "%*s", level * 2, ""); 466 | 467 | Expr *expr = expr_buffer_at(eb, expr_index); 468 | 469 | switch (expr->kind) { 470 | case EXPR_KIND_NUMBER: 471 | fprintf(stream, "NUMBER: %lf\n", expr->as.number); 472 | break; 473 | 474 | case EXPR_KIND_CELL: 475 | fprintf(stream, "CELL(%zu, %zu)\n", expr->as.cell.row, expr->as.cell.col); 476 | break; 477 | 478 | case EXPR_KIND_UOP: 479 | switch (expr->as.uop.kind) { 480 | case UOP_KIND_MINUS: 481 | fprintf(stream, "UOP(MINUS):\n"); 482 | break; 483 | default: 484 | UNREACHABLE("unknown Unary Operator Kind"); 485 | } 486 | 487 | dump_expr(stream, eb, expr->as.uop.param, level + 1); 488 | break; 489 | 490 | case EXPR_KIND_BOP: 491 | switch (expr->as.bop.kind) { 492 | case BOP_KIND_PLUS: 493 | fprintf(stream, "BOP(PLUS):\n"); 494 | break; 495 | 496 | case BOP_KIND_MINUS: 497 | fprintf(stream, "BOP(MINUS):\n"); 498 | break; 499 | 500 | case BOP_KIND_MULT: 501 | fprintf(stream, "BOP(MULT):\n"); 502 | break; 503 | 504 | case BOP_KIND_DIV: 505 | fprintf(stream, "BOP(DIV):\n"); 506 | break; 507 | 508 | case COUNT_BOP_KINDS: 509 | default: 510 | UNREACHABLE("unknown Binary Operator Kind"); 511 | } 512 | 513 | dump_expr(stream, eb, expr->as.bop.lhs, level + 1); 514 | dump_expr(stream, eb, expr->as.bop.rhs, level + 1); 515 | break; 516 | 517 | default: 518 | UNREACHABLE("unknown Expression Kind"); 519 | } 520 | } 521 | 522 | Expr_Index parse_expr(Lexer *lexer, Tmp_Cstr *tc, Expr_Buffer *eb) 523 | { 524 | return parse_bop_expr(lexer, tc, eb, BOP_PRECEDENCE0); 525 | } 526 | 527 | void usage(FILE *stream) 528 | { 529 | fprintf(stream, "Usage: ./minicel \n"); 530 | } 531 | 532 | char *slurp_file(const char *file_path, size_t *size) 533 | { 534 | char *buffer = NULL; 535 | 536 | FILE *f = fopen(file_path, "rb"); 537 | if (f == NULL) { 538 | goto error; 539 | } 540 | 541 | if (fseek(f, 0, SEEK_END) < 0) { 542 | goto error; 543 | } 544 | 545 | long m = ftell(f); 546 | if (m < 0) { 547 | goto error; 548 | } 549 | 550 | buffer = malloc(sizeof(char) * m); 551 | if (buffer == NULL) { 552 | goto error; 553 | } 554 | 555 | if (fseek(f, 0, SEEK_SET) < 0) { 556 | goto error; 557 | } 558 | 559 | size_t n = fread(buffer, 1, m, f); 560 | assert(n == (size_t) m); 561 | 562 | if (ferror(f)) { 563 | goto error; 564 | } 565 | 566 | if (size) { 567 | *size = n; 568 | } 569 | 570 | fclose(f); 571 | 572 | return buffer; 573 | 574 | error: 575 | if (f) { 576 | fclose(f); 577 | } 578 | 579 | if (buffer) { 580 | free(buffer); 581 | } 582 | 583 | return NULL; 584 | } 585 | 586 | void parse_table_from_content(Table *table, Expr_Buffer *eb, Tmp_Cstr *tc, String_View content) 587 | { 588 | for (size_t row = 0; row < table->rows; ++row) { 589 | String_View line = sv_chop_by_delim(&content, '\n'); 590 | const char *const line_start = line.data; 591 | for (size_t col = 0; col < table->cols; ++col) { 592 | String_View cell_value = sv_trim(sv_chop_by_delim(&line, '|')); 593 | Cell_Index cell_index = { 594 | .row = row, 595 | .col = col, 596 | }; 597 | Cell *cell = table_cell_at(table, cell_index); 598 | cell->file_row = row + 1; 599 | cell->file_col = cell_value.data - line_start + 1; 600 | 601 | if (sv_starts_with(cell_value, SV("="))) { 602 | sv_chop_left(&cell_value, 1); 603 | cell->kind = CELL_KIND_EXPR; 604 | Lexer lexer = { 605 | .source = cell_value, 606 | .file_path = table->file_path, 607 | .file_row = cell->file_row, 608 | .line_start = line_start, 609 | }; 610 | cell->as.expr.index = parse_expr(&lexer, tc, eb); 611 | lexer_expect_no_tokens(&lexer); 612 | } else if (sv_starts_with(cell_value, SV(":"))) { 613 | sv_chop_left(&cell_value, 1); 614 | cell->kind = CELL_KIND_CLONE; 615 | if (sv_eq(cell_value, SV("<"))) { 616 | cell->as.clone = DIR_LEFT; 617 | } else if (sv_eq(cell_value, SV(">"))) { 618 | cell->as.clone = DIR_RIGHT; 619 | } else if (sv_eq(cell_value, SV("^"))) { 620 | cell->as.clone = DIR_UP; 621 | } else if (sv_eq(cell_value, SV("v"))) { 622 | cell->as.clone = DIR_DOWN; 623 | } else { 624 | fprintf(stderr, "%s:%zu:%zu: ERROR: "SV_Fmt" is not a correct direction to clone a cell from\n", table->file_path, cell->file_row, cell->file_col, SV_Arg(cell_value)); 625 | exit(1); 626 | } 627 | } else { 628 | if (sv_strtod(cell_value, tc, &cell->as.number)) { 629 | cell->kind = CELL_KIND_NUMBER; 630 | } else { 631 | cell->kind = CELL_KIND_TEXT; 632 | cell->as.text = cell_value; 633 | } 634 | } 635 | } 636 | } 637 | } 638 | 639 | void estimate_table_size(String_View content, size_t *out_rows, size_t *out_cols) 640 | { 641 | size_t rows = 0; 642 | size_t cols = 0; 643 | for (; content.count > 0; ++rows) { 644 | String_View line = sv_chop_by_delim(&content, '\n'); 645 | size_t col = 0; 646 | for (; line.count > 0; ++col) { 647 | sv_chop_by_delim(&line, '|'); 648 | } 649 | 650 | if (cols < col) { 651 | cols = col; 652 | } 653 | } 654 | 655 | if (out_rows) { 656 | *out_rows = rows; 657 | } 658 | 659 | if (out_cols) { 660 | *out_cols = cols; 661 | } 662 | } 663 | 664 | void table_eval_cell(Table *table, Expr_Buffer *eb, Cell_Index cell_index); 665 | 666 | double table_eval_expr(Table *table, Expr_Buffer *eb, Expr_Index expr_index) 667 | { 668 | Expr *expr = expr_buffer_at(eb, expr_index); 669 | 670 | switch (expr->kind) { 671 | case EXPR_KIND_NUMBER: 672 | return expr->as.number; 673 | 674 | case EXPR_KIND_CELL: { 675 | table_eval_cell(table, eb, expr->as.cell); 676 | 677 | Cell *target_cell = table_cell_at(table, expr->as.cell); 678 | switch (target_cell->kind) { 679 | case CELL_KIND_NUMBER: 680 | return target_cell->as.number; 681 | case CELL_KIND_TEXT: { 682 | fprintf(stderr, "%s:%zu:%zu: ERROR: text cells may not participate in math expressions\n", expr->file_path, expr->file_row, expr->file_col); 683 | fprintf(stderr, "%s:%zu:%zu: NOTE: the text cell is located here\n", 684 | table->file_path, target_cell->file_row, target_cell->file_col); 685 | exit(1); 686 | } 687 | 688 | case CELL_KIND_EXPR: 689 | return target_cell->as.expr.value; 690 | 691 | case CELL_KIND_CLONE: 692 | UNREACHABLE("cell should never be a clone after the evaluation"); 693 | } 694 | } 695 | break; 696 | 697 | case EXPR_KIND_BOP: { 698 | double lhs = table_eval_expr(table, eb, expr->as.bop.lhs); 699 | double rhs = table_eval_expr(table, eb, expr->as.bop.rhs); 700 | 701 | switch (expr->as.bop.kind) { 702 | case BOP_KIND_PLUS: 703 | return lhs + rhs; 704 | case BOP_KIND_MINUS: 705 | return lhs - rhs; 706 | case BOP_KIND_MULT: 707 | return lhs * rhs; 708 | case BOP_KIND_DIV: 709 | return lhs / rhs; 710 | case COUNT_BOP_KINDS: 711 | default: 712 | UNREACHABLE("unknown Binary Operator Kind"); 713 | } 714 | } 715 | break; 716 | 717 | case EXPR_KIND_UOP: { 718 | double param = table_eval_expr(table, eb, expr->as.uop.param); 719 | 720 | switch (expr->as.uop.kind) { 721 | case UOP_KIND_MINUS: 722 | return -param; 723 | default: 724 | UNREACHABLE("unknown Unary Operator Kind"); 725 | } 726 | } 727 | break; 728 | } 729 | return 0; 730 | } 731 | 732 | Dir opposite_dir(Dir dir) 733 | { 734 | switch (dir) { 735 | case DIR_LEFT: 736 | return DIR_RIGHT; 737 | case DIR_RIGHT: 738 | return DIR_LEFT; 739 | case DIR_UP: 740 | return DIR_DOWN; 741 | case DIR_DOWN: 742 | return DIR_UP; 743 | default: 744 | UNREACHABLE("unknown direction"); 745 | } 746 | } 747 | 748 | Cell_Index nbor_in_dir(Cell_Index index, Dir dir) 749 | { 750 | switch (dir) { 751 | case DIR_LEFT: 752 | index.col -= 1; 753 | break; 754 | case DIR_RIGHT: 755 | index.col += 1; 756 | break; 757 | case DIR_UP: 758 | index.row -= 1; 759 | break; 760 | case DIR_DOWN: 761 | index.row += 1; 762 | break; 763 | default: 764 | UNREACHABLE("unknown direction"); 765 | } 766 | 767 | return index; 768 | } 769 | 770 | Expr_Index move_expr_in_dir(Table *table, Cell_Index cell_index, Expr_Buffer *eb, Expr_Index root, Dir dir) 771 | { 772 | Cell *cell = table_cell_at(table, cell_index); 773 | 774 | switch (expr_buffer_at(eb, root)->kind) { 775 | case EXPR_KIND_NUMBER: 776 | return root; 777 | 778 | case EXPR_KIND_CELL: { 779 | Expr_Index new_index = expr_buffer_alloc(eb); 780 | { 781 | Expr *new_expr = expr_buffer_at(eb, new_index); 782 | new_expr->kind = EXPR_KIND_CELL; 783 | new_expr->as.cell = nbor_in_dir(expr_buffer_at(eb, root)->as.cell, dir); 784 | new_expr->file_path = table->file_path; 785 | new_expr->file_row = cell->file_row; 786 | new_expr->file_col = cell->file_col; 787 | } 788 | 789 | return new_index; 790 | } 791 | break; 792 | 793 | case EXPR_KIND_BOP: { 794 | Expr_Bop bop = {0}; 795 | 796 | { 797 | Expr *root_expr = expr_buffer_at(eb, root); 798 | bop = root_expr->as.bop; 799 | } 800 | 801 | bop.lhs = move_expr_in_dir(table, cell_index, eb, bop.lhs, dir); 802 | bop.rhs = move_expr_in_dir(table, cell_index, eb, bop.rhs, dir); 803 | 804 | Expr_Index new_index = expr_buffer_alloc(eb); 805 | { 806 | Expr *new_expr = expr_buffer_at(eb, new_index); 807 | new_expr->kind = EXPR_KIND_BOP; 808 | new_expr->as.bop = bop; 809 | new_expr->file_path = table->file_path; 810 | new_expr->file_row = cell->file_row; 811 | new_expr->file_col = cell->file_col; 812 | } 813 | 814 | return new_index; 815 | } 816 | break; 817 | 818 | case EXPR_KIND_UOP: { 819 | Expr_Uop uop = {0}; 820 | { 821 | Expr *root_expr = expr_buffer_at(eb, root); 822 | uop = root_expr->as.uop; 823 | } 824 | 825 | uop.param = move_expr_in_dir(table, cell_index, eb, uop.param, dir); 826 | 827 | Expr_Index new_index = expr_buffer_alloc(eb); 828 | { 829 | Expr *new_expr = expr_buffer_at(eb, new_index); 830 | new_expr->kind = EXPR_KIND_UOP; 831 | new_expr->as.uop = uop; 832 | new_expr->file_path = table->file_path; 833 | new_expr->file_row = cell->file_row; 834 | new_expr->file_col = cell->file_col; 835 | } 836 | 837 | return new_index; 838 | } 839 | break; 840 | 841 | default: 842 | UNREACHABLE("Unknown Expression Kind"); 843 | } 844 | } 845 | 846 | void table_eval_cell(Table *table, Expr_Buffer *eb, Cell_Index cell_index) 847 | { 848 | Cell *cell = table_cell_at(table, cell_index); 849 | 850 | switch (cell->kind) { 851 | case CELL_KIND_TEXT: 852 | case CELL_KIND_NUMBER: 853 | cell->status = EVALUATED; 854 | break; 855 | case CELL_KIND_EXPR: { 856 | if (cell->status == INPROGRESS) { 857 | fprintf(stderr, "%s:%zu:%zu: ERROR: circular dependency is detected!\n", table->file_path, cell->file_row, cell->file_col); 858 | exit(1); 859 | } 860 | 861 | if (cell->status == UNEVALUATED) { 862 | cell->status = INPROGRESS; 863 | cell->as.expr.value = table_eval_expr(table, eb, cell->as.expr.index); 864 | cell->status = EVALUATED; 865 | } 866 | } 867 | break; 868 | 869 | case CELL_KIND_CLONE: { 870 | if (cell->status == INPROGRESS) { 871 | fprintf(stderr, "%s:%zu:%zu: ERROR: circular dependency is detected!\n", table->file_path, cell->file_row, cell->file_col); 872 | exit(1); 873 | } 874 | 875 | if (cell->status == UNEVALUATED) { 876 | cell->status = INPROGRESS; 877 | 878 | Dir dir = cell->as.clone; 879 | Cell_Index nbor_index = nbor_in_dir(cell_index, dir); 880 | if (nbor_index.row >= table->rows || nbor_index.col >= table->cols) { 881 | fprintf(stderr, "%s:%zu:%zu: ERROR: trying to clone a cell outside of the table\n", table->file_path, cell->file_row, cell->file_col); 882 | exit(1); 883 | } 884 | 885 | table_eval_cell(table, eb, nbor_index); 886 | 887 | Cell *nbor = table_cell_at(table, nbor_index); 888 | cell->kind = nbor->kind; 889 | cell->as = nbor->as; 890 | 891 | if (cell->kind == CELL_KIND_EXPR) { 892 | cell->as.expr.index = move_expr_in_dir(table, cell_index, eb, cell->as.expr.index, opposite_dir(dir)); 893 | cell->as.expr.value = table_eval_expr(table, eb, cell->as.expr.index); 894 | } 895 | 896 | cell->status = EVALUATED; 897 | } else { 898 | UNREACHABLE("evaluated clones are an absurd. When a clone cell is evaluated it becomes its neighbor kind"); 899 | } 900 | } 901 | break; 902 | 903 | default: 904 | UNREACHABLE("unknown Cell Kind"); 905 | } 906 | } 907 | 908 | int main(int argc, char **argv) 909 | { 910 | if (argc < 2) { 911 | usage(stderr); 912 | fprintf(stderr, "ERROR: input file is not provided\n"); 913 | exit(1); 914 | } 915 | 916 | const char *input_file_path = argv[1]; 917 | 918 | size_t content_size = 0; 919 | char *content = slurp_file(input_file_path, &content_size); 920 | if (content == NULL) { 921 | fprintf(stderr, "ERROR: could not read file %s: %s\n", 922 | input_file_path, strerror(errno)); 923 | exit(1); 924 | } 925 | 926 | String_View input = { 927 | .count = content_size, 928 | .data = content, 929 | }; 930 | 931 | Expr_Buffer eb = {0}; 932 | Table table = { 933 | .file_path = input_file_path, 934 | }; 935 | Tmp_Cstr tc = {0}; 936 | 937 | estimate_table_size(input, &table.rows, &table.cols); 938 | table.cells = malloc(sizeof(*table.cells) * table.rows * table.cols); 939 | memset(table.cells, 0, sizeof(*table.cells) * table.rows * table.cols); 940 | parse_table_from_content(&table, &eb, &tc, input); 941 | 942 | // Evaluate each cell 943 | for (size_t row = 0; row < table.rows; ++row) { 944 | for (size_t col = 0; col < table.cols; ++col) { 945 | Cell_Index cell_index = { 946 | .row = row, 947 | .col = col, 948 | }; 949 | table_eval_cell(&table, &eb, cell_index); 950 | } 951 | } 952 | 953 | // Estimate column widths 954 | size_t *col_widths = malloc(sizeof(size_t) * table.cols); 955 | { 956 | for (size_t col = 0; col < table.cols; ++col) { 957 | col_widths[col] = 0; 958 | for (size_t row = 0; row < table.rows; ++row) { 959 | Cell_Index cell_index = { 960 | .row = row, 961 | .col = col, 962 | }; 963 | 964 | Cell *cell = table_cell_at(&table, cell_index); 965 | size_t width = 0; 966 | switch (cell->kind) { 967 | case CELL_KIND_TEXT: 968 | width = cell->as.text.count; 969 | break; 970 | 971 | case CELL_KIND_NUMBER: { 972 | int n = snprintf(NULL, 0, "%lf", cell->as.number); 973 | assert(n >= 0); 974 | width = (size_t) n; 975 | } 976 | break; 977 | 978 | case CELL_KIND_EXPR: { 979 | int n = snprintf(NULL, 0, "%lf", cell->as.expr.value); 980 | assert(n >= 0); 981 | width = (size_t) n; 982 | } 983 | break; 984 | 985 | case CELL_KIND_CLONE: 986 | UNREACHABLE("cell should never be a clone after the evaluation"); 987 | } 988 | 989 | if (col_widths[col] < width) { 990 | col_widths[col] = width; 991 | } 992 | } 993 | } 994 | } 995 | 996 | // Render the table 997 | for (size_t row = 0; row < table.rows; ++row) { 998 | for (size_t col = 0; col < table.cols; ++col) { 999 | Cell_Index cell_index = { 1000 | .row = row, 1001 | .col = col, 1002 | }; 1003 | 1004 | Cell *cell = table_cell_at(&table, cell_index); 1005 | int n = 0; 1006 | switch (cell->kind) { 1007 | case CELL_KIND_TEXT: 1008 | n = printf(SV_Fmt, SV_Arg(cell->as.text)); 1009 | break; 1010 | 1011 | case CELL_KIND_NUMBER: 1012 | n = printf("%lf", cell->as.number); 1013 | break; 1014 | 1015 | case CELL_KIND_EXPR: 1016 | n = printf("%lf", cell->as.expr.value); 1017 | break; 1018 | 1019 | case CELL_KIND_CLONE: 1020 | UNREACHABLE("cell should never be a clone after the evaluation"); 1021 | } 1022 | assert(0 <= n); 1023 | assert((size_t) n <= col_widths[col]); 1024 | printf("%*s", (int) (col_widths[col] - n), ""); 1025 | 1026 | if (col < table.cols - 1) { 1027 | printf("|"); 1028 | } 1029 | } 1030 | printf("\n"); 1031 | } 1032 | 1033 | free(col_widths); 1034 | free(content); 1035 | free(table.cells); 1036 | free(eb.items); 1037 | free(tc.cstr); 1038 | 1039 | return 0; 1040 | } 1041 | 1042 | -------------------------------------------------------------------------------- /src/sv.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Alexey Kutepov 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #ifndef SV_H_ 23 | #define SV_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | typedef struct { 32 | size_t count; 33 | const char *data; 34 | } String_View; 35 | 36 | #define SV(cstr_lit) \ 37 | ((String_View) { \ 38 | .count = sizeof(cstr_lit) - 1, \ 39 | .data = (cstr_lit) \ 40 | }) 41 | 42 | #define SV_STATIC(cstr_lit) \ 43 | { \ 44 | .count = sizeof(cstr_lit) - 1, \ 45 | .data = (cstr_lit) \ 46 | } 47 | 48 | #define SV_NULL (String_View) {0} 49 | 50 | // printf macros for String_View 51 | #define SV_Fmt "%.*s" 52 | #define SV_Arg(sv) (int) (sv).count, (sv).data 53 | // USAGE: 54 | // String_View name = ...; 55 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 56 | 57 | String_View sv_from_cstr(const char *cstr); 58 | String_View sv_trim_left(String_View sv); 59 | String_View sv_trim_right(String_View sv); 60 | String_View sv_trim(String_View sv); 61 | String_View sv_take_left_while(String_View sv, bool (*predicate)(char x)); 62 | String_View sv_chop_by_delim(String_View *sv, char delim); 63 | bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk); 64 | String_View sv_chop_left(String_View *sv, size_t n); 65 | String_View sv_chop_right(String_View *sv, size_t n); 66 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)); 67 | bool sv_index_of(String_View sv, char c, size_t *index); 68 | bool sv_eq(String_View a, String_View b); 69 | bool sv_starts_with(String_View sv, String_View prefix); 70 | bool sv_ends_with(String_View sv, String_View suffix); 71 | uint64_t sv_to_u64(String_View sv); 72 | 73 | #endif // SV_H_ 74 | 75 | #ifdef SV_IMPLEMENTATION 76 | String_View sv_from_cstr(const char *cstr) 77 | { 78 | return (String_View) { 79 | .count = strlen(cstr), 80 | .data = cstr, 81 | }; 82 | } 83 | 84 | String_View sv_trim_left(String_View sv) 85 | { 86 | size_t i = 0; 87 | while (i < sv.count && isspace(sv.data[i])) { 88 | i += 1; 89 | } 90 | 91 | return (String_View) { 92 | .count = sv.count - i, 93 | .data = sv.data + i, 94 | }; 95 | } 96 | 97 | String_View sv_trim_right(String_View sv) 98 | { 99 | size_t i = 0; 100 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 101 | i += 1; 102 | } 103 | 104 | return (String_View) { 105 | .count = sv.count - i, 106 | .data = sv.data 107 | }; 108 | } 109 | 110 | String_View sv_trim(String_View sv) 111 | { 112 | return sv_trim_right(sv_trim_left(sv)); 113 | } 114 | 115 | String_View sv_chop_left(String_View *sv, size_t n) 116 | { 117 | if (n > sv->count) { 118 | n = sv->count; 119 | } 120 | 121 | String_View result = { 122 | .data = sv->data, 123 | .count = n, 124 | }; 125 | 126 | sv->data += n; 127 | sv->count -= n; 128 | 129 | return result; 130 | } 131 | 132 | String_View sv_chop_right(String_View *sv, size_t n) 133 | { 134 | if (n > sv->count) { 135 | n = sv->count; 136 | } 137 | 138 | String_View result = { 139 | .data = sv->data + sv->count - n, 140 | .count = n 141 | }; 142 | 143 | sv->count -= n; 144 | 145 | return result; 146 | } 147 | 148 | bool sv_index_of(String_View sv, char c, size_t *index) 149 | { 150 | size_t i = 0; 151 | while (i < sv.count && sv.data[i] != c) { 152 | i += 1; 153 | } 154 | 155 | if (i < sv.count) { 156 | if (index) { 157 | *index = i; 158 | } 159 | return true; 160 | } else { 161 | return false; 162 | } 163 | } 164 | 165 | bool sv_try_chop_by_delim(String_View *sv, char delim, String_View *chunk) 166 | { 167 | size_t i = 0; 168 | while (i < sv->count && sv->data[i] != delim) { 169 | i += 1; 170 | } 171 | 172 | String_View result = { 173 | .count = i, 174 | .data = sv->data, 175 | }; 176 | 177 | if (i < sv->count) { 178 | sv->count -= i + 1; 179 | sv->data += i + 1; 180 | if (chunk) { 181 | *chunk = result; 182 | } 183 | return true; 184 | } 185 | 186 | return false; 187 | } 188 | 189 | String_View sv_chop_by_delim(String_View *sv, char delim) 190 | { 191 | size_t i = 0; 192 | while (i < sv->count && sv->data[i] != delim) { 193 | i += 1; 194 | } 195 | 196 | String_View result = { 197 | .count = i, 198 | .data = sv->data, 199 | }; 200 | 201 | if (i < sv->count) { 202 | sv->count -= i + 1; 203 | sv->data += i + 1; 204 | } else { 205 | sv->count -= i; 206 | sv->data += i; 207 | } 208 | 209 | return result; 210 | } 211 | 212 | bool sv_starts_with(String_View sv, String_View expected_prefix) 213 | { 214 | if (expected_prefix.count <= sv.count) { 215 | String_View actual_prefix = { 216 | .data = sv.data, 217 | .count = expected_prefix.count, 218 | }; 219 | 220 | return sv_eq(expected_prefix, actual_prefix); 221 | } 222 | 223 | return false; 224 | } 225 | 226 | bool sv_ends_with(String_View sv, String_View expected_suffix) 227 | { 228 | if (expected_suffix.count <= sv.count) { 229 | String_View actual_suffix = { 230 | .data = sv.data + sv.count - expected_suffix.count, 231 | .count = expected_suffix.count 232 | }; 233 | 234 | return sv_eq(expected_suffix, actual_suffix); 235 | } 236 | 237 | return false; 238 | } 239 | 240 | bool sv_eq(String_View a, String_View b) 241 | { 242 | if (a.count != b.count) { 243 | return false; 244 | } else { 245 | return memcmp(a.data, b.data, a.count) == 0; 246 | } 247 | } 248 | 249 | uint64_t sv_to_u64(String_View sv) 250 | { 251 | uint64_t result = 0; 252 | 253 | for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) { 254 | result = result * 10 + (uint64_t) sv.data[i] - '0'; 255 | } 256 | 257 | return result; 258 | } 259 | 260 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)) 261 | { 262 | size_t i = 0; 263 | while (i < sv->count && predicate(sv->data[i])) { 264 | i += 1; 265 | } 266 | return sv_chop_left(sv, i); 267 | } 268 | 269 | String_View sv_take_left_while(String_View sv, bool (*predicate)(char x)) 270 | { 271 | size_t i = 0; 272 | while (i < sv.count && predicate(sv.data[i])) { 273 | i += 1; 274 | } 275 | return (String_View) { 276 | .count = i, 277 | .data = sv.data, 278 | }; 279 | } 280 | 281 | #endif // SV_IMPLEMENTATION 282 | --------------------------------------------------------------------------------