├── tests ├── eol.mdr ├── fake_cmd.mdr ├── not_blk.mdr ├── not_blk2.mdr ├── not_inc.mdr ├── unstarted.mdr ├── unwritable_view.mdr ├── err │ ├── missing_exec.err │ ├── missing_snip.err │ ├── missing_view.err │ ├── recurs.err │ ├── non_existant_file.err │ ├── unwritable_exec.err │ ├── unwritable_view.err │ └── invalid_include.err ├── missing_view.mdr ├── invalid_include.mdr ├── recurs.mdr ├── file_snip.mdr ├── unfinished.mdr ├── missing_file.mdr ├── file_view.mdr ├── snip_done.mdr ├── missing_snip.mdr ├── file_file.mdr ├── include_view.mdr ├── create_unwritable.mdr ├── file_append.mdr ├── unwritable_file.mdr ├── realloc.mdr ├── include_snip.mdr └── include_file.mdr ├── _config.yml ├── assets ├── logo.png └── logoreadme.png ├── TODO ├── .gitignore ├── config.mk.orig ├── include ├── mdr_string.h ├── lexer.h ├── line_counter.h ├── viewopt.h ├── know.h ├── io.h ├── range.h ├── ast.h ├── mdr.h └── container.h ├── .github └── workflows │ ├── windows.yml │ ├── linux.yml │ ├── macos.yml │ ├── nightly.yml │ └── codeql-analysis.yml ├── src ├── mdr_string.c ├── container.c ├── viewopt.c ├── util.c ├── mdr.c ├── know.c ├── file.c ├── range.c ├── main.c ├── view.c ├── snip.c ├── parser.c └── lexer.c ├── Makefile ├── scripts └── test.sh ├── README.mdr └── README.md /tests/eol.mdr: -------------------------------------------------------------------------------- 1 | @exec 2 | -------------------------------------------------------------------------------- /tests/fake_cmd.mdr: -------------------------------------------------------------------------------- 1 | @elkj 2 | -------------------------------------------------------------------------------- /tests/not_blk.mdr: -------------------------------------------------------------------------------- 1 | @` 2 | -------------------------------------------------------------------------------- /tests/not_blk2.mdr: -------------------------------------------------------------------------------- 1 | @`` 2 | -------------------------------------------------------------------------------- /tests/not_inc.mdr: -------------------------------------------------------------------------------- 1 | @[ 2 | -------------------------------------------------------------------------------- /tests/unstarted.mdr: -------------------------------------------------------------------------------- 1 | @``` 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /tests/unwritable_view.mdr: -------------------------------------------------------------------------------- 1 | # test 2 | -------------------------------------------------------------------------------- /tests/err/missing_exec.err: -------------------------------------------------------------------------------- 1 | can't find 2 | -------------------------------------------------------------------------------- /tests/err/missing_snip.err: -------------------------------------------------------------------------------- 1 | can't find 2 | -------------------------------------------------------------------------------- /tests/err/missing_view.err: -------------------------------------------------------------------------------- 1 | can't find 2 | -------------------------------------------------------------------------------- /tests/err/recurs.err: -------------------------------------------------------------------------------- 1 | recursive snippet 2 | -------------------------------------------------------------------------------- /tests/missing_view.mdr: -------------------------------------------------------------------------------- 1 | @[[ missing ]] 2 | -------------------------------------------------------------------------------- /tests/err/non_existant_file.err: -------------------------------------------------------------------------------- 1 | can't open 2 | -------------------------------------------------------------------------------- /tests/err/unwritable_exec.err: -------------------------------------------------------------------------------- 1 | can't open 2 | -------------------------------------------------------------------------------- /tests/err/unwritable_view.err: -------------------------------------------------------------------------------- 1 | can't open 2 | -------------------------------------------------------------------------------- /tests/err/invalid_include.err: -------------------------------------------------------------------------------- 1 | missing include name 2 | -------------------------------------------------------------------------------- /tests/invalid_include.mdr: -------------------------------------------------------------------------------- 1 | @``` test 2 | @[[ 3 | @``` 4 | -------------------------------------------------------------------------------- /tests/recurs.mdr: -------------------------------------------------------------------------------- 1 | @``` recurs 2 | @[[ recurs ]] 3 | @``` 4 | -------------------------------------------------------------------------------- /tests/file_snip.mdr: -------------------------------------------------------------------------------- 1 | @``` test 2 | @exec echo "Hello, World" 3 | @``` 4 | -------------------------------------------------------------------------------- /tests/unfinished.mdr: -------------------------------------------------------------------------------- 1 | # testing unfinished blocks 2 | @``` unfinished 3 | -------------------------------------------------------------------------------- /tests/missing_file.mdr: -------------------------------------------------------------------------------- 1 | @``` result/test.test 2 | @[[ include ]] 3 | @``` 4 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fennecdjay/mdr/HEAD/assets/logo.png -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | check mdr_open_write 2 | 3 | we need lines and filename 4 | 5 | test ranges 6 | -------------------------------------------------------------------------------- /tests/file_view.mdr: -------------------------------------------------------------------------------- 1 | # Testing @@exec in view 2 | 3 | @exec echo "Hello, World!" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.mk 2 | result 3 | mdr 4 | *.err 5 | *.md 6 | *.o 7 | *.gcda 8 | *.gcno 9 | -------------------------------------------------------------------------------- /assets/logoreadme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fennecdjay/mdr/HEAD/assets/logoreadme.png -------------------------------------------------------------------------------- /config.mk.orig: -------------------------------------------------------------------------------- 1 | DEBUG ?= 0 2 | ASAN ?= 0 3 | COVERAGE ?= 0 4 | PREFIX ?= /usr/local 5 | -------------------------------------------------------------------------------- /tests/snip_done.mdr: -------------------------------------------------------------------------------- 1 | @``` include 2 | include 3 | @``` 4 | 5 | @``` test 6 | @[[ include ]] 7 | @``` 8 | -------------------------------------------------------------------------------- /tests/missing_snip.mdr: -------------------------------------------------------------------------------- 1 | @``` test 2 | @[[ include]] 3 | @``` 4 | 5 | @``` include 6 | @[[ missing ]] 7 | @``` 8 | -------------------------------------------------------------------------------- /tests/file_file.mdr: -------------------------------------------------------------------------------- 1 | # Testing exec cmd in exec 2 | 3 | @``` result/test.cmd 4 | @exec echo "Hello, World!" 5 | @``` 6 | -------------------------------------------------------------------------------- /tests/include_view.mdr: -------------------------------------------------------------------------------- 1 | # Testing include 2 | 3 | We'll be including @[[ some_text ]]. 4 | 5 | @``` some_text 6 | some text 7 | @``` 8 | -------------------------------------------------------------------------------- /tests/create_unwritable.mdr: -------------------------------------------------------------------------------- 1 | # Create an unwritable file 2 | 3 | @exec touch tests/unwritable_view.md 4 | @exec chmod -w tests/unwritable_view.md 5 | -------------------------------------------------------------------------------- /tests/file_append.mdr: -------------------------------------------------------------------------------- 1 | # Test append to @@exec 2 | 3 | @``` result/append.test 4 | first line 5 | @``` 6 | 7 | @``` result/append.test 8 | second line 9 | @``` 10 | -------------------------------------------------------------------------------- /tests/unwritable_file.mdr: -------------------------------------------------------------------------------- 1 | @``` test 2 | @exec touch result/unwritable.file 3 | @exec chmod -w result/unwritable.file 4 | @``` 5 | 6 | @``` result/unwritable.file 7 | test 8 | @``` 9 | -------------------------------------------------------------------------------- /tests/realloc.mdr: -------------------------------------------------------------------------------- 1 | @``` include0 2 | @[[ include1 ]] 3 | @``` 4 | @``` include1 5 | @[[ include2 ]] 6 | @``` 7 | @``` include2 8 | @[[ include3 ]] 9 | @``` 10 | @``` include3 11 | @[[ include4 ]] 12 | @``` 13 | -------------------------------------------------------------------------------- /tests/include_snip.mdr: -------------------------------------------------------------------------------- 1 | # Testing include 2 | 3 | @``` include 4 | We'll be including @[[ some_text ]]. 5 | @``` 6 | 7 | @``` some_text these five words are ignored 8 | some 9 | @``` 10 | 11 | @``` some_text 12 | text 13 | @``` 14 | -------------------------------------------------------------------------------- /include/mdr_string.h: -------------------------------------------------------------------------------- 1 | struct MdrString { 2 | char *str; 3 | size_t sz; 4 | }; 5 | 6 | struct MdrString* new_string(const char *, const size_t); 7 | void free_string(struct MdrString *a); 8 | void string_append(struct MdrString *old, struct MdrString *new); 9 | -------------------------------------------------------------------------------- /tests/include_file.mdr: -------------------------------------------------------------------------------- 1 | # Testing include 2 | 3 | @``` result/include.file 4 | We'll be including @[[ some_text ]]. 5 | @``` 6 | 7 | @``` some_text these five words are ignored 8 | some text. 9 | @``` 10 | 11 | @``` some_text 12 | And another line 13 | @``` 14 | -------------------------------------------------------------------------------- /include/lexer.h: -------------------------------------------------------------------------------- 1 | struct Lexer { 2 | char *str; 3 | char *filename; 4 | struct AstInfo info; 5 | size_t idx; 6 | unsigned int alt; 7 | unsigned int line; 8 | }; 9 | 10 | enum mdr_status tokenize(struct Lexer*); 11 | 12 | struct AstInfo lex_info(struct Lexer *lex); 13 | -------------------------------------------------------------------------------- /include/line_counter.h: -------------------------------------------------------------------------------- 1 | struct LineCounter { 2 | char *str; 3 | long sz; 4 | long count; 5 | }; 6 | 7 | static inline void linecounter_run(struct LineCounter* lc, const long n) { 8 | while(*lc->str && lc->count < n) { 9 | if(*lc->str == '\n') 10 | ++lc->count; 11 | ++lc->sz; 12 | ++lc->str; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /include/viewopt.h: -------------------------------------------------------------------------------- 1 | struct ViewOpt { 2 | struct MdrString *blk_pre; 3 | struct MdrString *blk_ini; 4 | struct MdrString *blk_mid; 5 | struct MdrString *blk_end; 6 | struct MdrString *cmd_ini; 7 | struct MdrString *cmd_mid; 8 | struct MdrString *cmd_end; 9 | }; 10 | 11 | void viewopt_fill(struct ViewOpt*); 12 | void viewopt_release(struct ViewOpt*); 13 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Windows 8 | runs-on: windows-latest 9 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 10 | 11 | steps: 12 | - name: checkout 13 | uses: actions/checkout@v2 14 | - name: make 15 | run: make 16 | env: 17 | CC: gcc 18 | - name: test 19 | run: bash scripts/test.sh 20 | -------------------------------------------------------------------------------- /include/know.h: -------------------------------------------------------------------------------- 1 | struct Know { 2 | struct Map_ curr; 3 | Map global; 4 | struct Map_ file_done; 5 | }; 6 | 7 | struct MdrString* snippet_find(struct Know*, const struct MdrString*); 8 | void snippet_set(struct Know *, const char*, const struct MdrString*); 9 | 10 | void file_set(struct Know *, const struct Ast *ast, const struct MdrString*); 11 | 12 | struct MdrString* know_get(struct Know*, const struct Ast*); 13 | void know_release(struct Know*); 14 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Linux ${{ matrix.cc }} 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 10 | strategy: 11 | matrix: 12 | cc: [gcc, clang] 13 | 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v2 17 | - name: make 18 | run: ASAN=1 make 19 | - name: make test 20 | run: make test 21 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOs 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | name: Mac ${{ matrix.cc }} 8 | runs-on: macos-latest 9 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 10 | strategy: 11 | matrix: 12 | cc: [gcc, clang] 13 | 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v2 17 | - name: make 18 | run: ASAN=1 make 19 | - name: make test 20 | run: bash scripts/test.sh 21 | -------------------------------------------------------------------------------- /include/io.h: -------------------------------------------------------------------------------- 1 | struct MdrString* filename2str(const char*, const struct Loc*); 2 | struct MdrString* cmd(const struct Ast*); 3 | static inline void trimsz(char *str, size_t sz) { 4 | if(sz) 5 | str[sz - 1] = '\0'; 6 | } 7 | static inline void trim(char *str) { 8 | trimsz(str, strlen(str)); 9 | } 10 | 11 | FILE* mdr_open_write(const char*, const struct Loc*); 12 | FILE* mdr_open_read(const char*, const struct Loc*); 13 | void write_file(const char*, const struct Loc*, const struct MdrString *text); 14 | -------------------------------------------------------------------------------- /include/range.h: -------------------------------------------------------------------------------- 1 | struct Range { 2 | long ini; 3 | long end; 4 | }; 5 | 6 | struct Range actual_range(char*, const struct Range*); 7 | 8 | 9 | struct RangeIncluder { 10 | struct MdrString *str; 11 | struct Range range; 12 | }; 13 | 14 | void string_append_range(struct MdrString *base, struct RangeIncluder *sr); 15 | 16 | 17 | struct RangeExcluder { 18 | struct MdrString *end; 19 | struct MdrString *src; 20 | }; 21 | 22 | struct MdrString* excluder_ini(struct RangeExcluder*, const struct Range*); 23 | void excluder_end(struct RangeExcluder*, struct MdrString*); 24 | -------------------------------------------------------------------------------- /include/ast.h: -------------------------------------------------------------------------------- 1 | enum mdr_status { 2 | mdr_str, 3 | mdr_inc, 4 | mdr_cmd, 5 | mdr_blk, 6 | mdr_end, 7 | mdr_eof, 8 | mdr_ok, 9 | mdr_err 10 | }; 11 | 12 | struct Loc { 13 | char *filename; 14 | short unsigned int start; 15 | short unsigned int end; 16 | }; 17 | 18 | struct AstInfo { 19 | struct MdrString *str; 20 | struct Range range; 21 | int dot; 22 | }; 23 | 24 | struct Ast { 25 | struct AstInfo info; 26 | struct Ast *ast; 27 | struct Ast *next; 28 | struct Loc loc; 29 | enum mdr_status type; 30 | }; 31 | 32 | void free_ast(struct Ast*); 33 | -------------------------------------------------------------------------------- /include/mdr.h: -------------------------------------------------------------------------------- 1 | #ifdef __AFL_HAVE_MANUAL_CONTROL 2 | #define fprintf(a,b, ...) 3 | #define fopen(a,b) (FILE*)1 4 | #define fclose(a) 5 | #endif 6 | 7 | struct Mdr { 8 | struct Map_ snip; 9 | struct Map_ file; 10 | struct Know know; 11 | struct ViewOpt *vopt; 12 | char *name; 13 | }; 14 | 15 | enum mdr_status snip(struct Mdr *mdr); 16 | enum mdr_status file(struct Mdr *mdr); 17 | enum mdr_status view_ast(struct Mdr *mdr, struct Ast *ast); 18 | struct Ast* mdr_parse(struct Mdr *, char *const); 19 | enum mdr_status mdr_fail(const struct Loc *loc, const char *fmt, ...); 20 | 21 | // mdr.c 22 | void mdr_init(struct Mdr*); 23 | void mdr_run(struct Mdr*, struct Ast*); 24 | void mdr_release(struct Mdr*); 25 | -------------------------------------------------------------------------------- /src/mdr_string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | 11 | struct MdrString* new_string(const char *str, const size_t sz) { 12 | struct MdrString *a = malloc(sizeof(struct MdrString)); 13 | a->str = malloc(sz + 1); 14 | strncpy(a->str, str, sz); 15 | a->str[sz] = '\0'; 16 | a->sz = sz; 17 | return a; 18 | } 19 | 20 | void free_string(struct MdrString *a) { 21 | free(a->str); 22 | free(a); 23 | } 24 | 25 | void string_append(struct MdrString *old, struct MdrString *new) { 26 | if(!new->sz) 27 | return; 28 | const size_t len = old->sz + new->sz + 1; 29 | char *str = realloc(old->str, len); 30 | strncpy(str + old->sz, new->str, new->sz); 31 | old->sz += new->sz; 32 | str[old->sz] = '\0'; 33 | old->str = str; 34 | } 35 | -------------------------------------------------------------------------------- /src/container.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void map_init(const Map a) { 7 | a->ptr = calloc(MAP_CAP, SZ_INT); 8 | VCAP(a) = MAP_CAP; 9 | } 10 | 11 | vtype map_get(const Map map, const char *key) { 12 | for(vtype i = VLEN(map) + 1; --i;) 13 | if(!strcmp(key, (char*)VKEY(map, i - 1))) 14 | return VVAL(map, i - 1); 15 | return 0; 16 | } 17 | 18 | void map_set(const Map map, const vtype key, const vtype ptr) { 19 | vector_realloc((Vector)map); 20 | VKEY(map, VLEN(map)) = key; 21 | VVAL(map, VLEN(map)++) = ptr; 22 | } 23 | 24 | void map_release(const Map map) { 25 | free(map->ptr); 26 | } 27 | 28 | void map_release_vector(const Map map) { 29 | for(vtype i = 0; i < map_size(map); ++i) { 30 | vector_release((Vector)VVAL(map, i)); 31 | free((Vector)VVAL(map, i)); 32 | } 33 | map_release(map); 34 | } 35 | 36 | void map_release_string(Map map) { 37 | for(vtype i = 0; i < map_size(map); ++i) { 38 | free((char*)VKEY(map, i)); 39 | free_string((struct MdrString*)VVAL(map, i)); 40 | } 41 | map_release(map); 42 | } 43 | -------------------------------------------------------------------------------- /src/viewopt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "mdr_string.h" 4 | #include "viewopt.h" 5 | 6 | static const char* view_opt[][2] = { 7 | { "MDR_BLK_PRE", " > " }, 8 | { "MDR_BLK_INI", "\n``` " }, 9 | { "MDR_BLK_MID", " \n" }, 10 | { "MDR_BLK_END", "``` " }, 11 | { "MDR_CMD_INI", " > exec: " }, 12 | { "MDR_CMD_MID", " \n```\n" }, 13 | { "MDR_CMD_END", "``` \n" }, 14 | }; 15 | 16 | static inline struct MdrString* viewopt_get(const size_t idx) { 17 | const char *str = getenv(view_opt[idx][0]) ?: view_opt[idx][1]; 18 | return new_string(str, strlen(str)); 19 | } 20 | 21 | void viewopt_fill(struct ViewOpt *vo) { 22 | vo->blk_pre = viewopt_get(0); 23 | vo->blk_ini = viewopt_get(1); 24 | vo->blk_mid = viewopt_get(2); 25 | vo->blk_end = viewopt_get(3); 26 | vo->cmd_ini = viewopt_get(4); 27 | vo->cmd_mid = viewopt_get(5); 28 | vo->cmd_end = viewopt_get(6); 29 | } 30 | 31 | void viewopt_release(struct ViewOpt *vo) { 32 | free_string(vo->blk_pre); 33 | free_string(vo->blk_ini); 34 | free_string(vo->blk_mid); 35 | free_string(vo->blk_end); 36 | free_string(vo->cmd_ini); 37 | free_string(vo->cmd_mid); 38 | free_string(vo->cmd_end); 39 | } 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq (,$(wildcard config.mk)) 2 | $(shell cp config.mk.orig config.mk) 3 | endif 4 | include config.mk 5 | 6 | src := $(wildcard src/*.c) 7 | obj := $(src:.c=.o) 8 | 9 | INCLUDES += -Iinclude 10 | WARNINGS += -Wall -Wextra 11 | 12 | CFLAGS += -Ofast ${INCLUDES} ${WARNINGS} 13 | 14 | ifeq (${ASAN}, 1) 15 | DEBUG = 1 16 | CFLAGS += -fsanitize=address -fno-omit-frame-pointer 17 | LDFLAGS += -fsanitize=address -fno-omit-frame-pointer 18 | endif 19 | 20 | ifeq (${DEBUG}, 1) 21 | CFLAGS += -g -Og 22 | else 23 | CFLAGS += -DNDEBUG -flto -Ofast 24 | LDFLAGS += -flto 25 | CFLAGS += -fomit-frame-pointer -fno-stack-protector -fno-common -fstrict-aliasing 26 | LDFLAGS += -fomit-frame-pointer -fno-stack-protector -fno-common 27 | endif 28 | 29 | ifeq (${COVERAGE}, 1) 30 | CFLAGS += --coverage -O0 31 | LDFLAGS += -ftest-coverage -fprofile-arcs 32 | endif 33 | 34 | all: mdr 35 | 36 | mdr: ${obj} 37 | ${CC} ${obj} ${LDFLAGS} -o mdr 38 | 39 | install: mdr 40 | install mdr ${PREFIX}/bin 41 | 42 | uninstall: 43 | rm ${PREFIX}/bin/mdr 44 | 45 | test: mdr 46 | @MDR_MAIN="README.mdr:non_:tests/invalid_include.mdr" bash scripts/test.sh 47 | @make mostly-clean 48 | @bash scripts/test.sh 49 | 50 | mostly-clean: 51 | @rm -rf result 52 | @rm -f tests/*.md tests/*.err 53 | 54 | clean: mostly-clean 55 | @rm -f ${obj} mdr $(src:.c=.gcda) $(src:.c=.gcno) 56 | 57 | .SUFFIXES: .c .o 58 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "container.h" 7 | #include "mdr_string.h" 8 | #include "range.h" 9 | #include "ast.h" 10 | #include "know.h" 11 | #include "mdr.h" 12 | #include "io.h" 13 | 14 | static struct MdrString* file2str(FILE *f) { 15 | char line[128]; 16 | struct MdrString *buf = new_string("", 0); 17 | while(fgets(line, sizeof(line), f)) { 18 | buf->sz += strlen(line); 19 | strcat(buf->str = realloc(buf->str, buf->sz + 1), line); 20 | } 21 | return buf; 22 | } 23 | 24 | struct MdrString* filename2str(const char *filename, const struct Loc *loc) { 25 | FILE * f = mdr_open_read(filename, loc); 26 | if(!f) 27 | return NULL; 28 | struct MdrString *str = file2str(f); 29 | fclose(f); 30 | return str; 31 | } 32 | 33 | struct MdrString* cmd(const struct Ast *ast) { 34 | #ifdef __AFL_COMPILER 35 | return new_string("", 0); 36 | #endif 37 | const char *str = ast->info.str->str; 38 | FILE *f = popen(str, "r"); 39 | if(!f) { 40 | mdr_fail(&ast->loc, "can't exec '%s'\n", str); 41 | return NULL; 42 | } 43 | struct MdrString *ret = file2str(f); 44 | pclose(f); 45 | return ret; 46 | } 47 | 48 | FILE* mdr_open_read(const char *str, const struct Loc *loc) { 49 | FILE *const file = fopen(str, "rb"); 50 | if(file) 51 | return file; 52 | mdr_fail(loc, "can't open '%s' for reading\n", str); 53 | return NULL; 54 | } 55 | 56 | FILE* mdr_open_write(const char *str, const struct Loc *loc) { 57 | FILE *const file = fopen(str, "w"); 58 | if(file) 59 | return file; 60 | mdr_fail(loc, "can't open '%s' for writing\n", str); 61 | return NULL; 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: Nightly 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | nightly: 10 | name: Deploy nightly 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macOS-latest] 15 | env: 16 | CC: gcc 17 | 18 | runs-on: ${{ matrix.os }} 19 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Build 25 | run: make 26 | 27 | - name: Build asset 28 | run: | 29 | strip mdr* 30 | 7z a mdr-nightly.zip mdr* 31 | 32 | - name: Set tag 33 | if: ${{ matrix.os == 'ubuntu-latest' }} 34 | run: | 35 | git config --local user.email "action@github.com" 36 | git config --local user.name "GitHub Action" 37 | git tag -f -a nightly -m "Nightly update" 38 | git push origin -f --follow-tags nightly 39 | 40 | - name: Deploy release 41 | uses: WebFreak001/deploy-nightly@v1.1.0 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | upload_url: https://uploads.github.com/repos/fennecdjay/mdr/releases/40889237/assets{?name,label} 46 | release_id: 40889237 # same as above (id can just be taken out the upload_url, it's used to find old releases) 47 | asset_path: ./mdr-nightly.zip # path to archive to upload 48 | asset_name: mdr-nightly-${{matrix.os}}-$$.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash 49 | asset_content_type: application/zip # required by GitHub API 50 | max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted 51 | -------------------------------------------------------------------------------- /src/mdr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "container.h" 6 | #include "mdr_string.h" 7 | #include "range.h" 8 | #include "ast.h" 9 | #include "know.h" 10 | #include "mdr.h" 11 | #include "viewopt.h" 12 | #include "io.h" 13 | 14 | void mdr_init(struct Mdr *mdr) { 15 | map_init(&mdr->snip); 16 | map_init(&mdr->file); 17 | map_init(&mdr->know.file_done); 18 | map_init(&mdr->know.curr); 19 | } 20 | 21 | void mdr_release(struct Mdr *mdr) { 22 | know_release(&mdr->know); 23 | map_release_vector(&mdr->snip); 24 | map_release_vector(&mdr->file); 25 | } 26 | 27 | void write_file(const char *str, const struct Loc *loc, const struct MdrString *text) { 28 | FILE *f = mdr_open_write(str, loc); 29 | if(!f) 30 | return; 31 | fwrite(text->str, text->sz, 1, f); 32 | fclose(f); 33 | } 34 | 35 | static void do_file(struct Map_ *map) { 36 | #ifndef __AFL_COMPILER 37 | for(vtype i = 0; i < map_size(map); ++i) { 38 | struct MdrString *text = (struct MdrString*)VVAL(map, i); 39 | if(text) { 40 | char *ptr = (char*)VKEY(map, i), 41 | *str = ptr + (sizeof (struct Loc)); 42 | write_file(str, (struct Loc*)ptr, text); 43 | } 44 | } 45 | #endif 46 | } 47 | 48 | void mdr_run(struct Mdr *mdr, struct Ast *ast) { 49 | if(snip(mdr) == mdr_err || file(mdr) == mdr_err) 50 | return; 51 | do_file(&mdr->know.file_done); 52 | view_ast(mdr, ast); 53 | } 54 | 55 | enum mdr_status mdr_fail(const struct Loc *loc, const char* fmt, ...) { 56 | #ifdef __AFL_COMPILER 57 | return mdr_err; 58 | #endif 59 | fprintf(stderr, "\033[31mMDR\033[0m:%s:%u-%u\n", loc->filename, loc->start, loc->end); 60 | va_list arg; 61 | va_start(arg, fmt); 62 | vfprintf(stderr, fmt, arg); 63 | va_end(arg); 64 | return mdr_err; 65 | } 66 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | set -Eeu 4 | 5 | readonly dir="result" 6 | 7 | if [ -t 1 ] || [ -n "$GITHUB_ACTIONS" ] 8 | then 9 | GREEN="\033[32m" 10 | RED="\033[31m" 11 | CLEAR="\033[0m" 12 | BOLD="\033[1m" 13 | else 14 | GREEN="" 15 | RED="" 16 | CLEAR="" 17 | BOLD="" 18 | fi 19 | 20 | [ -d "$dir" ] || mkdir "$dir" 21 | 22 | ok() { 23 | printf "${GREEN}OK${CLEAR} ${BOLD}% 3i${CLEAR} %s\n" "$1" "$2" 24 | return 0 25 | } 26 | 27 | not_ok() { 28 | printf "${RED}NOT OK${CLEAR} ${BOLD}% 3i${CLEAR} %s\n" "$1" "$2" 29 | return 1 30 | } 31 | 32 | file_test() { 33 | number=$1 34 | file=$2 35 | filename="$(basename $file)" 36 | err="tests/err/${filename:${#filename}-3}err" 37 | md="tests/result/${filename:${#filename}-1}" 38 | err_result="${file}.err" 39 | 40 | ./mdr "$2" 2> "$err_result" 41 | 42 | if [ -f "$md" ] 43 | then if [ -n "$(diff "$md" "$md_result")" ] 44 | then 45 | not_ok "$number" "$file" 46 | return 1 47 | fi 48 | elif [ -f "$err" ] && [[ $(cat "$err_result") != *"$(cat "$err")"* ]] 49 | then 50 | not_ok "$number" "$file" 51 | return 1 52 | fi 53 | 54 | ok "$number" "$file" 55 | return 0 56 | } 57 | 58 | TOTAL=$((2 + $(find tests/*.mdr | wc -l))) 59 | index=1 60 | echo "$index..$TOTAL" 61 | if [ "$(./mdr 2>&1)" = "usage: mdr " ] 62 | then ok "$index" "usage" 63 | else not_ok "$index" "usage" 64 | fi 65 | 66 | for file in tests/non_existant_file.mdr $(find tests/*.mdr) 67 | do 68 | index=$((index + 1)) 69 | file_test "$index" "$file" 70 | done 71 | 72 | #limit_test() { 73 | # which prlimit || return 74 | # prlimit -n4 ./mdr tests/exec_snip.mdr &>/dev/null 75 | # prlimit -n4 ./mdr tests/exec_exec.mdr &>/dev/null 76 | # prlimit -n4 ./mdr tests/exec_view.mdr &>/dev/null 77 | #} 78 | 79 | #[ $(uname) = "Linux" ] && limit_test || true 80 | -------------------------------------------------------------------------------- /include/container.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAP_PRIVATE 2 | #define _MAP_PRIVATE 3 | #include "inttypes.h" 4 | typedef uintptr_t vtype; 5 | struct Vector_ { 6 | vtype* ptr; 7 | }; 8 | 9 | struct Map_ { 10 | vtype* ptr; 11 | }; 12 | 13 | #define SZ_INT sizeof(uintptr_t) 14 | #define MAP_CAP 8 15 | #define OFFSET 2 16 | #define VLEN(v) (v)->ptr[0] 17 | #define VCAP(v) (v)->ptr[1] 18 | #define VPTR(v, i) (v)->ptr[OFFSET + (i)] 19 | #define VKEY(v, i) (v)->ptr[OFFSET + (i) * 2] 20 | #define VVAL(v, i) (v)->ptr[OFFSET + (i) * 2 + 1] 21 | #endif 22 | #ifndef __VECTOR 23 | #define __VECTOR 24 | 25 | typedef struct Vector_ * Vector; 26 | 27 | static inline vtype vector_front(const Vector v) { 28 | return VPTR(v, 0); 29 | } 30 | static inline vtype vector_at(const Vector v, const vtype i) { 31 | return VPTR(v, i); 32 | } 33 | static inline vtype vector_size(Vector const v) { 34 | return VLEN(v); 35 | } 36 | 37 | static inline void vector_realloc(const Vector v) { 38 | if((OFFSET + (VLEN(v) << 1) + 1) > VCAP(v)) 39 | v->ptr = realloc(v->ptr, (VCAP(v) <<= 1) * SZ_INT); 40 | } 41 | 42 | static inline void vector_init(const Vector v) { 43 | v->ptr = calloc(MAP_CAP, SZ_INT); 44 | VCAP(v) = MAP_CAP; 45 | } 46 | 47 | static inline void vector_release(const Vector v) { 48 | free(v->ptr); 49 | } 50 | 51 | static inline void vector_add(const Vector v, const vtype data) { 52 | vector_realloc(v); 53 | VPTR(v, VLEN(v)++) = (vtype)data; 54 | } 55 | 56 | typedef struct Map_ * Map; 57 | void map_init(const Map); 58 | vtype map_get(const Map, const char*); 59 | 60 | static inline vtype map_at(const Map map, const vtype index) { 61 | return VVAL(map, index); 62 | } 63 | void map_set(const Map, const vtype, const vtype); 64 | void map_release(const Map); 65 | 66 | static inline vtype map_size(const Map map) { 67 | return VLEN(map); 68 | } 69 | void map_release_vector(const Map map); 70 | void map_release_string(const Map map); 71 | #endif 72 | -------------------------------------------------------------------------------- /src/know.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "io.h" 11 | 12 | struct MdrString* snippet_find(struct Know *know, const struct MdrString *str) { 13 | const char *s = str->str; 14 | return (struct MdrString*)(map_get(&know->curr, s) ?: map_get(know->global, s)); 15 | } 16 | 17 | static struct MdrString* snippet_get(struct Know *know, const struct Ast *ast) { 18 | const char *s = ast->info.str->str; 19 | struct MdrString* ret = snippet_find(know, ast->info.str); 20 | if(ret) 21 | return ret; 22 | (void)mdr_fail(&ast->loc, "can't find '%s' snippet\n", s); 23 | return NULL; 24 | } 25 | 26 | void snippet_set(struct Know *know, const char *name, const struct MdrString *str) { 27 | map_set(&know->curr, (vtype)strdup(name), (vtype)str); 28 | } 29 | 30 | static struct MdrString* get_done(const Map map, const char *key) { 31 | for(vtype i = VLEN(map) + 1; --i;) { 32 | char *ptr = (char*)VKEY(map, i - 1); 33 | char* str = ptr + (sizeof (struct Loc)); 34 | if(!strcmp(key, str)) 35 | return (struct MdrString*)VVAL(map, i - 1); 36 | } 37 | return NULL; 38 | } 39 | 40 | static struct MdrString* file_get(struct Know *know, const struct Ast *ast) { 41 | const char *s = ast->info.str->str; 42 | struct MdrString *exists = get_done(&know->file_done, s); 43 | if(exists) 44 | return exists; 45 | struct MdrString *ret = filename2str(ast->info.str->str, &ast->loc); 46 | if(ret) 47 | file_set(know, ast, ret); 48 | return ret; 49 | } 50 | 51 | struct MdrString* know_get(struct Know *know, const struct Ast *ast) { 52 | return (!ast->info.dot ? snippet_get : file_get)(know, ast); 53 | } 54 | 55 | void file_set(struct Know *know, const struct Ast *key, const struct MdrString *text) { 56 | const Map map = &know->file_done; 57 | const char *str = key->info.str->str; 58 | char *ptr = malloc((sizeof (struct Loc)) + strlen(str) + 1); 59 | *(struct Loc*)ptr = key->loc; 60 | strcpy(ptr + (sizeof (struct Loc)), str); 61 | map_set(map, (vtype)ptr, (vtype)text); 62 | } 63 | 64 | void know_release(struct Know *know) { 65 | map_release_string(&know->curr); 66 | map_release_string(&know->file_done); 67 | } 68 | -------------------------------------------------------------------------------- /src/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "io.h" 11 | 12 | struct File { 13 | struct MdrString *curr; 14 | struct Know *know; 15 | struct Map_ *done; 16 | }; 17 | 18 | static enum mdr_status file_str(struct File *file, struct Ast *ast) { 19 | string_append(file->curr, ast->info.str); 20 | return mdr_ok; 21 | } 22 | 23 | static enum mdr_status file_inc(struct File *file, struct Ast *ast) { 24 | struct MdrString *str = know_get(file->know, ast); 25 | if(!str) 26 | return mdr_err; 27 | struct RangeIncluder rs = { .str=str, .range=ast->info.range }; 28 | string_append_range(file->curr, &rs); 29 | return mdr_ok; 30 | } 31 | 32 | static enum mdr_status file_cmd(struct File *file, struct Ast *ast) { 33 | struct MdrString *str = cmd(ast); 34 | if(!str) 35 | return mdr_err; 36 | string_append(file->curr, str); 37 | free_string(str); 38 | return mdr_ok; 39 | } 40 | 41 | typedef enum mdr_status (*file_ast_fn)(struct File*, struct Ast*); 42 | 43 | static const file_ast_fn _file_ast[] = { 44 | file_str, 45 | file_inc, 46 | file_cmd, 47 | }; 48 | 49 | static enum mdr_status actual_file_ast(struct File *file, struct Ast *src) { 50 | enum mdr_status ret = mdr_ok; 51 | struct Ast* ast = src->ast; 52 | struct RangeExcluder ex = { .src=file->curr }; 53 | if(src->info.range.ini) 54 | file->curr = excluder_ini(&ex, &src->info.range); 55 | do ret = _file_ast[ast->type](file, ast); 56 | while(ret != mdr_err && (ast = ast->next)); 57 | if(ex.end) 58 | excluder_end(&ex, file->curr); 59 | return ret; 60 | } 61 | 62 | enum mdr_status file(struct Mdr *mdr) { 63 | enum mdr_status ret = mdr_ok; 64 | for(vtype i = 0; ret == mdr_ok && i < map_size(&mdr->file); ++i) { 65 | struct File file = { .know=&mdr->know, .curr=new_string("", 0) }; 66 | const Vector v = (Vector)map_at(&mdr->file, i); 67 | for(vtype j = 0; ret == mdr_ok && j < vector_size(v); ++j) { 68 | struct Ast *ast = (struct Ast*)vector_at(v, j); 69 | ret = actual_file_ast(&file, ast); 70 | } 71 | if(ret == mdr_ok) 72 | file_set(&mdr->know, (struct Ast*)vector_at((Vector)VVAL(&mdr->file, i), 0) , file.curr); 73 | else 74 | free_string(file.curr); 75 | } 76 | return ret; 77 | } 78 | -------------------------------------------------------------------------------- /src/range.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "container.h" 6 | #include "mdr_string.h" 7 | #include "range.h" 8 | #include "ast.h" 9 | #include "know.h" 10 | #include "line_counter.h" 11 | #include "mdr.h" 12 | 13 | static long str_lines(char *str) { 14 | struct LineCounter lc = { .str=str }; 15 | linecounter_run(&lc, LONG_MAX); 16 | return lc.count; 17 | } 18 | 19 | struct Range actual_range(char *str, const struct Range *src) { 20 | if(!(src->ini < 0 || src->end < 0)) 21 | return *src; 22 | long lines = str_lines(str); 23 | struct Range range = { .ini=0, .end=0 }; 24 | range.ini = src->ini >= 0 ? src->ini : lines + src->ini; 25 | if(range.ini < 0) 26 | range.ini = 0; 27 | range.end = src->end >= 0 ? src->end : lines + src->end; 28 | if(range.end < 0) 29 | range.end = 0; 30 | return range; 31 | } 32 | 33 | static void _string_append_range(struct MdrString *base, struct RangeIncluder *sr) { 34 | if(!base) // return mdr_err 35 | return; 36 | char *str = sr->str->str; 37 | if(!str) // return mdr_err 38 | return; 39 | sr->range = actual_range(base->str, &sr->range); 40 | struct LineCounter lc = { .str=str }; 41 | linecounter_run(&lc, sr->range.ini - 1); 42 | size_t sz = lc.sz; 43 | lc.sz = 0; 44 | linecounter_run(&lc, sr->range.end + 1); 45 | struct MdrString new = { .str=str + sz, .sz=lc.sz }; 46 | string_append(base, &new); 47 | } 48 | 49 | void string_append_range(struct MdrString *base, struct RangeIncluder *sr) { 50 | if(!sr->range.ini) 51 | string_append(base, sr->str); 52 | else 53 | _string_append_range(base, sr); 54 | } 55 | 56 | struct MdrString* excluder_ini(struct RangeExcluder *ex, const struct Range *range) { 57 | char *str = ex->src->str; 58 | struct Range r = actual_range(str, range); 59 | struct LineCounter lc = { .str=ex->src->str }; 60 | linecounter_run(&lc, r.ini - 1); 61 | struct MdrString *ini = new_string(ex->src->str, lc.sz); 62 | linecounter_run(&lc, r.end); 63 | const long sz = lc.sz; 64 | lc.count = lc.sz = 0; 65 | linecounter_run(&lc, LONG_MAX); 66 | if(r.end >= r.ini) 67 | ex->end = new_string(ex->src->str + sz, lc.sz); 68 | return ini; 69 | } 70 | 71 | void excluder_end(struct RangeExcluder *ex, struct MdrString *str) { 72 | free_string(ex->src); 73 | if(!ex->end) 74 | return; 75 | string_append(str, ex->end); 76 | free_string(ex->end); 77 | } 78 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 18 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['cpp'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "viewopt.h" 11 | #include "io.h" 12 | 13 | static struct MdrString* mdr_open(char *filename) { 14 | struct Loc loc = { .filename="[global]" }; 15 | return filename2str(filename, &loc ); 16 | } 17 | 18 | struct Ast* prepare(struct Mdr *mdr, char *filename) { 19 | mdr->name = "global"; 20 | struct MdrString *str= mdr_open(filename); 21 | if(!str) 22 | return NULL; 23 | struct Ast *ast = mdr_parse(mdr, str->str); 24 | free_string(str); 25 | return ast; 26 | } 27 | 28 | void run(struct Mdr *mdr, char* buf) { 29 | mdr_init(mdr); 30 | struct Ast *ast = mdr_parse(mdr, buf); 31 | if(ast) { 32 | mdr_run(mdr, ast); 33 | free_ast(ast); 34 | } 35 | mdr_release(mdr); 36 | } 37 | 38 | #ifndef __AFL_HAVE_MANUAL_CONTROL 39 | 40 | static int usage(void) { 41 | fputs("usage: mdr \n", stderr); 42 | return EXIT_FAILURE; 43 | } 44 | 45 | static void _fill_global(Map map, char *str) { 46 | struct Mdr mdr = { .know= { .global = map } }; 47 | mdr.know.curr.ptr = map->ptr; 48 | struct Vector_ v; 49 | vector_init(&v); 50 | map_init(&mdr.snip); 51 | map_init(&mdr.file); 52 | map_init(&mdr.know.file_done); 53 | while(str) { 54 | char *old = str; 55 | if((str = strchr(++str, ':'))) { 56 | str[0] = '\0'; 57 | ++str; 58 | } 59 | struct Ast *ast = prepare(&mdr, old); 60 | if(ast) 61 | vector_add(&v, (vtype)ast); 62 | } 63 | snip(&mdr); 64 | map_release_vector(&mdr.file); 65 | map_release_vector(&mdr.snip); 66 | map_release_string(&mdr.know.file_done); 67 | for(vtype i = 0; i < vector_size(&v); ++i) 68 | free_ast((struct Ast*)vector_at(&v, i)); 69 | vector_release(&v); 70 | } 71 | 72 | static inline void fill_global(Map map) { 73 | char *str = getenv("MDR_MAIN"); 74 | if(str) 75 | _fill_global(map, str); 76 | } 77 | 78 | int main(int argc, char **argv) { 79 | if(argc < 2) 80 | return usage(); 81 | struct Map_ global; 82 | map_init(&global); 83 | fill_global(&global); 84 | struct ViewOpt vopt; 85 | viewopt_fill(&vopt); 86 | for(int i = 1; i < argc; ++i) { 87 | struct Mdr mdr = { .name=argv[i], .know = { .global=&global }, .vopt=&vopt }; 88 | struct MdrString *str= mdr_open(argv[i]); 89 | if(str) { 90 | run(&mdr, str->str); 91 | free_string(str); 92 | } 93 | } 94 | for(vtype i = 0; i < map_size(&global); ++i) { 95 | free((char*)VKEY(&global, i)); 96 | free_string((struct MdrString*)VVAL(&global, i)); 97 | } 98 | map_release(&global); 99 | viewopt_release(&vopt); 100 | return EXIT_SUCCESS; 101 | } 102 | 103 | #else 104 | 105 | #define BUFSIZE 1024 106 | 107 | int main(int argc, char **argv) { 108 | char buf[BUFSIZE]; 109 | struct ViewOpt vopt; 110 | viewopt_fill(&vopt); 111 | __AFL_INIT(); 112 | struct Map_ global; 113 | map_init(&global); 114 | while(__AFL_LOOP(1000)) { 115 | memset(buf, 0, BUFSIZE); 116 | read(0, buf, BUFSIZE); 117 | struct Mdr mdr = { .name="afl", .know={ .global=&global }, .vopt=&vopt }; 118 | run(&mdr, buf); 119 | } 120 | map_release(&global); 121 | viewopt_release(&vopt); 122 | return EXIT_SUCCESS; 123 | } 124 | 125 | #endif 126 | -------------------------------------------------------------------------------- /README.mdr: -------------------------------------------------------------------------------- 1 | # MDR, a markdown runner 2 | 3 | ![Linux](https://github.com/fennecdjay/mdr/workflows/Linux/badge.svg) 4 | ![MacOs](https://github.com/fennecdjay/mdr/workflows/MacOs/badge.svg) 5 | ![Windows](https://github.com/fennecdjay/mdr/workflows/Windows/badge.svg) 6 | [![Line Count](https://tokei.rs/b1/github/fennecdjay/mdr)](https://github.com/Gwion/mdr) 7 | 8 | mdr is a **small** *program* and *markup* 9 | designed to facilitate documentation and testing. 10 | It can both generate documentation pages and 11 | generate and actualaly test small code examples 12 | in your documentation. (This ensures any code 13 | examples you present to your users actually work.) 14 | 15 | ![logo](assets/logoreadme.png "The Mdr logo! (WIP)") 16 | 17 | I started this tool to help with [Gwion](https://github.com/fennecdjay/gwion)'s 18 | development, but it is not tied in any way to this project. 19 | 20 | Let's go over the basic functionality... :smile: 21 | 22 | 23 | ## How to write documentation pages with `mdr` 24 | 25 | For how to do a basic documentation 26 | page, [see this page's original source]( 27 | https://github.com/fennecdjay/mdr/blob/master/README.mdr). 28 | 29 | 30 | ## How to write code examples with `mdr` 31 | 32 | Let's write our first code example created from our 33 | documentation page, which also shows off templating and 34 | how to have the code automatically compiled and tested. 35 | 36 | 37 | ### Define a program structure 38 | 39 | @``` hello_world.c 40 | @[[Includes]] 41 | 42 | int main(int argc, char** argv) { 43 | @[[Print]] 44 | } 45 | @``` 46 | We fill in the `Includes` template variable as follows: 47 | 48 | 49 | ### Filling in template variables 50 | 51 | As we need the *puts* function, we need **stdio** headers. 52 | 53 | @``` Includes 54 | #include 55 | @``` 56 | 57 | We also fill in the print function we use above: 58 | 59 | @``` Print 60 | puts("Hello, World!"); 61 | @``` 62 | 63 | 64 | ### Compiling the example code 65 | 66 | Now, let's compile *hello_world.c* to make sure 67 | this test code owrks. 68 | 69 | @exec cc hello_world.c -o hello_world 70 | 71 | Yes, there should be no output, and that's good news 72 | since that means no compilation errors. 73 | 74 | 75 | ### Including a code file or other output 76 | 77 | Let's look at *hello_world.c*: 78 | 79 | @exec cat hello_world.c 80 | 81 | That's the content of the source file we generated (and compiled). 82 | You can see how this can be used for arbitrary shell commands 83 | to generate output. 84 | 85 | 86 | ### Testing example code 87 | 88 | Then we run our test program: 89 | 90 | @exec ./hello_world 91 | 92 | Do we read *Hello World!* ? 93 | Assuming yes, let's continue. 94 | 95 | 96 | ### More test 97 | 98 | @exec [ "$(./hello_world)" = "Hello, World!" ] && echo "OK" || echo "NOT_OK" 99 | 100 | 101 | ## Building `mdr` 102 | 103 | As a C program, it seemed natural to use [make](https://www.gnu.org/software/make) 104 | as a build system. 105 | 106 | ``` sh 107 | make 108 | ``` 109 | 110 | ## Testing `mdr` 111 | 112 | Also using make: 113 | ``` sh 114 | make test 115 | ``` 116 | 117 | You can also try 118 | ``` sh 119 | bash scripts/test.sh 120 | ``` 121 | 122 | ## Installing `mdr` 123 | 124 | As easy as before, just type. 125 | 126 | ``` sh 127 | make install 128 | ``` 129 | or just copy `mdr` somewhere in your path. 130 | 131 | 132 | ## Using `mdr` 133 | 134 | To generate this doc page itself, use this command 135 | in the repository root: 136 | 137 | ``` sh 138 | mdr README.mdr 139 | ``` 140 | 141 | ------- 142 | 143 | generated from [this file](https://github.com/fennecdjay/mdr/blob/master/README.mdr) 144 | 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MDR, a markdown runner 2 | 3 | ![Linux](https://github.com/fennecdjay/mdr/workflows/Linux/badge.svg) 4 | ![MacOs](https://github.com/fennecdjay/mdr/workflows/MacOs/badge.svg) 5 | ![Windows](https://github.com/fennecdjay/mdr/workflows/Windows/badge.svg) 6 | [![Line Count](https://tokei.rs/b1/github/fennecdjay/mdr)](https://github.com/Gwion/mdr) 7 | 8 | mdr is a **small** *program* and *markup* 9 | designed to facilitate documentation and testing. 10 | It can both generate documentation pages and 11 | generate and actualaly test small code examples 12 | in your documentation. (This ensures any code 13 | examples you present to your users actually work.) 14 | 15 | ![logo](assets/logoreadme.png "The Mdr logo! (WIP)") 16 | 17 | I started this tool to help with [Gwion](https://github.com/fennecdjay/gwion)'s 18 | development, but it is not tied in any way to this project. 19 | 20 | Let's go over the basic functionality... :smile: 21 | 22 | 23 | ## How to write documentation pages with `mdr` 24 | 25 | For how to do a basic documentation 26 | page, [see this page's original source]( 27 | https://github.com/fennecdjay/mdr/blob/master/README.mdr). 28 | 29 | 30 | ## How to write code examples with `mdr` 31 | 32 | Let's write our first code example created from our 33 | documentation page, which also shows off templating and 34 | how to have the code automatically compiled and tested. 35 | 36 | 37 | ### Define a program structure 38 | 39 | > hello_world.c 40 | ``` hello_world.c 41 | @[[ Includes ]] 42 | 43 | int main(int argc, char** argv) { 44 | @[[ Print ]] 45 | } 46 | ``` 47 | We fill in the `Includes` template variable as follows: 48 | 49 | 50 | ### Filling in template variables 51 | 52 | As we need the *puts* function, we need **stdio** headers. 53 | 54 | > Includes 55 | ``` Includes 56 | #include 57 | ``` 58 | 59 | We also fill in the print function we use above: 60 | 61 | > Print 62 | ``` Print 63 | puts("Hello, World!"); 64 | ``` 65 | 66 | 67 | ### Compiling the example code 68 | 69 | Now, let's compile *hello_world.c* to make sure 70 | this test code owrks. 71 | 72 | > exec: cc hello_world.c -o hello_world 73 | ``` 74 | ``` 75 | 76 | Yes, there should be no output, and that's good news 77 | since that means no compilation errors. 78 | 79 | 80 | ### Including a code file or other output 81 | 82 | Let's look at *hello_world.c*: 83 | 84 | > exec: cat hello_world.c 85 | ``` 86 | #include 87 | 88 | int main(int argc, char** argv) { 89 | puts("Hello, World!"); 90 | } 91 | ``` 92 | 93 | That's the content of the source file we generated (and compiled). 94 | You can see how this can be used for arbitrary shell commands 95 | to generate output. 96 | 97 | 98 | ### Testing example code 99 | 100 | Then we run our test program: 101 | 102 | > exec: ./hello_world 103 | ``` 104 | Hello, World! 105 | ``` 106 | 107 | Do we read *Hello World!* ? 108 | Assuming yes, let's continue. 109 | 110 | 111 | ### More test 112 | 113 | > exec: [ "$(./hello_world)" = "Hello, World!" ] && echo "OK" || echo "NOT_OK" 114 | ``` 115 | OK 116 | ``` 117 | 118 | 119 | ## Building `mdr` 120 | 121 | As a C program, it seemed natural to use [make](https://www.gnu.org/software/make) 122 | as a build system. 123 | 124 | ``` sh 125 | make 126 | ``` 127 | 128 | ## Testing `mdr` 129 | 130 | Also using make: 131 | ``` sh 132 | make test 133 | ``` 134 | 135 | You can also try 136 | ``` sh 137 | bash scripts/test.sh 138 | ``` 139 | 140 | ## Installing `mdr` 141 | 142 | As easy as before, just type. 143 | 144 | ``` sh 145 | make install 146 | ``` 147 | or just copy `mdr` somewhere in your path. 148 | 149 | 150 | ## Using `mdr` 151 | 152 | To generate this doc page itself, use this command 153 | in the repository root: 154 | 155 | ``` sh 156 | mdr README.mdr 157 | ``` 158 | 159 | ------- 160 | 161 | generated from [this file](https://github.com/fennecdjay/mdr/blob/master/README.mdr) 162 | 163 | -------------------------------------------------------------------------------- /src/view.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "viewopt.h" 11 | #include "io.h" 12 | 13 | struct View { 14 | struct MdrString *curr; 15 | struct Know *know; 16 | struct ViewOpt *vopt; 17 | int blk; 18 | }; 19 | 20 | static enum mdr_status actual_view_ast(struct View*, struct Ast*); 21 | 22 | static enum mdr_status view_str(struct View *view, struct Ast *ast) { 23 | string_append(view->curr, ast->info.str); 24 | return mdr_ok; 25 | } 26 | 27 | static inline void _format_long(char c[64], long n, size_t *idx) { 28 | if(n / 10) 29 | _format_long(c, n / 10, idx); 30 | c[*idx] = n % 10 + '0'; 31 | ++(*idx); 32 | } 33 | 34 | static size_t format_long(char c[64], long n) { 35 | size_t ret = 0; 36 | if(n < 0) { 37 | c[0] = '-'; 38 | ++ret; 39 | n = -n; 40 | } 41 | _format_long(c, n, &ret); 42 | return ret; 43 | } 44 | 45 | 46 | static void string_append_long(struct MdrString *str, const char prefix, long n) { 47 | char c[128]; 48 | memset(c, 0, 64); 49 | c[0] = prefix; 50 | size_t sz = format_long(c + 1, n); 51 | struct MdrString tmp = { .str=c, .sz=sz + 1 }; 52 | string_append(str, &tmp); 53 | } 54 | 55 | static void range_print(struct Range *range, struct MdrString *str) { 56 | if(!range->ini) 57 | return; 58 | string_append_long(str, ' ', range->ini); 59 | if(range->end) 60 | string_append_long(str, ':', range->end); 61 | } 62 | 63 | static enum mdr_status view_blk(struct View *view, struct Ast *ast) { 64 | string_append(view->curr, view->vopt->blk_pre); 65 | string_append(view->curr, ast->info.str); 66 | range_print(&ast->info.range, view->curr);// inverse arg order 67 | string_append(view->curr, view->vopt->blk_ini); 68 | string_append(view->curr, ast->info.str); 69 | range_print(&ast->info.range, view->curr);// inverse arg order 70 | string_append(view->curr, view->vopt->blk_mid); 71 | struct View new = { .know=view->know, .curr=view->curr, .blk=1, .vopt=view->vopt }; 72 | const enum mdr_status ret = actual_view_ast(&new, ast->ast); 73 | string_append(view->curr, view->vopt->blk_end); 74 | return ret; 75 | } 76 | 77 | static struct MdrString inc_ini = { .str="@[[ ", .sz = 4 }; 78 | static struct MdrString inc_end = { .str=" ]]", .sz = 3 }; 79 | 80 | static enum mdr_status view_inc(struct View *view, struct Ast *ast) { 81 | if(view->blk) { 82 | string_append(view->curr, &inc_ini); 83 | string_append(view->curr, ast->info.str); 84 | range_print(&ast->info.range, view->curr); 85 | string_append(view->curr, &inc_end); 86 | return mdr_ok; 87 | } 88 | struct MdrString *str = know_get(view->know, ast); 89 | if(!str) // err_msg 90 | return mdr_err; 91 | struct RangeIncluder sr = { .str=str, .range=ast->info.range }; 92 | string_append_range(view->curr, &sr); 93 | return mdr_ok; 94 | } 95 | 96 | static enum mdr_status view_cmd(struct View *view, struct Ast *ast) { 97 | struct MdrString *str = cmd(ast); 98 | if(!str) 99 | return mdr_err; 100 | if(!ast->info.dot) { 101 | string_append(view->curr, view->vopt->cmd_ini); 102 | string_append(view->curr, ast->info.str); 103 | string_append(view->curr, view->vopt->cmd_mid); 104 | } 105 | string_append(view->curr, str); 106 | if(!ast->info.dot) 107 | string_append(view->curr, view->vopt->cmd_end); 108 | free_string(str); 109 | return mdr_ok; 110 | } 111 | 112 | typedef enum mdr_status (*view_ast_fn)(struct View*, struct Ast*); 113 | 114 | static const view_ast_fn _view_ast[] = { 115 | view_str, 116 | view_inc, 117 | view_cmd, 118 | view_blk, 119 | }; 120 | 121 | static enum mdr_status actual_view_ast(struct View *view, struct Ast *ast) { 122 | do _view_ast[ast->type](view, ast); 123 | while((ast = ast->next)); 124 | return mdr_ok; 125 | } 126 | 127 | enum mdr_status view_ast(struct Mdr *mdr, struct Ast *ast) { 128 | struct View view = { .know=&mdr->know, .curr=new_string("", 0), .vopt=mdr->vopt }; 129 | const enum mdr_status ret = actual_view_ast(&view, ast); 130 | if(ret == mdr_ok) { 131 | const size_t sz = strlen(mdr->name); 132 | char c[sz]; 133 | memcpy(c, mdr->name, sz - 1); 134 | c[sz - 1] = '\0'; 135 | write_file(c, &ast->loc, view.curr); 136 | } 137 | free_string(view.curr); 138 | return ret; 139 | } 140 | -------------------------------------------------------------------------------- /src/snip.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "io.h" 11 | 12 | struct Snip { 13 | struct MdrString *str; 14 | Map known; 15 | struct Know *know; 16 | Vector vec; 17 | }; 18 | 19 | static enum mdr_status actual_snip_ast(struct Snip * snip, struct Ast *ast); 20 | static struct MdrString* expand(struct Snip* base, const char *name, const Vector v); 21 | 22 | static int snip_exists(struct Snip *snip, char *name) { 23 | for(vtype i = 0; i < vector_size(snip->vec); ++i) { 24 | if(!strcmp(name, (char*)vector_at(snip->vec, i))) 25 | return 1; 26 | } 27 | return 0; 28 | } 29 | 30 | static enum mdr_status snip_str(struct Snip *snip, struct Ast *ast) { 31 | string_append(snip->str, ast->info.str); 32 | return mdr_ok; 33 | } 34 | 35 | static int snip_done(struct Snip * snip, struct Ast *ast) { 36 | struct MdrString *str = snippet_find(snip->know, ast->info.str); 37 | if(!str) 38 | return 0; 39 | string_append(snip->str, str); 40 | return 1; 41 | } 42 | 43 | static struct MdrString* include_string(struct Snip *snip, struct Ast *ast) { 44 | if(snip_done(snip, ast)) 45 | return (struct MdrString*)mdr_ok; 46 | const Vector v = (Vector)map_get(snip->known, ast->info.str->str); 47 | if(v) 48 | return expand(snip, ast->info.str->str, v); 49 | (void)mdr_fail(&ast->loc, "missing snippet '%s'\n", ast->info.str->str); 50 | return NULL; 51 | } 52 | 53 | 54 | static struct MdrString* _snip_get(struct Snip *snip, struct Ast *ast) { 55 | return !ast->info.dot ? 56 | include_string(snip, ast) : filename2str(ast->info.str->str, &ast->loc); 57 | } 58 | 59 | static enum mdr_status snip_inc(struct Snip *snip, struct Ast *ast) { 60 | if(snip_exists(snip, ast->info.str->str)) 61 | return mdr_fail(&ast->loc, "recursive snippet '%s'\n", ast->info.str->str); 62 | struct MdrString *str = _snip_get(snip, ast); 63 | if(!str) 64 | return mdr_err; 65 | if(str == (struct MdrString*)mdr_ok) 66 | return mdr_ok; 67 | if(!ast->info.range.ini) 68 | string_append(snip->str, str); 69 | else { 70 | struct RangeIncluder range = { .str=str, .range=ast->info.range }; 71 | string_append_range(snip->str, &range); 72 | } 73 | // add to file_done ? 74 | if(ast->info.dot) 75 | free_string(str); 76 | return mdr_ok; 77 | } 78 | 79 | static enum mdr_status snip_cmd(struct Snip * snip, struct Ast *ast) { 80 | #ifdef __AFL_COMPILER 81 | return mdr_ok; 82 | #endif 83 | struct MdrString *str = cmd(ast); 84 | if(!str) 85 | return mdr_err; 86 | string_append(snip->str, str); 87 | free_string(str); 88 | return mdr_ok; 89 | } 90 | 91 | typedef enum mdr_status (*snip_ast_fn)(struct Snip * snip, struct Ast*); 92 | 93 | static const snip_ast_fn _snip_ast[] = { 94 | snip_str, 95 | snip_inc, 96 | snip_cmd, 97 | }; 98 | 99 | static enum mdr_status actual_snip_ast(struct Snip * snip, struct Ast *ast) { 100 | do if(_snip_ast[ast->type](snip, ast) == mdr_err) 101 | return mdr_err; 102 | while((ast = ast->next)); 103 | return mdr_ok; 104 | } 105 | 106 | static struct MdrString* snip_get(struct Snip* base, struct Ast *ast) { 107 | struct Snip snip = { .known=base->known, .know=base->know, .vec=base->vec }; 108 | snip.str = new_string("", 0); 109 | if(actual_snip_ast(&snip, ast) != mdr_err) 110 | return snip.str; 111 | free_string(snip.str); 112 | return NULL; 113 | } 114 | 115 | static struct MdrString* expand(struct Snip* base, const char *name, const Vector v) { 116 | vector_add(base->vec, (vtype)name); 117 | struct MdrString *str = new_string("", 0); 118 | for(vtype i = 0; i < vector_size(v); ++i) { 119 | struct MdrString *curr = snip_get(base, ((struct Ast*)vector_at(v, i))->ast); 120 | if(!curr) { 121 | free_string(str); 122 | return NULL; 123 | } 124 | string_append(str, curr); 125 | if(str->sz && str->str[str->sz - 1] == '\n') { 126 | trim(str->str); 127 | --str->sz; 128 | } 129 | free_string(curr); 130 | } 131 | snippet_set(base->know, name, str); 132 | return str; 133 | } 134 | 135 | enum mdr_status snip(struct Mdr *mdr) { 136 | for(vtype i = 0; i < map_size(&mdr->snip); ++i) { 137 | const char*name = (char*)VKEY(&mdr->snip, i); 138 | if(map_get(&mdr->know.curr, name)) 139 | continue; 140 | const Vector v = (Vector)VVAL(&mdr->snip, i); 141 | struct Vector_ vec; 142 | vector_init(&vec); 143 | struct Snip snip = { .known=&mdr->snip, .know=&mdr->know, .vec=&vec }; 144 | const struct MdrString *ret = expand(&snip, name, v); 145 | vector_release(&vec); 146 | if(!ret) 147 | return mdr_err; 148 | } 149 | return mdr_ok; 150 | } 151 | -------------------------------------------------------------------------------- /src/parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "container.h" 5 | #include "mdr_string.h" 6 | #include "range.h" 7 | #include "ast.h" 8 | #include "know.h" 9 | #include "mdr.h" 10 | #include "lexer.h" 11 | 12 | struct Parser { 13 | struct Lexer *lex; 14 | Map snip; 15 | Map file; 16 | struct Ast *sec; 17 | struct Ast *ast; 18 | char *filename; 19 | int blk; 20 | short unsigned int start; 21 | }; 22 | 23 | static inline void set_loc(struct Ast *ast, struct Parser *parser) { 24 | ast->loc.start = parser->start; 25 | ast->loc.end = parser->lex->line; 26 | } 27 | 28 | static inline void set_locnl(struct Ast *ast, struct Parser *parser) { 29 | ast->loc.start = parser->start; 30 | ast->loc.end = parser->lex->line - 1; 31 | } 32 | 33 | static void known_set(const Map map, const char *key, struct Ast *ast) { 34 | const Vector exists = (Vector)map_get(map, key); 35 | if(exists) { 36 | vector_add(exists, (vtype)ast); 37 | return; 38 | } 39 | const Vector v = malloc(sizeof(*v)); 40 | vector_init(v); 41 | vector_add(v, (vtype)ast); 42 | map_set(map, (vtype)key, (vtype)v); 43 | } 44 | 45 | static void parser_add(struct Parser *parser, struct Ast *ast) { 46 | if(parser->ast) 47 | parser->sec = (parser->sec->next = ast); 48 | else 49 | parser->sec = (parser->ast = ast); 50 | } 51 | 52 | static struct Ast* new_ast(struct Parser *parser, const enum mdr_status type) { 53 | struct Ast *ast = calloc(1, sizeof(struct Ast)); 54 | ast->type = type; 55 | ast->loc.filename = strdup(parser->filename); 56 | parser_add(parser, ast); 57 | return ast; 58 | } 59 | 60 | static struct Ast* parse(struct Parser*); 61 | 62 | static enum mdr_status ast_str(struct Parser *parser) { 63 | struct Ast *ast = new_ast(parser, mdr_str); 64 | ast->info = lex_info(parser->lex); 65 | set_loc(ast, parser); 66 | return mdr_str; 67 | } 68 | 69 | static enum mdr_status ast_cmd(struct Parser *parser) { 70 | struct AstInfo info = lex_info(parser->lex); 71 | if(!info.str) // err_msg 72 | return mdr_err; 73 | struct Ast *ast = new_ast(parser, mdr_cmd); 74 | ast->info = info; 75 | set_loc(ast, parser); 76 | return mdr_cmd; 77 | } 78 | 79 | static enum mdr_status ast_blk(struct Parser *parser) { 80 | if(parser->blk) 81 | return mdr_end; 82 | struct AstInfo info = lex_info(parser->lex); 83 | if(!info.str) { 84 | struct Loc loc = { .start=parser->start, .end=parser->lex->line }; 85 | return mdr_fail(&loc, "missing end block\n"); 86 | } 87 | struct Parser new_parser = { 88 | .lex=parser->lex, 89 | .blk=1, 90 | .snip=parser->snip, 91 | .file=parser->file, 92 | .filename=parser->filename 93 | }; 94 | struct Ast *section = parse(&new_parser); 95 | if(!section) { 96 | free_string(info.str); 97 | return mdr_err; 98 | } 99 | struct Ast *ast = new_ast(parser, mdr_blk); 100 | ast->info = info; 101 | ast->ast = section; 102 | set_loc(ast, parser); 103 | known_set(info.dot ? parser->file : parser->snip, info.str->str, ast); 104 | return mdr_blk; 105 | } 106 | 107 | static enum mdr_status ast_inc(struct Parser *parser) { 108 | struct Ast *ast = new_ast(parser, mdr_inc); 109 | ast->info = lex_info(parser->lex); 110 | set_loc(ast, parser); 111 | return mdr_inc; // err_msg 112 | } 113 | 114 | static enum mdr_status ast_end(struct Parser *parser __attribute__((unused))) { 115 | return mdr_end; 116 | } 117 | 118 | static enum mdr_status ast_err(struct Parser *parser __attribute__((unused))) { 119 | return mdr_err; 120 | } 121 | 122 | typedef enum mdr_status (*new_ast_fn)(struct Parser*); 123 | 124 | static const new_ast_fn create_ast[] = { 125 | ast_str, 126 | ast_inc, 127 | ast_cmd, 128 | ast_blk, 129 | ast_end, 130 | ast_end, 131 | ast_end, 132 | ast_err 133 | }; 134 | 135 | static enum mdr_status _parse(struct Parser *parser) { 136 | parser->start = parser->lex->line; 137 | enum mdr_status type = tokenize(parser->lex); 138 | return create_ast[type](parser); 139 | } 140 | 141 | void free_ast(struct Ast *ast) { 142 | free_string(ast->info.str); 143 | if(ast->next) 144 | free_ast(ast->next); 145 | if(ast->ast) 146 | free_ast(ast->ast); 147 | free(ast->loc.filename); 148 | free(ast); 149 | } 150 | 151 | static enum mdr_status parser_check(enum mdr_status type) { 152 | if(type == mdr_err || type == mdr_end) 153 | return type; 154 | return mdr_ok; 155 | } 156 | 157 | static struct Ast* parse(struct Parser *parser) { 158 | enum mdr_status type; 159 | enum mdr_status ret; 160 | do type = _parse(parser); 161 | while((ret = parser_check(type)) == mdr_ok); 162 | if(ret == mdr_err) { 163 | if(parser->ast) 164 | free_ast(parser->ast); 165 | return parser->ast = NULL; 166 | } 167 | return parser->ast; 168 | } 169 | 170 | struct Ast* mdr_parse(struct Mdr *mdr, char *const str) { 171 | struct Lexer lex = { .str = str, .line=1, .filename=mdr->name }; 172 | struct Parser parser = { 173 | .lex=&lex, 174 | .snip=&mdr->snip, 175 | .file=&mdr->file, 176 | .filename=mdr->name 177 | }; 178 | return parse(&parser); 179 | } 180 | -------------------------------------------------------------------------------- /src/lexer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "container.h" 6 | #include "mdr_string.h" 7 | #include "range.h" 8 | #include "ast.h" 9 | #include "know.h" 10 | #include "mdr.h" 11 | #include "lexer.h" 12 | 13 | static inline void lex_nl(struct Lexer *lex) { 14 | if(*lex->str == '\n') 15 | ++lex->line; 16 | } 17 | 18 | static inline unsigned int lex_end(struct Lexer *lex) { 19 | return *lex->str == '\0'; 20 | } 21 | 22 | static void lex_adv(struct Lexer *lex) { 23 | lex_nl(lex); 24 | ++lex->str; 25 | ++lex->idx; 26 | } 27 | 28 | static int lex_eol(struct Lexer *lex) { 29 | int escape = 0; 30 | while(!lex_end(lex) && *lex->str != '\n') { 31 | escape = (*lex->str == '\\'); 32 | lex_adv(lex); 33 | } 34 | lex_adv(lex); 35 | return escape; 36 | } 37 | 38 | static inline unsigned int is_path(const char c) { 39 | return c == '.' || c == '/'; 40 | } 41 | 42 | static unsigned int lex_is_path(struct Lexer *lex) { 43 | if(!is_path(*lex->str)) 44 | return 0; 45 | return lex->info.dot = 1; 46 | } 47 | 48 | static unsigned int path(struct Lexer *lex) { 49 | while(!lex_end(lex) && (isalnum(*lex->str) || 50 | *lex->str == '_' || *lex->str == '-' || lex_is_path(lex))) 51 | lex_adv(lex); 52 | return 1; 53 | } 54 | 55 | static inline void eat_space(struct Lexer *lex) { 56 | while(*lex->str != '\n' && isspace(*lex->str)) 57 | ++lex->str; 58 | } 59 | 60 | static struct MdrString* snippet_name(struct Lexer *lex) { 61 | eat_space(lex); 62 | char *const buf = lex->str; 63 | path(lex); 64 | if(!lex->idx) 65 | return NULL; 66 | struct MdrString *ret = new_string(buf, lex->idx); 67 | eat_space(lex); 68 | return ret; 69 | } 70 | 71 | static inline unsigned int is_comment(const char *str) { 72 | return str[0] == '@'; 73 | } 74 | 75 | static inline unsigned int is_blk(const char *str) { 76 | return str[0] == '`' && 77 | str[1] == '`' && 78 | str[2] == '`'; 79 | } 80 | 81 | static inline unsigned int is_inc(const char *str) { 82 | return str[0] == '[' && 83 | str[1] == '['; 84 | } 85 | 86 | static inline unsigned int is_inc_end(const char *str) { 87 | return str[0] == ']' && 88 | str[1] == ']'; 89 | } 90 | 91 | static inline unsigned int is_cmd(const char *str) { 92 | return !strncmp(str, "exec", 4); 93 | } 94 | 95 | static inline unsigned int is_hide(const char *str) { 96 | return !strncmp(str, "hide", 4); 97 | } 98 | 99 | static enum mdr_status _comment(struct Lexer *lex) { 100 | lex->str += 2; 101 | lex->info.str = new_string("@", 1); 102 | return mdr_str; 103 | } 104 | 105 | static char* get_end(char *str) { 106 | while(*str) { 107 | if(*str == ':') 108 | return str; 109 | if(*str == '\n') 110 | break; 111 | ++str; 112 | } 113 | return NULL; 114 | } 115 | 116 | static void lex_range(struct Lexer *lex) { 117 | char *str = lex->str; 118 | if(!str) 119 | return; 120 | lex->info.range.ini = lex->info.range.end = 0; 121 | lex->info.range.ini = strtol(str, &lex->str, 10); 122 | const char *end = get_end(str); 123 | if(end) 124 | lex->info.range.end = strtol(end + 1, &lex->str, 10); 125 | } 126 | 127 | static enum mdr_status _inc(struct Lexer *lex) { 128 | lex->str += 3; 129 | if(!(lex->info.str = snippet_name(lex))) { 130 | struct Loc loc = { .start=lex->line, .end=lex->line, .filename=lex->filename }; 131 | return mdr_fail(&loc, "missing include name\n"); 132 | } 133 | eat_space(lex); 134 | lex_range(lex); 135 | eat_space(lex); 136 | if(!is_inc_end(lex->str)) { 137 | free_string(lex->info.str); 138 | return mdr_err; 139 | } 140 | lex_adv(lex); 141 | lex_adv(lex); 142 | return mdr_inc; 143 | } 144 | 145 | static enum mdr_status _blk(struct Lexer *lex) { 146 | lex->alt = !lex->alt; 147 | lex->str += 4; 148 | if(lex->alt) { 149 | if(!(lex->info.str = snippet_name(lex))) { 150 | struct Loc loc = { .start=lex->line, .end = lex->line, .filename=lex->filename }; 151 | return mdr_fail(&loc, "unstarted block\n"); 152 | } 153 | eat_space(lex); 154 | lex_range(lex); 155 | lex_eol(lex); 156 | } 157 | return mdr_blk; 158 | } 159 | 160 | static void escape_nl(struct Lexer *lex, char * buf) { 161 | int escape = 1; 162 | do { 163 | const int base = lex->idx; 164 | escape = lex_eol(lex); 165 | struct MdrString str = { buf + base, lex->idx - base - escape}; 166 | string_append(lex->info.str, &str); 167 | } 168 | while(escape); 169 | } 170 | 171 | static enum mdr_status _cmd(struct Lexer *lex) { 172 | lex->str += 5; 173 | eat_space(lex); 174 | char *const buf = lex->str; 175 | int escape = lex_eol(lex); 176 | if(lex->idx == 1) { 177 | struct Loc loc = { .start=lex->line, .end = lex->line, .filename=lex->filename }; 178 | return mdr_fail(&loc, "missing exec command\n"); 179 | } 180 | lex->info.str = new_string(buf, lex->idx - 1 - escape); 181 | if(escape) 182 | escape_nl(lex, buf); 183 | return mdr_cmd; 184 | } 185 | 186 | static enum mdr_status _hide(struct Lexer *lex) { 187 | lex->info.dot = 1; 188 | _cmd(lex); 189 | return mdr_cmd; 190 | } 191 | 192 | static enum mdr_status mdr_command(struct Lexer *lex) { 193 | char *const str = lex->str + 1; 194 | if(is_comment(str)) 195 | return _comment(lex); 196 | if(is_blk(str)) 197 | return _blk(lex); 198 | if(is_inc(str)) 199 | return _inc(lex); 200 | if(is_cmd(str)) 201 | return _cmd(lex); 202 | if(is_hide(str)) 203 | return _hide(lex); 204 | lex->info.str = new_string("@", 1); 205 | ++lex->str; 206 | return mdr_str; 207 | } 208 | 209 | enum mdr_status tokenize(struct Lexer *lex) { 210 | char *const buf = lex->str; 211 | char c; 212 | while((c = *lex->str)) { 213 | if(c == '@') { 214 | if(lex->idx) 215 | break; 216 | return mdr_command(lex); 217 | } 218 | lex_adv(lex); 219 | } 220 | if(!lex->idx) 221 | return mdr_end; 222 | lex->info.str = new_string(buf, lex->idx); 223 | return mdr_str; 224 | } 225 | 226 | struct AstInfo lex_info(struct Lexer *lex) { 227 | struct AstInfo info = lex->info; 228 | lex->info.str = NULL; 229 | lex->info.range.ini = lex->info.range.end = 0; 230 | lex->info.dot = 0; 231 | lex->idx = 0; 232 | return info; 233 | } 234 | --------------------------------------------------------------------------------