/* [ʞ] mkup.c * ~ lexi hale * © AGPLv3 * ? mkup is a document generator based loosely * on markup, which produces html files formatted * according to my website's template. */ #include #include #include #include #include #include #include #include #include #define k_static # include "clib/compose.c" #undef k_static #define _sz(x) ( sizeof (x) / sizeof (x) [0] ) #define _doc_page_sz (40 * 1024) #if __STDC__ < 202000L enum /* constants */ { null = 0, false = 0, true = 1 }; typedef _Bool bool; #else # define null nullptr #endif #define try(x) {bad _E_ = (x); if(_E_ != ok) return _E_;} #define zero(x) memset(&(x), sizeof(x), 0) #define mkup_error_list \ e(usage,"usage was displayed to user") \ e(insane,"your system is not in a sane state") \ e(file,"file specified could not be mapped") \ e(empty,"nothing to do; bailing") typedef enum result { ok = 0, # define e(code,desc) bad_##code, mkup_error_list # undef e } bad; const char* error_messages[] = { "all systems nominal", # define e(code,desc) desc, mkup_error_list # undef e }; bad hnd(bad error) { if (error > 0) printf("\x1b[1;31mhalt:\x1b[m %s\n", error_messages[error]); return error; } bad mapfile(const char* filename, void** buf, size_t* rsz) { int fd = open(filename, O_RDONLY); if (fd == -1) return bad_file; size_t sz = lseek(fd, 0, SEEK_END); lseek(fd,0,SEEK_SET); void* p = mmap(0, sz, PROT_READ, MAP_SHARED, fd, 0); if (p == MAP_FAILED) return bad_file; *buf = p; *rsz = sz; return ok; } typedef struct dstr { const char *start, *end; } dstr; struct ref { dstr key, val; struct ref* next; }; #define mkup_node_list \ N(txt,"text" ) \ \ N(cmd,"command" ) \ N(lnk,"link" ) \ N(ref,"reference" ) \ N(ft, "footnote" ) \ \ N(hd, "heading" ) \ N(for,"foreign text") \ N(raw,"raw" ) \ N(hl, "strong" ) \ N(em, "emphasis" ) \ N(cd, "code" ) \ N(b, "div block" ) \ N(sp, "span" ) \ N(qt, "quote" ) const char* node_kind_name[] = { #define N(name,desc) desc, mkup_node_list #undef N }; enum node_kind { #define N(name,desc) node_##name, mkup_node_list #undef N }; struct node { enum node_kind kind; dstr body; union { dstr key; uint8_t depth; }; struct node* next; struct { struct node *first, *last; } branch; }; enum node_cmd_kind { cmd_set, cmd_push, cmd_by, cmd_byl, cmd_img }; struct node_cmd { enum node_kind kind; enum node_cmd_kind cmd; dstr params[]; }; struct document { void* bottom; size_t free; struct { struct node* first, *last; } nodes; struct { struct ref * first, *last; } refs; struct page { void* hnd; struct page* last; }* page; size_t pagect; }; /* forgive me father for i have sinned */ const char* spacer = "│ │ │ │ │ │ │ │ │ │ "; #define HL(x) "\x1b[1m" x "\x1b[m" #define indent(fmt,...) printf("%.*s" fmt "\n", dep*5, spacer, __VA_ARGS__) void dump_node(struct node*n, uint8_t dep) { indent(HL("node kind:") " %s", node_kind_name[n -> kind]); indent(HL("node body:") " %.*s", n->body.end - n->body.start, n->body.start); if (n -> kind == node_hd) indent(HL("header depth:") " %u", n->depth); } void dump_node_list(struct node* n, uint8_t dep) { indent("printing node",0); dump_node(n,dep+1); indent("printing branches",0); if (n -> branch.first != null) dump_node_list(n -> branch.first,dep+1); else indent("node has no branches",0); indent("printing leaves",0); if (n -> next != null) dump_node_list(n -> next,dep); else indent("node has no more leaves",0); } #undef indent void dump_document(struct document* d, uint8_t dep) { if (d->nodes.first == null) { printf("document has no nodes\n"); } else { printf("document nodes:\n"); dump_node_list(d -> nodes.first, dep + 1); } } void newpage(struct document* d) { struct taggedPage { struct page p; char b [_doc_page_sz]; }* pg = malloc(sizeof(struct taggedPage)); d -> bottom = &(pg->b); d -> free = _doc_page_sz; pg -> p = (struct page) { .hnd = pg, .last = d -> page, }; d -> page = &(pg->p); ++ d -> pagect; }; void* dalloc(struct document* d, size_t sz, size_t align) { char* p = d->bottom; size_t ab = 0; if (align != 0) { ab = align - (((uintptr_t)p) % align); p += ab; } if (sz + ab <= d->free) { d->free -= sz + ab; d->bottom = p + sz; if (d->free == 0) newpage(d); return p; } else { newpage(d); return dalloc(d, sz, align); } } #define _dalloc(d,t) dalloc((d), sizeof(t), _Alignof(t)) struct node* addnode(struct document* d) { struct node* n = _dalloc(d, struct node); if (d -> nodes.first == null) { d -> nodes.first = n; } else { d -> nodes.last -> next = n; } d -> nodes.last = n; n -> next = null; n -> branch.first = null; return n; } struct node* leafnode(struct document* d, struct node* n) { struct node* c = _dalloc(d, struct node); if (d -> nodes.last == n) d -> nodes.last = c; n -> next = c; c -> next = null; c -> branch.first = null; return c; } struct node* branchnode(struct document* d, struct node* n) { struct node* c = _dalloc(d, struct node); if (n -> branch.first == null) { n -> branch.first = c; n -> branch.last = c; } else { n -> branch.last -> next = c; n -> branch.last = c; } c -> next = null; c -> branch.first = null; return c; } struct ref* addref(struct document* d) { struct ref* r = _dalloc(d, struct ref); if (d -> refs.first == null) { d -> refs.first = r; } else { d -> refs.last -> next = r; } d -> refs.last = r; r -> next = null; return r; } const char* skip_line_to(const char* str, char seek) { while ((seek == 0 || *str != 0) && (seek == '\n' || *str != '\n')) { if (*str == seek) return str; ++str; } return null; } bool p_span_line(const char* str, const char* seek) { const char* p, * s; bool round2 = false; search: for(p = seek, s = str; *s != 0 && *s != '\n'; ++p, ++s) { if (*p == 0 ) if(round2)return true; else goto scan_for_next; //success if (*p != *s) break; // failure } return false; scan_for_next: { if (*s=='\n' || *s=='\t' || *s==' ') return false; p = seek; const char* go = skip_line_to(s, *p); if (go == null) return false; s = go - 1; if (*s=='\n' || *s=='\t' || *s==' ') return false; ++ s; round2=true; goto search; }; } bad parse(const char* file, size_t sz, struct document* root, void* mem) { # define incguard(p) {if(++(p)>(char*)(mem+sz))goto end_document;} # define advance(p) {while(*(p) == ' ' || *(p) == '\t') \ {incguard(p)}} zero(*root); root -> bottom = mem; struct node* active = null; const char* ptr = file; start_line: { while(*ptr=='\n') incguard(ptr); uint8_t depth = 0; while(*ptr==' ' || *ptr=='\t') { depth += (*ptr==' '?1:4); incguard(ptr); } if (depth >= 4) goto parse_raw; else if (depth > 0) goto parse_span; else switch(*ptr) { case '#': goto parse_header; case 0: goto end_document; default: goto parse_span; } } parse_span: { advance(ptr); active=addnode(root); active -> kind = node_txt; const char* end = skip_line_to(ptr,'\n'); // TODO actually parse it active -> body = (dstr) {ptr, end}; ptr = end; goto start_line; } parse_ref: { active=addnode(root); active -> kind = node_ref; } parse_raw: { active=addnode(root); active -> kind = node_raw; } parse_header: { uint8_t depth = 0; while(*ptr == '#') { incguard(ptr); ++depth; } active = addnode(root); goto grab_line; } grab_line: { advance(ptr); const char* end = skip_line_to(ptr,'\n'); active -> body = (dstr) {ptr, end}; ptr = end + 1; goto start_line; } end_document: { } return ok; } #define compile_fn_params_raw struct node* n, char* dest, size_t lvl #define compile_fn_params (compile_fn_params_raw) char* node_compile compile_fn_params; #define node_recurse \ {if (n -> branch.first != null) dest = node_compile(n->branch.first,dest,lvl+1);} #define _P(s) ((struct pstr)_p(s)) #define pstr_node_body \ ((struct pstr){ n->body.end - n->body.start, n->body.start}) char* simple_node (pstr tag, compile_fn_params_raw) { pstr open[] = { _p("<"), tag, _p(">"), pstr_node_body, }; pstr close[] = { _p("") }; dest = impose(open, _sz(open), null,dest); node_recurse; return impose(close, _sz(close), null,dest); } typedef char* (compile_fn compile_fn_params); #define def_node_comp(name)\ char* node_compile_##name compile_fn_params def_node_comp(txt) { if (lvl == 0) { pstr open[] = { _p("

"), pstr_node_body, }; dest = impose(open, _sz(open), null,dest); node_recurse; return imprint((struct pstr)_p("

\n"),null,dest); } else { /*

tags only go on the top level */ dest = imprint(pstr_node_body,null,dest); node_recurse; return dest; } } def_node_comp(cmd) {return dest;} def_node_comp(lnk) { pstr key = {n->key.end - n->key.start, n->key.start}; pstr open[] = { _p(""), pstr_node_body, }; dest = impose(open, _sz(open), null,dest); node_recurse; return imprint((struct pstr){4, ""},null,dest); } def_node_comp(ref) {return dest;} def_node_comp(ft) {return dest;} def_node_comp(hd) { size_t ct = n -> depth; if (ct>6) ct = 6; char d = '0' + ct; pstr open[] = { _p(""), pstr_node_body, }; pstr close[] = { _p("\n") }; dest = impose(open, _sz(open), null,dest); node_recurse; return impose(close,_sz(close), null,dest); } def_node_comp(for) {return dest;} def_node_comp(raw) {return dest;} def_node_comp(hl) { simple_node(_P("strong"), n,dest,lvl); } def_node_comp(em) { simple_node(_P("strong"), n,dest,lvl); } def_node_comp(cd) {return dest;} def_node_comp(b) {return dest;} def_node_comp(sp) {return dest;} def_node_comp(qt) {return dest;} #undef def_node_comp compile_fn* node_compile_funcs[] = { #define N(name,desc) node_compile_##name, mkup_node_list #undef N }; char* node_compile compile_fn_params { compile_fn* func = node_compile_funcs[n->kind]; dest = (*func)(n, dest, lvl); if (n -> next != null) dest = node_compile(n->next,dest,lvl); return dest; } bad compile(struct document* root, char* dest, size_t dsz, size_t* rsz) { /* TODO bounds checking! */ char* end; const char* title_seq = "test-title<title>"; if (root -> nodes.first != null) { pstr open[] = { _p( "<!doctype html>\n" "<html>\n" "<head>\n"), {0, title_seq}, _p("\n</head>\n" "<body>\n")}; pstr close[] = { _p("</body>\n</html>") }; end = impose(open, _sz(open), null, dest); end = node_compile(root -> nodes.first, end, 0); end = impose(close, _sz(close), null, end); } else return bad_empty; *rsz = end - dest; return ok; } const char headermsg[]="you could win up to ¤50000 GALACTIC ZORBLATS", bodytext []="enter to win now - just rend your meatling carapace and ululate for the glory of PHLEGETHON ETERNUM, ", boldtext []="glorious Soul-Poisoner of the Sunless Realm!", normtext []=" it's that easy! ", linktext []="CLICK HERE FOR NATURAL MALE ENHANCEMENT", linkhref []="www.3masculatr1x.xxx"; bad fakeparse(char* file, size_t sz, struct document* root, void* mem) { zero(*root); /* put our first page on the stack to avoid mallocs * for small documents */ root -> bottom = mem; typedef struct node* nd; nd header = addnode(root); header -> kind = node_hd; header -> depth = 1; header -> body.start = headermsg; header -> body.end = headermsg+sizeof headermsg; nd body = addnode(root); body -> kind = node_txt; body -> body.start = bodytext; body -> body.end = bodytext+sizeof bodytext; nd boldbranch = branchnode(root, body); boldbranch -> kind = node_hl; boldbranch -> body.start = boldtext; boldbranch -> body.end = boldtext+sizeof boldtext; nd normbranch = branchnode(root, body); normbranch -> kind = node_txt; normbranch -> body.start = normtext; normbranch -> body.end = normtext+sizeof normtext; nd linkbranch = branchnode(root, body); linkbranch -> kind = node_lnk; linkbranch -> body.start = linktext; linkbranch -> body.end = linktext+sizeof linktext; linkbranch -> key.start = linkhref; linkbranch -> key.end = linkhref+sizeof linkhref; return ok; } bad run(const char* filename) { void* file; size_t sz; try(mapfile(filename, &file, &sz)); uint8_t work[_doc_page_sz]; /* safe-sized buffer for working space - might waste some space but it's faster than malloc */ struct document doc; try(parse(file,sz,&doc,work)); /* try(fakeparse(file,sz,work)); */ dump_document(&doc,0); char outbuf[_doc_page_sz * (1 + doc.pagect) * 2]; /* should be plenty of space */ printf("outbuf: %llu\n", sizeof outbuf); size_t outsz=0; try(compile(&doc,outbuf,sizeof outbuf,&outsz)); printf("final doc:\n"); write(1, outbuf, outsz); return ok; } int main(int argc, char** argv) { if (argc == 0) return hnd(bad_insane); if (argc == 1) return hnd(bad_usage); char* opts[argc], *args[argc]; char**copt=opts,**carg=args; for (char** arg = argv + 1; *arg != null; ++arg) { if ((*arg)[0] == '-') *copt++=*arg; else *carg++=*arg; } if (copt != opts) { /* parse options */ } if (carg == args) return hnd(bad_usage); return hnd(run(args[0])); }