978 lines
36 KiB
C
978 lines
36 KiB
C
// C documentation generator using markdeep.
|
|
// - rlyeh, public domain
|
|
//
|
|
// build:
|
|
// cl docs.c && docs ..\..\v4k.h -x=3rd_glad.h,v4k.h,v4k_main.h,v4k_compat.h, > docs.html
|
|
//
|
|
// how to document:
|
|
// - start documentation on a blank line with a /// 3 slashes comment.
|
|
// - all comments must be typed right before the symbol declaration.
|
|
// - use `see: symbol1,symbol2^` to create links. `^` char indicates an external link.
|
|
// - use `keyword: comment` to create tables. `return:` and `returns` are reserved keywords for function returns.
|
|
// - use `> code` to create a snippet. snippets will be multi-lined.
|
|
// - use `!!! text` to create a note. severity based on number of exclamations (1:note,2:tip,3:warn,4:alert)
|
|
//
|
|
// exceptions:
|
|
// - a ///- comment can be used to exclude a symbol from any further documentation processing.
|
|
// - this ///- comment must be appended to any declaration, at the very end of string.
|
|
//
|
|
// #define my_macro() macro_implementation_here() ///-
|
|
//
|
|
// some examples below:
|
|
//
|
|
// /// !!! `filename` must contain extension
|
|
// /// load dynamic library `file` and search for `symbol`
|
|
// /// return: NULL if not found, found symbol otherwise.
|
|
// /// filename: path to dynamic library file. must contain extension.
|
|
// /// symbol: symbol name. must not be NULL.
|
|
// /// see: dlopen^, dlclose^
|
|
// /// > bool (*plugin_init)(void) = dll("plugin.dll", "init");
|
|
// /// > assert(plugin_init());
|
|
// API void* dll(const char *filename, const char *symbol);
|
|
//
|
|
// /// split `string` after any of `delimiters` character is found.
|
|
// /// returns temporary array of split strings. see: strjoin
|
|
// /// > array(char*) tokens = strsplit("hello! world!", " !"); // [0]="hello",[1]="world",
|
|
// API array(char*) strsplit(const char *string, const char *delimiters);
|
|
//
|
|
// /// concatenate all elements within `list`, with `separator` string in between.
|
|
// /// returns: temporary joint string. see: strsplit
|
|
// /// > array(char*) tokens = strsplit("hello! world!", " !"); // [0]="hello",[1]="world",
|
|
// /// > char *joint = strjoin(tokens, "+"); // joint="hello+world"
|
|
// API char* strjoin(array(char*) list, const char *separator);
|
|
//
|
|
// /// flags when constructing the image_t type. see: image, image_from_mem
|
|
// /// IMAGE_R: 1-channel image (R)
|
|
// /// IMAGE_RG: 2-channel image (R,G)
|
|
// /// IMAGE_RGB: 3-channel image (R,G,B)
|
|
// /// IMAGE_RGBA: 4-channel image (R,G,B,A)
|
|
// /// IMAGE_FLIP: Flip image vertically
|
|
// enum IMAGE_FLAGS {
|
|
// IMAGE_R = 0x01000,
|
|
// IMAGE_RG = 0x02000,
|
|
// IMAGE_RGB = 0x04000,
|
|
// IMAGE_RGBA = 0x08000,
|
|
// IMAGE_FLIP = 0x10000,
|
|
// };
|
|
//
|
|
// /// type that holds linear uncompressed bitmap of any given dimensions.
|
|
// /// w,h: image dimensions in pixels. `x,y` alias.
|
|
// /// comps: number of components per pixel. `n` alias.
|
|
// /// pixels: untyped pointer to linear bitmap data. typed pointers use `pixels8/16/32/f` aliases.
|
|
// /// see: texture_t
|
|
// typedef struct image_t {
|
|
// union { unsigned x, w; };
|
|
// union { unsigned y, h; };
|
|
// union { unsigned n, comps; };
|
|
// union { void *pixels; uint8_t *pixels8; uint16_t *pixels16; uint32_t *pixels32; float *pixelsf; };
|
|
// } image_t;
|
|
|
|
#include "v4k.h"
|
|
#include <ctype.h>
|
|
|
|
int DO_CSS = 1;
|
|
int DO_DEBUG = 0;
|
|
int DO_README = 1;
|
|
int DO_CHANGELOG = 1;
|
|
|
|
// C preprocessor:
|
|
// 1. remove all /* C comments */
|
|
// 2. remove all // C++ comments
|
|
// 3. remove all \macro continuation lines
|
|
// 4. preserve /// comments
|
|
void preprocess_c(char *raw) {
|
|
for(int i = 0, in_string = 0; raw[i]; ++i) {
|
|
if( !in_string ) {
|
|
if( raw[i] == '\"' || raw[i] == '\'' ) if( raw[i-1] != '\\' ) {
|
|
in_string = raw[i];
|
|
continue;
|
|
}
|
|
|
|
// preserve ///comments
|
|
if( memcmp(&raw[i], "///", 3) == 0 ) {
|
|
while( raw[i] && raw[i] >= 32 ) ++i;
|
|
--i; continue;
|
|
}
|
|
|
|
// remove C++ comments
|
|
if( raw[i] == '/' && raw[i+1] == '/' && raw[i+2] != '/' ) {
|
|
do raw[i++] = ' '; while( raw[i] && (raw[i] >= 32 || raw[i] == '\t'));
|
|
--i; continue;
|
|
}
|
|
|
|
// remove \macro continuation lines
|
|
if(raw[i] == '\\' && (raw[i+1] && raw[i+1] < 32) && (raw[i+2] && raw[i+2] < 32)) raw[i]=raw[i+1]=raw[i+2] = ' ';
|
|
if(raw[i] == '\\' && (raw[i+1] && raw[i+1] < 32)) raw[i]=raw[i+1] = ' ';
|
|
|
|
// remove /* C comments */
|
|
if(raw[i] == '/' && raw[i+1] == '*') {
|
|
raw[i]=raw[i+1]=' ';
|
|
while(raw[i]) if(raw[i] == '*' && raw[i+1] == '/') break; else raw[i++] = ' ';
|
|
if (raw[i]) raw[i] = ' ';
|
|
if (raw[i + 1]) raw[i + 1] = ' ';
|
|
--i; continue;
|
|
}
|
|
|
|
} else {
|
|
while(raw[i] == '\\' && raw[i+1]) i += 2;
|
|
if(raw[i] == in_string ) in_string = 0;
|
|
//if(DO_DEBUG) raw[i] = toupper(raw[i]);
|
|
}
|
|
}
|
|
// remove all linefeeds within {} scopes (flatten scopes into single-lines)
|
|
for(int i = 0, open = 0, in_string = 0; raw[i]; ++i) {
|
|
if( !in_string ) {
|
|
if( raw[i] == '\"' || raw[i] == '\'' ) if( raw[i-1] != '\\' ) {
|
|
in_string = raw[i];
|
|
continue;
|
|
}
|
|
// inline brackets. do not process extern "C" { ... } brackets
|
|
if(raw[i] == '{') if(i >= 12 && !memcmp("extern \"C\" {", &raw[i-11], 12) ? 0 : 1) ++open;
|
|
if(raw[i] == '}') if(open > 0) --open;
|
|
if(open > 0) if(raw[i] && raw[i] < 32) raw[i] = ' ';
|
|
} else {
|
|
while(raw[i] == '\\' && raw[i+1]) i += 2;
|
|
if(raw[i] == in_string ) in_string = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Markdown preprocessor:
|
|
// 1. replace all {{TEMPLATES}} if defined in OS environment && small enough for inlining
|
|
char* preprocess_md(char *raw) {
|
|
for(int i = 0, in_string = 0; raw[i]; ++i) {
|
|
if( !in_string ) {
|
|
if( raw[i] == '\"' /* || raw[i] == '\'' */ ) if( raw[i-1] != '\\' ) {
|
|
in_string = raw[i];
|
|
continue;
|
|
}
|
|
|
|
// replace {{TEMPLATE}} symbols if they exist and fit in-place
|
|
if( raw[i] == '{' && raw[i+1] == '{' && raw[i+2] >= 'A') {
|
|
char buf[128];
|
|
if( sscanf(raw+i+2, "%127[^}]", buf) == 1 ) {
|
|
int buflen = strlen(buf);
|
|
char *eos = &raw[i+2] + buflen;
|
|
if( buflen && eos[0] == '}' && eos[1] == '}' ) {
|
|
char *repl = getenv(buf);
|
|
if( repl && strlen(repl) <= (buflen + 4) ) {
|
|
memset(raw+i, ' ', buflen + 4);
|
|
memcpy(raw+i, repl, strlen(repl));
|
|
i += buflen+4;
|
|
--i; continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
while(raw[i] == '\\' && raw[i+1]) i += 2;
|
|
if(raw[i] == in_string ) in_string = 0;
|
|
//if(DO_DEBUG) raw[i] = toupper(raw[i]);
|
|
}
|
|
}
|
|
return raw;
|
|
}
|
|
|
|
// remove whitespaces within a line, unless they are present within a \"string\"
|
|
char *minify( const char *line ) {
|
|
// skip initial whitespaces
|
|
line += strspn(line, " \t\r\n");
|
|
|
|
// reserve space
|
|
char *bak = strdup/*STRDUP*/(line), *s = bak;
|
|
|
|
for(int in_string = 0, seen_alphanum = 0; *line; ++line ) {
|
|
if( !in_string ) {
|
|
// early return till eos if literal /// comment is found.
|
|
if( line[0] == '/' && line[1] == '/' && line[2] == '/' ) {
|
|
strcpy(s, line);
|
|
return bak;
|
|
}
|
|
// detect string open
|
|
if( *line == '\"' || *line == '\'' ) if( line[-1] != '\\' ) {
|
|
in_string = *line;
|
|
*s++ = *line;
|
|
continue;
|
|
}
|
|
// remove spaces
|
|
if( *line > 0 && *line <= 32 ) {
|
|
// keep spacing only if previous char was alphanum && next char is not operator
|
|
const char *next_nbsp = line+1 + strspn(line+1, " \t\r\n");
|
|
#define PUNCT_CHARSET "#" "!&|~" "+-/*%()" "<=>" "[]{}" "?:;" ",'\"\\"
|
|
if(seen_alphanum && !strchr(PUNCT_CHARSET, *next_nbsp)) *s++ = ' ';
|
|
|
|
while( *line > 0 && *line <= 32 ) ++line;
|
|
|
|
--line;
|
|
continue;
|
|
}
|
|
// save for later
|
|
seen_alphanum = isalnum(*line);
|
|
*s++ = *line;
|
|
} else {
|
|
while(*line == '\\' && line[1]) { memcpy(s, line, 2); s += 2; line += 2; }
|
|
if(*line == in_string ) in_string = 0;
|
|
|
|
seen_alphanum = isalnum(*line);
|
|
*s++ = *line;
|
|
}
|
|
}
|
|
|
|
*s = 0;
|
|
return bak;
|
|
}
|
|
|
|
bool parse_c_function(const char *raw, int *out_name_pos, int *out_name_len, int *out_args_pos, bool clean_parens) {
|
|
// try to clean up name from worst pathological case with parentheses. ie:
|
|
// API __declspec(dllexport) int(*)(int,...)(MACRO_CALLBACK)(int(*my_callback)(int,...));
|
|
// split declaration into 3 parts: return value, function name, and arguments.
|
|
// 1. string is parsed backwards, from tail to head.
|
|
// 2. arguments are parsed till all matching ( ) chars are balanced.
|
|
// 3. function name is parsed then till '(' or ' ' are found. we remove any parens if present.
|
|
// 4. everything else is the return type.
|
|
bool is_function = false;
|
|
int name = 0, args = 0;
|
|
const char *eos = raw + strlen(raw) - 1;
|
|
for( int i = eos - raw, open = 0; i >= 0; --i ) {
|
|
if( raw[i] == ')' ) ++open;
|
|
if( raw[i] == '(' ) if(open > 0) --open;
|
|
|
|
if( raw[i] == '(' && open == 0 ) {
|
|
/**/ if( args == 0 ) args = i;
|
|
else if( name == 0 ) name = i;
|
|
}
|
|
if( (raw[i] == ' ' || raw[i] == '*') && open == 0 && args != 0 && name == 0 ) {
|
|
name = i;
|
|
}
|
|
|
|
if( i == 0 ) is_function = (open == 0); // if parens are not balanced, this is not a function
|
|
}
|
|
if( is_function ) {
|
|
// printf("%d %d\n", name, args);
|
|
// printf(">> %.*s\n", args-name, raw+name);
|
|
// printf(">> %s\n", raw + args);
|
|
*out_args_pos = args;
|
|
*out_name_pos = name;
|
|
*out_name_len = args-name;
|
|
if( clean_parens && raw[name] == '(' && raw[args-1] == ')' ) {
|
|
*out_name_pos += 1;
|
|
*out_name_len -= 2;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// reintroduce spacing and tabs where due, so code is pretty much readable again.
|
|
// some difficult notes when indenting7cleaning up:
|
|
// 1. structs, unions, enums use '{' to increase indenation, and '}' to decrease it.
|
|
// 2. structs, unions do line carriage after every member declaration, and keep indentation.
|
|
// 3. enums use ',' to linefeed
|
|
// 4. we do conversion from static [inline] type func() { ... } -> to -> type func();
|
|
// 5. rename functions that are declared between parens to disambiguate macros: int (print)(...);
|
|
// 6. do ellipsis on macro contents: #define print(args) ...
|
|
char *prettify(char *raw, char **opt_name) {
|
|
if( strbegi(raw, "/""//") ) { raw[0] = 0; return raw; }
|
|
if( strstr(raw, "/""//") ) { *strstr(raw, "/""//") = '\0'; }
|
|
|
|
enum { TABS = 4 };
|
|
|
|
if(DO_DEBUG) puts(raw);
|
|
|
|
static char *buf = 0; buf = REALLOC(buf, strlen(raw) * 16 + 256);
|
|
char *s = buf;
|
|
|
|
// hide implementation from static functions
|
|
int is_static = strbegi(raw, "static ");
|
|
int is_static_inline = strbegi(raw, "static inline ");
|
|
if( is_static || is_static_inline ) {
|
|
raw += strlen(is_static_inline ? "static inline " : "static ");
|
|
char *eos = strchr(raw, '{');
|
|
if(eos) eos[0] = ';', eos[1] = 0;
|
|
}
|
|
|
|
// hide macro details. expose only name and args
|
|
int is_macro = strbegi(raw, "#define ");
|
|
if( is_macro ) {
|
|
if( raw[8] == '_' && raw[8+1] == '_' ) return raw[0] = 0, raw; // do not process compiler __symbols
|
|
#define KEYWORD_CHARSET "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
#define MACRO_CHARSET KEYWORD_CHARSET "$"
|
|
int wordlen = strspn(raw+8, MACRO_CHARSET);
|
|
for( int i = 8 + wordlen, open = 0; raw[i]; ++i ) {
|
|
if( raw[i] == '(' ) { ++open; continue; }
|
|
if( raw[i] == ')' ) { if(open > 0) --open; continue; }
|
|
if( open == 0 ) {
|
|
// hide implementation, until either any of eos or ///comment are found
|
|
while( raw[i] && strcmp(raw+i, "/""//") ) {
|
|
raw[i++] = ' ';
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
memcpy(raw+=2, "macro", 5); // #define -> macro
|
|
}
|
|
|
|
int is_function = raw[0] != '#' && !strchr(raw, '{') && !strchr(raw, '}') && strchr(raw, '(') && strchr(raw, ')') && !strbegi(raw, "typedef");
|
|
if( is_function ) {
|
|
int name = 0, args = 0, namelen = 0;
|
|
is_function = parse_c_function( raw, &name, &namelen, &args, false );
|
|
// clean parens `int (function)(...);` cases
|
|
if( is_function ) {
|
|
if( raw[name+namelen-1] == ')' ) {
|
|
sprintf(buf, "%.*s%s%.*s%s", name, raw, raw[name-1] == '*' ? "":" ", namelen-2, raw+name+1, raw+args);
|
|
raw = strdup(buf);
|
|
*buf = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int is_typedef = strbegi(raw, "typedef ");
|
|
int is_typedef_enum = strbegi(raw, "typedef enum");
|
|
int is_typedef_union = strbegi(raw, "typedef union");
|
|
int is_typedef_struct = strbegi(raw, "typedef struct");
|
|
int is_typedef_block = is_typedef_enum || is_typedef_union || is_typedef_struct;
|
|
if( is_typedef_block ) {
|
|
raw += strlen("typedef "); // shorten decls on large structs/unions/enums
|
|
char *eos = strrchr(raw, '}');
|
|
if(eos) eos[1] = ';', eos[2] = 0;
|
|
}
|
|
|
|
int is_enum = strbegi(raw, "enum");
|
|
|
|
const char *remaps[] = { // sort by strlen, then by priority
|
|
"**", "** ", // char**k
|
|
"(*", " (*", // int(*fn)();
|
|
"*,", "*, ", // int printf(char*,...);
|
|
"*)", "*)", // (char*)k here to fix ')' case below
|
|
"*", "* ", // char*k
|
|
",}", "}", // enum{A,B,}
|
|
");", ");", // int func(); here to fix ')' case below
|
|
"};", "}; ", // struct k{union{int k;};}; here to fix '}' case below
|
|
"}", "} ", // typedef struct k{int k;}type;
|
|
"{", " { ", // struct k{}
|
|
// ",", ", ", // print(int a,int b)
|
|
";", "; ", // struct{int a;int b;};
|
|
"=", " = ", // enum{N=1,}
|
|
};
|
|
// 1st pass, spacing
|
|
for( int i = 0; raw[i]; ++i ) {
|
|
*s++ = raw[i];
|
|
for( int j = 0; j < countof(remaps) / 2; ++j ) {
|
|
if(!strncmp(&raw[i], remaps[j*2+0], strlen(remaps[j*2+0]))) {
|
|
--s;
|
|
s += sprintf(s, "%s", remaps[j*2+1]);
|
|
i += strlen(remaps[j*2+0]) - 1;
|
|
break;
|
|
}
|
|
}
|
|
// fix array(char*)strplit(...) or array(int)list(...) case
|
|
// but dont in int(*callback_t)(...) case
|
|
if( i > 0 && s[-1] == ')' && strchr(KEYWORD_CHARSET, raw[i+1]) ) {
|
|
*s++ = ' ';
|
|
}
|
|
}
|
|
if(DO_DEBUG) puts(buf);
|
|
|
|
// prepare for 2nd pass
|
|
*s++ = 0;
|
|
raw = buf + ALLOCSIZE(buf)-1 - strlen(buf)-1;
|
|
strcpy(buf + ALLOCSIZE(buf)-1 - strlen(buf)-1, buf);
|
|
s = buf;
|
|
|
|
// 2nd pass, multi-lines
|
|
for(int i = 0, level = 0; raw[i]; ++i) {
|
|
int indent_inc = (raw[i] == '{');
|
|
int indent_dec = (raw[i] == '}');
|
|
int linefeed = indent_inc || indent_dec || (raw[i] == ';') || (is_enum && raw[i] == ',');
|
|
|
|
if( !linefeed ) {
|
|
*s++ = raw[i];
|
|
} else {
|
|
|
|
if( indent_dec ) {
|
|
if( is_enum ) s += sprintf(s, "\n%*s", (level-1)*TABS, "");
|
|
else if( level > 0 ) s -= TABS;
|
|
}
|
|
|
|
/**/ if( indent_inc ) s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "", ++level * TABS, "");
|
|
else if( indent_dec ) s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "", --level * TABS, "");
|
|
else s += sprintf(s, "%c%s\n%*s", raw[i], raw[i+1] == ';' ? ";" : "", level * TABS, "");
|
|
|
|
// consume semicolon
|
|
while(raw[i+1] == ';') ++i;
|
|
// consume whitespaces
|
|
while(raw[i+1] == ' ') ++i;
|
|
}
|
|
}
|
|
// if(DO_DEBUG) puts(buf); // no need to debug this one, gonna be printed anyways
|
|
|
|
// prepare for last pass
|
|
*s = 0;
|
|
s = buf;
|
|
// last pass: remove extra linefeeds at eos
|
|
while( s[strlen(s)-1] == '\n' ) s[strlen(s)-1] = 0;
|
|
|
|
// extract type+name if needed
|
|
if( opt_name ) {
|
|
const char *line = buf;
|
|
bool is_function = strchr(line, '(') && strchr(line, '(') < strrchr(line, ')');
|
|
|
|
char s[1024]; int n,nl,a;
|
|
/**/ if( line[0] == '#' ) { sscanf(line, "%*s %s", s); *opt_name = stringf("#%s",s); }
|
|
else if( strbegi(line, "macro")) { sscanf(line, "%*s %[^(]",s); *opt_name = stringf("m%s",s); }
|
|
else if( strbegi(line, "union")) { sscanf(line, "%*s %s",s); *opt_name = stringf("u%s",s); }
|
|
else if( strbegi(line, "struct")) { sscanf(line, "%*s %s",s); *opt_name = stringf("s%s",s); }
|
|
else if( strbegi(line, "enum")) { sscanf(line, "%*s %s",s); *opt_name = stringf("e%s",s); }
|
|
else if( strbegi(line, "typedef")) {
|
|
char *last_word = strrchr(line, ' ') + 1;
|
|
sscanf(last_word, "%[^[;]", s);
|
|
if( strchr(s, ')') ) {
|
|
line += strlen("typedef ");
|
|
parse_c_function(line,&n,&nl,&a,1);
|
|
line += n;
|
|
if(*line == '*') line++, --nl;
|
|
sprintf(s, "%.*s", nl, line);
|
|
}
|
|
*opt_name = stringf("t%s",s);
|
|
}
|
|
else if( line[0] != '#' && is_function && parse_c_function(line,&n,&nl,&a,1)) {
|
|
while(line[n] == ' ' || line[n] == '*') ++n, --nl;
|
|
sprintf(s,"%.*s",nl,line+n);
|
|
*opt_name = stringf("f%s",s);
|
|
}
|
|
// else isvar {}
|
|
|
|
// trim whitespaces
|
|
if(*opt_name && strchr(*opt_name, ' ')) {
|
|
*strchr(*opt_name, ' ') = 0;
|
|
}
|
|
|
|
// disambiguate macros()<->defines
|
|
if(*opt_name && *opt_name[0] == 'm') {
|
|
#if 1
|
|
char upper[128] = {0}, *u = upper, lower[128] = {0}, *l = lower;
|
|
for( char *p = *opt_name+1; *p; ++p) {
|
|
*u++ = toupper(*p);
|
|
*l++ = tolower(*l);
|
|
}
|
|
if( !strcmp(lower, *opt_name+1) ) *opt_name[0] = 'm';
|
|
if( !strcmp(upper, *opt_name+1) ) *opt_name[0] = 'd';
|
|
#else
|
|
char *found = strstr(line, va("%s(", (*opt_name)+1));
|
|
if(!found)
|
|
*opt_name[0] = 'd';
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
int sort_strcmp( const void *str1, const void *str2 ) {
|
|
const char *const *pp1 = str1;
|
|
const char *const *pp2 = str2;
|
|
return strcmpi(*pp1, *pp2);
|
|
}
|
|
|
|
// db cache
|
|
array(char*) description;
|
|
array(char*) arguments;
|
|
array(char*) returns;
|
|
array(char*) example;
|
|
array(char*) links;
|
|
array(char*) notes;
|
|
|
|
array(char*) defines;
|
|
array(char*) enums;
|
|
array(char*) types; // unions,structs,typedefs
|
|
array(char*) functions;
|
|
array(char*) macros;
|
|
|
|
char *learn(char *line) {
|
|
// @todo: learn markdeep from [line] based on array(context)
|
|
// @todo: update caches & context
|
|
// [...]
|
|
|
|
// continue with pretty line
|
|
char* typename = 0;
|
|
char *pretty = prettify(strdup(line), &typename);
|
|
|
|
if( strbegi(line, "/""//") && line[3] != '/' ) {
|
|
char *md = line + 3 + (line[3] == ' ');
|
|
|
|
if( strbegi(md, "returns" ) ) {
|
|
// inline replacement
|
|
memcpy(md, "return:", 7);
|
|
}
|
|
|
|
// grab links & truncate line if needed
|
|
if( strstr(md, "see:") ) {
|
|
char *found = strstr(md, "see:");
|
|
array(char*) tokens = strsplit(found+4, " ,.");
|
|
for( int i = 0, end = array_count(tokens); i < end; ++i) {
|
|
array_push(links, tokens[i]);
|
|
}
|
|
*found = '\0';
|
|
}
|
|
|
|
if( strbegi(md, "example:") ) {
|
|
array_push(example, va("%s", md+8));
|
|
}
|
|
else if( strbegi(md, "> ") ) {
|
|
array_push(example, va("%s", md+2));
|
|
}
|
|
else if( strbegi(md, "!!!! ") ) {
|
|
array_push(notes, va("%d%s", 4, md+5));
|
|
}
|
|
else if( strbegi(md, "!!! ") ) {
|
|
array_push(notes, va("%d%s", 3, md+4));
|
|
}
|
|
else if( strbegi(md, "!! ") ) {
|
|
array_push(notes, va("%d%s", 2, md+3));
|
|
}
|
|
else if( strbegi(md, "! ") ) {
|
|
array_push(notes, va("%d%s", 1, md+2));
|
|
}
|
|
else if( md[0] ) {
|
|
char heading[512]; sscanf(md, "%s", heading);
|
|
char *text = heading[0] && strendi(heading, ":") ? md+strlen(heading) : 0;
|
|
if( text ) {
|
|
if( strbegi(heading, "return") ) {
|
|
array_push(returns, text);
|
|
} else {
|
|
text[-1] = '\0'; // remove ':'
|
|
array_push(arguments, md); // heading
|
|
array_push(arguments, text);
|
|
}
|
|
} else {
|
|
array_push(description, md);
|
|
}
|
|
}
|
|
}
|
|
else if( typename ) {
|
|
if( typename[0] == 'd' ) array_push(defines, typename+1);
|
|
if( typename[0] == 't' ) array_push(types/*typedefs*/, typename+1);
|
|
if( typename[0] == 'e' ) array_push(enums, typename+1);
|
|
if( typename[0] == 's' ) array_push(types/*structs*/, typename+1);
|
|
if( typename[0] == 'u' ) array_push(types/*unions*/, typename+1);
|
|
if( typename[0] == 'f' ) array_push(functions, typename+1);
|
|
if( typename[0] == 'm' ) array_push(macros, typename+1);
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
bool group_together() {
|
|
// find relevant syntax blocks that belong together:
|
|
// - they are a group if lines are sticked together, with no newlines.
|
|
// - they are part of a group if both syntax blocks are sharing keywords on them.
|
|
return true;
|
|
}
|
|
|
|
void print_markdeep(const char *line) { // "/// #hello markdeep"
|
|
const char *text = line + 3 + (line[3] == ' ');
|
|
const char *word = text + strspn(text, " \t");
|
|
if( !word[0] ) return;
|
|
|
|
int needs_blank =
|
|
strbegi(word, "---") || strbegi(word, "~~~") || strbegi(word, "```");
|
|
|
|
int needs_heading =
|
|
strbegi(word, "# ") || // section
|
|
strbegi(word, "**") || strbegi(word, "* ") || // heading and diagrams
|
|
strbegi(word, "- ") || strbegi(word, "1. ") || // un/ordered lists
|
|
strbegi(word, "---") || strbegi(word, "~~~") || strbegi(word, "```") // linebreaks and snippets
|
|
? 0 : 1;
|
|
|
|
printf("%s%s%s\n", needs_blank ? "\n":"", needs_heading ? "- ":"", text);
|
|
}
|
|
|
|
void print_decl(char *line) { // non-const, since prettify can modify input
|
|
if(strbegi(line, "/""//")) return;
|
|
|
|
char *typename;
|
|
char *pretty = prettify(line, &typename);
|
|
|
|
char type = typename[0], *name = typename+1;
|
|
|
|
char *header = "";
|
|
/**/ if( type == 'd' ) header = "🄳"; //
|
|
else if( type == 't' ) header = "🅃"; // 🅣
|
|
else if( type == 'e' ) header = "🄴"; // 🅔
|
|
else if( type == 'u' ) header = "🅂"; // 🅢
|
|
else if( type == 's' ) header = "🅂"; // 🅢
|
|
else if( type == 'f' ) header = "🄵"; // 🅕Ⓕ
|
|
else if( type == 'm' ) header = "🄼"; // 🅜
|
|
|
|
// make pretty->oneliner
|
|
bool truncated = 0;
|
|
char *oneliner = CALLOC(1, strlen(pretty) + 64); strcpy(oneliner, pretty);
|
|
if( strchr(oneliner, '\n') ) { // union,struct,enum
|
|
char *patch = strstr(oneliner, name);
|
|
if( patch ) patch += strlen(name), *patch++ = ';', *patch++ = 0, truncated = 1;
|
|
}
|
|
else // typedef
|
|
if( strbegi(oneliner, "typedef") && strchr(oneliner, '(') ) {
|
|
sprintf(oneliner, "typedef %s;", name); // truncate pointer-to-functions to max
|
|
truncated = 1;
|
|
}
|
|
else // function
|
|
if( strchr(oneliner, '(') ) {
|
|
#if 0
|
|
// ret name(args); >> name(args)->ret
|
|
char *name_args = va("%s%s", name,strstr(pretty,name)+strlen(name));
|
|
char *ret = va("%.*s", (int)(strstr(pretty,name)-pretty),pretty);
|
|
sprintf(oneliner, "%.*s; // -> %s", (int)strlen(name_args)-1, name_args, ret);
|
|
truncated = 1;
|
|
#elif 0
|
|
// ret name(args); >> name(args)->ret
|
|
char *open = va("%s(", name);
|
|
char *ret = va("%.*s", (int)(strstr(pretty,open)-pretty),pretty);
|
|
char *args = va("%s", strstr(pretty,name)+strlen(name));
|
|
// sprintf(oneliner, "%16s %-16s%s", ret, name, args); // API void print44 (mat44 m)
|
|
// sprintf(oneliner, "%-16s %-16s%s", ret, name, args); // API void print44 (mat44 m)
|
|
sprintf(oneliner, "%-16s %s%s", ret, name, args);
|
|
strrepl(&oneliner, " ", "·"); // windows 0xA0 character nbsp, 0xB7 char middle dot
|
|
truncated = 1;
|
|
#endif
|
|
}
|
|
else // macro, define
|
|
{}
|
|
|
|
// anchor
|
|
printf("<a name=\"%s\"></a>\n", name);
|
|
|
|
// functions
|
|
printf("<details><summary><code lang=C>%s %s</code></summary>\n", header, oneliner);
|
|
|
|
if( truncated ) {
|
|
printf("~~~~~~C\n"); // linenumbers\n");
|
|
printf("%s\n", pretty); // header, pretty);
|
|
printf("~~~~~~\n");
|
|
}
|
|
|
|
if(1) {
|
|
if( description ) {
|
|
puts("");
|
|
for( int i = 0, end = array_count(description); i < end; ++i) {
|
|
description[i][0] = toupper(description[i][0]);
|
|
printf("%s%s\n", description[i], strendi(description[i], ".") || strendi(description[i], ". ") ? "":".");
|
|
}
|
|
}
|
|
if( arguments || returns ) {
|
|
char *glue = returns ? strjoin(returns, ". ") : "";
|
|
|
|
glue[0] = toupper(glue[0]);
|
|
|
|
if( !arguments ) {
|
|
printf("\nReturns: %s\n", glue);
|
|
} else {
|
|
const char *head =
|
|
type == 'e' ? "Enum" :
|
|
type == 'u' || type == 's' ? "Member" :
|
|
/*type == 'f' ? */ (glue ? "Return" : "Argument");
|
|
|
|
printf("\n\n|%s|%s|\n", head, glue);
|
|
printf("|-----|------|\n");
|
|
for( int i = 0, end = array_count(arguments)/2; i < end; ++i ) {
|
|
char *arg = arguments[i*2+0]; // arg[0] = toupper(arg[0]);
|
|
char *nfo = arguments[i*2+1]; nfo[0] = toupper(nfo[0]);
|
|
printf("|`%s`|%s|\n", arg, nfo);
|
|
}
|
|
}
|
|
}
|
|
if( notes ) {
|
|
for( int i = 0, end = array_count(notes); i < end; ++i) {
|
|
int level = notes[i][0] - '0';
|
|
const char *severity[] = { "Note", "Note", "Tip", "WARNING", "ERROR" };
|
|
notes[i][1] = toupper(notes[i][1]);
|
|
if( level < 4 )
|
|
printf("!!! %s\n%4s%s\n", severity[ level ], "", notes[i]+1);
|
|
else
|
|
printf("!!! %s: ALERT\n%4s%s\n", severity[ level ], "", notes[i]+1);
|
|
}
|
|
}
|
|
if( example ) {
|
|
puts("\n~~~~~~C linenumbers");
|
|
printf("/* %s example */\n", name);
|
|
for( int i = 0, end = array_count(example); i < end; ++i) {
|
|
puts(example[i]);
|
|
}
|
|
puts("~~~~~~\n");
|
|
}
|
|
if( links ) {
|
|
printf("\nSee also: ");
|
|
const char *sep = "";
|
|
for( int i = 0, end = array_count(links); i < end; ++i) {
|
|
int slen = strlen(links[i]);
|
|
char is_external = links[i][slen-1] == '^';
|
|
if( is_external ) {
|
|
printf("%s<a href=\"https://google.com/search?q=%.*s\" target=\"_blank\">%.*s</a>", sep, slen-1, links[i], slen-1, links[i]);
|
|
} else {
|
|
printf("%s[%s](#%s)", sep, links[i], links[i]);
|
|
}
|
|
sep = ", ";
|
|
}
|
|
puts("");
|
|
}
|
|
|
|
if(!(description || example || notes || links || returns || arguments)) {
|
|
puts("\nUnder construction. Yet to be documented.");
|
|
puts("\nOther documentation examples: [dll](#dll), [strsplit](#strsplit), [strjoin](#strjoin), [IMAGE_FLAGS](#IMAGE_FLAGS) or [image_t](#image_t).");
|
|
}
|
|
|
|
array_free(description);
|
|
array_free(example);
|
|
array_free(notes);
|
|
array_free(links);
|
|
array_free(returns);
|
|
array_free(arguments);
|
|
}
|
|
|
|
printf("\n</details>\n\n");
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
// write header+intro
|
|
DO_CSS = flag("-nocss") ? 0 : optioni("--css", DO_CSS);
|
|
DO_DEBUG = optioni("--debug", DO_DEBUG) | flag("-dbg");
|
|
const char *excludes = option("--excluded-sections,--excluded,-x", "");
|
|
|
|
if( DO_CSS ) {
|
|
const char *paths[] = {
|
|
"editor/docs/",
|
|
"art/docs/",
|
|
"tools/docs/",
|
|
};
|
|
char *css = 0, *md = 0;
|
|
for( int i = 0; i < countof(paths); ++i ) {
|
|
char *path_md = va("%sdocs.md", paths[i]);
|
|
char *path_css = va("%sdocs.css", paths[i]);
|
|
if(file_exist(path_md)) md = file_read(path_md);
|
|
if(file_exist(path_css)) css = file_read(path_css);
|
|
}
|
|
if(!css || !css[0]) exit(-puts("error! cannot find docs.css"));
|
|
if(!md || !md[0]) exit(-puts("error! cannot find docs.md"));
|
|
|
|
strrepl(&css, "\r\n", "\n");
|
|
strrepl(&md, "\r\n", "\n");
|
|
puts( css );
|
|
puts( preprocess_md(md) );
|
|
}
|
|
if( DO_README ) {
|
|
char *md = file_read("README.md");
|
|
strrepl(&md, "\r\n", "\n");
|
|
// seek to first header
|
|
md = strstr(md, "## ");
|
|
// process headers
|
|
while( md && *md ) {
|
|
char *header = strstr(md, "## ") + 3;
|
|
char *footer = strchr(header, '\n'); if(!footer) footer = header + strlen(header) - 1;
|
|
printf("</details>\n\n<details><summary>%.*s</summary>\n\n", (int)(footer - header), header);
|
|
md = strstr(footer, "## ");
|
|
if( md ) printf("%.*s", (int)(md - footer), footer);
|
|
else printf("%s</details>\n\n", footer);
|
|
}
|
|
}
|
|
|
|
// headers are enabled by default. sources do not.
|
|
const char *fname = argc > 1 ? argv[1] : __FILE__;
|
|
int is_header_file = 1; // strstr(".h.H", file_ext(fname)) ? 1 : 0;
|
|
char *section = STRDUP(file_name(fname));
|
|
|
|
// read & preprocess C file
|
|
char *raw = file_read(fname);
|
|
preprocess_c(raw);
|
|
// if(DO_DEBUG) fprintf(stderr, "%s\n", raw);
|
|
|
|
// split into lines
|
|
array(char*) split = strsplit(raw, "\r\n");
|
|
if(DO_DEBUG) fprintf(stderr, "split %d lines\n", (int)array_count(split));
|
|
|
|
// minify every line, then learn & cache
|
|
array(char*) file = 0;
|
|
for(int i = 0; i < array_count(split); ++i) {
|
|
char *line = minify(split[i]);
|
|
if( !line[0] ) continue;
|
|
|
|
is_header_file |= (strbegi(line, "#pragma once") || strbegi(line, "#line")); // likely a header file
|
|
if(!is_header_file) continue;
|
|
|
|
// handle new heading sections
|
|
if( strbegi(line, "#line") ) {
|
|
char newfile[128];
|
|
if(sscanf(line, "%*s %*d %[^\r\n]", newfile) == 1 ) {
|
|
// remove quotes if present
|
|
char *p = &newfile[ newfile[0] == '\"' ];
|
|
if(strrchr(p, '\"')) *strrchr(p, '\"') = 0;
|
|
if(strrchr(p, '/')) p = strrchr(p, '/')+5;
|
|
// store
|
|
section = STRDUP(p);
|
|
}
|
|
}
|
|
// do not process if section is excluded from processing...
|
|
if( section && excludes[0] ) {
|
|
bool is_in_list = !!strstr(excludes, section);
|
|
bool is_whole_list = !strcmp(excludes, section);
|
|
if( is_whole_list || is_in_list ) {
|
|
continue;
|
|
}
|
|
}
|
|
// ...else, process if needed
|
|
if( section ) {
|
|
// remove extension if present
|
|
char *found = strrchr(section, '.'); if(found) *found = 0;
|
|
// print section in Uppercase form
|
|
char *name = section + (strbegi(section, "v4k_") ? 4 : 0);
|
|
//name[0] = toupper(name[0]);
|
|
printf("## %s\n\n", name);
|
|
// reset
|
|
section = NULL;
|
|
}
|
|
|
|
if(strstr/*strendi*/(line, "/""//-")) continue; // discard explicitly excluded lines
|
|
|
|
learn(line);
|
|
|
|
bool is_macro = line[0] == '#';
|
|
bool is_enum = strbegi(line, "enum ") || strbegi(line, "enum{");
|
|
bool is_struct = strbegi(line, "struct ") || strbegi(line, "struct{");
|
|
bool is_union = strbegi(line, "union ") || strbegi(line, "union{");
|
|
bool is_block = is_enum || is_struct || is_union;
|
|
bool is_variable = !!strstr(line, "extern ");
|
|
bool is_function = !is_macro && strchr(line, '(') && strchr(line, '(') < strrchr(line, ')');
|
|
bool is_typedef = !is_block && strbegi(line, "typedef ");
|
|
|
|
// @todo: sort out display priorities
|
|
|
|
// if(DO_DEBUG) printf("L%d [%s]\n", i, line);
|
|
|
|
char *request = strstr(line, "/""//+");
|
|
if( request ) {
|
|
// explicit request
|
|
request[3] = ' '; // hide plus sign
|
|
print_markdeep(request);
|
|
request[0] = 0;
|
|
// ...then print decl
|
|
print_decl(line);
|
|
continue;
|
|
}
|
|
|
|
if( is_variable || is_function || is_typedef ) {
|
|
print_decl(line);
|
|
continue;
|
|
}
|
|
if( is_block ) {
|
|
print_decl(line);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
puts("\n## 🄳 defines");
|
|
array_sort(defines, sort_strcmp);
|
|
array_unique(defines, sort_strcmp);
|
|
{ char *sep = ""; for(int i = 0, end = array_count(defines); i < end; ++i) printf("%s[%s](#%s)", sep, defines[i], defines[i]), sep = ", "; }
|
|
|
|
puts("\n## 🄴 enums");
|
|
array_sort(enums, sort_strcmp);
|
|
array_unique(enums, sort_strcmp);
|
|
{ char *sep = ""; for(int i = 0, end = array_count(enums); i < end; ++i) printf("%s[%s](#%s)", sep, enums[i], enums[i]), sep = ", "; }
|
|
|
|
puts("\n## 🅃 types");
|
|
array_sort(types, sort_strcmp);
|
|
array_unique(types, sort_strcmp);
|
|
{ char *sep = ""; for(int i = 0, end = array_count(types); i < end; ++i) printf("%s[%s](#%s)", sep, types[i], types[i]), sep = ", "; }
|
|
|
|
puts("\n## 🄵 functions");
|
|
array_sort(functions, sort_strcmp);
|
|
array_unique(functions, sort_strcmp);
|
|
{ char *sep = ""; for(int i = 0, end = array_count(functions); i < end; ++i) printf("%s[%s](#%s)", sep, functions[i], functions[i]), sep = ", "; }
|
|
|
|
puts("\n## 🄼 macros");
|
|
array_sort(macros, sort_strcmp);
|
|
array_unique(macros, sort_strcmp);
|
|
{ char *sep = ""; for(int i = 0, end = array_count(macros); i < end; ++i) printf("%s[%s](#%s)", sep, macros[i], macros[i]), sep = ", "; }
|
|
|
|
if ( DO_CHANGELOG ) {
|
|
printf("\n## c h a n g e l o g\n\n");
|
|
char *chg = file_read("changelog.txt");
|
|
strrepl(&chg, "\r\n", "\n");
|
|
for each_substring(chg, "\n", it) {
|
|
if (strstr(it, "sync depot")) continue;
|
|
// printf("<details><summary>%s</summary></details>\n", it);
|
|
printf("* %s\n", it);
|
|
}
|
|
}
|
|
|
|
puts("\n<script>");
|
|
puts("\nmarkdeepOptions = {");
|
|
puts("\n tocStyle:'medium', /* auto,none,short,medium,long */");
|
|
puts("\n definitionStyle:'auto', /* auto,short,long */");
|
|
puts("\n linkAPIDefinitions: true, /* true */");
|
|
puts("\n};");
|
|
puts("\n</script>");
|
|
puts("\n<!-- Markdeep: --><script src=\"https://morgan3d.github.io/markdeep/1.13/markdeep.min.js?\" charset=\"utf-8\"></script>");
|
|
|
|
fprintf(stderr, "%s\n", "Ok");
|
|
}
|
|
|
|
// full example below:
|
|
|
|
#line 1000 "docs.h"
|
|
|
|
/// # This is a demo
|
|
/// Below you can find a diagram that illustrates the whole process.
|
|
/// *************************************
|
|
/// * *
|
|
/// * +----------> [] ----+ *
|
|
/// * | | *
|
|
/// * +-------------------+ *
|
|
/// * *
|
|
/// *************************************
|
|
|
|
/* this is a C comment
|
|
*/
|
|
|
|
# include\
|
|
<stdio.h>
|
|
# define my_test(...) \
|
|
do_nothing
|
|
|
|
/// this is my image class
|
|
struct my_image_t {
|
|
unsigned int width, height, bpp : 10;
|
|
void *pixels;
|
|
union {
|
|
int dummy;
|
|
};
|
|
};
|
|
|
|
typedef struct my_image_t my_image;
|
|
|
|
/// this is an enumeration
|
|
enum my_enumeration {
|
|
ONE = 1,
|
|
TWO = 2,
|
|
THREE,
|
|
FOUR
|
|
};
|
|
|
|
#define my_macro_exposed1() ///
|
|
#define my_macro_exposed2() ///exposed with a comment
|
|
#define my_macro_not_exposed() ///-
|
|
|
|
/// this function acts like printf. `fmt` is a C formatted string.
|
|
void function_exposed1(char *fmt, ...);
|
|
map(char*,array(int)) (function_exposed2)(
|
|
int line1,
|
|
int line2,
|
|
...
|
|
);
|
|
|
|
array(char*) function_exposed_3(int line);
|
|
|
|
extern int version;
|
|
|
|
///------------------
|