commit bec8cb71028e87cd34fe40ef9e18779259da9de1 Author: kamkow1 Date: Mon Jun 9 14:42:51 2025 +0200 Simple server diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..974ff5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +mongoose.o +aboba +build +gpp1 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3736444 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "mongoose"] + path = mongoose + url = https://github.com/cesanta/mongoose.git +[submodule "gebs"] + path = gebs + url = https://gitlab.com/kamkow1/gebs.git diff --git a/build.c b/build.c new file mode 100644 index 0000000..f439720 --- /dev/null +++ b/build.c @@ -0,0 +1,32 @@ +#define GEBS_NO_PREFIX +#define GEBS_IMPLEMENTATION +#include "gebs/gebs.h" + +char *prog = NULL; + +int main(int argc, char ** argv) +{ + rebuild_self(argc, argv, "cc", "-o", "build", "build.c"); + + prog = SHIFT(&argc, &argv); + char *cmd = SHIFT(&argc, &argv); + if (strcmp(cmd, "make") == 0) { + RULE("./aboba", "./main.c", "./mongoose.o", "./gpp1") { + RULE("./mongoose.o", "./mongoose/mongoose.c") { + CMD("cc", "-c", "-o", "./mongoose.o", "./mongoose/mongoose.c"); + } + + RULE("./gpp1", "./gpp/gpp.c") { + CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c"); + } + + CMD("cc", "-o", "./aboba", "./main.c", "./mongoose.o"); + } + } else if (strcmp(cmd, "clean") == 0) { + remove1("./build"); + remove1("./aboba"); + remove("./mongoose.o"); + } + + return 0; +} diff --git a/gebs b/gebs new file mode 160000 index 0000000..9582176 --- /dev/null +++ b/gebs @@ -0,0 +1 @@ +Subproject commit 9582176638123bcdb955311c3bbd3243a6388595 diff --git a/gpp/gpp.c b/gpp/gpp.c new file mode 100644 index 0000000..6851dd2 --- /dev/null +++ b/gpp/gpp.c @@ -0,0 +1,3267 @@ +/* File: gpp.c -- generic preprocessor +** Author: Denis Auroux, Tristan Miller +** Contact: tristan@logological.org +** +** Copyright (C) 1996, 1999, 2001 Denis Auroux +** Copyright (C) 2003-2023 Tristan Miller +** +** This program is free software: you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public License as +** published by the Free Software Foundation, either version 3 of the +** License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program. If not, see . +*/ + +/* To compile under MS VC++, one must define WIN_NT */ +#if HAVE_CONFIG_H +# include +#endif + +#ifdef WIN_NT /* WIN NT settings */ +#define popen _popen +#define pclose _pclose +#define my_strdup _strdup +#define my_strcasecmp _stricmp +#define SLASH '\\' +#define DEFAULT_CRLF 1 +#else /* UNIX settings */ +#define SLASH '/' +#define DEFAULT_CRLF 0 +#endif + +#include +#include +#include +#include +#if HAVE_FNMATCH_H +# include +#endif +#include + +#define STACKDEPTH 50 +#define MAXARGS 100 +#define MAXINCL 128 /* max # of include dirs */ + +#define MAX_GPP_NUM_SIZE 15 +#define MAX_GPP_DATE_SIZE 1024 + +typedef struct MODE { + char *mStart; /* before macro name */ + char *mEnd; /* end macro without arg */ + char *mArgS; /* start 1st argument */ + char *mArgSep; /* separate arguments */ + char *mArgE; /* end last argument */ + char *mArgRef; /* how to refer to arguments in a def */ + char quotechar; /* quote next char */ + char *stackchar; /* characters to stack */ + char *unstackchar; /* characters to unstack */ +} MODE; + +/* translation for delimiters : + \001 = \b = ' ' = one or more spaces \201 = \!b = non-space + \002 = \w = zero or more spaces + \003 = \B = one or more spaces or \n \203 = \!B = non-space nor \n + \004 = \W = zero or more spaces or \n + \005 = \a = alphabetic (a-z, A-Z) \205 = \!a = non-alphabetic + \006 = \A = alphabetic or space/\n \206 = \!A + \007 = \# = numeric (0-9) \207 = \!# + \010 = \i = identifier (a-zA-Z0-9_) \210 = \!i + \011 = \t, \012 = \n \211 = \!t, \212 = \!n + \013 = \o = operator (+-*\/^<>=`~:.?@#&!%|) \213 = \!o + \014 = \O = operator or ()[]{} \214 = \!O +*/ +/* st end args sep arge ref quot stk unstk*/ +struct MODE CUser = {"", "", "(", ",", ")", "#", '\\', "(", ")" }; +struct MODE CMeta = {"#", "\n", "\001","\001","\n","#", '\\', "(", ")" }; +struct MODE KUser = {"", "", "(", ",", ")", "#", 0, "(", ")" }; +struct MODE KMeta = {"\n#\002","\n", "\001","\001","\n","#", 0, "", "" }; +struct MODE Tex = {"\\", "", "{", "}{", "}", "#", '@', "{", "}" }; +struct MODE Html = {"<#", ">", "\003","|", ">", "#", '\\', "<", ">" }; +struct MODE XHtml = {"<#", "/>", "\003","|", "/>","#", '\\', "<", ">" }; + +#define DEFAULT_OP_STRING (unsigned char *)"+-*/\\^<>=`~:.?@#&!%|" +#define PROLOG_OP_STRING (unsigned char *)"+-*/\\^<>=`~:.?@#&" +#define DEFAULT_OP_PLUS (unsigned char *)"()[]{}" +#define DEFAULT_ID_STRING (unsigned char *)"\005\007_" /* or equiv. "A-Za-z0-9_" */ + +/* here we assume that longs are at least 32 bit... if not, change this ! */ +#define LOG_LONG_BITS 5 +#define CHARSET_SUBSET_LEN (256 >> LOG_LONG_BITS) +typedef unsigned long *CHARSET_SUBSET; + +CHARSET_SUBSET DefaultOp, DefaultExtOp, PrologOp, DefaultId; + +typedef struct COMMENT { + char *start; /* how the comment/string starts */ + char *end; /* how it ends */ + char quote; /* how to prevent it from ending */ + char warn; /* a character that shouldn't be in there */ + int flags[3]; /* meta, user, text */ + struct COMMENT *next; +} COMMENT; + +#define OUTPUT_TEXT 0x1 /* what's inside will be output */ +#define OUTPUT_DELIM 0x2 /* the delimiters will be output */ +#define PARSE_MACROS 0x4 /* macros inside will be parsed */ +#define FLAG_IGNORE 0x40 + +#define FLAG_STRING (OUTPUT_TEXT|OUTPUT_DELIM) +#define FLAG_COMMENT 0 + +#define FLAG_META 0 +#define FLAG_USER 1 +#define FLAG_TEXT 2 + +/* Some stuff I removed because it made for some impossible situations : + + #define PARSE_COMMENTS 0x8 + comments inside comments will not be parsed because nesting comments is + too complicated (syntax conflicts, esp. to find a comment's end) + -- of course, unless the comment is ignored. + + #define MACRO_FRIENDLY 0x20 + a comment-end is to be processed even if an unfinished macro call has + started inside the comment, otherwise it's too hard do decide in advance + where a comment ends. In particular foo('bar((((') is valid. + + #define PREVENT_DELIM 0x10 + all comments will prevent macro delimitation, i.e. foo('bar) is invalid. + -- of course, unless the comment is ignored. + Too bad, #define foo '... terminates only at following "'". + Unless one adds quotechars like in #define foo \' ... + + ALSO NOTE : comments are not allowed before the end of the first argument + to a meta-macro. E.g. this is legal : #define foo <* blah *> 3 + This is not legal : #define <* blah *> foo 3 + If a comment occurs here, the behavior depends on the actual meta-macro : + most will yield an error and stop gpp (#define, #undef, #ifdef/ifndef, + #defeval, #include, #mode) ; #exec, #if and #eval should be ok ; + #ifeq will always fail while #ifneq will always succeed ; +*/ + +typedef struct SPECS { + struct MODE User, Meta; + struct COMMENT *comments; + struct SPECS *stack_next; + int preservelf; + CHARSET_SUBSET op_set, ext_op_set, id_set; +} SPECS; + +struct SPECS *S; + +typedef struct MACRO { + char *username, *macrotext, **argnames; + int macrolen, nnamedargs; + struct SPECS *define_specs; + int defined_in_comment; +} MACRO; + +struct MACRO *macros; +int nmacros, nalloced; +char *includedir[MAXINCL]; +int nincludedirs; +int execallowed; +int dosmode; +int autoswitch; +/* must be a format-like string that has % % % in it. + The first % is replaced with line number, the second with "filename", and + the third with 1, 2 or blank + Can also use ? instead of %. + */ +char *include_directive_marker = NULL; +short WarningLevel = 2; + +/* controls if standard dirs, like /usr/include, are to be searched for + #include and whether the current dir is to be searched first or last. */ +int NoStdInc = 0; +int NoCurIncFirst = 0; +int CurDirIncLast = 0; +int file_and_stdout = 0; +char *IncludeFile = NULL; + +typedef struct OUTPUTCONTEXT { + char *buf; + int len, bufsize; + FILE *f; +} OUTPUTCONTEXT; + +typedef struct INPUTCONTEXT { + char *buf; + char *malloced_buf; /* what was actually malloc-ed (buf may have shifted) */ + int len, bufsize; + int lineno; + char *filename; + FILE *in; + int argc; + char **argv; + char **namedargs; + struct OUTPUTCONTEXT *out; + int eof; + int in_comment; + int ambience; /* FLAG_TEXT, FLAG_USER or FLAG_META */ + int may_have_args; +} INPUTCONTEXT; + +struct INPUTCONTEXT *C; + +int commented[STACKDEPTH], iflevel; +/* commented = 0: output, 1: not output, + 2: not output because we're in a #elif and we've already gone through + the right case (so #else/#elif can't toggle back to output) */ + +int parselevel; + +void ProcessContext(void); /* the main loop */ + +int findIdent(const char *b, int l); +void delete_macro(int i); + +/* various recent additions */ +void usage(void); +void display_version(void); +void bug(const char *s); +void warning(const char *s); +static void getDirname(const char *fname, char *dirname); +static FILE *openInCurrentDir(const char *incfile); +char *ArithmEval(int pos1, int pos2); +void replace_definition_with_blank_lines(const char *start, const char *end, + int skip); +void replace_directive_with_blank_line(FILE *file); +void write_include_marker(FILE *f, int lineno, char *filename, + const char *marker); +void construct_include_directive_marker(char **include_directive_marker, + const char *includemarker_input); +void escape_backslashes(const char *instr, char **outstr); +static void DoInclude(char *file_name, int ignore_nonexistent); + +/* + ** strdup() and my_strcasecmp() are not ANSI C, so here we define our own + ** versions in case the compiler does not support them + */ +#if ! HAVE_STRDUP +inline char *my_strdup(const char *s); +inline char *my_strdup(const char *s) { + size_t len = strlen(s) + 1; + char *newstr = malloc(len); + return newstr ? (char *) memcpy(newstr, s, len) : NULL ; +} +#else +# undef my_strdup +# define my_strdup strdup +#endif +#if ! HAVE_STRCASECMP +int my_strcasecmp(const char *s, const char *s2) { + do { + char c1 = tolower(*s); + char c2 = tolower(*s2); + if (c1 > c2) + return 1; + if (c1 < c2) + return -1; + } while (*s++ && *s2++); + return 0; +} +#else +# undef my_strcasecmp +# define my_strcasecmp strcasecmp +#endif + +void bug(const char *s) { + fprintf(stderr, "%s:%d: error: %s\n", C->filename, C->lineno, s); + exit(EXIT_FAILURE); +} + +void warning(const char *s) { + fprintf(stderr, "%s:%d: warning: %s\n", C->filename, C->lineno, s); +} + +struct SPECS *CloneSpecs(const struct SPECS *Q) { + struct SPECS *P; + struct COMMENT *x, *y; + + P = malloc(sizeof *P); + if (P == NULL ) + bug("Out of memory."); + memcpy(P, Q, sizeof(struct SPECS)); + P->stack_next = NULL; + if (Q->comments != NULL ) + P->comments = malloc(sizeof *(P->comments)); + for (x = Q->comments, y = P->comments; x != NULL ; + x = x->next, y = y->next) { + memcpy(y, x, sizeof(struct COMMENT)); + y->start = my_strdup(x->start); + y->end = my_strdup(x->end); + if (x->next != NULL ) + y->next = malloc(sizeof *(y->next)); + } + return P; +} + +void FreeComments(struct SPECS *Q) { + struct COMMENT *p; + + while (Q && Q->comments != NULL ) { + p = Q->comments; + Q->comments = p->next; + free(p->start); + free(p->end); + free(p); + } +} + +void PushSpecs(const struct SPECS *X) { + struct SPECS *P; + + P = CloneSpecs(X); + P->stack_next = S; + S = P; +} + +void PopSpecs(void) { + struct SPECS *P; + + P = S; + S = P->stack_next; + FreeComments(P); + free(P); + if (S == NULL ) + bug("#mode restore without #mode save"); +} + +void display_version(void) { + printf("gpp\n"); + printf("Copyright (C) 1996-2001 Denis Auroux\n"); + printf("Copyright (C) 2003-2020 Tristan Miller\n"); + printf("This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); +} + +void usage(void) { + printf("Usage : gpp [-{o|O} outfile] [-I/include/path] [-Dname=val ...] [-z] [-x] [-m]\n"); + printf(" [-n] [-C | -T | -H | -X | -P | -U ... [-M ...]] [+c str1 str2]\n"); + printf(" [+s str1 str2 c] [long options] [infile]\n\n"); + printf(" default: #define x y macro(arg,...)\n"); + printf(" -C : maximum cpp compatibility (includes -n, +c, +s, ...)\n"); + printf(" -T : TeX-like \\define{x}{y} \\macro{arg}{...}\n"); + printf(" -H : HTML-like <#define x|y> <#macro arg|...>\n"); + printf(" -X : XHTML-like <#define x|y/> <#macro arg|.../>\n"); + printf(" -P : prolog compatible cpp-like mode\n"); + printf(" -U : user-defined syntax (specified in 9 following args; see manual)\n"); + printf(" -M : user-defined syntax for meta-macros (specified in 7 following args)\n\n"); + printf(" -o : output to outfile\n"); + printf(" -O : output to outfile and stdout\n"); + printf(" -z : line terminator is CR-LF (MS-DOS style)\n"); + printf(" -x : enable #exec built-in macro\n"); + printf(" -m : enable automatic mode switching upon including .h/.c files\n"); + printf(" -n : send LF characters serving as macro terminators to output\n"); + printf(" +c : use next 2 args as comment start and comment end sequences\n"); + printf(" +s : use next 3 args as string start, end and quote character\n\n"); + printf(" Long options:\n"); + printf(" --include file : process file before infile\n"); + printf(" --nostdinc : don't search standard directories for files to include\n"); + printf(" --nocurinc : don't search the current directory for files to include\n"); + printf(" --curdirinclast : search the current directory last\n"); + printf(" --warninglevel n : set warning level\n"); + printf(" --includemarker formatstring : keep track of #include directives in output\n\n"); + printf(" --version : display version information and exit\n"); + printf(" -h, --help : display this message and exit\n\n"); +} + +int isDelim(unsigned char c) { + if (c >= 128) + return 0; + if ((c >= '0') && (c <= '9')) + return 0; + if ((c >= 'A') && (c <= 'Z')) + return 0; + if ((c >= 'a') && (c <= 'z')) + return 0; + if (c == '_') + return 0; + return 1; +} + +int isWhite(char c) { + if (c == ' ') + return 1; + if (c == '\t') + return 1; + if (c == '\n') + return 1; + return 0; +} + +void newmacro(const char *s, int len, int hasspecs) { + if (nmacros == nalloced) { + nalloced = 2 * nalloced + 1; + macros = realloc(macros, nalloced * sizeof *macros); + if (macros == NULL ) + bug("Out of memory"); + } + macros[nmacros].username = malloc(len + 1); + strncpy(macros[nmacros].username, s, len); + macros[nmacros].username[len] = 0; + macros[nmacros].argnames = NULL; + macros[nmacros].nnamedargs = 0; + macros[nmacros].defined_in_comment = 0; + if (hasspecs) + macros[nmacros].define_specs = CloneSpecs(S); + else + macros[nmacros].define_specs = NULL; +} + +void lookupArgRefs(int n) { + int i, l; + char *p; + + if (macros[n].argnames != NULL ) + return; /* don't mess with those */ + macros[n].nnamedargs = -1; + l = strlen(S->User.mArgRef); + for (i = 0, p = macros[n].macrotext; i < macros[n].macrolen; i++, p++) { + if ((*p != 0) && (*p == S->User.quotechar)) { + i++; + p++; + } else if (!strncmp(p, S->User.mArgRef, l)) + if ((p[l] >= '1') && (p[l] <= '9')) { + macros[n].nnamedargs = 0; + return; + } + } +} + +char *strNl0(const char *s) /* replace "\\n" by "\n" in a cmd-line arg */ +{ + char *t, *u; + t = malloc(strlen(s) + 1); + u = t; + while (*s != 0) { + if ((*s == '\\') && (s[1] == 'n')) { + *u = '\n'; + s++; + } else + *u = *s; + s++; + u++; + } + *u = 0; + return t; +} + +char *strNl(const char *s) /* the same but with whitespace specifier handling */ +{ + char *t, *u; + int neg; + t = malloc(strlen(s) + 1); + u = t; + if (!isDelim(*s)) + bug("character not allowed to start a syntax specifier"); + while (*s != 0) { + if (((*s & 0x60) == 0) && (*s != '\n') && (*s != '\t')) + bug("character not allowed in syntax specifier"); + if (*s == '\\') { + neg = (s[1] == '!'); + switch (s[neg + 1]) { + case 'n': + case 'r': + *u = '\n'; + break; + case 't': + *u = '\t'; + break; + case 'b': /* one or more spaces */ + *u = '\001'; + break; + case 'w': /* zero or more spaces */ + if (neg) + bug("\\w and \\W cannot be negated"); + *u = '\002'; + break; + case 'B': /* one or more spaces or \n */ + *u = '\003'; + break; + case 'W': /* zero or more spaces or \n */ + if (neg) + bug("\\w and \\W cannot be negated"); + *u = '\004'; + break; + case 'a': /* alphabetic */ + *u = '\005'; + break; + case 'A': /* alphabetic + space */ + *u = '\006'; + break; + case '#': /* numeric */ + *u = '\007'; + break; + case 'i': /* identifier */ + *u = '\010'; + break; + case 'o': /* operator */ + *u = '\013'; + break; + case 'O': /* operator/parenthesis */ + *u = '\014'; + break; + default: + *u = '\\'; + neg = -1; + } + if (neg > 0) + *u += (char) 128; + s += neg + 1; + } else if (*s == ' ') + *u = '\001'; + else + *u = *s; + s++; + u++; + } + *u = 0; + return t; +} + +/* same as strnl() but for C strings & in-place */ +char *strNl2(char *s, int check_delim) { + char *u; + int neg; + u = s; + if (check_delim && !isDelim(*s)) + bug("character not allowed to start a syntax specifier"); + while (*s != '"') { + if (((*s & 0x60) == 0) && (*s != '\n') && (*s != '\t')) + bug("character not allowed in syntax specifier"); + if (*s == '\\') { + neg = (s[1] == '!'); + switch (s[neg + 1]) { + case 'n': + case 'r': + *u = '\n'; + break; + case 't': + *u = '\t'; + break; + case 'b': /* one or more spaces */ + *u = '\001'; + break; + case 'w': /* zero or more spaces */ + if (neg) + bug("\\w and \\W cannot be negated"); + *u = '\002'; + break; + case 'B': /* one or more spaces or \n */ + *u = '\003'; + break; + case 'W': /* zero or more spaces or \n */ + if (neg) + bug("\\w and \\W cannot be negated"); + *u = '\004'; + break; + case 'a': /* alphabetic */ + *u = '\005'; + break; + case 'A': /* alphabetic + space */ + *u = '\006'; + break; + case '#': /* numeric */ + *u = '\007'; + break; + case 'i': /* identifier */ + *u = '\010'; + break; + case 'o': /* operator */ + *u = '\013'; + break; + case 'O': /* operator/parenthesis */ + *u = '\014'; + break; + case '"': + case '\\': + if (!neg) { + *u = s[1]; + break; + } + default: + bug("unknown escape sequence in syntax specifier"); + } + if (neg > 0) + *u += (char) 128; + s += neg + 1; + } else if (*s == ' ') + *u = '\001'; + else + *u = *s; + if (*s == 0) + bug("unterminated string in #mode command"); + s++; + u++; + } + *u = 0; + return (s + 1); +} + +int isWhitesep(const char *s) { + while (isWhite(*s) || (*s == '\001') || (*s == '\002') || (*s == '\003') + || (*s == '\004')) + s++; + return (*s == 0); +} + +int nowhite_strcmp(char *s, char *t) { + char *p; + + while (isWhite(*s)) + s++; + while (isWhite(*t)) + t++; + if ((*s == 0) || (*t == 0)) + return strcmp(s, t); + p = s + strlen(s) - 1; + while (isWhite(*p)) + *(p--) = 0; + p = t + strlen(t) - 1; + while (isWhite(*p)) + *(p--) = 0; + return strcmp(s, t); +} + +void parseCmdlineDefine(const char *s) { + int l, i, argc; + + for (l = 0; s[l] && (s[l] != '=') && (s[l] != '('); l++) + ; + i = findIdent(s, l); + if (i >= 0) + delete_macro(i); + newmacro(s, l, 0); + + /* possibly allow named arguments: -Dmacro(arg1,arg2)=... (no spaces) */ + if (s[l] == '(') { + argc = 0; + do { + l++; + i = l; + while (!isDelim(s[i])) + i++; + if (s[i] != ',' && s[i] != ')') + bug("invalid syntax in -D declaration"); + if (i > l) + argc++; + macros[nmacros].argnames = realloc(macros[nmacros].argnames, + (argc + 1) * sizeof(char *)); + if (i > l) { + macros[nmacros].argnames[argc - 1] = malloc(i - l + 1); + memcpy(macros[nmacros].argnames[argc - 1], s + l, i - l); + macros[nmacros].argnames[argc - 1][i - l] = 0; + } + l = i; + } while (s[l] != ')'); + l++; + macros[nmacros].nnamedargs = argc; + macros[nmacros].argnames[argc] = NULL; + } + + /* the macro definition afterwards ! */ + if (s[l] == '=') + l++; + else if (s[l] != 0) + bug("invalid syntax in -D declaration"); + macros[nmacros].macrolen = strlen(s + l); + macros[nmacros++].macrotext = my_strdup(s + l); +} + +int readModeDescription(char **args, struct MODE *mode, int ismeta) { + if (!(*(++args))) + return 0; + mode->mStart = strNl(*args); + if (!(*(++args))) + return 0; + mode->mEnd = strNl(*args); + if (!(*(++args))) + return 0; + mode->mArgS = strNl(*args); + if (!(*(++args))) + return 0; + mode->mArgSep = strNl(*args); + if (!(*(++args))) + return 0; + mode->mArgE = strNl(*args); + if (!(*(++args))) + return 0; + mode->stackchar = strNl(*args); + if (!(*(++args))) + return 0; + mode->unstackchar = strNl(*args); + if (ismeta) + return 1; + if (!(*(++args))) + return 0; + mode->mArgRef = strNl(*args); + if (!(*(++args))) + return 0; + mode->quotechar = **args; + return 1; +} + +int parse_comment_specif(char c) { + switch (c) { + case 'I': + case 'i': + return FLAG_IGNORE; + case 'c': + return FLAG_COMMENT; + case 's': + return FLAG_STRING; + case 'q': + return OUTPUT_TEXT; + case 'S': + return FLAG_STRING | PARSE_MACROS; + case 'Q': + return OUTPUT_TEXT | PARSE_MACROS; + case 'C': + return FLAG_COMMENT | PARSE_MACROS; + default: + bug("Invalid comment/string modifier"); + return 0; + } +} + +void add_comment(struct SPECS *S, const char *specif, char *start, char *end, + char quote, char warn) { + struct COMMENT *p; + + if (*start == 0) + bug("Comment/string start delimiter must be non-empty"); + for (p = S->comments; p != NULL ; p = p->next) + if (!strcmp(p->start, start)) { + if (strcmp(p->end, end)) /* already exists with a different end */ + bug("Conflicting comment/string delimiter specifications"); + free(p->start); + free(p->end); + break; + } + + if (p == NULL ) { + p = malloc(sizeof *p); + p->next = S->comments; + S->comments = p; + } + p->start = start; + p->end = end; + p->quote = quote; + p->warn = warn; + if (strlen(specif) != 3) + bug("Invalid comment/string modifier"); + p->flags[FLAG_META] = parse_comment_specif(specif[0]); + p->flags[FLAG_USER] = parse_comment_specif(specif[1]); + p->flags[FLAG_TEXT] = parse_comment_specif(specif[2]); +} + +void delete_comment(struct SPECS *S, char *start) { + struct COMMENT *p, *q; + + q = NULL; + for (p = S->comments; p != NULL ; p = p->next) { + if (!strcmp(p->start, start)) { + if (q == NULL ) + S->comments = p->next; + else + q->next = p->next; + free(p->start); + free(p->end); + free(p); + free(start); + return; + } else + q = p; + } + free(start); +} + +void outchar(char c) { + if (C->out->bufsize) { + if (C->out->len + 1 == C->out->bufsize) { + C->out->bufsize = C->out->bufsize * 2; + C->out->buf = realloc(C->out->buf, C->out->bufsize); + if (C->out->buf == NULL ) + bug("Out of memory"); + } + C->out->buf[C->out->len++] = c; + } else { + if (dosmode && (c == 10)) { + fputc(13, C->out->f); + if (file_and_stdout) + fputc(13, stdout); + } + if (c != 13) { + fputc(c, C->out->f); + if (file_and_stdout) + fputc(c, stdout); + } + } +} + +void sendout(const char *s, int l, int proc) /* only process the quotechar, that's all */ +{ + int i; + + if (!commented[iflevel]) + for (i = 0; i < l; i++) { + if (proc && (s[i] != 0) && (s[i] == S->User.quotechar)) { + i++; + if (i == l) + return; + } + if (s[i] != 0) + outchar(s[i]); + } + else + replace_definition_with_blank_lines(s, s + l - 1, 0); +} + +void extendBuf(int pos) { + char *p; + if (C->bufsize <= pos) { + C->bufsize += pos; /* approx double */ + p = malloc(C->bufsize); + memcpy(p, C->buf, C->len); + free(C->malloced_buf); + C->malloced_buf = C->buf = p; + if (C->buf == NULL ) + bug("Out of memory"); + } +} + +char getChar(int pos) { + static int lastchar = -666; + int c; + + if (lastchar == -666 && !strcmp(S->Meta.mEnd, "\n")) + lastchar = '\n'; + + if (C->in == NULL ) { + if (pos >= C->len) + return 0; + else + return C->buf[pos]; + } + extendBuf(pos); + while (pos >= C->len) { + do { + c = fgetc(C->in); + } while (c == 13); + if (lastchar == '\n') + C->lineno++; + lastchar = c; + if (c == EOF) + c = 0; + C->buf[C->len++] = (char) c; + } + return C->buf[pos]; +} + +int whiteout(int *pos1, int *pos2) /* remove whitespace on both sides */ +{ + while ((*pos1 < *pos2) && isWhite(getChar(*pos1))) + (*pos1)++; + while ((*pos1 < *pos2) && isWhite(getChar(*pos2 - 1))) + (*pos2)--; + return (*pos1 < *pos2); +} + +int identifierEnd(int start) { + char c; + + c = getChar(start); + if (c == 0) + return start; + if (c == S->User.quotechar) { + c = getChar(start + 1); + if (c == 0) + return (start + 1); + if (isDelim(c)) + return (start + 2); + start += 2; + c = getChar(start); + } + while (!isDelim(c)) + c = getChar(++start); + return start; +} + +int iterIdentifierEnd(int start) { + int x; + while (1) { + x = identifierEnd(start); + if (x == start) + return x; + start = x; + } +} + +int IsInCharset(CHARSET_SUBSET x, int c) { + return (x[c >> LOG_LONG_BITS] & 1L << (c & ((1 << LOG_LONG_BITS) - 1))) != 0; +} + +int matchSequence(const char *s, int *pos) { + int i = *pos; + int match; + char c; + + while (*s != 0) { + if (!((*s) & 0x60)) { /* special sequences */ + match = 1; + switch ((*s) & 0x1f) { + case '\001': + c = getChar(i++); + if ((c != ' ') && (c != '\t')) { + match = 0; + break; + } + case '\002': + i--; + do { + c = getChar(++i); + } while ((c == ' ') || (c == '\t')); + break; + case '\003': + c = getChar(i++); + if ((c != ' ') && (c != '\t') && (c != '\n')) { + match = 0; + break; + } + case '\004': + i--; + do { + c = getChar(++i); + } while ((c == ' ') || (c == '\t') || (c == '\n')); + break; + case '\006': + c = getChar(i++); + match = ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')) + || (c == ' ') || (c == '\t') || (c == '\n'); + break; + case '\005': + c = getChar(i++); + match = ((c >= 'a') && (c <= 'z')) + || ((c >= 'A') && (c <= 'Z')); + break; + case '\007': + c = getChar(i++); + match = ((c >= '0') && (c <= '9')); + break; + case '\010': + c = getChar(i++); + match = IsInCharset(S->id_set, c); + break; + case '\011': + c = getChar(i++); + match = (c == '\t'); + break; + case '\012': + c = getChar(i++); + match = (c == '\n'); + break; + case '\013': + c = getChar(i++); + match = IsInCharset(S->op_set, c); + break; + case '\014': + c = getChar(i++); + match = IsInCharset(S->ext_op_set, c) + || IsInCharset(S->op_set, c); + break; + } + if ((*s) & 0x80) + match = !match; + if (!match) + return 0; + } else if (getChar(i++) != *s) + return 0; + s++; + } + *pos = i; + return 1; +} + +int matchEndSequence(const char *s, int *pos) { + if (*s == 0) + return 1; + /* if terminator is \n and we're at end of input, let it be... */ + if (getChar(*pos) == 0 && s[0] == '\n' && s[1] == 0) + return 1; + if (!matchSequence(s, pos)) + return 0; + if (S->preservelf && isWhite(getChar(*pos - 1))) + (*pos)--; + return 1; +} + +int matchStartSequence(const char *s, int *pos) { + char c; + int match; + + if (!((*s) & 0x60)) { /* special sequences from prev. context */ + c = getChar(*pos - 1); + match = 1; + if (*s == 0) + return 1; + switch ((*s) & 0x1f) { + case '\001': + if ((c != ' ') && (c != '\t')) { + match = 0; + break; + } + case '\002': + break; + case '\003': + if ((c != ' ') && (c != '\t') && (c != '\n')) { + match = 0; + break; + } + case '\004': + break; + case '\006': + if ((c == ' ') || (c == '\t') || (c == '\n')) + break; + case '\005': + match = ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); + break; + case '\007': + match = ((c >= '0') && (c <= '9')); + break; + case '\010': + match = IsInCharset(S->id_set, c); + break; + case '\011': + match = (c == '\t'); + break; + case '\012': + match = (c == '\n'); + break; + case '\013': + match = IsInCharset(S->op_set, c); + break; + case '\014': + match = IsInCharset(S->ext_op_set, c) || IsInCharset(S->op_set, c); + break; + } + if ((*s) & 0x80) + match = !match; + if (!match) + return 0; + s++; + } + return matchSequence(s, pos); +} + +void AddToCharset(CHARSET_SUBSET x, int c) { + x[c >> LOG_LONG_BITS] |= 1L << (c & ((1 << LOG_LONG_BITS) - 1)); +} + +CHARSET_SUBSET MakeCharsetSubset(unsigned char *s) { + CHARSET_SUBSET x; + int i; + unsigned char c; + + x = malloc(CHARSET_SUBSET_LEN * sizeof(unsigned long)); + for (i = 0; i < CHARSET_SUBSET_LEN; i++) + x[i] = 0; + while (*s != 0) { + if (!((*s) & 0x60)) { /* special sequences */ + if ((*s) & 0x80) + bug( + "negated special sequences not allowed in charset specifications"); + switch ((*s) & 0x1f) { + case '\002': /* \w, \W, \i, \o, \O not allowed */ + case '\004': + case '\010': + case '\013': + case '\014': + bug("special sequence not allowed in charset specification"); + case '\003': + AddToCharset(x, '\n'); + case '\001': + AddToCharset(x, ' '); + case '\011': + AddToCharset(x, '\t'); + break; + case '\006': + AddToCharset(x, '\n'); + AddToCharset(x, ' '); + AddToCharset(x, '\t'); + case '\005': + for (c = 'A'; c <= 'Z'; c++) + AddToCharset(x, c); + for (c = 'a'; c <= 'z'; c++) + AddToCharset(x, c); + break; + case '\007': + for (c = '0'; c <= '9'; c++) + AddToCharset(x, c); + break; + case '\012': + AddToCharset(x, '\n'); + break; + } + } else if ((s[1] == '-') && ((s[2] & 0x60) != 0) && (s[2] >= *s)) { + for (c = *s; c <= s[2]; c++) + AddToCharset(x, c); + s += 2; + } else + AddToCharset(x, *s); + s++; + } + return x; +} + +int idequal(const char *b, int l, const char *s) { + int i; + + if ((int) strlen(s) != l) + return 0; + for (i = 0; i < l; i++) + if (b[i] != s[i]) + return 0; + return 1; +} + +int findIdent(const char *b, int l) { + int i; + + for (i = 0; i < nmacros; i++) + if (idequal(b, l, macros[i].username)) + return i; + return -1; +} + +int findNamedArg(const char *b, int l) { + char *s; + int i; + + for (i = 0;; i++) { + s = C->namedargs[i]; + if (s == NULL ) + return -1; + if (idequal(b, l, s)) + return i; + } +} + +void shiftIn(int l) { + int i; + + if (l <= 1) + return; + l--; + if (l >= C->len) + C->len = 0; + else { + if (C->len - l > 100) { /* we want to shrink that buffer */ + C->buf += l; + C->bufsize -= l; + } else + for (i = l; i < C->len; i++) + C->buf[i - l] = C->buf[i]; + C->len -= l; + C->eof = (C->buf[0] == 0); + } + if (C->len <= 1) { + if (C->in == NULL ) + C->eof = 1; + else + C->eof = feof(C->in); + } +} + +void initthings(int argc, char **argv) { + char **arg, *s; + int i, isinput, isoutput, ishelp, ismode, hasmeta, usrmode; + + DefaultOp = MakeCharsetSubset(DEFAULT_OP_STRING); + PrologOp = MakeCharsetSubset(PROLOG_OP_STRING); + DefaultExtOp = MakeCharsetSubset(DEFAULT_OP_PLUS); + DefaultId = MakeCharsetSubset(DEFAULT_ID_STRING); + + nmacros = 0; + nalloced = 31; + macros = malloc(nalloced * sizeof *macros); + + S = malloc(sizeof *S); + S->User = CUser; + S->Meta = CMeta; + S->comments = NULL; + S->stack_next = NULL; + S->preservelf = 0; + S->op_set = DefaultOp; + S->ext_op_set = DefaultExtOp; + S->id_set = DefaultId; + + C = malloc(sizeof *C); + C->in = stdin; + C->argc = 0; + C->argv = NULL; + C->filename = my_strdup("stdin"); + C->out = malloc(sizeof *(C->out)); + C->out->f = stdout; + C->out->bufsize = 0; + C->lineno = 1; + isinput = isoutput = ismode = ishelp = hasmeta = usrmode = 0; + nincludedirs = 0; + C->bufsize = 80; + C->len = 0; + C->buf = C->malloced_buf = malloc(C->bufsize); + C->eof = 0; + C->namedargs = NULL; + C->in_comment = 0; + C->ambience = FLAG_TEXT; + C->may_have_args = 0; + commented[0] = 0; + iflevel = 0; + execallowed = 0; + autoswitch = 0; + dosmode = DEFAULT_CRLF; + + for (arg = argv + 1; *arg; arg++) { + if (strcmp(*arg, "--help") == 0 || strcmp(*arg, "-h") == 0) { + usage(); + exit(EXIT_SUCCESS); + } + if (strcmp(*arg, "--version") == 0) { + display_version(); + exit(EXIT_SUCCESS); + } +#define DEPRECATED_WARNING fprintf(stderr, "gpp: warning: deprecated option `%s'; use `-%s' instead\n", *arg, *arg) + if (strcmp(*arg, "-nostdinc") == 0) { + DEPRECATED_WARNING; + NoStdInc = 1; + continue; + } + if (strcmp(*arg, "-nocurinc") == 0) { + DEPRECATED_WARNING; + NoCurIncFirst = 1; + continue; + } + if (strcmp(*arg, "-curdirinclast") == 0) { + DEPRECATED_WARNING; + CurDirIncLast = 1; + NoCurIncFirst = 1; + continue; + } + if (strcmp(*arg, "-includemarker") == 0) { + DEPRECATED_WARNING; + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + construct_include_directive_marker(&include_directive_marker, *arg); + continue; + } + if (strcmp(*arg, "--include") == 0) { + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + IncludeFile = *arg; + continue; + } + if (strcmp(*arg, "-warninglevel") == 0) { + DEPRECATED_WARNING; + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + WarningLevel = atoi(*arg); + continue; + } + if (strcmp(*arg, "--nostdinc") == 0) { + NoStdInc = 1; + continue; + } + if (strcmp(*arg, "--nocurinc") == 0) { + NoCurIncFirst = 1; + continue; + } + if (strcmp(*arg, "--curdirinclast") == 0) { + CurDirIncLast = 1; + NoCurIncFirst = 1; + continue; + } + if (strcmp(*arg, "--includemarker") == 0) { + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + construct_include_directive_marker(&include_directive_marker, *arg); + continue; + } + if (strcmp(*arg, "--warninglevel") == 0) { + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + WarningLevel = atoi(*arg); + continue; + } + + if (**arg == '+') { + switch ((*arg)[1]) { + case 'c': + s = (*arg) + 2; + if (*s == 0) + s = "ccc"; + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + add_comment(S, s, strNl(*(arg - 1)), strNl(*arg), 0, 0); + break; + case 's': + s = (*arg) + 2; + if (*s == 0) + s = "sss"; + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + add_comment(S, s, strNl(*(arg - 2)), strNl(*(arg - 1)), **arg, + 0); + break; + case 'z': + dosmode = 0; + break; + case 'n': + S->preservelf = 0; + break; + default: + ishelp = 1; + } + } else if (**arg != '-') { + ishelp |= isinput; + isinput = 1; + C->in = fopen(*arg, "r"); + free(C->filename); + C->filename = my_strdup(*arg); + if (C->in == NULL ) + bug("Cannot open input file"); + } else + switch ((*arg)[1]) { + case 'I': + if (nincludedirs == MAXINCL) + bug("too many include directories"); + if ((*arg)[2] == 0) { + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + includedir[nincludedirs++] = my_strdup(*arg); + } else + includedir[nincludedirs++] = my_strdup((*arg) + 2); + break; + case 'C': + ishelp |= ismode | hasmeta | usrmode; + ismode = 1; + S->User = KUser; + S->Meta = KMeta; + S->preservelf = 1; + add_comment(S, "ccc", my_strdup("/*"), my_strdup("*/"), 0, 0); + add_comment(S, "ccc", my_strdup("//"), my_strdup("\n"), 0, 0); + add_comment(S, "ccc", my_strdup("\\\n"), my_strdup(""), 0, 0); + add_comment(S, "sss", my_strdup("\""), my_strdup("\""), '\\', + '\n'); + add_comment(S, "sss", my_strdup("'"), my_strdup("'"), '\\', + '\n'); + break; + case 'P': + ishelp |= ismode | hasmeta | usrmode; + ismode = 1; + S->User = KUser; + S->Meta = KMeta; + S->preservelf = 1; + S->op_set = PrologOp; + add_comment(S, "css", my_strdup("\213/*"), my_strdup("*/"), 0, + 0); /* \!o */ + add_comment(S, "cii", my_strdup("\\\n"), my_strdup(""), 0, 0); + add_comment(S, "css", my_strdup("%"), my_strdup("\n"), 0, 0); + add_comment(S, "sss", my_strdup("\""), my_strdup("\""), 0, + '\n'); + add_comment(S, "sss", my_strdup("\207'"), my_strdup("'"), 0, + '\n'); /* \!# */ + break; + case 'T': + ishelp |= ismode | hasmeta | usrmode; + ismode = 1; + S->User = S->Meta = Tex; + break; + case 'H': + ishelp |= ismode | hasmeta | usrmode; + ismode = 1; + S->User = S->Meta = Html; + break; + case 'X': + ishelp |= ismode | hasmeta | usrmode; + ismode = 1; + S->User = S->Meta = XHtml; + break; + case 'U': + ishelp |= ismode | usrmode; + usrmode = 1; + if (!readModeDescription(arg, &(S->User), 0)) { + usage(); + exit(EXIT_FAILURE); + } + arg += 9; + if (!hasmeta) + S->Meta = S->User; + break; + case 'M': + ishelp |= ismode | hasmeta; + hasmeta = 1; + if (!readModeDescription(arg, &(S->Meta), 1)) { + usage(); + exit(EXIT_FAILURE); + } + arg += 7; + break; + case 'O': + file_and_stdout = 1; + case 'o': + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + ishelp |= isoutput; + isoutput = 1; + C->out->f = fopen(*arg, "w"); + if (C->out->f == NULL ) + bug("Cannot create output file"); + break; + case 'D': + if ((*arg)[2] == 0) { + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + s = strNl0(*arg); + } else + s = strNl0((*arg) + 2); + parseCmdlineDefine(s); + free(s); + break; + case 'x': + execallowed = 1; + break; + case 'n': + S->preservelf = 1; + break; + case 'z': + dosmode = 1; + break; + case 'c': + case 's': + if (!(*(++arg))) { + usage(); + exit(EXIT_FAILURE); + } + delete_comment(S, strNl(*arg)); + break; + case 'm': + autoswitch = 1; + break; + default: + ishelp = 1; + } + if (hasmeta && !usrmode) { + usage(); + exit(EXIT_FAILURE); + } + if (ishelp) { + usage(); + exit(EXIT_FAILURE); + } + } + +#ifndef WIN_NT + if ((nincludedirs == 0) && !NoStdInc) { + includedir[0] = my_strdup("/usr/include"); + nincludedirs = 1; + } +#endif + + for (i = 0; i < nmacros; i++) { + if (macros[i].define_specs == NULL ) + macros[i].define_specs = CloneSpecs(S); + lookupArgRefs(i); /* for macro aliasing */ + } +} + +int findCommentEnd(const char *endseq, char quote, char warn, int pos, + int flags) { + int i; + char c; + + while (1) { + c = getChar(pos); + i = pos; + if (matchEndSequence(endseq, &i)) + return pos; + if (c == 0) + bug("Input ended while scanning a comment/string"); + if (c == warn) { + warn = 0; + if (WarningLevel > 1) + warning("possible comment/string termination problem"); + } + if (c == quote) + pos += 2; + else if ((flags & PARSE_MACROS) && (c == S->User.quotechar)) + pos += 2; + else + pos++; + } +} + +void SkipPossibleComments(int *pos, int cmtmode, int silentonly) { + int found; + struct COMMENT *c; + + if (C->in_comment) + return; + do { + found = 0; + if (getChar(*pos) == 0) + return; /* EOF */ + for (c = S->comments; c != NULL ; c = c->next) + if (!(c->flags[cmtmode] & FLAG_IGNORE)) + if (!silentonly || (c->flags[cmtmode] == FLAG_COMMENT)) + if (matchStartSequence(c->start, pos)) { + *pos = findCommentEnd(c->end, c->quote, c->warn, *pos, + c->flags[cmtmode]); + matchEndSequence(c->end, pos); + found = 1; + break; + } + } while (found); +} + +/* look for a possible user macro. + Input : idstart = scan start + idcheck = check id for long macro forms before splicing args ? + cmtmode = comment mode (FLAG_META or FLAG_USER) + Output : idstart/idend = macro name location + sh_end/lg_end = macro form end (-1 if no match) + argb/arge = argument locations for long form + argc = argument count for long form + id = macro id, if idcheck was set at input +*/ +int SplicePossibleUser(int *idstart, int *idend, int *sh_end, int *lg_end, + int *argb, int *arge, int *argc, int idcheck, int *id, int cmtmode) { + int match, k, pos; + + if (!matchStartSequence(S->User.mStart, idstart)) + return 0; + *idend = identifierEnd(*idstart); + if ((*idend) && !getChar(*idend - 1)) + return 0; + + /* look for args or no args */ + *sh_end = *idend; + if (!matchEndSequence(S->User.mEnd, sh_end)) + *sh_end = -1; + pos = *idend; + match = matchSequence(S->User.mArgS, &pos); + + if (idcheck) { + *id = findIdent(C->buf + *idstart, *idend - *idstart); + if (*id < 0) + match = 0; + } + *lg_end = -1; + + if (match) { + *argc = 0; + while (1) { + if (*argc >= MAXARGS) + bug("too many macro parameters"); + argb[*argc] = pos; + k = 0; + while (1) { /* look for mArgE, mArgSep, or comment-start */ + pos = iterIdentifierEnd(pos); + SkipPossibleComments(&pos, cmtmode, 0); + if (getChar(pos) == 0) + return (*sh_end >= 0); /* EOF */ + if (strchr(S->User.stackchar, getChar(pos))) + k++; + if (k) { + if (strchr(S->User.unstackchar, getChar(pos))) + k--; + } else { + arge[*argc] = pos; + if (matchSequence(S->User.mArgSep, &pos)) { + match = 0; + break; + } + if (matchEndSequence(S->User.mArgE, &pos)) { + match = 1; + break; + } + } + pos++; /* nothing matched, go forward */ + } + (*argc)++; + if (match) { /* no more args */ + *lg_end = pos; + break; + } + } + } + return ((*lg_end >= 0) || (*sh_end >= 0)); +} + +int findMetaArgs(int start, int *p1b, int *p1e, int *p2b, int *p2e, int *endm, + int *argc, int *argb, int *arge) { + int pos, k; + int hyp_end1, hyp_end2; + + /* look for mEnd or mArgS */ + pos = start; + if (!matchSequence(S->Meta.mArgS, &pos)) { + if (!matchEndSequence(S->Meta.mEnd, &pos)) + return -1; + *endm = pos; + return 0; + } + *p1b = pos; + + /* special syntax for #define : 1st arg is a macro call */ + if ((*argc) + && SplicePossibleUser(&pos, p1e, &hyp_end1, &hyp_end2, argb, arge, + argc, 0, NULL, FLAG_META)) { + *p1b = pos; + if (hyp_end2 >= 0) + pos = hyp_end2; + else { + pos = hyp_end1; + *argc = 0; + } + if (!matchSequence(S->Meta.mArgSep, &pos)) { + if (!matchEndSequence(S->Meta.mArgE, &pos)) + bug( + "#define/#defeval requires an identifier or a single macro call"); + *endm = pos; + return 1; + } + } else { + *argc = 0; + k = 0; + while (1) { /* look for mArgE, mArgSep, or comment-start */ + pos = iterIdentifierEnd(pos); + SkipPossibleComments(&pos, FLAG_META, 0); + if (getChar(pos) != 0 && strchr(S->Meta.stackchar, getChar(pos))) + k++; + if (k) { + if (getChar(pos) != 0 + && strchr(S->Meta.unstackchar, getChar(pos))) + k--; + } else { + *p1e = pos; + if (matchSequence(S->Meta.mArgSep, &pos)) + break; + if (matchEndSequence(S->Meta.mArgE, &pos)) { + *endm = pos; + return 1; + } + } + if (getChar(pos) == 0) + bug("unfinished macro argument"); + pos++; /* nothing matched, go forward */ + } + } + + *p2b = pos; + k = 0; + while (1) { /* look for mArgE or comment-start */ + pos = iterIdentifierEnd(pos); + SkipPossibleComments(&pos, FLAG_META, 0); + if (getChar(pos) != 0 && strchr(S->Meta.stackchar, getChar(pos))) + k++; + if (k) { + if (getChar(pos) != 0 && strchr(S->Meta.unstackchar, getChar(pos))) + k--; + } else { + *p2e = pos; + if (matchEndSequence(S->Meta.mArgE, &pos)) + break; + } + if (getChar(pos) == 0) + bug("unfinished macro"); + pos++; /* nothing matched, go forward */ + } + *endm = pos; + return 2; +} + +char *ProcessText(const char *buf, int l, int ambience) { + char *s; + struct INPUTCONTEXT *T; + + if (l == 0) { + s = malloc(1); + s[0] = 0; + return s; + } + s = malloc(l + 2); + s[0] = '\n'; + memcpy(s + 1, buf, l); + s[l + 1] = 0; + T = C; + C = malloc(sizeof *C); + C->out = malloc(sizeof *(C->out)); + C->in = NULL; + C->argc = T->argc; + C->argv = T->argv; + C->filename = T->filename; + C->out->buf = malloc(80); + C->out->len = 0; + C->out->bufsize = 80; + C->out->f = NULL; + C->lineno = T->lineno; + C->bufsize = l + 2; + C->len = l + 1; + C->buf = C->malloced_buf = s; + C->eof = 0; + C->namedargs = T->namedargs; + C->in_comment = T->in_comment; + C->ambience = ambience; + C->may_have_args = T->may_have_args; + + ProcessContext(); + outchar(0); /* note that outchar works with the half-destroyed context ! */ + s = C->out->buf; + free(C->out); + free(C); + C = T; + return s; +} + +int SpliceInfix(const char *buf, int pos1, int pos2, char *sep, int *spl1, + int *spl2) { + int pos, numpar, l; + const char *p; + + numpar = 0; + l = strlen(sep); + for (pos = pos2 - 1, p = buf + pos; pos >= pos1; pos--, p--) { + if (*p == ')') + numpar++; + if (*p == '(') + numpar--; + if (numpar < 0) + return 0; + if ((numpar == 0) && (pos2 - pos >= l) && !strncmp(p, sep, l)) { + *spl1 = pos; + *spl2 = pos + l; + return 1; + } + } + return 0; +} + +int DoArithmEval(char *buf, int pos1, int pos2, int *result) { + int spl1, spl2, result1, result2, l; + char c, *p; + + while ((pos1 < pos2) && isWhite(buf[pos1])) + pos1++; + while ((pos1 < pos2) && isWhite(buf[pos2 - 1])) + pos2--; + if (pos1 == pos2) + return 0; + + /* look for C operators starting with lowest precedence */ + + if (SpliceInfix(buf, pos1, pos2, "||", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 || result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "&&", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 && result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "|", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 | result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "^", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 ^ result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "&", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 & result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "!=", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + if (spl1 - pos1 != pos2 - spl2) + *result = 1; + else + *result = (strncmp(buf + pos1, buf + spl2, spl1 - pos1) != 0); + } else + *result = (result1 != result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "==", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + if (spl1 - pos1 != pos2 - spl2) + *result = 0; + else + *result = (strncmp(buf + pos1, buf + spl2, spl1 - pos1) == 0); + } else + *result = (result1 == result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "=~", &spl1, &spl2)) { +#if ! HAVE_FNMATCH_H + bug("globbing support has not been compiled in"); +#endif + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + char *str1, *str2; + + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + str1 = strdup(buf + pos1); + str1[spl1 - pos1] = '\0'; + str2 = strdup(buf + spl2); + str2[pos2 - spl2] = '\0'; + *result = (fnmatch(str2, str1, 0) == 0); + free(str1); + free(str2); + } else + *result = (result1 == result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, ">=", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + l = spl1 - pos1; + if (l > pos2 - spl2) + l = pos2 - spl2; + result1 = strncmp(buf + pos1, buf + spl2, l); + *result = (result1 > 0) + || ((result1 == 0) && (spl1 - pos1 >= pos2 - spl2)); + } else + *result = (result1 >= result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, ">", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + l = spl1 - pos1; + if (l > pos2 - spl2) + l = pos2 - spl2; + result1 = strncmp(buf + pos1, buf + spl2, l); + *result = (result1 > 0) + || ((result1 == 0) && (spl1 - pos1 > pos2 - spl2)); + } else + *result = (result1 > result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "<=", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + l = spl1 - pos1; + if (l > pos2 - spl2) + l = pos2 - spl2; + result1 = strncmp(buf + pos1, buf + spl2, l); + *result = (result1 < 0) + || ((result1 == 0) && (spl1 - pos1 <= pos2 - spl2)); + } else + *result = (result1 <= result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "<", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) { + /* revert to string comparison */ + while ((pos1 < spl1) && isWhite(buf[spl1 - 1])) + spl1--; + while ((pos2 > spl2) && isWhite(buf[spl2])) + spl2++; + l = spl1 - pos1; + if (l > pos2 - spl2) + l = pos2 - spl2; + result1 = strncmp(buf + pos1, buf + spl2, l); + *result = (result1 < 0) + || ((result1 == 0) && (spl1 - pos1 < pos2 - spl2)); + } else + *result = (result1 < result2); + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "+", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 + result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "-", &spl1, &spl2)) + if (spl1 != pos1) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 - result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "*", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + *result = result1 * result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "/", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + if (result2 == 0) + bug("Division by zero in expression"); + *result = result1 / result2; + return 1; + } + + if (SpliceInfix(buf, pos1, pos2, "%", &spl1, &spl2)) { + if (!DoArithmEval(buf, pos1, spl1, &result1) + || !DoArithmEval(buf, spl2, pos2, &result2)) + return 0; + if (result2 == 0) + bug("Division by zero in expression"); + *result = result1 % result2; + return 1; + } + + if (buf[pos1] == '~') { + if (!DoArithmEval(buf, pos1 + 1, pos2, &result1)) + return 0; + *result = ~result1; + return 1; + } + + if (buf[pos1] == '!') { + if (!DoArithmEval(buf, pos1 + 1, pos2, &result1)) + return 0; + *result = !result1; + return 1; + } + + if (buf[pos1] == '-') { + if (!DoArithmEval(buf, pos1 + 1, pos2, &result1)) + return 0; + *result = -result1; + return 1; + } + + /* add the length() builtin to measure the length of the macro expansion */ + if (strncmp(buf + pos1, "length(", strlen("length(")) == 0) { + if (buf[pos2 - 1] != ')') + return 0; + *result = pos2 - pos1 - strlen("length()"); + return 1; + } + + if (buf[pos1] == '(') { + if (buf[pos2 - 1] != ')') + return 0; + return DoArithmEval(buf, pos1 + 1, pos2 - 1, result); + } + + c = buf[pos2]; + buf[pos2] = 0; + *result = (int) strtol(buf + pos1, &p, 0); + buf[pos2] = c; + return (p == buf + pos2); +} + +void delete_macro(int i) { + int j; + nmacros--; + free(macros[i].username); + free(macros[i].macrotext); + if (macros[i].argnames != NULL ) { + for (j = 0; j < macros[i].nnamedargs; j++) + free(macros[i].argnames[j]); + free(macros[i].argnames); + macros[i].argnames = NULL; + } + FreeComments(macros[i].define_specs); + free(macros[i].define_specs); + memcpy(macros + i, macros + nmacros, sizeof(struct MACRO)); +} + +char *ArithmEval(int pos1, int pos2) { + char *s, *t; + int i; + + /* first define the defined(...) operator */ + i = findIdent("defined", strlen("defined")); + if (i >= 0) + warning("the defined(...) macro is already defined"); + else { + newmacro("defined", strlen("defined"), 1); + macros[nmacros].macrolen = 0; + macros[nmacros].macrotext = malloc(1); + macros[nmacros].macrotext[0] = 0; + macros[nmacros].nnamedargs = -2; /* trademark of the defined(...) macro */ + nmacros++; + } + /* process the text in a usual way */ + s = ProcessText(C->buf + pos1, pos2 - pos1, FLAG_META); + /* undefine the defined(...) operator */ + if (i < 0) { + i = findIdent("defined", strlen("defined")); + if ((i < 0) || (macros[i].nnamedargs != -2)) + warning("the defined(...) macro was redefined in expression"); + else + delete_macro(i); + } + + if (!DoArithmEval(s, 0, strlen(s), &i)) + return s; /* couldn't compute */ + t = malloc(MAX_GPP_NUM_SIZE); + sprintf(t, "%d", i); + free(s); + return t; +} + +int comment_or_white(int start, int end, int cmtmode) { + char c; + + while (start < end) { + SkipPossibleComments(&start, cmtmode, 1); + if (start < end) { + c = getChar(start++); + if ((c != ' ') && (c != '\n') && (c != '\t')) + return 0; + } + } + return 1; +} + +char *remove_comments(int start, int end, int cmtmode) { + char *s, *t; + + t = s = malloc(end - start + 1); + while (start < end) { + SkipPossibleComments(&start, cmtmode, 1); + if (start < end) { + *t = getChar(start++); + if ((*t == S->User.quotechar) && (start < end)) { + *(++t) = getChar(start++); + } + t++; + } + } + *t = 0; + return s; +} + +void SetStandardMode(struct SPECS *P, const char *opt) { + P->op_set = DefaultOp; + P->ext_op_set = DefaultExtOp; + P->id_set = DefaultId; + FreeComments(P); + if (!strcmp(opt, "C") || !strcmp(opt, "cpp")) { + P->User = KUser; + P->Meta = KMeta; + P->preservelf = 1; + add_comment(P, "ccc", my_strdup("/*"), my_strdup("*/"), 0, 0); + add_comment(P, "ccc", my_strdup("//"), my_strdup("\n"), 0, 0); + add_comment(P, "ccc", my_strdup("\\\n"), my_strdup(""), 0, 0); + add_comment(P, "sss", my_strdup("\""), my_strdup("\""), '\\', '\n'); + add_comment(P, "sss", my_strdup("'"), my_strdup("'"), '\\', '\n'); + } else if (!strcmp(opt, "TeX") || !strcmp(opt, "tex")) { + P->User = Tex; + P->Meta = Tex; + P->preservelf = 0; + } else if (!strcmp(opt, "HTML") || !strcmp(opt, "html")) { + P->User = Html; + P->Meta = Html; + P->preservelf = 0; + } else if (!strcmp(opt, "XHTML") || !strcmp(opt, "xhtml")) { + P->User = XHtml; + P->Meta = XHtml; + P->preservelf = 0; + } else if (!strcmp(opt, "default")) { + P->User = CUser; + P->Meta = CMeta; + P->preservelf = 0; + } else if (!strcmp(opt, "Prolog") || !strcmp(opt, "prolog")) { + P->User = KUser; + P->Meta = KMeta; + P->preservelf = 1; + P->op_set = PrologOp; + add_comment(P, "css", my_strdup("\213/*"), my_strdup("*/"), 0, 0); /* \!o */ + add_comment(P, "cii", my_strdup("\\\n"), my_strdup(""), 0, 0); + add_comment(P, "css", my_strdup("%"), my_strdup("\n"), 0, 0); + add_comment(P, "sss", my_strdup("\""), my_strdup("\""), 0, '\n'); + add_comment(P, "sss", my_strdup("\207'"), my_strdup("'"), 0, '\n'); /* \!# */ + } else + bug("unknown standard mode"); +} + +void ProcessModeCommand(int p1start, int p1end, int p2start, int p2end) { + struct SPECS *P; + char *s, *p, *opt; + int nargs, check_isdelim; + char *args[10]; /* can't have more than 10 arguments */ + + whiteout(&p1start, &p1end); + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("invalid #mode syntax"); + if (p2start < 0) + s = my_strdup(""); + else + s = ProcessText(C->buf + p2start, p2end - p2start, FLAG_META); + + /* argument parsing */ + p = s; + opt = NULL; + while (isWhite(*p)) + p++; + if ((*p != '"') && (*p != 0)) { + opt = p; + while ((*p != 0) && !isWhite(*p)) + p++; + if (*p != 0) { + *(p++) = 0; + while (isWhite(*p)) + p++; + } + } + nargs = 0; + check_isdelim = !idequal(C->buf + p1start, p1end - p1start, "charset"); + while (*p != 0) { + if (nargs == 10) + bug("too many arguments in #mode command"); + if (*(p++) != '"') + bug("syntax error in #mode command (missing \" or trailing data)"); + args[nargs++] = p; + p = strNl2(p, check_isdelim); + while (isWhite(*p)) + p++; + } + + if (idequal(C->buf + p1start, p1end - p1start, "quote")) { + if (opt || (nargs > 1)) + bug("syntax error in #mode quote command"); + if (nargs == 0) + args[0] = ""; + S->stack_next->User.quotechar = args[0][0]; + } else if (idequal(C->buf + p1start, p1end - p1start, "comment")) { + if ((nargs < 2) || (nargs > 4)) + bug("syntax error in #mode comment command"); + if (!opt) + opt = "ccc"; + if (nargs < 3) + args[2] = ""; + if (nargs < 4) + args[3] = ""; + add_comment(S->stack_next, opt, my_strdup(args[0]), my_strdup(args[1]), + args[2][0], args[3][0]); + } else if (idequal(C->buf + p1start, p1end - p1start, "string")) { + if ((nargs < 2) || (nargs > 4)) + bug("syntax error in #mode string command"); + if (!opt) + opt = "sss"; + if (nargs < 3) + args[2] = ""; + if (nargs < 4) + args[3] = ""; + add_comment(S->stack_next, opt, my_strdup(args[0]), my_strdup(args[1]), + args[2][0], args[3][0]); + } else if (idequal(C->buf + p1start, p1end - p1start, "save") + || idequal(C->buf + p1start, p1end - p1start, "push")) { + if ((opt != NULL )||nargs) + bug("too many arguments to #mode save"); + P = CloneSpecs(S->stack_next); + P->stack_next = S->stack_next; + S->stack_next = P; + } else if (idequal(C->buf + p1start, p1end - p1start, "restore") + || idequal(C->buf + p1start, p1end - p1start, "pop")) { + if ((opt != NULL )||nargs) + bug("too many arguments to #mode restore"); + P = S->stack_next->stack_next; + if (P == NULL ) + bug("#mode restore without #mode save"); + FreeComments(S->stack_next); + free(S->stack_next); + S->stack_next = P; + } else if (idequal(C->buf + p1start, p1end - p1start, "standard")) { + if ((opt == NULL )||nargs) + bug("syntax error in #mode standard"); + SetStandardMode(S->stack_next, opt); + } else if (idequal(C->buf + p1start, p1end - p1start, "user")) { + if ((opt != NULL )||(nargs!=9))bug("#mode user requires 9 arguments"); + S->stack_next->User.mStart=my_strdup(args[0]); + S->stack_next->User.mEnd=my_strdup(args[1]); + S->stack_next->User.mArgS=my_strdup(args[2]); + S->stack_next->User.mArgSep=my_strdup(args[3]); + S->stack_next->User.mArgE=my_strdup(args[4]); + S->stack_next->User.stackchar=my_strdup(args[5]); + S->stack_next->User.unstackchar=my_strdup(args[6]); + S->stack_next->User.mArgRef=my_strdup(args[7]); + S->stack_next->User.quotechar=args[8][0]; + } + else if (idequal(C->buf+p1start,p1end-p1start,"meta")) { + if ((opt!=NULL)&&!nargs&&!strcmp(opt,"user")) + S->stack_next->Meta=S->stack_next->User; + else { + if ((opt!=NULL)||(nargs!=7)) bug("#mode meta requires 7 arguments"); + S->stack_next->Meta.mStart=my_strdup(args[0]); + S->stack_next->Meta.mEnd=my_strdup(args[1]); + S->stack_next->Meta.mArgS=my_strdup(args[2]); + S->stack_next->Meta.mArgSep=my_strdup(args[3]); + S->stack_next->Meta.mArgE=my_strdup(args[4]); + S->stack_next->Meta.stackchar=my_strdup(args[5]); + S->stack_next->Meta.unstackchar=my_strdup(args[6]); + } + } + else if (idequal(C->buf+p1start,p1end-p1start,"preservelf")) { + if ((opt==NULL)||nargs) bug("syntax error in #mode preservelf"); + if (!strcmp(opt,"1")||!my_strcasecmp(opt,"on")) S->stack_next->preservelf=1; + else if (!strcmp(opt,"0")||!my_strcasecmp(opt,"off")) S->stack_next->preservelf=0; + else bug("#mode preservelf requires on/off argument"); + } + else if (idequal(C->buf+p1start,p1end-p1start,"nocomment") + ||idequal(C->buf+p1start,p1end-p1start,"nostring")) { + if ((opt!=NULL)||(nargs>1)) + bug("syntax error in #mode nocomment/nostring"); + if (nargs==0) FreeComments(S->stack_next); + else delete_comment(S->stack_next,my_strdup(args[0])); + } + else if (idequal(C->buf+p1start,p1end-p1start,"charset")) { + if ((opt==NULL)||(nargs!=1)) bug("syntax error in #mode charset"); + if (!my_strcasecmp(opt,"op")) + S->stack_next->op_set=MakeCharsetSubset((unsigned char *)args[0]); + else if (!my_strcasecmp(opt,"par")) + S->stack_next->ext_op_set=MakeCharsetSubset((unsigned char *)args[0]); + else if (!my_strcasecmp(opt,"id")) + S->stack_next->id_set=MakeCharsetSubset((unsigned char *)args[0]); + else bug("unknown charset subset name in #mode charset"); + } + else bug("unrecognized #mode command"); + + free(s); +} + +static void DoInclude(char *file_name, int ignore_nonexistent) { + struct INPUTCONTEXT *N; + char *incfile_name = NULL; + FILE *f = NULL; + int j; + int len = strlen(file_name); + + /* if absolute path name is specified */ + if (file_name[0] == SLASH +#ifdef WIN_NT + || (isalpha(file_name[0]) && file_name[1]==':') +#endif + ) + f = fopen(file_name, "r"); + else /* search current dir, if this search isn't turned off */ + if (!NoCurIncFirst) { + f = openInCurrentDir(file_name); + } + + for (j = 0; (f == NULL )&&(jin = f; + C->argc = 0; + C->argv = NULL; + C->filename = file_name; + C->out = N->out; + C->lineno = 1; + C->bufsize = 80; + C->len = 0; + C->buf = C->malloced_buf = malloc(C->bufsize); + C->eof = 0; + C->namedargs = NULL; + C->in_comment = 0; + C->ambience = FLAG_TEXT; + C->may_have_args = 0; + PushSpecs(S); + if (autoswitch) { + if (!strcmp(file_name + strlen(file_name) - 2, ".h") + || !strcmp(file_name + strlen(file_name) - 2, ".c")) + SetStandardMode(S, "C"); + } + + /* Include marker before the included contents */ + write_include_marker(N->out->f, 1, C->filename, "1"); + ProcessContext(); + /* Include marker after the included contents */ + write_include_marker(N->out->f, N->lineno, N->filename, "2"); + /* Need to leave the blank line in lieu of #include, like cpp does */ + replace_directive_with_blank_line(N->out->f); + free(C); + PopSpecs(); + C = N; +} + +int ParsePossibleMeta(void) { + int cklen, nameend; + int id, expparams, nparam, i, j; + int p1start, p1end, p2start, p2end, macend; + int argc, argb[MAXARGS], arge[MAXARGS]; + char *tmpbuf; + + cklen = 1; + if (!matchStartSequence(S->Meta.mStart, &cklen)) + return -1; + nameend = identifierEnd(cklen); + if (nameend && !getChar(nameend - 1)) + return -1; + + argc = 0; /* for #define with named args */ + if (idequal(C->buf + cklen, nameend - cklen, "define")) /* check identifier */ + { + id = 1; + expparams = 2; + argc = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "undef")) { + id = 2; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "ifdef")) { + id = 3; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "ifndef")) { + id = 4; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "else")) { + id = 5; + expparams = 0; + } else if (idequal(C->buf + cklen, nameend - cklen, "endif")) { + id = 6; + expparams = 0; + } else if (idequal(C->buf + cklen, nameend - cklen, "include")) { + id = 7; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "exec")) { + id = 8; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "defeval")) { + id = 9; + expparams = 2; + argc = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "ifeq")) { + id = 10; + expparams = 2; + } else if (idequal(C->buf + cklen, nameend - cklen, "ifneq")) { + id = 11; + expparams = 2; + } else if (idequal(C->buf + cklen, nameend - cklen, "eval")) { + id = 12; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "if")) { + id = 13; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "mode")) { + id = 14; + expparams = 2; + } else if (idequal(C->buf + cklen, nameend - cklen, "line")) { + id = 15; + expparams = 0; + } else if (idequal(C->buf + cklen, nameend - cklen, "file")) { + id = 16; + expparams = 0; + } else if (idequal(C->buf + cklen, nameend - cklen, "elif")) { + id = 17; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "error")) { + id = 18; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "warning")) { + id = 19; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "date")) { + id = 20; + expparams = 1; + } else if (idequal(C->buf + cklen, nameend - cklen, "sinclude")) { + id = 21; + expparams = 1; + } else + return -1; + + /* #MODE magic : define "..." to be C-style strings */ + if (id == 14) { + PushSpecs(S); + S->preservelf = 1; + delete_comment(S, my_strdup("\"")); + add_comment(S, "sss", my_strdup("\""), my_strdup("\""), '\\', '\n'); + } + + nparam = findMetaArgs(nameend, &p1start, &p1end, &p2start, &p2end, &macend, + &argc, argb, arge); + if (nparam == -1) + return -1; + + if ((nparam == 2) && isWhitesep(S->Meta.mArgSep)) + if (comment_or_white(p2start, p2end, FLAG_META)) + nparam = 1; + if ((nparam == 1) && isWhitesep(S->Meta.mArgS)) + if (comment_or_white(p1start, p1end, FLAG_META)) + nparam = 0; + if (expparams && !nparam) + bug("Missing argument in meta-macro"); + + switch (id) { + case 1: /* DEFINE */ + if (!commented[iflevel]) { + whiteout(&p1start, &p1end); /* recall comments are not allowed here */ + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("#define requires an identifier (A-Z,a-z,0-9,_ only)"); + /* buf starts 1 char before the macro */ + i = findIdent(C->buf + p1start, p1end - p1start); + if (i >= 0) + delete_macro(i); + newmacro(C->buf + p1start, p1end - p1start, 1); + if (nparam == 1) { + p2end = p2start = p1end; + } + replace_definition_with_blank_lines(C->buf + 1, C->buf + p2end, + S->preservelf); + macros[nmacros].macrotext = remove_comments(p2start, p2end, + FLAG_META); + macros[nmacros].macrolen = strlen(macros[nmacros].macrotext); + macros[nmacros].defined_in_comment = C->in_comment; + + if (argc) { + for (j = 0; j < argc; j++) + whiteout(argb + j, arge + j); + /* define with one empty argument */ + if ((argc == 1) && (arge[0] == argb[0])) + argc = 0; + macros[nmacros].argnames = malloc((argc + 1) * sizeof(char *)); + macros[nmacros].argnames[argc] = NULL; + } + macros[nmacros].nnamedargs = argc; + for (j = 0; j < argc; j++) { + if ((argb[j] == arge[j]) || (identifierEnd(argb[j]) != arge[j])) + bug( + "#define with named args needs identifiers as arg names"); + macros[nmacros].argnames[j] = malloc(arge[j] - argb[j] + 1); + memcpy(macros[nmacros].argnames[j], C->buf + argb[j], + arge[j] - argb[j]); + macros[nmacros].argnames[j][arge[j] - argb[j]] = 0; + } + lookupArgRefs(nmacros++); + } else + replace_directive_with_blank_line(C->out->f); + break; + + case 2: /* UNDEF */ + replace_directive_with_blank_line(C->out->f); + if (!commented[iflevel]) { + if (nparam == 2 && WarningLevel > 0) + warning("Extra argument to #undef ignored"); + whiteout(&p1start, &p1end); + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("#undef requires an identifier (A-Z,a-z,0-9,_ only)"); + i = findIdent(C->buf + p1start, p1end - p1start); + if (i >= 0) + delete_macro(i); + } + break; + + case 3: /* IFDEF */ + replace_directive_with_blank_line(C->out->f); + iflevel++; + if (iflevel == STACKDEPTH) + bug("Too many nested #ifdefs"); + commented[iflevel] = commented[iflevel - 1]; + + if (!commented[iflevel]) { + if (nparam == 2 && WarningLevel > 0) + warning("Extra argument to #ifdef ignored"); + whiteout(&p1start, &p1end); + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("#ifdef requires an identifier (A-Z,a-z,0-9,_ only)"); + i = findIdent(C->buf + p1start, p1end - p1start); + commented[iflevel] = (i == -1); + } + break; + + case 4: /* IFNDEF */ + replace_directive_with_blank_line(C->out->f); + iflevel++; + if (iflevel == STACKDEPTH) + bug("Too many nested #ifdefs"); + commented[iflevel] = commented[iflevel - 1]; + if (!commented[iflevel]) { + if (nparam == 2 && WarningLevel > 0) + warning("Extra argument to #ifndef ignored"); + whiteout(&p1start, &p1end); + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("#ifndef requires an identifier (A-Z,a-z,0-9,_ only)"); + i = findIdent(C->buf + p1start, p1end - p1start); + commented[iflevel] = (i != -1); + } + break; + + case 5: /* ELSE */ + replace_directive_with_blank_line(C->out->f); + if (!commented[iflevel] && (nparam > 0) && WarningLevel > 0) + warning("Extra argument to #else ignored"); + if (iflevel == 0) + bug("#else without #if"); + if (!commented[iflevel - 1] && commented[iflevel] != 2) + commented[iflevel] = !commented[iflevel]; + break; + + case 6: /* ENDIF */ + replace_directive_with_blank_line(C->out->f); + if (!commented[iflevel] && (nparam > 0) && WarningLevel > 0) + warning("Extra argument to #endif ignored"); + if (iflevel == 0) + bug("#endif without #if"); + iflevel--; + break; + + case 7: /* INCLUDE */ + if (!commented[iflevel]) { + char *incfile_name; + + if (nparam == 2 && WarningLevel > 0) + warning("Extra argument to #include ignored"); + if (!whiteout(&p1start, &p1end)) + bug("Missing file name in #include"); + /* user may put "" or <> */ + if (((getChar(p1start) == '\"') && (getChar(p1end - 1) == '\"')) + || ((getChar(p1start) == '<') && (getChar(p1end - 1) == '>'))) { + p1start++; + p1end--; + } + if (p1start >= p1end) + bug("Missing file name in #include"); + incfile_name = malloc(p1end - p1start + 1); + /* extract the orig include filename */ + for (i = 0; i < p1end - p1start; i++) + incfile_name[i] = getChar(p1start + i); + incfile_name[p1end - p1start] = 0; + + DoInclude(incfile_name, 0); + } else + replace_directive_with_blank_line(C->out->f); + break; + + case 8: /* EXEC */ + if (!commented[iflevel]) { + if (!execallowed) + warning( + "Not allowed to #exec. Command output will be left blank"); + else { + char *s, *t; + int c; + FILE *f; + s = ProcessText(C->buf + p1start, p1end - p1start, FLAG_META); + if (nparam == 2) { + t = ProcessText(C->buf + p2start, p2end - p2start, + FLAG_META); + i = strlen(s); + s = realloc(s, i + strlen(t) + 2); + s[i] = ' '; + strcpy(s + i + 1, t); + free(t); + } + f = popen(s, "r"); + free(s); + if (f == NULL ) + warning("Cannot #exec. Command not found(?)"); + else { + while ((c = fgetc(f)) != EOF) + outchar((char) c); + pclose(f); + } + } + } + break; + + case 9: /* DEFEVAL */ + if (!commented[iflevel]) { + whiteout(&p1start, &p1end); + if ((p1start == p1end) || (identifierEnd(p1start) != p1end)) + bug("#defeval requires an identifier (A-Z,a-z,0-9,_ only)"); + tmpbuf = ProcessText(C->buf + p2start, p2end - p2start, FLAG_META); + i = findIdent(C->buf + p1start, p1end - p1start); + if (i >= 0) + delete_macro(i); + newmacro(C->buf + p1start, p1end - p1start, 1); + if (nparam == 1) { + p2end = p2start = p1end; + } + replace_definition_with_blank_lines(C->buf + 1, C->buf + p2end, + S->preservelf); + macros[nmacros].macrotext = tmpbuf; + macros[nmacros].macrolen = strlen(macros[nmacros].macrotext); + macros[nmacros].defined_in_comment = C->in_comment; + + if (argc) { + for (j = 0; j < argc; j++) + whiteout(argb + j, arge + j); + /* define with one empty argument */ + if ((argc == 1) && (arge[0] == argb[0])) + argc = 0; + macros[nmacros].argnames = malloc((argc + 1) * sizeof(char *)); + macros[nmacros].argnames[argc] = NULL; + } + macros[nmacros].nnamedargs = argc; + for (j = 0; j < argc; j++) { + if ((argb[j] == arge[j]) || (identifierEnd(argb[j]) != arge[j])) + bug( + "#defeval with named args needs identifiers as arg names"); + macros[nmacros].argnames[j] = malloc(arge[j] - argb[j] + 1); + memcpy(macros[nmacros].argnames[j], C->buf + argb[j], + arge[j] - argb[j]); + macros[nmacros].argnames[j][arge[j] - argb[j]] = 0; + } + lookupArgRefs(nmacros++); + } else + replace_directive_with_blank_line(C->out->f); + break; + + case 10: /* IFEQ */ + replace_directive_with_blank_line(C->out->f); + iflevel++; + if (iflevel == STACKDEPTH) + bug("Too many nested #ifeqs"); + commented[iflevel] = commented[iflevel - 1]; + if (!commented[iflevel]) { + char *s, *t; + if (nparam != 2) + bug("#ifeq requires two arguments"); + s = ProcessText(C->buf + p1start, p1end - p1start, FLAG_META); + t = ProcessText(C->buf + p2start, p2end - p2start, FLAG_META); + commented[iflevel] = (nowhite_strcmp(s, t) != 0); + free(s); + free(t); + } + break; + + case 11: /* IFNEQ */ + replace_directive_with_blank_line(C->out->f); + iflevel++; + if (iflevel == STACKDEPTH) + bug("Too many nested #ifeqs"); + commented[iflevel] = commented[iflevel - 1]; + if (!commented[iflevel]) { + char *s, *t; + if (nparam != 2) + bug("#ifneq requires two arguments"); + s = ProcessText(C->buf + p1start, p1end - p1start, FLAG_META); + t = ProcessText(C->buf + p2start, p2end - p2start, FLAG_META); + commented[iflevel] = (nowhite_strcmp(s, t) == 0); + free(s); + free(t); + } + break; + + case 12: /* EVAL */ + if (!commented[iflevel]) { + char *s, *t; + if (nparam == 2) + p1end = p2end; /* we really want it all ! */ + s = ArithmEval(p1start, p1end); + for (t = s; *t; t++) + outchar(*t); + free(s); + } + break; + + case 13: /* IF */ + replace_directive_with_blank_line(C->out->f); + iflevel++; + if (iflevel == STACKDEPTH) + bug("Too many nested #ifs"); + commented[iflevel] = commented[iflevel - 1]; + if (!commented[iflevel]) { + char *s; + if (nparam == 2) + p1end = p2end; /* we really want it all ! */ + s = ArithmEval(p1start, p1end); + commented[iflevel] = ((s[0] == '0') && (s[1] == 0)); + free(s); + } + break; + + case 14: /* MODE */ + replace_directive_with_blank_line(C->out->f); + if (nparam == 1) + p2start = -1; + if (!commented[iflevel]) + ProcessModeCommand(p1start, p1end, p2start, p2end); + PopSpecs(); + break; + + case 15: { /* LINE */ + char buf[MAX_GPP_NUM_SIZE]; + sprintf(buf, "%d", C->lineno); + replace_directive_with_blank_line(C->out->f); + sendout(buf, strlen(buf), 0); + } + break; + + case 16: /* FILE */ + replace_directive_with_blank_line(C->out->f); + sendout(C->filename, strlen(C->filename), 0); + break; + + case 17: /* ELIF */ + replace_directive_with_blank_line(C->out->f); + if (iflevel == 0) + bug("#elif without #if"); + if (!commented[iflevel - 1]) { + if (commented[iflevel] != 1) + commented[iflevel] = 2; + else { + char *s; + commented[iflevel] = 0; + if (nparam == 2) + p1end = p2end; /* we really want it all ! */ + s = ArithmEval(p1start, p1end); + commented[iflevel] = ((s[0] == '0') && (s[1] == 0)); + free(s); + } + } + break; + + case 18: /* ERROR */ + replace_directive_with_blank_line(C->out->f); + if (!commented[iflevel]) + bug( + ProcessText(C->buf + p1start, + (nparam == 2 ? p2end : p1end) - p1start, + FLAG_META)); + break; + + case 19: /* WARNING */ + replace_directive_with_blank_line(C->out->f); + if (!commented[iflevel]) { + char *s; + s = ProcessText(C->buf + p1start, + (nparam == 2 ? p2end : p1end) - p1start, FLAG_META); + warning(s); + free(s); + } + break; + + case 20: { /* DATE */ + char buf[MAX_GPP_DATE_SIZE]; + char *fmt; + time_t now = time(NULL ); + fmt = ProcessText(C->buf + p1start, + (nparam == 2 ? p2end : p1end) - p1start, FLAG_META); + if (!strftime(buf, MAX_GPP_DATE_SIZE, fmt, localtime(&now))) + bug("date buffer exceeded"); + replace_directive_with_blank_line(C->out->f); + sendout(buf, strlen(buf), 0); + free(fmt); + } + break; + + case 21: /* SINCLUDE */ + if (!commented[iflevel]) { + char *incfile_name; + + if (nparam == 2 && WarningLevel > 0) + warning("Extra argument to #sinclude ignored"); + if (!whiteout(&p1start, &p1end)) + bug("Missing file name in #sinclude"); + /* user may put "" or <> */ + if (((getChar(p1start) == '\"') && (getChar(p1end - 1) == '\"')) + || ((getChar(p1start) == '<') && (getChar(p1end - 1) == '>'))) { + p1start++; + p1end--; + } + if (p1start >= p1end) + bug("Missing file name in #sinclude"); + incfile_name = malloc(p1end - p1start + 1); + /* extract the orig include filename */ + for (i = 0; i < p1end - p1start; i++) + incfile_name[i] = getChar(p1start + i); + incfile_name[p1end - p1start] = 0; + + DoInclude(incfile_name, 1); + } else + replace_directive_with_blank_line(C->out->f); + break; + + default: + bug("Internal meta-macro identification error"); + } + shiftIn(macend); + return 0; +} + +int ParsePossibleUser(void) { + int idstart, idend, sh_end, lg_end, macend; + int argc, id, i, l; + char *argv[MAXARGS]; + int argb[MAXARGS], arge[MAXARGS]; + struct INPUTCONTEXT *T; + + idstart = 1; + id = 0; + if (!SplicePossibleUser(&idstart, &idend, &sh_end, &lg_end, argb, arge, + &argc, 1, &id, FLAG_USER)) + return -1; + if ((sh_end >= 0) && (C->namedargs != NULL )) { + i = findNamedArg(C->buf + idstart, idend - idstart); + if (i >= 0) { + if (i < C->argc) + sendout(C->argv[i], strlen(C->argv[i]), 0); + shiftIn(sh_end); + return 0; + } + } + + if (id < 0) + return -1; + if (lg_end >= 0) + macend = lg_end; + else { + macend = sh_end; + argc = 0; + } + + if (macros[id].nnamedargs == -2) { /* defined(...) macro for arithmetic */ + char *s, *t; + if (argc != 1) + return -1; + s = remove_comments(argb[0], arge[0], FLAG_USER); + t = s + strlen(s) - 1; + if (*s != 0) + while ((t != s) && isWhite(*t)) + *(t--) = 0; + t = s; + while (isWhite(*t)) + t++; + if (findIdent(t, strlen(t)) >= 0) + outchar('1'); + else + outchar('0'); + free(s); + shiftIn(macend); + return 0; + } + if (!macros[id].macrotext[0]) { /* the empty macro */ + shiftIn(macend); + return 0; + } + + for (i = 0; i < argc; i++) + argv[i] = ProcessText(C->buf + argb[i], arge[i] - argb[i], FLAG_USER); + /* process macro text */ + T = C; + C = malloc(sizeof *C); + C->out = T->out; + C->in = NULL; + C->argc = argc; + C->argv = argv; + C->filename = T->filename; + C->lineno = T->lineno; + C->may_have_args = 1; + if ((macros[id].nnamedargs == -1) && (lg_end >= 0) + && (macros[id].define_specs->User.mEnd[0] == 0)) { + /* build an aliased macro call */ + l = strlen(macros[id].macrotext) + 2 + + strlen(macros[id].define_specs->User.mArgS) + + strlen(macros[id].define_specs->User.mArgE) + + (argc - 1) * strlen(macros[id].define_specs->User.mArgSep); + for (i = 0; i < argc; i++) + l += strlen(argv[i]); + C->buf = C->malloced_buf = malloc(l); + l = strlen(macros[id].macrotext) + 1; + C->buf[0] = '\n'; + strcpy(C->buf + 1, macros[id].macrotext); + while ((l > 1) && isWhite(C->buf[l - 1])) + l--; + strcpy(C->buf + l, macros[id].define_specs->User.mArgS); + for (i = 0; i < argc; i++) { + if (i > 0) + strcat(C->buf, macros[id].define_specs->User.mArgSep); + strcat(C->buf, argv[i]); + } + strcat(C->buf, macros[id].define_specs->User.mArgE); + C->may_have_args = 0; + } else { + C->buf = C->malloced_buf = malloc(strlen(macros[id].macrotext) + 2); + C->buf[0] = '\n'; + strcpy(C->buf + 1, macros[id].macrotext); + } + C->len = strlen(C->buf); + C->bufsize = C->len + 1; + C->eof = 0; + C->namedargs = macros[id].argnames; + C->in_comment = macros[id].defined_in_comment; + C->ambience = FLAG_META; + PushSpecs(macros[id].define_specs); + ProcessContext(); + PopSpecs(); + free(C); + C = T; + + for (i = 0; i < argc; i++) + free(argv[i]); + shiftIn(macend); + return 0; +} + +void ParseText(void) { + int l, cs, ce; + char c, *s; + struct COMMENT *p; + + if (++parselevel == STACKDEPTH) + bug("Stack depth exceeded during parse"); + + /* look for comments first */ + if (!C->in_comment) { + cs = 1; + for (p = S->comments; p != NULL ; p = p->next) + if (!(p->flags[C->ambience] & FLAG_IGNORE)) + if (matchStartSequence(p->start, &cs)) { + l = ce = findCommentEnd(p->end, p->quote, p->warn, cs, + p->flags[C->ambience]); + matchEndSequence(p->end, &l); + if (p->flags[C->ambience] & OUTPUT_DELIM) + sendout(C->buf + 1, cs - 1, 0); + if (!(p->flags[C->ambience] & OUTPUT_TEXT)) + replace_definition_with_blank_lines(C->buf + 1, + C->buf + ce - 1, 0); + if (p->flags[C->ambience] & PARSE_MACROS) { + C->in_comment = 1; + s = ProcessText(C->buf + cs, ce - cs, C->ambience); + if (p->flags[C->ambience] & OUTPUT_TEXT) + sendout(s, strlen(s), 0); + C->in_comment = 0; + free(s); + } else if (p->flags[C->ambience] & OUTPUT_TEXT) + sendout(C->buf + cs, ce - cs, 0); + if (p->flags[C->ambience] & OUTPUT_DELIM) + sendout(C->buf + ce, l - ce, 0); + shiftIn(l); + parselevel--; + return; + } + } + + if (ParsePossibleMeta() >= 0) { + parselevel--; + return; + } + if (ParsePossibleUser() >= 0) { + parselevel--; + return; + } + + l = 1; + /* If matching numbered macro argument and inside a macro */ + if (matchSequence(S->User.mArgRef, &l) && C->may_have_args) { + /* Process macro arguments referenced as #1,#2,... */ + c = getChar(l); + if ((c >= '1') && (c <= '9')) { + c = c - '1'; + if (c < C->argc) + sendout(C->argv[(int) c], strlen(C->argv[(int) c]), 0); + shiftIn(l + 1); + parselevel--; + return; + } + } + + l = identifierEnd(1); + if (l == 1) + l = 2; + sendout(C->buf + 1, l - 1, 1); + shiftIn(l); + parselevel--; +} + +void ProcessContext(void) { + if (C->len == 0) { + C->buf[0] = '\n'; + C->len++; + } + while (!C->eof) + ParseText(); + if (C->in != NULL ) + fclose(C->in); + free(C->malloced_buf); +} + +/* additions by M. Kifer - revised D.A. 12/16/01 */ + +/* copy SLASH-terminated name of the directory of fname */ +static void getDirname(const char *fname, char *dirname) { + int i; + + for (i = strlen(fname) - 1; i >= 0; i--) { + if (fname[i] == SLASH) + break; + } + if (i >= 0) { + strncpy(dirname, fname, i); + dirname[i] = SLASH; + } else + /* just a precaution: i must be -1 in this case anyway */ + i = -1; + + dirname[i + 1] = '\0'; +} + +static FILE *openInCurrentDir(const char *incfile) { + FILE *f; + char *absfile; + + if (IncludeFile) { + return fopen(incfile, "r"); + } + + absfile = calloc(strlen(C->filename) + strlen(incfile) + 1, 1); + getDirname(C->filename, absfile); + strcat(absfile, incfile); + f = fopen(absfile, "r"); + free(absfile); + return f; +} + +/* skip = # of \n's already output by other mechanisms, to be skipped */ +void replace_definition_with_blank_lines(const char *start, const char *end, + int skip) { + if ((include_directive_marker != NULL )&& (C->out->f != NULL)){ + while (start <= end) { + if (*start == '\n') { + if (skip) skip--; else fprintf(C->out->f,"\n"); + } + start++; + } +} +} + + /* insert blank line where the metas IFDEF,ELSE,INCLUDE, etc., stood in the + input text + */ +void replace_directive_with_blank_line(FILE *f) { + if ((include_directive_marker != NULL )&& (f != NULL) + && (!S->preservelf) && (S->Meta.mArgE[0]=='\n')){ + fprintf(f,"\n"); +} +} + + /* If lineno is > 15 digits - the number won't be printed correctly */ +void write_include_marker(FILE *f, int lineno, char *filename, + const char *marker) { + static char lineno_buf[MAX_GPP_NUM_SIZE]; + static char *escapedfilename = NULL; + + if ((include_directive_marker != NULL )&& (f != NULL)){ +#ifdef WIN_NT + escape_backslashes(filename,&escapedfilename); +#else + escapedfilename = filename; +#endif + sprintf(lineno_buf,"%d", lineno); + fprintf(f, include_directive_marker, lineno_buf, escapedfilename, marker); + } + } + + /* Under windows, files can have backslashes in them. + These should be escaped. + */ +void escape_backslashes(const char *instr, char **outstr) { + int out_idx = 0; + + if (*outstr != NULL ) + free(*outstr); + *outstr = malloc(2 * strlen(instr)); + + while (*instr != '\0') { + if (*instr == '\\') { + *(*outstr + out_idx) = '\\'; + out_idx++; + } + *(*outstr + out_idx) = *instr; + out_idx++; + instr++; + } + *(*outstr + out_idx) = '\0'; +} + +/* includemarker_input should have 3 ?-marks, which are replaced with %s. + Also, @ is replaced with a space. These symbols can be escaped with a + backslash. + */ +void construct_include_directive_marker(char **include_directive_marker, + const char *includemarker_input) { + int len = strlen(includemarker_input); + char ch; + int in_idx = 0, out_idx = 0; + int quoted = 0, num_repl = 0; + + /* only 6 extra chars are needed: 3 for the three %'s, 2 for \n, 1 for \0 */ + *include_directive_marker = malloc(len + 18); + + ch = *includemarker_input; + while (ch != '\0' && in_idx < len) { + if (quoted) { + *(*include_directive_marker + out_idx) = ch; + out_idx++; + quoted = 0; + } else { + switch (ch) { + case '\\': + quoted = 1; + break; + case '@': + *(*include_directive_marker + out_idx) = ' '; + out_idx++; + break; + case '%': + case '?': + *(*include_directive_marker + out_idx) = '%'; + out_idx++; + *(*include_directive_marker + out_idx) = 's'; + out_idx++; + if (++num_repl > 3) + bug("only 3 substitutions allowed in -includemarker"); + break; + default: + *(*include_directive_marker + out_idx) = ch; + out_idx++; + } + } + + in_idx++; + ch = *(includemarker_input + in_idx); + } + + *(*include_directive_marker + out_idx) = '\n'; + out_idx++; + *(*include_directive_marker + out_idx) = '\0'; +} + +int main(int argc, char **argv) { + initthings(argc, argv); + /* The include marker at the top of the file */ + if (IncludeFile) + DoInclude(IncludeFile, 0); + IncludeFile = NULL; + write_include_marker(C->out->f, 1, C->filename, ""); + ProcessContext(); + fclose(C->out->f); + return EXIT_SUCCESS; +} + diff --git a/main.c b/main.c new file mode 100644 index 0000000..a1cb0a1 --- /dev/null +++ b/main.c @@ -0,0 +1,86 @@ +#define GEBS_NO_PREFIX +#define GEBS_IMPLEMENTATION +#include "gebs/gebs.h" +#include "mongoose/mongoose.h" +#define STB_DS_IMPLEMENTATION +#include "stb/stb_ds.h" + +typedef void (*Route_Handler)(struct mg_connection *conn, struct mg_http_message *msg); + +typedef struct { + char *key; // path + Route_Handler value; +} Route; + +Route *route_hashtable = NULL; + +void event_handler(struct mg_connection *conn, int ev, void *ev_data) +{ + if (ev == MG_EV_HTTP_MSG) { + struct mg_http_message *msg = (struct mg_http_message *)ev_data; + Route_Handler handler = hmget(route_hashtable, msg->uri.buf); + handler(conn, msg); + } +} + +bool gpp1(char *path, NString_List *env, String_Builder *out) +{ + Cmd cmd = {0}; + defer { cmd_free(&cmd); } + + cmd_append(&cmd, "./gpp1"); + cmd_append(&cmd, "-H"); + cmd_append(&cmd, "-x"); + cmd_append(&cmd, "--nostdinc"); + cmd_append(&cmd, path); + + for (size_t i = 0; i < env->count; i++) { + cmd_append(&cmd, env->items[i]); + } + + return cmd_run_collect(&cmd, out) == 0; +} + +void handle_page_not_found(struct mg_connection *conn, struct mg_http_message *msg) +{ + NString_List env = {0}; + defer { list_free(&env); } + + list_append(&env, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf)); + + String_Builder out = {0}; + defer { sb_free(&out); } + bool ok = gpp1("./tmpls/404.t", &env, &out); + sb_finish(&out); + + if (!ok) { + mg_http_reply(conn, 500, "Content-Type: text/html\r\n", "Internal server error ;("); + } else { + mg_http_reply(conn, 200, "Content-Type: text/html\r\n", out.items); + } +} + +static void init_route_hashtable(void) +{ + hmdefault(route_hashtable, &handle_page_not_found); +} + +int main(int argc, char ** argv) +{ + mg_log_set(MG_LL_DEBUG); + struct mg_mgr mgr; + mg_mgr_init(&mgr); + init_route_hashtable(); + + mg_http_listen(&mgr, "http://localhost:8080", &event_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 1000); + scratch_arena_reset(); + } + + mg_mgr_free(&mgr); + hmfree(route_hashtable); + + return 0; +} diff --git a/mongoose b/mongoose new file mode 160000 index 0000000..9411f1c --- /dev/null +++ b/mongoose @@ -0,0 +1 @@ +Subproject commit 9411f1c7b6667e3f7c52fde50fb1eac4388e7e20 diff --git a/stb/stb_ds.h b/stb/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/stb/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/tmpls/404.t b/tmpls/404.t new file mode 100644 index 0000000..a21e7e0 --- /dev/null +++ b/tmpls/404.t @@ -0,0 +1,10 @@ + + + + 404 - Page not found + + + The page you were looking for doesn't exist.
+ URL: <#URL> + +