/** This C file contains both the header and source file for c-pp, a.k.a. libcmpp. */ #if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED) #define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED #if !defined(_POSIX_C_SOURCE) # define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */ #endif #define CMPP_AMALGAMATION #if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED) /** This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated using: ./c-pp -I. -I./src -Dsrcdir=./src -Dsed=/usr/bin/sed -o libcmpp.h ./tool/libcmpp.c-pp.h -o libcmpp.c ./tool/libcmpp.c-pp.c with libcmpp 2.0.x c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37 @ 2026-03-08 14:50:35.123 UTC */ #define CMPP_PACKAGE_NAME "libcmpp" #define CMPP_LIB_VERSION "2.0.x" #define CMPP_LIB_VERSION_HASH "c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37" #define CMPP_LIB_VERSION_TIMESTAMP "2026-03-08 14:50:35.123 UTC" #define CMPP_LIB_CONFIG_TIMESTAMP "2026-03-08 15:32 GMT" #define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP #define CMPP_PLATFORM_EXT_DLL ".so" #define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp" #if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_) #define NET_WANDERINGHORSE_CMPP_H_INCLUDED_ /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** ** The C-minus Preprocessor: C-like preprocessor. Why? Because C ** preprocessors _can_ process non-C code but generally make quite a ** mess of it. The purpose of this library is a customizable ** preprocessor suitable for use with arbitrary UTF-8-encoded text. ** ** The supported preprocessor directives are documented in the ** README.md hosted with this file (or see the link below). ** ** Any mention of "#" in the docs, e.g. "#if", is symbolic. The ** directive delimiter is configurable and defaults to "##". Define ** CMPP_DEFAULT_DELIM to a string when compiling to define the default ** at build-time. ** ** This API is presented as a library but was evolved from a ** monolithic app. Thus is library interface is likely still missing ** some pieces needed to make it more readily usable as a library. ** ** Author(s): ** ** - Stephan Beal ** ** Canonical homes: ** ** - https://fossil.wanderinghorse.net/r/c-pp ** - https://sqlite.org/src/file/ext/wasm/c-pp-lite.c ** ** With the former hosting this app's SCM and the latter being the ** original deployment of c-pp.c, from which this library ** evolved. SQLite uses a "lite" version of c-pp, whereas _this_ copy ** is its much-heavier-weight fork. */ #if defined(CMPP_HAVE_AUTOCONFIG_H) #include "libcmpp-autoconfig.h" #endif #if defined(HAVE_AUTOCONFIG_H) #include "autoconfig.h" #endif #if defined(HAVE_CONFIG_H) #include "config.h" #endif #ifdef _WIN32 # if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD) # define CMPP_EXPORT extern # elif defined(BUILD_libcmpp) # define CMPP_EXPORT extern __declspec(dllexport) # else # define CMPP_EXPORT extern __declspec(dllimport) # endif #else # define CMPP_EXPORT extern #endif /** cmpp_FILE is a portability hack for WASM builds, where we want to elide the (FILE*)-using pieces to avoid having a dependency on Emscripten's POSIX I/O proxies. In all non-WASM builds it is guaranteed to be an alias for FILE. On WASM builds it is guaranteed to be an alias for void and the cmpp APIs which use it become inoperative in WASM builds. That said: the code does not yet support completely compiling out (FILE*) dependencies, and may not be able to because canonical sqlite3 (upon which it is based) depends heavily on file descriptors and slightly on FILE handles. */ #if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__) typedef void cmpp_FILE; # define CMPP_PLATFORM_IS_WASM 1 #else #include typedef FILE cmpp_FILE; # define CMPP_PLATFORM_IS_WASM 0 #endif #include #include /* PRIu32 and friends */ #include #ifdef __cplusplus extern "C" { #endif /** 32-bit flag bitmask type. This typedef exists primarily to improve legibility of function signatures and member structs by conveying their intent for use as flags instead of result codes or lengths. */ typedef uint32_t cmpp_flag32_t; //typedef uint16_t cmpp_flag16_t; /** An X-macro which invokes its argument (a macro name) to expand to all possible values of cmpp_rc_e entries. The macro name passed to it is invoked once for each entry and passed 3 arguments: the enum entry's full name (CMPP_RC_...), its integer value, and a help-text string. */ #define cmpp_rc_e_map(E) \ E(CMPP_RC_OK, 0, \ "The quintessential not-an-error value.") \ E(CMPP_RC_ERROR, 100, \ "Generic/unknown error.") \ E(CMPP_RC_NYI, 101, \ "A placeholder return value for not yet implemented functions.") \ E(CMPP_RC_OOM, 102, \ "Out of memory. Indicates that a resource allocation " \ "request failed.") \ E(CMPP_RC_MISUSE, 103, \ "API misuse (invalid args)") \ E(CMPP_RC_RANGE, 104, \ "A range was violated.") \ E(CMPP_RC_ACCESS, 105, \ "Access to or locking of a resource was denied " \ "by some security mechanism or other.") \ E(CMPP_RC_IO, 106, \ "Indicates an I/O error. Whether it was reading or " \ "writing is context-dependent.") \ E(CMPP_RC_NOT_FOUND, 107, \ "Requested resource not found.") \ E(CMPP_RC_ALREADY_EXISTS, 108, \ "Indicates that a to-be-created resource already exists.") \ E(CMPP_RC_CORRUPT, 109, \ "Data consistency problem.") \ E(CMPP_RC_SYNTAX, 110, \ "Some sort of syntax error.") \ E(CMPP_RC_NOOP, 111, \ "Special sentinel value for some APIs.") \ E(CMPP_RC_UNSUPPORTED, 112, \ "An unsupported operation was request.") \ E(CMPP_RC_DB, 113, \ "Indicates db-level error (e.g. statement prep failed). In such " \ "cases, the error state of the related db handle (cmpp_db) " \ "will be updated to contain more information directly from the " \ "db driver.") \ E(CMPP_RC_NOT_DEFINED, 114, \ "Failed to expand an undefined value.") \ E(CMPP_RC_ASSERT, 116, "An #assert failed.") \ E(CMPP_RC_TYPE, 118, \ "Indicates that some data type or logical type is incorrect.") \ E(CMPP_RC_CANNOT_HAPPEN, 140, \ "This is intended only for internal use, to " \ "report conditions which \"cannot possibly happen\".") \ E(CMPP_RC_HELP, 141, \ "--help was used in the arguments to cmpp_process_argv()") \ E(CMPP_RC_NO_DIRECTIVE, 142, \ "A special case of CMPP_RC_NOT_FOUND needed to disambiguate.") \ E(CMPP_RC_end,200, \ "Must be the final entry in the enum. Used for creating client-side " \ "result codes which are guaranteed to live outside of this one's " \ "range.") /** Most functions in this library which return an int return result codes from the cmpp_rc_e enum. None of these entries are guaranteed to have a specific value across library versions except for CMPP_RC_OK, which is guaranteed to always be 0 (and the API guarantees that no other code shall have a value of zero). The only reasons numbers are hard-coded to the values is to simplify debugging during development. Clients may use cmpp_rc_cstr() to get some human-readable (or programmer-readable) form for any given value in this enum. */ enum cmpp_rc_e { #define E(N,V,H) N = V, cmpp_rc_e_map(E) #undef E }; typedef enum cmpp_rc_e cmpp_rc_e; /** Returns the string form of the given cmpp_rc_e value or NULL if it's not a member of that enum. */ char const * cmpp_rc_cstr(int rc); /** CMPP_BITNESS specifies whether the library should use 32- or 64-bit integer types for its size/length measurements. It's difficult to envision use cases for a preprocessor which would require counters or rangers larger than 32 bits provide for, so the default is 32 bits. Builds created with different CMPP_BITNESS values are not binary-compatible. */ #define CMPP_BITNESS 32 #if 32==CMPP_BITNESS /** Unsigned integer type for string/stream lengths. 32 bits is sufficient for all but the weirdest of inputs and outputs. */ typedef uint32_t cmpp_size_t; /** A signed integer type indicating the maximum length of strings or byte ranges in a stream. It is most frequently used in API signatures where a negative value means "if it's negative then use strlen() to count it". */ typedef int32_t cmpp_ssize_t; /** The printf-format-compatible format letter (or group of letters) appropriate for use with cmpp_size_t. Contrary to popular usage, size_t cannot be portably used with printf(), without careful casting, because it has neither a fixed size nor a standardized printf/scanf format specifier (like the stdint.h types do). */ #define CMPP_SIZE_T_PFMT PRIu32 #elif 64==CMPP_BITNESS typedef uint64_t cmpp_size_t; typedef int64_t cmpp_ssize_t; #define CMPP_SIZE_T_PFMT PRIu64 #else #error "Invalid CMPP_BITNESS value. Expecting 32 or 64." #endif /** Generic interface for streaming in data. Implementations must read (at most) *n bytes from their input, copy it to dest, assign *n to the number of bytes actually read, return 0 on success, and return non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO). When called, *n is the max length to read. On return, *n must be set to the amount actually read. Implementations may need to internally distinguish a short read due to EOF from a short read due to an I/O error, e.g. using feof() and/or ferror(). A short read for EOF is not an error but a short read for input failure is. This library invariably treats a short read as EOF. The state parameter is the implementation-specified input file/buffer/whatever channel. */ typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n); /** Generic interface for streaming out data. Implementations must write n bytes from src to their destination channel and return 0 on success, or a value from the cmpp_rc_e enum on error (e.g. CMPP_RC_IO). The state parameter is the implementation-specified output channel. It is implementation-defined whether an n of 0 is legal. This library refrains from passing 0 to these functions. In the context of cmpp, the library makes no guarantees that output will always end at a character boundary. It may send any given multibyte character as the end resp. start of two calls to this function. If that is of a concern for implementors of these functions (e.g. because they're appending the output to a UI widget), they may need to buffer all of the output before applying it (see cmpp_b), or otherwise account for partial characters. That said: the core library, by an accident of design, will always emit data at character boundaries, assuming that its input is well-formed UTF-8 text (which cmpp does not validate to be the case). Custom cmpp_dx_f() implementations are not strictly required to do so but, because of how cmpp is used, almost certainly will. But relying on that is ill-advised. */ typedef int (*cmpp_output_f)(void * state, void const * src, cmpp_size_t n); /** Generic interface for flushing arbitrary output streams. Must return 0 on success, a non-0 cmpp_rc_e value on error. When in doubt, return CMPP_RC_IO on error. The interpretation of the state parameter is implementation-specific. */ typedef int (*cmpp_flush_f)(void * state); typedef struct cmpp_pimpl cmpp_pimpl; typedef struct cmpp_api_thunk cmpp_api_thunk; typedef struct cmpp_outputer cmpp_outputer; /** The library's primary class. Each one of these represents a separate preprocessor instance. See also: cmpp_dx (the class which client-side extensions interact with the most). */ struct cmpp { /** API thunk object to support use via loadable modules. Client code does not normally need to access this member, but it's exposed here to give loadable modules more flexibility in how they use the thunk. This pointer is _always_ the same singleton object. The library never exposes a cmpp object with a NULL api member. */ cmpp_api_thunk const * const api; /** Private internal state. */ cmpp_pimpl * const pimpl; }; typedef struct cmpp cmpp; /** Flags for use with cmpp_ctor_cfg::flags. */ enum cmpp_ctor_e { /* Sentinel value. */ cmpp_ctor_F_none = 0, /* Disables #include. */ cmpp_ctor_F_NO_INCLUDE = 0x01, /* Disables #pipe. */ cmpp_ctor_F_NO_PIPE = 0x02, /* Disables #attach, #detach, and #query. */ cmpp_ctor_F_NO_DB = 0x04, /* Disables #module. */ cmpp_ctor_F_NO_MODULE = 0x08, /** Disable all built-in directives which may work with the filesystem or invoke external processes. Client-defined directives with the cmpp_d_F_NOT_IN_SAFEMODE flag are also disabled. Directives disabled via the cmpp_ctor_F_NO_... flags (or equivalent library built-time options) do not get registered, so will trigger "unknown directive" errors rather than safe-mode violation errors. */ cmpp_ctor_F_SAFEMODE = 0x10, }; /** A configuration object for cmpp_ctor(). This type may be extended as new construction-time customization opportunities are discovered. */ struct cmpp_ctor_cfg { /** Bitmask from the cmpp_ctor_e enum. */ cmpp_flag32_t flags; /** If not NULL then this must name either an existing SQLite3 db file or the name of one which can be created on demand. If NULL then an in-memory or temporary database is used (which one is unspecified). The library copies these bytes, so they need not be valid after a call to cmpp_ctor(). */ char const * dbFile; }; typedef struct cmpp_ctor_cfg cmpp_ctor_cfg; /** Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return value must eventually be passed to cmpp_dtor() to free it. The cfg argument, if not NULL, holds config info for the new instance. If NULL, an instance with unspecified defaults is used. These configuration pieces may not be modified after the instance is created. It returns 0 if *pp is ready to use and non-0 if either allocation fails (in which case *pp will be set to 0) or initialization of *pp failed (in which case cmpp_err_get() can be used to determine why it failed). In either case, the caller must eventually pass *pp to cmpp_dtor() to free it. If the library is built with the symbol CMPP_CTOR_INSTANCE_INIT defined, it must refer to a function with this signature: int CMPP_CTOR_INSTANCE_INIT(cmpp *); The library calls this before returning and arranges to call it lazily if pp gets reset. The intent is that the init function installs custom directives using cmpp_d_register(). That initialization, on error, is expected to set its argument's error state with cmpp_err_set(). */ CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_cfg const * cfg); /** If pp is not NULL, it is passed to cmpp_reset() and then freed. */ CMPP_EXPORT void cmpp_dtor(cmpp *pp); /** realloc(3)-compatible allocator used by the library. This API very specifically uses sqlite3_realloc() as its basis. */ CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n); /** malloc(3)-compatible allocator used by the library. This API very specifically uses sqlite3_malloc() as its basis. */ CMPP_EXPORT void * cmpp_malloc(size_t n); /** free(3)-compatible deallocator. It can also be used as a destructor for cmpp_d_register() _if_ the memory in question is allocated by cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of APIs. This is not called cmpp_free() to try to avoid any confusion with cmpp_dtor(). */ CMPP_EXPORT void cmpp_mfree(void *); /** If m is NULL then pp's persistent error code is set to CMPP_RC_OOM, else this is a no-op. Returns pp's error code. To simplify certain uses, pp may be NULL, in which case this function returns CMPP_RC_OOM if m is NULL and 0 if it's not. */ CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * const m ); /** Re-initializes all state of pp. This saves some memory for reuse but resets it all to default states. This closes the database and will also reset any autoloader, policies, or delimiter configurations to their compile-time defaults. It retains only a small amount of state, like any configuration which was passed to cmpp_ctor(). After calling this, pp is in a cleanly-initialized state and may be re-used with the cmpp API. Its database will not be initialized until an API which needs it is called, so pp can be used with functions which may otherwise be prohibited after the db is opened. (Do we still have any?) As of this writing, this is the only way to reliably recover a cmpp instance from any significant errors. Errors may do things like leave savepoints out of balance, and this cleanup step resets all of that state. However, it also loses state like the autoloader. TODO?: we need(?) a partial-clear operation which keeps some of the instance's state, most notably custom directives, the db handle, and any cached prepared statements. See cmpp_err_set() for the distinction between recoverable and non-recoverable errors. */ CMPP_EXPORT void cmpp_reset(cmpp *pp); #if 0 Not yet; /** If called before pp has initialized its database, this sets the file name used for that database. If called afterwards, pp's error state is updated and CMPP_RC_MISUSE is returned. If called while pp has error state set, that code is returned without side-effects. This does not open the database. It is opened on demand when processing starts. On success it returns 0 and this function makes a copy of zName. As a special case, zName may be NULL to use the default name, but there is little reason to do so unless one changes their mind after setting it to non-NULL. */ CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName); #endif /** Returns true if the bytes in the range [zName, zName+n) comprise a legal name for a directive or a define. It disallows any control characters, spaces, and most punctuation, but allows alphanumeric (but must not start with a number) as well as any of: -./:_ (but it may not start with '-'). Any characters with a high bit set are assumed to be UTF-8 and are permitted as well. The name's length is limited, rather arbitrarily, to 64 bytes. If the key is not legal then false is returned and if zErrPos is not NULL then *zErrPos is set to the position in zName of the first offending character. If validation fails because n is too long then *zErrPos (if zErrPos is not NULL) will be set to 0. Design note: this takes unsigned characters because it most commonly takes input from cmpp_args::z strings. */ CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName, cmpp_size_t n, unsigned char const **zErrPos); /** Adds the given `#define` macro name to the list of macros, overwriting any previous value. zKey must be NUL-terminated and legal as a key. The rules are the same as for cmpp_is_legal_key() except that a '=' is also permitted if it's not at the start of the string because... If zVal is NULL then zKey may contain an '=', from which the value will be extracted. If zVal is not NULL then zKey may _not_ contain an '='. The ability for zKey to contain a key=val was initially to facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was initially a CLI app (as opposed to a library). It's considered a "legacy" feature, not recommended for most purposes, but it _is_ convenient for that particular purpose. Returns 0 on success and updates pp's error state on error. See: cmpp_define_v2() See: cmpp_undef() */ CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey, char const *zVal); /** Works like cmpp_define_legacy() except that it does not examine zKey to see if it contains an '='. */ CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal); /** Removes the given `#define` macro name from the list of macros. zKey is, in this case, treated as a GLOB pattern, and all matching defines are deleted. If nRemoved is not NULL then, on success, it is set to the number of entries removed by this call. Returns 0 on success and updates pp's error state on error. It is not an error if no value was undefined. This does _not_ affect defines made using cmpp_define_shadow(). */ CMPP_EXPORT int cmpp_undef(cmpp *pp, const char * zKey, unsigned int *nRemoved); /** This works similarly to cmpp_define_v2() except that: - It does not permit its zKey argument to contain the value part like that function does. - The new define "shadows", rather than overwrites, an existing define with the same name. All APIs which look up define keys will get the value of the shadow define. The shadow can be uninstalled with cmpp_define_unshadow(), effectively restoring its previous value (if any). That function should be called one time for each call to this one, passing the same key to each call. A given key may be shadowed any number of times by this routine. Each one saves the internal ID of the shadow into *pId (and pId must not be NULL). That value must be passed to cmpp_define_unshadow() to ensure that the "shadow stack" stays balanced in the face of certain error-handling paths. cmpp_undef() will _not_ undefine an entry added through this interface. Returns pp's persistent error code (0 on success). Design note: this function was added to support adding a define named __FILE__ to input scripts which works like it does in a C preprocessor. Alas, supporting __LINE__ would be much more costly, as it would have to be updated in the db from several places, so its cost would outweigh its meager benefits. */ CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey, char const *zVal, int64_t * pId); /** Removes the most shadow define matching the zKey and id values which where previously passed to cmpp_define_shadow(). It is not an error if no match is found, in which case this function has no visible side-effects. Unlike cmpp_undef(), zKey is matched precisely, not against a glob. In order to keep the "shadow stack" properly balanced, this will delete any shadow entries for the given key which have the same id or a newer one (i.e. they were left over from a missed call to cmpp_define_unshadow()). Returns pp's persistent error code (0 on success). */ CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey, int64_t id); /** Adds the given dir to the list of includes. They are checked in the order they are added. */ CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey); /** Sets pp's default output channel. If pp already has a channel, it is closed[^1]. The second argument, if not NULL, is _bitwise copied_, which has implications for the ownership of out->state (see below). If it is is NULL, cmpp_outputer_empty is copied in its place, which makes further output a no-op. The third argument is a symbolic name for the channel (perhaps its file name). It is used in debugging and error messages. cmpp does _not_ copy it, so its bytes must outlive the cmpp instance. (In practice, the byte names come from main()'s argv or scope-local strings in the same scope as the cmpp instance.) This argument should only be NULL if the second argument is. cmpp_reset(), or opening another channel, will end up calling out->cleanup() (if it's not NULL) and passing it a pointer to a _different_ cmpp_outputer object, but with the _same_ cmpp_outputer::state pointer, which may invalidate out->state. To keep cmpp from doing that, make a copy of the output object, set the cleanup member of that copy to NULL, then pass that copy to this function. It is then up to the client to call out->cleanup(out) when the time is right. For example: ``` cmpp_outputer my = cmpp_outputer_FILE; my.state = cmpp_fopen("/some/file", "wb"); cmpp_outputer tmp = my; tmp.cleanup = NULL; cmpp_outputer_set( pp, &tmp, "my file"); ... my.cleanup(&my); // will cmpp_fclose(my.state) ``` Potential TODO: internally store the output channel as a pointer. It's not clear whether that would resolve the above grief or compound it. [^1]: depending on the output channel, it might not _actually_ be closed, but pp is disassociated from it, in any case. */ CMPP_EXPORT void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName); /** Treats the range (zIn,zIn+nIn] as a complete cmpp input and process it appropriately. zName is the name of the input for purposes of error messages. If nIn is negative, strlen() is used to calculate it. This is a no-op if pp has any error state set. It returns pp's persistent error code. */ CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName, unsigned char const * zIn, cmpp_ssize_t nIn); /** A thin proxy for cmpp_process_string() which reads its input from the given file. Returns 0 on success, else returns pp's persistent error code. */ CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName); /** A thin proxy for cmpp_process_string() which reads its input from the given input source, consuming it all before passing it on. Returns 0 on success, else returns pp's persistent error code. */ CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName, cmpp_input_f src, void * srcState); /** Process the given main()-style arguments list. When calling from main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0] (the binary's name). Each argument is expected to be one of the following: 1) One of --help or -?: causes this function to return CMPP_RC_HELP without emitting any output. 2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to Y. 3) -UX: unsets all defines matching glob X. 4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to the contents of that file. 5) -IX: adds X to the "include path". If _no_ include path is provided then cmpp assumes a path of ".", but if _any_ paths are provided then it does not assume that "." is in the path. 6) --chomp-F: specifies whether subsequent -F flags should "chomp" one trailing newline from their input files. 7) --delimiter|-d=X sets the directive delimiter to X. Its default is a compile-time constant. 8) --output|-o=filename: sets the output channel to the given file. A value of "-" means stdout. If no output channel is opened when this is called, and files are to be processed, stdout is assumed. (That's a historical artifact from earlier evolutions.) To override that behavior use cmpp_outputer_set(). 9) --file|-f=filename: sets the input channel to the given file. A value of "-" means stdin. 10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it. Because it's difficult to pack multiple lines of text into this, it's really of use for testing #expr and #assert. 11) --@policy=X sets the @token@ parsing policy. X must be one of (retain, elide, error, off) and defaults to off. 12) -@: shorthand for --@policy=error. 13) --sql-trace: enables tracing of all SQL statements to stderr. This is useful for seeing how a script interacts with the database. Use --no-sql-trace to disable it. 14) --sql-trace-x: like --sql-trace but replaces bound parameter placeholders with their SQL values. Use --no-sql-trace to disable it. 15) --dump-defines: emit all defines to stdout. They should arguably go to stderr but that interferes with automated testing. Any argument which does not match one of the above flags, and does not start with a "-", is treated as if it were passed to the --file flag. Flags may start with either 1 or 2 dashes - they are equivalent. Flags which take a value may either be in the form X=Y or X Y, i.e. may be a single argv entry or a pair of them. It performs two passes on the arguments: the first is for validation checking for --help/-?. No processing of the input(s) and output(s) happens unless the first pass completes. Similarly, no validation of whether any provided filename are actually readable is performed until the second pass. Arguments are processed in the order they are given. Thus the following have completely different meanings: 1) -f foo.in -Dfoo 2) -Dfoo -f foo.in The former will process foo.in before defining foo. This behavior makes it possible to process multiple input files in a single go: --output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in It returns 0 on success. cmpp_err_get() can be used to fetch any error message. */ CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv); /** Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP. It emits --help-style text to the given output stream. As the first argument pass it either argv[0] or NULL. The second should normally be stdout or stderr. Reminder to self: this could take a (cmpp_output_f,void*) pair instead, and should do so for the sake of WASI builds, but its impl currently relies heavily on fprintf() formatting. */ CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *os); /** Returns pp's current error number (from the cmpp_rc_e enum) and sets *zMsg (if zMsg is not NULL) to the error string. The bytes are owned by pp and may be invalidated by any functions which take pp as an argument. See cmpp_err_get() for more information. */ CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg); /** Sets or clears (if 0==rc) pp's persistent error state. zFmt may be NULL or a format string compatible with sqlite3_mprintf(). To simplify certain uses, this is a no-op if pp is NULL, returning rc without other side effects. Returns rc with one exception: if allocation of a copy of the error string fails then CMPP_RC_OOM will be returned (and pp will be updated appropriately). If pp is currently processing a script, the resulting error string will be prefixed with the name of the current input script and the line number of the directive which triggered the error. It is legal for zFmt to be NULL or an empty string, in which case a default, vague error message is used (without requiring allocation of a new string). Recoverable vs. unrecoverable errors: Most cmpp APIs become no-ops if their cmpp object has error state set, treating any error as unrecoverable. That approach simplifies writing code for it by allowing multiple calls to be chained without concern for whether the previous one succeeded. ACHTUNG: simply clearing the error state by passing 0 as the 2nd argument to this function is _not_ enough to recover from certain errors. e.g. an error in the middle of a script may leave db savepoints imbalanced. The only way to _fully_ recover from any significant failures is to use cmpp_reset(), which resets all of pp's state. APIs which may set the error state but are recoverable by simply clearing that state will document that. Errors from APIs which do not claim to be recoverable in error cases must be treated as unrecoverable. See cmpp_err_get() for more information. FIXME: we need a different variant for WASM builds, where variadics aren't a usable thing. Potential TODO: change the error-reporting interface to support distinguishing from recoverable and non-recoverable errors. "The problem" is that no current uses need that - they simply quit and free up the cmpp instance on error. Maybe that's the way it _should_ be. */ CMPP_EXPORT int cmpp_err_set(cmpp *pp, int rc, char const *zFmt, ...); /** A variant of cmpp_err_set() which is not variadic, as a consolation for WASM builds. zMsg may be NULL. The given string, if not NULL, is copied. */ CMPP_EXPORT int cmpp_err_set1(cmpp *pp, int rc, char const *zMsg); #if 0 /** Clears any error state in pp. Most cmpp APIs become no-ops if their cmpp instance has its error flag set. See cmpp_err_get() for important details about doing this. */ //CMPP_EXPORT void cmpp_err_clear(cmpp *pp); /** This works like a combination of cmpp_err_get() and cmpp_err_clear(), in that it clears pp's error state by transferring ownership of it to the caller. If pp has any error state, *zMsg is set to the error string and the error code is returned, else 0 is returned and *zMsg is set to 0. The string returned via *zMsg must eventually be passed to cmpp_mfree() to free it. This function is provided simply as an optimization to avoid having to copy the error string in some cases. ACHTUNG: see the ACHTUNG in cmpp_err_clear(). */ CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg); #endif /** Returns pp's current error code, which will be 0 if it currently has no error state. To simplify certain uses, this is a no-op if pp is NULL, returning 0. */ CMPP_EXPORT int cmpp_err_has(cmpp const * pp); /** Returns true if pp was initialized in "safe mode". That is: if the cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor(). To simplify certain uses, this is a no-op if pp is NULL, returning false. */ CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp); /** Starts a new SAVEPOINT in the database. Returns non-0, and updates pp's persistent error state, on failure. If this returns 0, the caller is obligated to later call either cmpp_sp_commit() or cmpp_sp_rollback() later. */ CMPP_EXPORT int cmpp_sp_begin(cmpp *pp); /** Commits the most recently-opened savepoint UNLESS pp's error state is set, in which case this behaves like cmpp_sp_rollback(). Returns 0 on success. A call to cmpp_sp_begin() which returns 0 obligates the caller to call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal for either to be called in any other context. */ CMPP_EXPORT int cmpp_sp_commit(cmpp *pp); /** Rolls back the most recently-opened savepoint. Returns 0 on success. A call to cmpp_sp_begin() which returns 0 obligates the caller to call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal for either to be called in any other context. */ CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp); /** A cmpp_output_f() impl which requires state to be a (FILE*), which this function passes the call on to fwrite(). Returns 0 on success, CMPP_RC_IO on error. If state is NULL then stdout is used. */ CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n); /** A cmpp_output_f() impl which requires state to be a ([const] int*) referring to a writable file descriptor, which this function dereferences and passes to write(2). */ CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n); /** A cmpp_input_f() implementation which requires that state be a readable (FILE*) handle, which it passes to fread(3). */ CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n); /** A cmpp_input_f() implementation which requires that state be a readable file descriptor, in the form of an ([const] int*), which this function passes to write(2). */ CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n); /** A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened for writing, which this function passes the call on to fflush(). If fflush() returns 0, so does this function, else it returns non-0. */ CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile); /** A generic streaming routine which copies data from an cmpp_input_f() to an cmpp_outpuf_f(). Reads all data from inF(inState,...) in chunks of an unspecified size and passes them on to outF(outState,...). It reads until inF() returns fewer bytes than requested or returns non-0. Returns the result of the last call to outF() or (if reading fails) inF(). Results are undefined if either of inState or outState arguments are NULL and their callbacks require non-NULL. (This function cannot know whether a NULL state argument is legal for the given callbacks.) Here is an example which basically does the same thing as the cat(1) command on Unix systems: ``` cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout); ``` Or copy a FILE to a string buffer: ``` cmpp_b os = cmpp_b_empty; FILE * f = cmpp_fopen(...); rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os); // On error os might be partially populated. // Eventually clean up the buffer: cmpp_b_clear(&os); ``` */ CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState, cmpp_output_f outF, void * outState); /** Reads the entire contents of the given input stream, allocating it in a buffer. On success, returns 0, assigns *pOut to the buffer, and *nOut to the number of bytes read (which will be fewer than are allocated). It guarantees that on success it NUL-terminates the buffer at one byte after the returned size, with one exception: if the string has no input, both *pOut and *nOut will be set to 0. On error it returns whatever code xIn() returns. */ CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn, unsigned char **pOut, cmpp_size_t * nOut); /** _Almost_ equivalent to fopen(3) but: - If name=="-", it returns one of stdin or stdout, depending on the mode string: stdout is returned if 'w' or '+' appear, otherwise stdin. If it returns NULL, the global errno "should" contain a description of the problem unless the problem was argument validation. If at all possible, use cmpp_fclose() (as opposed to fclose()) to close these handles, as it has logic to skip closing the three standard streams. */ CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode); /** Passes f to fclose(3) unless f is NULL or one of the C-standard handles (stdin, stdout, stderr), in which cases it does nothing at all. */ CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f); /** A cleanup callback interface for use with cmpp_outputer::cleanup(). Implementations must handle self->state appropriately for its type, and clear self->state if appropriate, but must not free the self object. It is implementation-specified whether self->state and/or self->name are set to NULL by this function. Whether they should be often depends on how they're used. */ typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self); /** An interface which encapsulates data for managing a streaming output destination, primarily intended for use with cmpp_stream() but also used internally by cmpp for directing output to a buffer. */ struct cmpp_outputer { /** An optional descriptive name for the channel. The bytes are owned elsewhere and are typically static or similarly long-lived. */ char const * name; /** Output channel. */ cmpp_output_f out; /** flush() implementation. This may be NULL for most uses of this class. Cases which specifically require it must document that requirement so. */ cmpp_flush_f flush; /** Optional: if not NULL, it must behave appropriately for its state type, cleaning up any memory it owns. */ cmpp_outputer_cleanup_f cleanup; /** State to be used when calling this->out() and this->flush(), namely: this->out(this->state, ... ) and this->flush(this->state). Whether or not any given instance of this class owns the memory pointed to by this member must be documented for their cleanup() method. Because cmpp_outputer instances frequently need to be stashed and unstashed via bitwise copying, it is illegal to replace this pointer after its initial assignment. The object it points to may be mutated freely, but this pointer must stay stable for the life of this object. */ void * state; }; /** Empty-initialized cmpp_outputer instance, intended for const-copy initialization. */ #define cmpp_outputer_empty_m \ {.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL} /** Empty-initialized cmpp_outputer instance, intended for non-const-copy initialization. These copies can, for purposes of cmpp's output API, be used as-is to have cmpp process its inputs but generate no output. */ CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty; /** If o->out is not NULL, the result of o->out(o->state,p,n) is returned, else 0 is returned. */ CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n); /** If o->flush is not NULL, the result of o->flush(o->state) is returned, else 0 is returned. */ CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o); /** If o->cleanup is not NULL, it is called, otherwise this is a no-op. */ CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o); /** A cmpp_outputer initializer which uses cmpp_flush_f_FILE(), cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its implementation. After copying this, the state member must be pointed to an opened-for-writing (FILE*). */ CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE; /** The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE. If self->state is not NULL then it is passed to fclose() (_unless_ it is stdin, stdout, or stderr) and set to NULL. self->name is also set to NULL. */ CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self); /** Sets pp's current directive delimiter to a copy of the NUL-terminated zDelim. The delimiter is the sequence which starts line and distinguishes cmpp directives from other input, in the same way that C preprocessors use '#' as a delimiter. If zDelim is NULL then the default delimiter is used. The default delimiter can be set when compiling the library by defining CMPP_DEFAULT_DELIM to a quoted string value. zDelim is assumed to be in UTF-8 encoding. If any bytes in the range (0,32) are found, CMPP_RC_MISUSE is returned and pp's persistent error state is set. The delimiter must be short and syntactically unambiguous for the intended inputs. It has a rather arbitrary maximum length of 12, but it's difficult to envision it being remotely human-friendly with a delimiter longer than 3 bytes. It's conceivable, but seemingly far-fetched, that longer delimiters might be interesting in some machine-generated cases, e.g. using a random sequence as the delimiter. Returns 0 on success. Returns non-0 if called when the delimiter stack is empty, if it cannot copy the string or zDelim is deemed unsuitable for use as a delimiter. Calling this when the stack is empty represents a serious API misuse (indicating that cmpp_delimiter_pop() was used out of scope) and will trigger an assert() in debug builds. Except for that last case, errors from this function are recoverable (see cmpp_err_set()). */ CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim); /** Fetches pp's current delimiter string, assigning it to *zDelim. The string is owned by pp and will be invalidated by any call to cmpp_delimiter_set() or the #delimiter script directive. If, by some odd usage constellation, this is called after an allocation of the delimiter stack has failed, this will set *zDelim to the compile-time-default delimiter. That "cannot happen" in normal use because such a failure would have been reacted to and this would not be called. */ CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim); /** Pushes zDelim as the current directive delimiter. Returns 0 on success and non-zero on error (invalid zDelim value or allocation error). If this returns 0 then the caller is obligated to eventually call cmpp_delimiter_pop() one time. If it returns non-0 then they must _not_ call that function. */ CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim); /** Must be called one time for each successful call to cmpp_delimiter_push(). It restores the directive delimimter to the value it has when cmpp_delimiter_push() was last called. Returns pp's current error code, and will set it to non-0 if called when no cmpp_delimiter_push() is active. Popping an empty stack represents a serious API misuse and may fail an assert() in debug builds. */ CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp); /** If z[*n] ends on a \n or \r\n pair, it/they are stripped, *z is NUL-terminated there, and *n is adjusted downwards by 1 or 2. Returns true if it chomped, else false. */ CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n); /** A basic memory buffer class. This is primarily used with cmpp_outputer_b to capture arbitrary output for later use. It's also used for incrementally creating dynamic strings. TODO: add the heuristic that an nAlloc of 0 with a non-NULL z refers to externally-owned memory. This would change the buffer-write APIs to automatically copy it before making any changes. We have code for this in the trees this class derives from, it just needs to be ported over. It would allow us to avoid allocating in some cases where we need a buffer but it will always (or commonly) be a copy of a static string, like a single space. */ struct cmpp_b { /** This buffer's memory, owned by this object. This library exclusively uses sqlite3_realloc() and friends for memory management. If this pointer is taken away from this object then it must eventually be passed to cmpp_mfree(). */ unsigned char * z; /** Number of bytes of this->z which are in use, not counting any automatic NUL terminator which this class's APIs may add. */ cmpp_size_t n; /** Number of bytes allocated in this->z. Potential TODO: use a value of zero here, with a non-zero this->n, to mean that this->z is owned elsewhere. This would cause cmpp_b_append() to copy its original source before appending. Similarly, cmpp_b_clear() would necessarily _not_ free this->z. We've used that heuristic in a predecessor of this class in another tree to good effect for years, but it's not certain that we'd get the same level of utility out of that capability as we do in that other project. */ cmpp_size_t nAlloc; /** cmpp_b APIs which may fail will set this. Similarly, most of the cmpp_b APIs become no-ops if this is non-0. */ int errCode; }; typedef struct cmpp_b cmpp_b; /** An empty-initialized cmpp_b struct for use in const-copy initialization. */ #define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0} /** An empty-initialized cmpp_b struct for use in non-copy copy initialization. */ extern const cmpp_b cmpp_b_empty; /** Frees s->z and zeroes out s but does not free s. */ CMPP_EXPORT void cmpp_b_clear(cmpp_b *s); /** If s has content, s->nUsed is set to 0 and s->z is NUL-terminated at its first byte, else this is a no-op. s->errCode is set to 0. Returns s. */ CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *s); /** Swaps all contents of the given buffers, including their persistent error code. */ CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r); /** If s->errCode is 0 and s->nAlloc is less than n, s->z is reallocated to have at least n bytes, else this is a no-op. Returns 0 on success, CMPP_RC_OOM on error. */ CMPP_EXPORT int cmpp_b_reserve(cmpp_b *s, cmpp_size_t n); /** Works just like cmpp_b_reserve() but on allocation error it updates pp's error state. */ CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n); /** Appends n bytes from src to os, reallocating os as necessary. Returns 0 on succes, CMPP_RC_OOM on allocation error. Errors from this function, and the other cmpp_b_append...() variants, are recoverable (see cmpp_err_set()). */ CMPP_EXPORT int cmpp_b_append(cmpp_b * os, void const *src, cmpp_size_t n); /** Works just like cmpp_b_append() but on allocation error it updates pp's error state. */ CMPP_EXPORT int cmpp_b_append4(cmpp * pp, cmpp_b * os, void const * src, cmpp_size_t n); /** Appends ch to the end of os->z, expanding as necessary, and NUL-terminates os. Returns os->errCode and is a no-op if that is non-0 when this is called. This is slightly more efficient than passing length-1 strings to cmpp_b_append() _if_ os's memory is pre-allocated with cmpp_b_reserve(), otherwise it may be less efficient because it may need to allocate frequently if used repeatedly. */ CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch); /** Appends a decimal string representation of d to os. Returns os->errCode and is a no-op if that is non-0 when this is called. */ CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d); /** int64_t counterpart of cmpp_b_append_i32(). */ CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d); /** A thin wrapper around cmpp_chomp() which chomps b->z. */ CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b); /** A cmpp_output_f() impl which requires that its first argument be a (cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in reallocation error. On success it always NUL-terminates buffer->z. A NULL buffer is treated as success but has no side effects. Example usage: ``` cmpp_b os = cmpp_b_empty; int rc = cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_b, &os); ... cmpp_b_clear(&os); ``` */ CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src, cmpp_size_t n); /** A cmpp_outputer_cleanup_f() implementation which requires that self->state be either NULL or a cmpp_b pointer. This function passes it to cmpp_b_clear(). It does _not_ set self->state or self->name to NULL. */ CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self); /** A cmpp_outputer prototype which can be copied to use a dynamic string buffer as an output source. Its state member must be set (by the client) to a cmpp_b instance. Its out() method is cmpp_output_f_b(). Its cleanup() method is cmpp_outputer_cleanup_f_b(). It has no flush() method. */ extern const cmpp_outputer cmpp_outputer_b; /** Returns a string containing version information in an unspecified format. */ CMPP_EXPORT char const * cmpp_version(void); /** Type IDs for directive lines and argument-parsing tokens. This is largely a historical artifact and work is underway to factor this back out of the public API. */ enum cmpp_tt { /** X-macro which defines token types. It invokes E(X,Y) for each entry, where X is the base name part of the token type and Y is the token name as it appears in input scripts (if any, else it's 0). Maintenance reminder: their ordering in this map is insignificant except that None must be first and must have the value 0. Some of the more significant ones are: - Word: an unquoted word-like token. - String: a quoted string. - StringAt: an @"..." string. - GroupParen, GroupBrace, GroupSquiggly: (), [], and {} - All which start with D_ are directives. D_Line is a transitional state between "unparsed" and another D_... value. */ #define cmpp_tt_map(E) \ E(None, 0) \ E(RawLine, 0) \ E(Unknown, 0) \ E(Word, 0) \ E(Noop, 0) \ E(Int, 0) \ E(Null, 0) \ E(String, 0) \ E(StringAt, 0) \ E(GroupParen, 0) \ E(GroupBrace, 0) \ E(GroupSquiggly,0) \ E(OpEq, "=") \ E(OpNeq, "!=") \ E(OpLt, "<") \ E(OpLe, "<=") \ E(OpGt, ">") \ E(OpGe, ">=") \ E(ArrowR, "->") \ E(ArrowL, "<-") \ E(Plus, "+") \ E(Minus, "-") \ E(ShiftR, ">>") \ E(ShiftL, "<<") \ E(ShiftL3, "<<<") \ E(OpNot, "not") \ E(OpAnd, "and") \ E(OpOr, "or") \ E(OpDefined, "defined") \ E(OpGlob, "glob") \ E(OpNotGlob, "not glob") \ E(AnyType, 0) \ E(Eof, 0) #define E(N,TOK) cmpp_TT_ ## N, cmpp_tt_map(E) #undef E /** Used by cmpp_d_register() to assign new IDs. */ cmpp_TT__last }; typedef enum cmpp_tt cmpp_tt; /** For all of the cmpp_tt enum entries, returns a string form of the enum entry name, e.g. "cmpp_TT_D_If". Returns NULL for any other values */ CMPP_EXPORT char const * cmpp_tt_cstr(int tt); /** Policies for how to handle undefined @tokens@ when performing content filtering. */ enum cmpp_atpol_e { /** Sentinel value. */ cmpp_atpol_invalid = -1, /** Turn off @token@ parsing. */ cmpp_atpol_OFF = 0, /** Retain undefined @token@ - emit it as-is. */ cmpp_atpol_RETAIN, /** Elide undefined @token@. */ cmpp_atpol_ELIDE, /** Error for undefined @token@. */ cmpp_atpol_ERROR, /** A sentinel value for use with cmpp_dx_out_expand(). */ cmpp_atpol_CURRENT, /** This isn't _really_ the default. It's the default for the --@policy CLI flag and #@pragma when it's given no value. */ cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR, /** The compile-time default for all cmpp instances. */ cmpp_atpol_DEFAULT = cmpp_atpol_OFF }; typedef enum cmpp_atpol_e cmpp_atpol_e; /** Policies describing how cmpp should react to attempts to use undefined keys. */ enum cmpp_unpol_e { /* Sentinel. */ cmpp_unpol_invalid, /** Treat undefined keys as NULL/falsy. This is the default. */ cmpp_unpol_NULL, /** Trigger an error for undefined keys. This should probably be the default. */ cmpp_unpol_ERROR, /** The compile-time default for all cmpp instances. */ cmpp_unpol_DEFAULT = cmpp_unpol_NULL }; typedef enum cmpp_unpol_e cmpp_unpol_e; typedef struct cmpp_arg cmpp_arg; /** A single argument for a directive. When a cmpp_d::flags have cmpp_d_F_ARGS_V2 set then the part of the input immediately following the directive (and on the same line) is parsed into a cmpp_args, a container for these. */ struct cmpp_arg { /** Token type. */ cmpp_tt ttype; /** The arg's string value, shorn of any opening/closing quotes or () or {} or []. The args-parsing process guarantees to NUL-terminate this. The bytes are typically owned by a cmpp_args object, but clients may direct them wherever the need to, so long as the bytes are valid longer than this object is. */ unsigned char const * z; /** The arg's effective length, in bytes, after opening/closing chars are stripped. That is, its string form is the range [z,z+n). */ unsigned n; /** The next argument in the list. It is owned by whatever code set it up (typically cmpp_args_parse()). */ cmpp_arg const * next; }; /** Empty-initialized cmpp_arg instance, intended for const-copy initialization. */ #define cmpp_arg_empty_m {cmpp_TT_None,0,0,0} /** Empty-initialized cmpp_outputer instance, intended for non-const-copy initialization. */ extern const cmpp_arg cmpp_arg_empty; typedef struct cmpp_dx cmpp_dx; typedef struct cmpp_dx_pimpl cmpp_dx_pimpl; typedef struct cmpp_d cmpp_d; /** Flags for use with cmpp_d::flags. */ enum cmpp_d_e { /** Sentinel value. */ cmpp_d_F_none = 0, /** cmpp_dx_next() will not parse the directive's arguments. Instead, it makes cmpp_dx::arg0 encapsulate the whole line of the directive (sans the directive's name) as a single argument. The only transformation which is performed is the removal of backslashes from backslash-escaped newlines. It is up to the directive's callback to handle (or not) the arguments. */ cmpp_d_F_ARGS_RAW = 0x01, /** cmpp_dx_next() will parse the directive's arguments. cmpp_dx::arg0 will point to the first argument in the list, or NULL if there are no arguments. If both cmpp_d_F_ARGS_LIST and cmpp_d_F_ARGS_RAW are specified, cmpp_d_F_ARGS_LIST will win. */ cmpp_d_F_ARGS_LIST = 0x02, /** Indicates that the direction should not be available if the cmpp instance is configured with any of the cmpp_ctor_F_SAFEMODE flags. All directives when do any of the following are obligated to set this flag: - Filesystem or network access. - Invoking external processes. Or anything else which might be deamed "security-relevant". When registering a directive which has both opener and closer implementations, it is sufficient to set this only on the opener. The library imposes this flag in the following places: - Registration of a directive with this flag will fail if cmpp_is_safemode() is true for that cmpp instance. - cmpp_dx_process() will refuse to invoke a directive with this flag when cmpp_is_safemode() is true. */ cmpp_d_F_NOT_IN_SAFEMODE = 0x04, /** Call-only directives are only usable in [directive ...] "call" contexts. They are not permitted to have a closing directive. */ cmpp_d_F_CALL_ONLY = 0x08, /** Indicates that the directive is incapable of working in a [call] context and an error should be trigger if it is. _Most_ directives which have a closing directive should have this flag. The exceptions are directives which only conditionally use a closing directive, like #query. */ cmpp_d_F_NO_CALL = 0x10, /** Mask of the client-usable range for this enum. Values outside of this mask are reserved for internal use and will be stripped from registrations made with cmpp_d_register(). */ cmpp_d_F_MASK = 0x0000ffff }; /** Callback type for cmpp_d::impl::callback(). cmpp directives are all implemented as functions with this signature. Implementations are called only by cmpp_dx_process() (and only after cmpp_dx_next() has found a preprocessor line), passed the current context object. These callbacks are only ever passed directives which were specifically registered with them (see cmpp_d_register()). The first rule of callback is: to report errors (all of which end processing of the current input) call cmpp_dx_err_set(), passing it the callback's only argument, then clean up any local resources, then return. The library will recognize the error and propagate it. dx's memory is only valid for the duration of this call. It must not be held on to longer than that. dx->args.arg0 has slightly different lifetime: if this callback does _not_ call back in to cmpp_dx_next() then dx->args.arg0 and its neighbors will survive until this call is completed. Calling cmpp_dx_next(), or any API which invokes it, invalidates dx->args.arg0's memory. Thus directives which call into that must _copy_ any data they need from their own arguments before doing so, as their arguments list will be invalidated. */ typedef void (*cmpp_dx_f)(cmpp_dx * dx); /** A typedef for generic deallocation routines. */ typedef void (*cmpp_finalizer_f)(void *); /** State specific to concrete cmpp_d implementations. TODO: move this, except for the state pointer, out of cmpp_d so that directives cannot invoke these callbacks directly. Getting that to work requires moving the builtin directives into the dynamic directives list. */ struct cmpp_d_impl { /** Callback func. If any API other othan cmpp_dx_process() invokes this, behavior is undefined. */ cmpp_dx_f callback; /** For custom directives with a non-NULL this->state, this will be called, and passed that object, when the directive is cleaned up. For directives with both an opening and a closing tag, this destructor is only attached to the opening tag. If any API other othan cmpp's internal cleanup routines invoke this, behavior is undefined. */ cmpp_finalizer_f dtor; /** State for the directive's callback. It is accessible in cmpp_dx_f() impls via theDx->d->impl.state. For custom directives with both an opening and closing directive, this same state object gets assigned to both. */ void * state; }; typedef struct cmpp_d_impl cmpp_d_impl; #define cmpp_d_impl_empty_m {0,0,0} /** Each c-pp "directive" is modeled by one of these. */ struct cmpp_d { struct { /** The directive's name, as it must appear after the directive delimiter. Its bytes are assumed to be static or otherwise outlive this object. */ const char *z; /** Byte length of this->z. We record this to speed up searches. */ unsigned n; } name; /** Bitmask of flags from cmpp_d_e plus possibly internal flags. */ cmpp_flag32_t flags; /** The directive which acts as this directive's closing element element, or 0 if it has none. */ cmpp_d const * closer; /** State specific to concrete implementations. */ cmpp_d_impl impl; }; /** Each instance of the cmpp_dx class (a.k.a. "directive context") manages a single input source. It's responsible for the tokenization of all input, locating directives, and processing ("running") directives. The directive-specific work happens in cmpp_dx_f() implementations, and this class internally manages the setup, input traversal, and teardown. These objects only exist while cmpp is actively processing input. Client code interacts with them only through cmpp_dx_f() implementations which the library invokes. The process of filtering input to look for directives is to call cmpp_dx_next() until it indicates either an error or that a directive was found. In the latter case, the cmpp_dx object is populated with info about the current directive. cmpp_dx_process() will run that directive, but cmpp_dx_f() implementations sometimes need to make decisions based on the located directive before doing so (and sometimes they need to skip running it). If cmpp_dx_next() finds no directive, the end of the input has been reached and there is no further output to generate. Content encountered before a directive is found is passed on to the output stream via cmpp_dx_out_raw() or cmpp_dx_out_expand(). */ struct cmpp_dx { /** The cmpp object which owns this context. */ cmpp * const pp; /** The directive on whose behalf this context is active. */ cmpp_d const *d; /** Name of the input for error reporting. Typically an input script file name, but it need not refer to a file. */ unsigned const char * const sourceName; /** State related to arguments passed to the current directive. It is important to keep in mind that the memory for the members of this sub-struct may be modified or reallocated (i.e. invalidated) by any APIs which call in to cmpp_dx_next(). cmpp_dx_f() implementations must take care not to use any of this memory after calling into that function, cmpp_dx_consume(), or similar. If needed, it must be copied (e.g. using cmpp_args_clone() to create a local copy of the parsed arguments). */ struct { /** Starting byte of unparsed arguments. This is for cmpp_d_f() implementations which need custom argument parsing. */ unsigned const char * z; /** The byte length of z. */ cmpp_size_t nz; /** The parsed arg count for the this->arg0 list. */ unsigned argc; /** The first parsed arg or NULL. How this is set up is affected by cmpp_d::flags. This is specifically _NOT_ defined as a sequential array and using pointer math to traverse it invokes undefined behavior. To traverse the list: for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){ ... } */ cmpp_arg const * arg0; } args; /** Private impl details. */ cmpp_dx_pimpl * const pimpl; }; /** Thin proxy for cmpp_err_set(), replacing only the first argument. */ CMPP_EXPORT int cmpp_dx_err_set(cmpp_dx *dx, int rc, char const *zFmt, ...); /** Returns true if dx's current call into the API is the result of a function call, else false. Any APIs which recurse into input processing will reset this to false, so it needs to be evaluated before doing any such work. Design note: this flag is actually tied to dx's arguments, which get reset by APIs which consume from the input stream. */ CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx); /** Returns true if dx->pp has error state, else false. If this function returns true, cmpp_dx_f() implementations are required to stop working, clean up any local resources, and return. Continuing to use dx when it's in an error state may exacerbate the problem. */ #define cmpp_dx_err_check(DX) (DX)->pp->api->err_has((DX)->pp) /** Scans dx to the next directive line, emitting all input before that which is _not_ a directive line to dx->pp's output channel unless it's elided due to being inside a block which elides its content (e.g. #if). Returns 0 if no errors were triggered, else a cmpp_rc_e code. This is a no-op if dx->pp has persistent error state set, and that error code is returned. If it returns 0 then it sets *pGotOne to true if a directive was found and false if not (in which case the end of the input has been reached and further calls to this function for the same input source will be no-ops). If it sets *pGotOne to true then it also sets up dx's state for use with cmpp_dx_process(), which should (normally) then be called. ACHTUNG: calling this resets any argument-handling-related state of dx. That is important for cmpp_dx_f() implemenations, which _must not_ hold copies of any pointers from dx->args.arg0 or dx->args.z beyond a call to this function. Any state they need must be evaluated, potentially copied, before calling this function(). */ CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne); /** This is only legal to call immediately after a successful call to cmpp_dx_next(). It requires that cmpp_dx_next() has just located the next directive. This function runs that directive. Returns 0 on success and all that. Design note: directive-Search and directive-process are two distinctly separate steps because directives which have both open/closing tags frequently discard the closing directive without running it (it exists to tell the directive how far to read). Those closing directives exist independently, though, and will trigger errors when encountered outside of the context of their opening directive tag (e.g. an "#/if" without an "#if"). */ CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx); /** A bitmask of flags for use with cmpp_dx_consume() */ enum cmpp_dx_consume_e { /** Tells cmpp_dx_consume() to process any directives it encounters which are not in the specified set of closing directives. Its default is to fail if another directive is seen. */ cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01, /** Tells cmpp_dx_consume() that non-directive content encountered before the designated closing directive(s) must use an at-policy of cmpp_atpol_OFF. That is: the output target of that function will get the raw, unfiltered content. This is for cases where the consumer will later re-emit that content, delaying @token@ parsing until a later step (e.g. #query does this). This may misinteract in unpredictable ways when used with cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs. */ cmpp_dx_consume_F_RAW = 0x02 }; /** A helper for cmpp_dx_f() implementations which read in their blocked-off content instead of passing it through the normal output channel. e.g. `#define x <<` stores that content in a define named "x". This function runs a cmpp_dx_next() loop which does the following: If the given output channel is not NULL then it first replaces the output channel with the given one, such that all output which would normally be produced will be sent there until this function returns, at which point the output channel is restored. If the given channel is NULL then output is not captured - it instead goes dx's current output channel. dClosers must be a list of legal closing tags nClosers entries long. Typically this is the single closing directive/tag of the current directive, available to the opening directive's cmpp_dx_f() impl via dx->d->closer. Some special cases require multiple candidates, however. The flags argument may be 0 or a bitmask of values from the cmpp_dx_consume_e enum. If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then this function requires that the next directive in the input be one specified by dClosers. If the next directive is not one of those, it will fail with code CMPP_RC_SYNTAX. If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it will continue to search for and process directives until the dCloser directive is found. Calling into other directives will invalidate certain state that a cmpp_dx_f() has access to - see below for details. If dCloser is not found before EOF, a CMPP_RC_SYNTAX error is triggered. Once one of dCloser is found, this function returns with dx->d referring to the that directive. In practice, the caller should _not_ call cmpp_dx_process() at that point - the closing directive is typically a no-op placeholder which exists only to mark the end of the block. If the closer has work to do, however, the caller of this function should call cmpp_dx_process() at that point. On success it returns 0, the input stream will have been consumed between the directive dx and its closing tag, and dx->d will point to the new directive. If os is not NULL then os will have been sent any content. On error, processing of the directive must end immediately, returning from the cmpp_dx_f() impl after cleaning up any local resources. ACHTUNG: since this invokes cmpp_dx_next(), it invalidates dx->args.arg0. Its dx->d is also replaced but the previous value remains valid until the cmpp instance is cleaned up. Example from the context of a cmpp_dx_f() implementation ``` // "dx" is the cmpp_dx arg to this function cmpp_outputer oss = cmpp_outputer_b; cmpp_b os = cmpp_b_empty; oss.state = &os; if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 0) ){ cmpp_b_chomp( &os ); ... maybe modify the buffer or decorate the output in some way... cmpp_dx_out_raw(dx, os.z, os.n); } cmpp_b_clear(&os); ``` Design issue: this API does not currently have a way to handle directives which have multiple potential waypoints/endpoints, in the way that an #if may optionally have an #elif or #else before the #/if. Such processing has to be done in the directive's impl. */ CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os, cmpp_d const *const * dClosers, unsigned nClosers, cmpp_flag32_t flags); /** Equivalent to cmpp_dx_consume(), capturing to the given buffer instead of a cmpp_outputer object. */ CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b, cmpp_d const * const * dClosers, unsigned nClosers, cmpp_flag32_t flags); /** If arg is not NULL, cleans up any resources owned by arg but does not free arg. As of this writing, they own none and some code still requires that. That is Olde Thynking, though. */ CMPP_EXPORT void cmpp_arg_cleanup(cmpp_arg *arg); /** If arg is not NULL resets arg to be re-used. arg must have initially been cleanly initialized by copying cmpp_arg_empty (or equivalent, i.e. zeroing it out). */ CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg); /** This is the core argument-parsing function used by the library's provided directives. Its is available in the public API as a convenience for custom cmpp_dx_f() implementations, but custom implementations are not required to make use of it. Populates a cmpp_arg object by parsing the next token from its input source. Expects *pzIn to point to the start of input for parsing arguments and zInEnd to be the logical EOF of that range. This function populates pOut with the info of the parse. Returns 0 on success, non-0 (and updates pp's error state) on error. Output (the parsed token) is written to *pzOut. zOutEnd must be the logical EOF of *pzOut. *pzOut needs to be, at most, (zInEnd-*pzIn)+1 bytes long. This function range checks the output and will not write to or past zOutEnd, but that will trigger a CMPP_RC_RANGE error. On success, *pzIn will be set to 1 byte after the last one parsed for pOut and *pzOut will be set to one byte after the final output (NUL-terminated). pOut->z will point to the start of *pzOut and pOut->n will be set to the byte-length of pOut->z. When the end of the input is reached, this function returns 0 and sets pOut->ttype to cmpp_TT_EOF. General tokenization rules: Tokens come in the following flavors: - Quoted strings: single- or double-quoted. cmpp_arg::ttype: cmpp_TT_String. - "At-strings": @"..." and @'...'. cmpp_arg::ttype value: cmpp_TT_StringAt. - Decimal integers with an optional sign. cmpp_arg::ttype value: cmpp_TT_Int. - Groups: (...), {...}, and [...]. cmpp_arg::ttype values: cmpp_TT_GroupParen, cmpp_TT_GroupSquiggly, and cmpp_TT_GroupBrace. These types do not automatically get parsed recursively. To recurse into one of these, pass cmpp_arg_parse() the grouping argument's bytes as the input range. - Word: anything which doesn't look like one of these above. Token type IDs: cmpp_TT_Word. These are most often interpreted as #define keys but cmpp_dx_f() implementations sometimes treat them as literal values. - A small subset of words and operator-like tokens, e.g. '=' and '!=', get a very specific ttype, e.g. cmpp_TT_OpNeq, but these can generally be treated as strings. - Outside of strings and groups, spaces, tabs, carriage-returns, and newlines are skipped. These are explained in more detail in the user's manual (a.k.a. README.md). There are many other token types, mostly used internally. This function supports _no_ backslash escape sequences in tokens. All backslashes, with the obligatory exception of those which make up backslash-escaped newlines in the input stream, are retained as-is in all token types. That means, for example, that strings may not contain their own quote character. As an example of where this function is useful: cmpp_dx_f() implementations which need to selectively parse a subset of the directive's arguments can use this. As input, their dx argument's args.z and args.nz members delineate the current directive line's arguments. See c-pp.c:cmpp_dx_f_pipe() for an example. */ CMPP_EXPORT int cmpp_arg_parse(cmpp_dx * dx, cmpp_arg *pOut, unsigned char const **pzIn, unsigned char const *zInEnd, unsigned char ** pzOut, unsigned char const * zOutEnd); /** True if (cmpp_arg const *)ARG's contents match the string literal STR, else false. */ #define cmpp_arg_equals(ARG,STR) \ (sizeof(STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1)) /** True if (cmpp_arg const *)ARG's contents match the string literal STR or ("-" STR), else false. The intent is that "-flag" be passed here to tolerantly accept either "-flag" or "--flag". */ #define cmpp_arg_isflag(ARG,STR) \ cmpp_arg_equals(ARG,STR) || cmpp_arg_equals(ARG, "-" STR) /** Creates a copy of arg->z. If allocation fails then pp's persistent error code is set to CMPP_RC_OOM. If pp's error code is not 0 when this is called then this is a no-op and returns NULL. In other words, if this function returns NULL, pp's error state was either already set when this was called or it was set because allocation failed. Ownership of the returned memory is transferred to the caller, who must eventually free it using cmpp_mfree(). */ CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg); /** Flag bitmasks for use with cmpp_arg_to_b(). With my apologies for the long names (but consistency calls for them). */ enum cmpp_arg_to_b_e { /** Specifies that the argument's string value should be used as-is, rather than expanding it (if the arg's ttype would normally cause it to be expanded). */ cmpp_arg_to_b_F_FORCE_STRING = 0x01, /** Tells cmpp_arg_to_b() to not expand arguments with type cmpp_TT_Word, which it normally treats as define keys. It instead treats these as strings. */ cmpp_arg_to_b_F_NO_DEFINES = 0x02, /** If set, arguments with a ttype of cmpp_TT_GroupBrace will be "called" by passing them to cmpp_call_arg(). The space-trimmed result of the call becomes the output of the cmpp_arg_to_b() call. FIXME: make this opt-out instead of opt-in. We end up _almost_ always wanting this. */ cmpp_arg_to_b_F_BRACE_CALL = 0x04, /** Explicitly disable [call] expansion even if cmpp_arg_to_b_F_BRACE_CALL is set in the flags. */ cmpp_arg_to_b_F_NO_BRACE_CALL = 0x08 /** TODO? cmpp_arg_to_b_F_UNESCAPE */ }; /** Appends some form of arg to the given buffer. arg->ttype values of cmpp_TT_Word (define keys) and cmpp_TT_StringAt cause the value to be expanded appropriately (the latter according to dx->pp's current at-policy). Others get emitted as-is. The flags argument influences the expansion decisions, as documented in the cmpp_arg_to_b_e enum. Returns 0 on success and all that. See: cmpp_atpol_get(), cmpp_atpol_set() Reminder to self: though this function may, via script-side function call resolution, recurse into the library, any such recursion gets its own cmpp_dx instance. In this context that's significant because it means this call won't invalidate arg's memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on where args came from - typically it's owned by dx but cmpp_args_clone() exists solely to work around such potential invalidation). */ CMPP_EXPORT int cmpp_arg_to_b(cmpp_dx * dx, cmpp_arg const *arg, cmpp_b * os, cmpp_flag32_t flags); /** Flags for use with cmpp_call_str() and friends. */ enum cmpp_call_e { /** Do not trim a newline from the result. */ cmpp_call_F_NO_TRIM = 0x01, /** Trim all leading and trailing space and newlines from the result. */ cmpp_call_F_TRIM_ALL = 0x02 }; /** This assumes that arg->z holds a "callable" directive string in the form: directiveName ...args This function composes a new cmpp input source from that line (prefixed with dx's current directive prefix if it's not already got one), processes it with cmpp_process_string(), redirecting the output to dest (which gets appended to, so be sure to cmpp_b_reuse() it if needed before calling this). To simplify common expected usage, by default the output is trimmed of a single newline. The flags argument, 0 or a bitmask of values from the cmpp_call_e enum, can be used to modify that behavior. This is the basis of "function calls" in cmpp. Returns 0 on success. */ int cmpp_call_str(cmpp *dx, unsigned char const * z, cmpp_ssize_t n, cmpp_b * dest, cmpp_flag32_t flags); /** Convert an errno value to a cmpp_rc_e approximation, defaulting to dflt if no known match is found. This is intended for use by cmpp_dx_f implementations which use errno-using APIs. */ CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt); /** Configuration object for use with cmpp_d_register(). */ struct cmpp_d_reg { /** The name of the directive as it will be used in input scripts, e.g. "mydirective". It will be copied by cmpp_d_register(). */ char const *name; /** A combination of bits from the cmpp_d_e enum. These flags are currently applied only to this->opener. this->closer, because of how it's typically used, assumes */ struct { /** Callback for the directive's opening tag. */ cmpp_dx_f f; /** Flags from cmpp_d_e. Typically one of cmpp_d_F_ARGS_LIST or cmpp_d_F_ARGS_RAW. */ cmpp_flag32_t flags; } opener; struct { /** Callback for the directive's closing tag, if any. This is only relevant for directives which have both an open and a closing tag (even if that closing tag is only needed in some contexts, e.g. "#define X <<" (with a closer) vs "#define X Y" (without)). See cmpp_dx_f_dangling_closer() for a default implementation which triggers an error if it's seen in the input and not consumed by its counterpart opening directive. That implementation has proven useful for #define, #pipe, and friends. Design notes: it's as yet unclear how to model, in the public interface, directives which have a whole family of cooperating directives, namely #if/#elif/#else. */ cmpp_dx_f f; /** Flags from cmpp_d_e. For closers this can typically be left at 0. */ cmpp_flag32_t flags; } closer; /** If not NULL then it is assigned to the directive's opener part and will be called by the library in either of the following cases: - When the custom directive is cleaned up. - If cmpp_d_register() fails (returns non-0), regardless of how it fails. It is passed this->state. */ cmpp_finalizer_f dtor; /** Implementation state for the callbacks. */ void * state; }; typedef struct cmpp_d_reg cmpp_d_reg; /** Empty-initialized cmpp_d_reg instance, intended for const-copy initialization. */ #define cmpp_d_reg_empty_m {0,{0,0},{0,0},0,0} /** Empty-initialized instance, intended for non-const-copy initialization. */ //extern const cmpp_d_reg cmpp_d_reg_empty; /** Registers a new directive, or a pair of opening/closing directives, with pp. The semantics of r's members are documented in the cmpp_d_reg class. r->name and r->opener.f are required. The remainder may be 0/NULL. Its members are copied - r need not live longer than this call. When the new directive is seen in a script, r->opener.f() will be called. If the closing directive (if any) is seen in a script, r->closer.f() is called. In both cases, the callback implementation can get access to the r->state object via cmdd_dx::d::impl::state (a.k.a dx->d->impl.state). If r->closer.f is not NULL then the closing directive will be named "/${zName}". (Design note: it is thought that forcing a common end-directive syntax will lead to fewer issues than allowing free-form closing tag names, e.g. fewer chances of a name collision or not quite remembering the spelling of a given closing tag (#endef vs #enddefine vs #/define).) Returns 0 on success and updates pp's error state on error. Similarly, this is a no-op if pp has an error code when this is called, in which case it returns that result code without other side-effects. On success, if pOut is not NULL then it is set to the directive pointer, memory owned by pp until it cleans up its directives. This is the only place in the API a non-const pointer to a directive can be found, and it is provided only for very specific use-cases where a directive needs to be manipulated (carefully) after registration[^post-reg-manipulation]. If this function also registered a closing directive, it is available as (*pOut)->closer. pOut should normally be NULL. Failure modes include: - Returns CMPP_RC_RANGE if zName is not legal for use as a directive name. See cmpp_is_legal_key(). - Returns CMPP_RC_OOM on an allocation error. Errors from this function are recoverable (see cmpp_err_set()). A failed registration, even one which doesn't fail until the registration of the closing element, will leave pp in a well-defined state (with neither of r's directives being registered). [^post-reg-manipulation]: The one known use case if the #if family of directives, all of which use the same #/if closing directive. The public registration API does not account for sharing of closers that way, and whether it _should_ is still TBD. The workaround, for this case, is to get the directives as they're registered and point the cmpp_d::closer of each of #if, #elif, and #else to #/if. */ CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r, cmpp_d **pOut); /** A cmpp_dx_f() impl which is intended to be used as a callback for directive closing tags for directives in which the opening tag's implementation consumes the input up to the closing tag. This impl triggers an error if called, indicating that the directive closing was seen in the input without its accompanying directive opening. */ CMPP_EXPORT void cmpp_dx_f_dangling_closer(cmpp_dx *dx); /** Writes the first n bytes of z to dx->pp's current output channel without performing any @token@ parsing. Returns dx->pp's persistent error code (0 on success) and sets that code to non-0 on error. This is a no-op if dx->pp has a non-0 error state, returning that code. See: cmpp_dx_out_expand() */ CMPP_EXPORT int cmpp_dx_out_raw(cmpp_dx * dx, void const *z, cmpp_size_t n); /** Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the given policy says to (else it passes the content through as-is, as per cmpp_dx_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's current policy. A policy of cmpp_atpol_OFF behaves exactly like cmpp_dx_out_raw(). Returns dx->pp's persistent error code (0 on success) and sets that code to non-0 on error. This is a no-op if dx->pp has a non-0 error state, returning that code. If pOut is NULL then dx->pp's default channel is used, with the caveat that atPolicy's only legal value in that case is cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be overridden for that particular output channel, to avoid accidental filtering when it's not enabled. They do not impose that restriction for other output channels, which are frequently used for filtering intermediary results.) See: cmpp_dx_out_raw() Notes regarding how this is used internally: - This function currently specifically does nothing when invoked in skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode except when evaluating #elif expressions (previous #if/#elifs having failed and put us in skip-mode), where it's expanding expression operands. That part currently (as of 2025-10-21) uses dx->pp's current policy, and it's not clear whether that is sufficient or whether we need to force it to expand (and which policy to use when doing so). We could possibly get away with always using cmpp_atpol_ERROR for purposes of evaluating at-string expression operands. [^1]: Skip-mode is the internal mechanism which keeps directives from running, and content from being emitted, within a falsy branch of an #if/#elif block. Only flow-control directives are ever run when skip-mode is active, and client-provided directives cannot easily provide flow-control support. Ergo, much of this paragraph is not relevant for client-level code, but it is for this library's own use of this function. */ CMPP_EXPORT int cmpp_dx_out_expand(cmpp_dx const * dx, cmpp_outputer * pOut, unsigned char const * zFrom, cmpp_size_t n, cmpp_atpol_e policy); /** This creates a formatted string using sqlite3_mprintf() and emits it using cmpp_dx_out_raw(). Returns CMPP_RC_OOM if allocation of the string fails, else it returns whatever cmpp_dx_out_raw() returns. This is a no-op if dx->pp is in an error state, returning that code. */ CMPP_EXPORT int cmpp_dx_outf(cmpp_dx *dx, char const *zFmt, ...); /** Convenience form of cmpp_delimiter_get() which returns the delimiter which was active at the time when the currently-running cmpp_dx_f() was called. This memory may be invalidated by any calls into cmpp_dx_process() or cmpp_delimiter_set(), so a copy of this pointer must not be retained past such a point. This function is primarily intended for use in generating debug and error messages. If the delimiter stack is empty, this function returns NULL. */ CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx); /** Borrows a buffer from pp's buffer recycling pool, allocating one if needed. It returns NULL only on allocation error, in which case it updates pp's error state. This transfers ownership of the buffer to the caller, who is obligated to eventually do ONE of the following: - Pass it to cmpp_b_return() with the same dx argument. - Pass it to cmpp_b_clear() then cmpp_mfree(). The purpose of this function is a memory reuse optimization. Most directives, and many internals, need to use buffers for something or other and this gives them a way to reuse buffers. Potential TODO: How this pool optimizes (or not) buffer allotment is an internal detail. Maybe add an argument which provides a hint about the buffer usage. e.g. argument-conversion buffers are normally small but block content buffers can be arbitrarily large. */ CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *dx); /** Returns a buffer borrowed from cmpp_b_borrow(), transferring ownership back to pp. Passing a non-NULL b which was not returned by cmpp_b_borrow() invoked undefined behavior (possibly delayed until the list is cleaned up). To simplify usage, b may be NULL. After calling this, b must be considered "freed" - it must not be used again. This function is free (as it were) to immediately free the object's memory instead of recycling it. */ CMPP_EXPORT void cmpp_b_return(cmpp *dx, cmpp_b *b); /** If NUL-terminated z matches one of the strings listed below, its corresponding cmpp_atpol_e entry is returned, else cmpp_atpol_invalid is returned. If pp is not NULL then (A) this also sets its current at-policy and (B) it recognizes an additional string (see below). In this case, if z is not a valid string then pp's persistent error state is set. Its accepted values each correspond to a like-named policy value: - "off" (the default): no processing of `@` is performed. - "error": fail if an undefined `X` is referenced in @token@ parsing. - "retain": emit any unresolved `@X@` tokens as-is to the output stream. i.e. `@X@` renders as `@X@`. - "elide": omit unresolved `@X@` from the output, as if their values were empty. i.e. `@X@` renders as an empty string, i.e. is not emitted at all. - "current": if pp!=NULL then it returns the current policy, else this string resolves to cmpp_atpol_invalid. */ CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z); /** Returns pp's current at-token policy. */ CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp); /** Sets pp's current at-token policy. Returns 0 if pol is valid, else it updates pp's error state and returns CMPP_RC_RANGE. This is a no-op if pp has error state, returning that code instead. The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify certain client-side usage. */ CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol); /** Pushes pol as the current at-policy. Returns 0 on success and non-zero on error (bad pol value or allocation error). If this returns 0 then the caller is obligated to eventually call cmpp_atpol_pop() one time. If it returns non-0 then they _must not_ call that function. */ CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol); /** Must be called one time for each successful call to cmpp_atpol_push(). It restores the at-policy to the value it has when cmpp_atpol_push() was last called. If called when no cmpp_delimiter_push() is active then debug builds will fail an assert(), else pp's error state is updated if it has none already. */ CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp); /** The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It behaves identically, just for a different policy group with different names. Its accepted values are: "null" and "error". The value "current" is only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid. */ CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z); /** Returns pp's current policy regarding use of undefined define keys. */ CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp); /** Sets pp's current policy regarding use of undefined define keys. Returns 0 if pol is valid, else it updates pp's error state and returns CMPP_RC_RANGE. */ CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol); /** The undefined-policy counterpart of cmpp_atpol_push(). */ CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol); /** The undefined-policy counterpart of cmpp_atpol_pop(). */ CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp); /** The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen (if zOpen is not NULL) to the opening delimiter and *zClose (if zClose is not NULL) to the closing delimiter. The memory is owned by pp and may be invalidated by any calls to cmpp_atdelim_set(), cmpp_atdelim_push(), or any APIs which consume input. Each string is NUL-terminated and must be copied by the caller if they need these strings past a point where they might be invalidated. If called when the the delimiter stack is empty, debug builds with fail an assert() and non-debug builds will behave as if the stack contains the compile-time default delimiters. */ CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp, char const **zOpen, char const **zClose); /** The `@token@`-delimiter counterpart of cmpp_delimeter_set(). This sets the delimiter for `@token@` content to the given opening and closing strings (which the library makes a copy of). If zOpen is NULL then the compile-time default is assumed. If zClose is NULL then zOpen is assumed. Returns 0 on success. Returns non-0 if called when the delimiter stack is empty, if it cannot copy the string or zDelim is deemed unsuitable for use as a delimiter. In debug builds this will trigger an assert if no `@token@` delimiter has been set, but pp starts with one level in place, so it is safe to call without having made an explicit cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused. */ CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp, char const *zOpen, char const *zClose); /** The `@token@`-delimiter counterpart of cmpp_delimeter_push(). See cmpp_atdelim_set() for the semantics of the arguments. */ CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp, char const *zOpen, char const *zClose); /** The @token@-delimiter counterpart of cmpp_delimiter_pop(). */ CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp); /** Searches the given path (zPath), split on the given path separator (pathSep), for the given file (zBaseName), optionally with the given file extension (zExt). If zBaseName or zBaseName+zExt are found as-is, without any search path prefix, that will be the result, else the result is either zBaseName or zBaseName+zExt prefixed by one of the search directories. On success, returns a new string, transfering ownership to the caller (who must eventually pass it to cmpp_mfree() to deallocate). If no match is found, or on error, returns NULL. On a genuine error, pp's error state is updated and the error is unlikely to be recoverable (see cmpp_err_set()). This function is a no-op if called when pp's error state is set, returning NULL. Results are undefined (in the sense of whether it will work or not, as opposed to whether it will crash or not) if pathSep is a control character. Design note: this is implemented as a Common Table Expression query. */ CMPP_EXPORT char * cmpp_path_search(cmpp *pp, char const *zPath, char pathSep, char const *zBaseName, char const *zExt); /** Scans [*zPos,zEnd) for the next chSep character. Sets *zPos to one after the last consumed byte, so its result includes the separator character unless EOF is hit before then. If pCounter is not NULL then it does ++*pCounter when finding chSep. Returns true if any input is consumed, else false (EOF). When it returns false, *zPos will have the same value it had when this was called. If it returns true, *zPos will be greater than it was before this call and <= zEnd. Usage: ``` unsigned char const * zBegin = ...; unsigned char const * const zEnd = zBegin + strlen(zBegin); unsigned char const * zEol = zBegin; cmpp_size_t nLn = 0; while( cmpp_next_chunk(&zEol, zEnd, '\n', &nLn) ){ ... } ``` */ CMPP_EXPORT bool cmpp_next_chunk(unsigned char const **zPos, unsigned char const *zEnd, unsigned char chSep, cmpp_size_t *pCounter); /** Flags and constants related to the cmpp_args type. */ enum cmpp_args_e { /** cmpp_args_parse() flag which tells cmpp_args_parse() not to dive into (...) group tokens. It insteads leaves them to be parsed (or not) by downstream code. The only reason to parse them in advance is to catch syntax errors sooner rather than later. */ cmpp_args_F_NO_PARENS = 0x01 }; /** An internal detail of cmpp_args. */ typedef struct cmpp_args_pimpl cmpp_args_pimpl; /** A container for parsing a line's worth of cmpp_arg objects. Instances MUST be cleanly initialized by bitwise-copying either cmpp_args_empty or (depending on the context) cmpp_args_empty_m. Instances MUST eventually be passed to cmpp_args_cleanup(). Design notes: this class is provided to the public API as a convenience, not as a core/required component. It offers one of many possible solutions for dealing with argument lists and is not the End All/Be All of solutions. I didn't _really_ want to expose this class in the public API at all but I also want client-side directives to have the _option_ to to do some of the things currently builtin directives can do which are (as of this writing) unavailable in the public API, e.g. evaluate expressions (in that limited form which this library supports). A stepping stone to doing so is making this class public. */ struct cmpp_args { /** Number of parsed args. In the context of a cmpp_dx_f(), argument lists do not include their directive's name as an argument. */ unsigned argc; /** The list of args. This is very specifically NOT an array (or at least not one which client code can rely on to behave sensibly). Some internal APIs adjust a cmpp_args's arg list, re-linking the entries via cmpp_arg::next and making array-style traversal a foot-gun. To loop over them: for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...} This really ought to be const but it currenty cannot be for internal reasons. Client code really should not modify these objects, though. Doing so invokes undefined behavior. For directives with the cmpp_d_F_ARGS_RAW flag, this member will, after a successful call to cmpp_dx_next(), point to a single argument which holds the directive's entire argument string, stripped of leading spaces. */ cmpp_arg * arg0; /** Internal implementation details. This is initialized via cmpp_args_parse() and freed via cmpp_args_cleanup(). */ cmpp_args_pimpl * pimpl; }; typedef struct cmpp_args cmpp_args; /** Empty-initialized cmpp_args instance, intended for const-copy initialization. */ #define cmpp_args_empty_m { \ .argc = 0, \ .arg0 = 0, \ .pimpl = 0 \ } /** Empty-initialized instance, intended for non-const-copy initialization. */ extern const cmpp_args cmpp_args_empty; /** Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg objects by iteratively processing that range with cmpp_arg_parse(). If nIn is negative, strlen() is used to calculate it. Requires that arg be a cleanly-initialized instance (via bitwise-copying cmpp_args_empty) or that it have been successfully used with this function before. Behavior is undefined if pArgs was not properly initialized. The 3rd argument is an optional bitmask of flags from the cmpp_args_e enum. On success it populates arg, noting that an empty list is valid. The memory pointed to by the arguments made available via arg->arg0 is all owned by arg and will be invalidated by either a subsequent call to this function (the memory will be overwritten or reallocated) or cmpp_args_cleanup() (the memory will be freed). On error, returns non-0 and updates pp's error state with info about the problem. */ CMPP_EXPORT int cmpp_args_parse(cmpp_dx * dx, cmpp_args * pOut, unsigned char const * zInBegin, cmpp_ssize_t nIn, cmpp_flag32_t flags); /** Frees any resources owned by its argument but does not free the argument (which is typically stack-allocated). After calling this, the object may again be used with cmpp_args_parse() (in which case it eventually needs to be passed to this again). This is a harmless no-op if `a` is already cleaned up but `a` must not be NULL. */ CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a); /** A wrapper around cmpp_args_parse() which uses dx->args.z as an input source. This is sometimes convenient in cmpp_dx_f() implementations which use cmpp_dx_next(), or similar, to read and process custom directives, as doing so invalidates dx->arg's memory. On success, returns 0 and populates args. On error, returns non-0 and sets dx->pp's error state. cmpp_dx_args_clone() does essentially the same thing, but is more efficient when dx->args.arg0 is is already parsed. */ CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args); /** Populates pOut, replacing any current content, with a copy of each arg in dx->args.arg0 (traversing arg0->next). *pOut MUST be cleanly initialized via copying cmpp_args_empty or it must have previously been used with either cmpp_args_parse() (which has the same initialization requirement) or this function has undefined results. On success, pOut->argc and pOut->arg0 will refer to pOut's copy of the arguments. Copying of arguments is necessary in cmpp_dx_f() implementations which need to hold on to arguments for use _after_ calling cmpp_dx_next() or any API which calls that (which most directives don't do). See that function for why. */ CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut); /** Flags for cmpp_popen(). */ enum cmpp_popen_e { /** Use execl[p](CMD, CMD,0) instead of execl[p]("/bin/sh","-c",CMD,0). */ cmpp_popen_F_DIRECT = 0x01, /** Use execlp() or execvp() instead of execl() or execv(). */ cmpp_popen_F_PATH = 0x02 }; /** Result state for cmpp_popen() and friends. */ struct cmpp_popen_t { /** The child process ID. */ int childPid; /** The child process's stdout. */ int fdFromChild; /** If not NULL, cmpp_popen() will set *fpToChild to a FILE handle mapped to the child process's stdin. If it is NULL, the child process's stdin will be closed instead. */ cmpp_FILE **fpToChild; }; typedef struct cmpp_popen_t cmpp_popen_t; /** Empty-initialized cmpp_popen_t instance, intended for const-copy initialization. */ #define cmpp_popen_t_empty_m {-1,-1,0} /** Empty-initialized instance, intended for non-const-copy initialization. */ extern const cmpp_popen_t cmpp_popen_t_empty; /** Uses fork()/exec() to run a command in a separate process and open a two-way stream to it. It is provided in this API to facilitate the creation of custom directives which shell out to external processes. zCmd must contain the NUL-terminated command to run and any flags for that command, e.g. "myapp --flag --other-flag". It is passed as the 4th argument to: execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL) The po object MUST be cleanly initialized before calling this by bitwise copying cmpp_popen_t_empty or (depending on the context) cmpp_popen_t_empty_m. Flags: - cmpp_popen_F_DIRECT: zCmd is passed to execl(zCmd, zCmd, NULL). instead of exec(). That can only work if zCmd is a single command without arguments. - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which performs path lookup of its initial argument. Again, that can only work if zCmd is a single command without arguments. On success: - po->childPid will be set to the PID of the child process. - po->fdFromChild is set to the child's stdout file descriptor. read(2) from it to read from the child. - If po->fpToChild is not NULL then *po->fpToChild is set to a buffered output handle to the child's stdin. fwrite(3) to it to send the child stuff. Be sure to fflush(3) and/or fclose(3) it to keep it from hanging forever. If po->fpToChild is NULL then the stdin of the child is closed. (Why buffered instead of unbuffered? My attempts at getting unbuffered child stdin to work have all failed when write() is called on it.) On success, the caller is obligated to pass po to cmpp_pclose(). The caller may pass pi to cmpp_pclose() on error, if that's easier for them, provided that the po argument was cleanly initialized before passing it to this function. If the caller fclose(3)s *po->fpToChild then they must set it to NULL so that passing it to cmpp_pclose() knows not to close it. On error: you know the drill. This function is a no-op if pp has error state when it's called, and the current error code is returned instead. This function is only available on non-WASM Unix-like environments. On others it will always trigger a CMPP_RC_UNSUPPORTED error. Bugs: because the command is run via /bin/sh -c ... we cannot tell if it's actually found. All we can tell is that /bin/sh ran. Also: this doesn't capture stderr, so commands should redirect stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is a potential TODO without a current use case. See: cmpp_pclose() See: cmpp_popenv() */ CMPP_EXPORT int cmpp_popen(cmpp *pp, unsigned char const *zCmd, cmpp_flag32_t flags, cmpp_popen_t *po); /** Works like cmpp_popen() except that: - It takes it arguments in the form of a main()-style array of strings because it uses execv() instead of exec(). The cmpp_popen_F_PATH flag causes it to use execvp(). - It does not honor the cmpp_popen_F_DIRECT flag because all arguments have to be passed in via the arguments array. As per execv()'s requirements: azCmd _MUST_ end with a NULL entry. */ CMPP_EXPORT int cmpp_popenv(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags, cmpp_popen_t *po); /** Closes handles returned by cmpp_popen() and zeroes out po. If the caller fclose()d *po->fpToChild then they need to set it to NULL so that this function does not double-close it. Returns the result code of the child process. After calling this, po may again be used as an argument to cmpp_popen(). */ CMPP_EXPORT int cmpp_pclose(cmpp_popen_t *po); /** A cmpp_popenv() proxy which builds up an execv()-style array of arguments from the given args. It has a hard, and mostly arbitrary, upper limit on the number of args it can take in order to avoid extra allocation. */ CMPP_EXPORT int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args, cmpp_popen_t *p); /** Callback type for use with cmpp_kav_each(). cmpp_kav_each() calls this one time per key/value in such a list, passing it the relevant key/value strings and lengths, plus the opaque state pointer which is passed to cmpp_kav_each(). Must return 0 on success or update (or propagate) dx->pp's error state on error. */ typedef int cmpp_kav_each_f( cmpp_dx *dx, unsigned char const *zKey, cmpp_size_t nKey, unsigned char const *zVal, cmpp_size_t nVal, void* callbackState ); /** Flag bitmask for use with cmpp_kav_each() and cmpp_str_each(). */ enum cmpp_kav_each_e { /** The key argument should be expanded using cmpp_arg_to_b() with a 0 flags value. This flag should normally not be used. */ cmpp_kav_each_F_EXPAND_KEY = 0x01, /** The key argument should be expanded using cmpp_arg_to_b() with a 0 flags value. This flag should normally be used. */ cmpp_kav_each_F_EXPAND_VAL = 0x02, /** Treat (...) value tokens (ttype=cmpp_TT_GroupParen) as integer expressions. Keys are never treated this way. Without this flag, the token expands to the ... part of (...). */ cmpp_kav_each_F_PARENS_EXPR = 0x04, /** Indicates that an empty input list is an error. If this flag is not set and the list is empty, the callback will not be called and no error will be triggered. */ cmpp_kav_each_F_NOT_EMPTY = 0x08, /** Indicates that the list does not have the '->' part(s). That is, the list needs to be in pairs of KEY VAL rather than triples of KEY -> VALUE. */ cmpp_kav_each_F_NO_ARROW = 0x10, /** If set, keys get the cmpp_arg_to_b_F_BRACE_CALL flag added to them. This implies cmpp_kav_each_F_EXPAND_KEY. */ cmpp_kav_each_F_CALL_KEY = 0x20, /** Value counterpart of cmpp_kav_each_F_CALL_KEY. */ cmpp_kav_each_F_CALL_VAL = 0x40, /** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */ cmpp_kav_each_F_CALL = 0x60, //TODO: append to defines which already exist cmpp_kav_each_F_APPEND = 0, cmpp_kav_each_F_APPEND_SPACE = 0, cmpp_kav_each_F_APPEND_NL = 0 }; /** A helper for cmpp_dx_f() implementations in processing directive arguments which are lists in this form: { a -> b c -> d ... } ("kav" is short for "key arrow value".) The range [zBegin,zBegin+nIn) contains the raw list (not including any containing braces, parentheses, quotes, or the like). If nIn is negative, strlen() is used to calculate it. The range is parsed using cmpp_args_parse(). For each key/arrow/value triplet in that list, callback() is passed the stringified form of the key and the value, plus the callbackState pointer. The flags argument controls whether the keys and values get expanded or not. (Typically the keys should not be expanded but the values should.) Returns 0 on success. If the callback returns non-0, it is expected to have updated dx's error state. callback() will never be called when dx's error state is non-0. Error results include: - CMPP_RC_RANGE: the list is empty does not contain the correct number of entries (groups of 3, or 2 if flags has cmpp_kav_each_F_NO_ARROW). - CMPP_RC_OOM: allocation error. - Any value returned by cmpp_args_parse(). - Any number of errors can be triggered during expansion of keys and values. */ CMPP_EXPORT int cmpp_kav_each( cmpp_dx *dx, unsigned char const *zBegin, cmpp_ssize_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags ); /** This works like cmpp_kav_each() except that it treats each token in the list as a single entry. When the callback is triggered, the "key" part will be the raw token and the "value" part will be the expanded form of that value. Its flags may contain most of the cmpp_kav_each_F_... flags, with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the callback receives the same string for both the key and value. */ CMPP_EXPORT int cmpp_str_each( cmpp_dx *dx, unsigned char const *zBegin, cmpp_ssize_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags ); /** An interface for clients to provide directives to the library on-demand. This is called when pp has encountered a directive name is does not know. It is passed the cmpp object, the name of the directive, and the opaque state pointer which was passed to cmpp_d_autoloader_et(). Implementations should compare dname to any directives they know about. If they find no match they must return CMPP_RC_NO_DIRECTIVE _without_ using cmpp_err_set() to make the error persistent. If they find a match, they must use cmpp_d_register() to register it and (on success) return 0. The library will then look again in the registered directive list for the directive before giving up. If they find a match but registration fails then the result of that failure must be returned. For implementation-specific errors, e.g. trying to load a directive from a DLL but the loading of the DLL fails, implementations are expected to use cmpp_err_set() to report the error and to return that result code after performing any necessary cleanup. It is legal for an implementation to register multiple directives in a single invocation (in particular a pair of opening/closing directives), as well as to register directives other than the one requested (if necessary). Regardless of which one(s) it registers, it must return 0 only if it registers one named dname. */ typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state); /** A c-pp directive "autoloader". See cmpp_d_autoloader_set() and cmpp_d_autoloader_take(). */ struct cmpp_d_autoloader { /** The autoloader callback. */ cmpp_d_autoloader_f f; /** Finalizer for this->state. After calling this, if there's any chance that this object might be later used, then it is important that this->state be set to 0 (which this finalizer cannot do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty over any instances immediately after calling dtor(). */ cmpp_finalizer_f dtor; /** Implementation-specific state, to be passed as the final argument to this->f and this->dtor. */ void * state; }; typedef struct cmpp_d_autoloader cmpp_d_autoloader; /** Empty-initialized cmpp_d_autoloader instance, intended for const-copy initialization. */ #define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0} /** Empty-initialized cmpp_d_autoloader instance, intended for non-const-copy initialization. */ extern const cmpp_d_autoloader cmpp_d_autoloader_empty; /** Sets pp's "directive autoloader". Each cmpp instance has but a single autoloader but this API is provided so that several instances may be chained from client-side code. This function will call the existing autoloader's destructor (if any), invalidating any pointers to its state object. If pNew is not NULL then pp's autoloader is set to a bitwise copy of *pNew, otherwise it is zeroed out. This transfers ownership of pNew->state to pp. See cmpp_d_autoloader_f()'s docs for how pNew must behave. This function has no error conditions but downstream results are undefined if if pNew and an existing autoloader refer to the same dtor/state values (a gateway to double-frees). */ CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew); /** Moves pp's current autoloader state into pOld, transerring ownership of it to the caller. This obligates the caller to eventually either pass that same pointer to cmpp_d_autoloader_set() (to transfer ownership back to pp) or to call pOld->dtor() (if it's not NULL), passing it it pOld->state (even if pOld->state is NULL). In either case, all contents of pOld are semantically invalidated and perhaps freed. This would normally be a prelude to cmpp_d_autoloader_set() to install a custom, perhaps chained, autoloader. */ CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld); /** True only for ' ' and '\t'. */ CMPP_EXPORT bool cmpp_isspace(int ch); /** Reassigns *p to the address of the first non-space character at or after the initial *p value. It stops looking if it reaches zEnd. If `*p` does not point to memory before zEnd, or is not a part of the same logical string, results are undefined. Achtung: do not pass this the address of a cmpp_b::z, or similar, as that will effectively corrupt the buffer's memory. To trim a whole buffer, use something like: ``` cmpp_b ob = cmpp_b_empty; ... populate ob...; // get the trimmed range: unsigned char const *zB = ob.z; unsigned char const *zE = zB + n; cmpp_skip_snl(&zB, zE); assert( zB<=zE ); cmpp_skip_snl_trailing(zB, &zE); assert( zE>=zB ); printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB); ``` Those assert()s are not error handling - they're demonstrating invariants of the calls made before them. */ CMPP_EXPORT void cmpp_skip_space( unsigned char const **p, unsigned char const *zEnd ); /** Works just like cmpp_skip_space() but it also skips newlines. FIXME (2026-02-21): it does not recognize CRNL pairs as atomic newlines. */ CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p, unsigned char const *zEnd ); /** "Trims" trailing cmpp_isspace() characters from the range [zBegin, *p). *p must initially point to one byte after the end of zBegin (i.e. its NUL byte or virtual EOF). Upon return *p will be modified leftwards (if at all) until a non-space is found or *p==zBegin. */ CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin, unsigned char const **p ); /** Works just like cmpp_skip_space_trailing() but skips cmpp_skip_snl() characters. FIXME (2026-02-21): it does not recognize CRNL pairs as atomic newlines. */ CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin, unsigned char const **p ); /** Generic array-of-T list memory-reservation routine. *list is the input array-of-T. nDesired is the number of entries to reserve (list entry count, not byte length). *nAlloc is the number of entries allocated in the list. sizeOfEntry is the sizeof(T) for each entry in *list. T may be either a value type or a pointer type and sizeofEntry must match, i.e. it must be sizeof(T*) for a list-of-pointers and sizeof(T) for a list-of-objects. If pp is not NULL then this function updates pp's error state on error, else it simply returns CMPP_RC_OOM on error. If pp is not NULL then this function is a no-op if called when pp's error state is set, returning that code without other side-effects. If nDesired > *nAlloc then *list is reallocated to contain at least nDesired entries, else this function returns without side effects. On success *list is re-assigned to the reallocated list memory, all newly-(re)allocated memory is zeroed out, and *nAlloc is updated to the new allocation size of *list (the number of list entries, not the number of bytes). On failure neither *list nor *nAlloc are modified. Returns 0 on success or CMPP_RC_OOM on error. Errors generated by this routine are, at least in principle, recoverable (see cmpp_err_set()), though that simply means that the pp object is left in a well-defined state, not that the app can necessarily otherwise recover from an OOM. This seemingly-out-of-API-scope routine is in the public API as a convenience for client-level cmpp_dx_f() implementations[^1]. This API internally has an acute need for basic list management and non-core extensions inherit that as well. [^1]: this project's own directives are written as if they were client-side whenever feasible. Some require cmpp-internal state to do their jobs, though. */ CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired, cmpp_size_t * nAlloc, unsigned sizeOfEntry); /** The current cmpp_api_thunk::apiVersion value. See cmpp_api_thunk_map. */ #define cmpp_api_thunk_version 20260206 /** A helper for use with cmpp_api_thunk. V() defines the API version number. It invokes V(NAME,TYPE,VERSION) once. NAME is the member name for the cmpp_api_thunk struct. TYPE is an integer type. VERSION is the cmpp_api_thunk object version. This is initially 0 and will eventually be given a number which increments which new members appended. This is to enable DLLs to check whether their cmpp_api_thunk object has the methods they're looking for. Then it invokes F(NAME,RETTYPE,PARAMS) and O(NAME,TYPE) once for each cmpp_api_thunk member in an unspecified order, and and A(VERSION) an arbitrary number of times. F() is for functions. O() is for objects, which are exposed here as pointers to those objects so that we don't copy them. A() is injected at each point where a new API version was introduced, and that number (an integer) is its only argument. A()'s definition can normally be empty. In all cases, NAME is the public API symbol name minus the "cmpp_" prefix. RETTYPE is the function return type or object type. PARAMS is the function parameters, wrapped in (...). For O(), TYPE is the const-qualified type of the object referred to by NAME. cmpp_api_thunk necessarily exposes those as pointers, but that pointer is not part of the TYPE argument. See cmpp_api_thunk for details. In order to help DLLs to not inadvertently use invalid areas of the API object by referencing members which they loading c-pp version does not have, this list must only ever be modified by appending to it. That enables DLLs to check their compile-time cmpp_api_thunk_version against the dx->pp->api->apiVersion. If the runtime version is older (less than) than their compile-time version, the DLL must not access any methods added after dx->pp->api->apiVersion. */ #define cmpp_api_thunk_map(A,V,F,O) \ A(0) \ V(apiVersion,unsigned,cmpp_api_thunk_version) \ F(mrealloc,void *,(void * p, size_t n)) \ F(malloc,void *,(size_t n)) \ F(mfree,void,(void *)) \ F(ctor,int,(cmpp **pp, cmpp_ctor_cfg const *)) \ F(dtor,void,(cmpp *pp)) \ F(reset,void,(cmpp *pp)) \ F(check_oom,int,(cmpp * const pp, void const * m)) \ F(is_legal_key,bool,(unsigned char const *, cmpp_size_t n, \ unsigned char const **)) \ F(define_legacy,int,(cmpp *, const char *,char const *)) \ F(define_v2,int,(cmpp *, const char *, char const *)) \ F(undef,int,(cmpp *, const char *, unsigned int *)) \ F(define_shadow,int,(cmpp *, char const *, char const *, \ int64_t *)) \ F(define_unshadow,int,(cmpp *, char const *, int64_t)) \ F(process_string,int,(cmpp *, const char *, \ unsigned char const *, cmpp_ssize_t)) \ F(process_file,int,(cmpp *, const char *)) \ F(process_stream,int,(cmpp *, const char *, \ cmpp_input_f, void *)) \ F(process_argv,int,(cmpp *, int, char const * const *)) \ F(err_get,int,(cmpp *, char const **)) \ F(err_set,int,(cmpp *, int, char const *, ...)) \ F(err_set1,int,(cmpp *, int, char const *)) \ F(err_has,int,(cmpp const *)) \ F(is_safemode,bool,(cmpp const *)) \ F(sp_begin,int,(cmpp *)) \ F(sp_commit,int,(cmpp *)) \ F(sp_rollback,int,(cmpp *)) \ F(output_f_FILE,int,(void *, void const *, cmpp_size_t)) \ F(output_f_fd,int,(void *, void const *, cmpp_size_t)) \ F(input_f_FILE,int,(void *, void *, cmpp_size_t *)) \ F(input_f_fd,int,(void *, void *, cmpp_size_t *)) \ F(flush_f_FILE,int,(void *)) \ F(stream,int,(cmpp_input_f, void *, \ cmpp_output_f, void *)) \ F(slurp,int,(cmpp_input_f, void *, \ unsigned char **, cmpp_size_t *)) \ F(fopen,cmpp_FILE *,(char const *, char const *)) \ F(fclose,void,(cmpp_FILE * )) \ F(outputer_out,int,(cmpp_outputer *, void const *, cmpp_size_t)) \ F(outputer_flush,int,(cmpp_outputer *)) \ F(outputer_cleanup,void,(cmpp_outputer *)) \ F(outputer_cleanup_f_FILE,void,(cmpp_outputer *)) \ F(delimiter_set,int,(cmpp *, char const *)) \ F(delimiter_get,void,(cmpp const *, char const **)) \ F(chomp,bool,(unsigned char *, cmpp_size_t *)) \ F(b_clear,void,(cmpp_b *)) \ F(b_reuse,cmpp_b *,(cmpp_b *)) \ F(b_swap,void,(cmpp_b *, cmpp_b *)) \ F(b_reserve,int,(cmpp_b *, cmpp_size_t)) \ F(b_reserve3,int,(cmpp *, cmpp_b *,cmpp_size_t)) \ F(b_append,int,(cmpp_b *, void const *,cmpp_size_t)) \ F(b_append4,int,(cmpp *,cmpp_b *,void const *, \ cmpp_size_t)) \ F(b_append_ch, int,(cmpp_b *, char)) \ F(b_append_i32,int,(cmpp_b *, int32_t)) \ F(b_append_i64,int,(cmpp_b *, int64_t)) \ F(b_chomp,bool,(cmpp_b *)) \ F(output_f_b,int,(void *, void const *,cmpp_size_t)) \ F(outputer_cleanup_f_b,void,(cmpp_outputer *self)) \ F(version,char const *,(void)) \ F(tt_cstr,char const *,(int tt)) \ F(dx_err_set,int,(cmpp_dx *dx, int rc, char const *zFmt, ...)) \ F(dx_next,int,(cmpp_dx * dx, bool * pGotOne)) \ F(dx_process,int,(cmpp_dx * dx)) \ F(dx_consume,int,(cmpp_dx *, cmpp_outputer *, \ cmpp_d const *const *, unsigned, cmpp_flag32_t)) \ F(dx_consume_b,int,(cmpp_dx *, cmpp_b *, cmpp_d const * const *, \ unsigned, cmpp_flag32_t)) \ F(arg_parse,int,(cmpp_dx * dx, cmpp_arg *, \ unsigned char const **, unsigned char const *, \ unsigned char ** , unsigned char const * )) \ F(arg_strdup,char *,(cmpp *pp, cmpp_arg const *arg)) \ F(arg_to_b,int,(cmpp_dx * dx, cmpp_arg const *arg, \ cmpp_b * os, cmpp_flag32_t flags)) \ F(errno_rc,int,(int errNo, int dflt)) \ F(d_register,int,(cmpp * pp, cmpp_d_reg const * r, cmpp_d **pOut)) \ F(dx_f_dangling_closer,void,(cmpp_dx *dx)) \ F(dx_out_raw,int,(cmpp_dx * dx, void const *z, cmpp_size_t n)) \ F(dx_out_expand,int,(cmpp_dx const * dx, cmpp_outputer * pOut, \ unsigned char const * zFrom, cmpp_size_t n, \ cmpp_atpol_e policy)) \ F(dx_outf,int,(cmpp_dx *dx, char const *zFmt, ...)) \ F(dx_delim,char const *,(cmpp_dx const *dx)) \ F(atpol_from_str,cmpp_atpol_e,(cmpp * pp, char const *z)) \ F(atpol_get,cmpp_atpol_e,(cmpp const * const pp)) \ F(atpol_set,int,(cmpp * const pp, cmpp_atpol_e pol)) \ F(atpol_push,int,(cmpp * pp, cmpp_atpol_e pol)) \ F(atpol_pop,void,(cmpp * pp)) \ F(unpol_from_str,cmpp_unpol_e,(cmpp * pp,char const *z)) \ F(unpol_get,cmpp_unpol_e,(cmpp const * const pp)) \ F(unpol_set,int,(cmpp * const pp, cmpp_unpol_e pol)) \ F(unpol_push,int,(cmpp * pp, cmpp_unpol_e pol)) \ F(unpol_pop,void,(cmpp * pp)) \ F(path_search,char *,(cmpp *pp, char const *zPath, char pathSep, \ char const *zBaseName, char const *zExt)) \ F(args_parse,int,(cmpp_dx * dx, cmpp_args * pOut, \ unsigned char const * zInBegin, \ cmpp_ssize_t nIn, cmpp_flag32_t flags)) \ F(args_cleanup,void,(cmpp_args *a)) \ F(dx_args_clone,int,(cmpp_dx * dx, cmpp_args *pOut)) \ F(popen,int,(cmpp *, unsigned char const *, cmpp_flag32_t, \ cmpp_popen_t *)) \ F(popenv,int,(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags, \ cmpp_popen_t *po)) \ F(pclose,int,(cmpp_popen_t *po)) \ F(popen_args,int,(cmpp_dx *, cmpp_args const *, cmpp_popen_t *)) \ F(kav_each,int, (cmpp_dx *,unsigned char const *, cmpp_ssize_t, \ cmpp_kav_each_f, void *, cmpp_flag32_t)) \ F(d_autoloader_set,void,(cmpp *pp, cmpp_d_autoloader const * pNew)) \ F(d_autoloader_take,void,(cmpp *pp, cmpp_d_autoloader * pOld)) \ F(isspace,bool,(int ch)) \ F(skip_space,void,(unsigned char const **, unsigned char const *)) \ F(skip_snl,void,(unsigned char const **, unsigned char const *)) \ F(skip_space_trailing,void,(unsigned char const *zBegin, \ unsigned char const **p)) \ F(skip_snl_trailing,void,(unsigned char const *zBegin, \ unsigned char const **p)) \ F(array_reserve,int,(cmpp *pp, void **list, cmpp_size_t nDesired, \ cmpp_size_t * nAlloc, unsigned sizeOfEntry)) \ F(module_load,int,(cmpp *, char const *,char const *)) \ F(module_dir_add,int,(cmpp *, const char *)) \ O(outputer_FILE,cmpp_outputer const) \ O(outputer_b,cmpp_outputer const) \ O(outputer_empty,cmpp_outputer const) \ O(b_empty,cmpp_b const) \ A(20251116) \ F(next_chunk,bool,(unsigned char const **,unsigned char const *, \ unsigned char,cmpp_size_t*)) \ A(20251118) \ F(atdelim_get,void,(cmpp const *,char const **,char const **)) \ F(atdelim_set,int,(cmpp *,char const *,char const *)) \ F(atdelim_push,int,(cmpp *,char const *,char const *)) \ F(atdelim_pop,int,(cmpp *)) \ A(20251224) \ F(dx_pos_save,void,(cmpp_dx const *, cmpp_dx_pos *)) \ F(dx_pos_restore,void,(cmpp_dx *, cmpp_dx_pos const *)) \ A(20260130) \ F(dx_is_call,bool,(cmpp_dx * const)) \ A(20260206) \ F(b_borrow,cmpp_b *,(cmpp *dx)) \ F(b_return,void,(cmpp *dx, cmpp_b*)) \ A(1+cmpp_api_thunk_version) /** Callback signature for cmpp module import routines. This is called by the library after having first encountering this module (typically after looking for it in a DLL, but static instances are supported). The primary intended purpose of this interface is for implementations to call cmpp_d_register() (any number of times). It is also legal to use APIs which set or query defines. This interface is not intended to interact with pp's I/O in any way (that's the job of the directives which these functions register). Violating that will invoke undefined results, perhaps stepping on the toes of any being-processed directive which triggered the dynamic load of this directive. Errors in module initialization must be reported via cmpp_err_set() and that code must be returned. Implementations must typically call cmpp_api_init(pp) as their first operation. See the files named d-*.c in libcmpp's source tree for examples. */ typedef int (*cmpp_module_init_f)(cmpp * pp); /** Holds information for mapping a cmpp_module_init_f to a name. Its purpose is to get installed by the CMPP_MODULE_xxx family of macros and referenced later via a module-loading mechanism. */ struct cmpp_module{ /** Symbolic name of the module. */ char const * name; /** The initialization routine for the module. */ cmpp_module_init_f init; }; /** Convenience typedef. */ typedef struct cmpp_module cmpp_module; /** @def CMPP_MODULE_DECL Declares an extern (cmpp_module*) symbol called cmpp_module__#\#CNAME. Use CMPP_MODULE_IMPL2() or CMPP_MODULE_IMPL3() to create the matching implementation code. This macro should be used in the C or H file for a loadable module. It may be compined in a file with a single CMPP_MODULE_IMPL_SOLO() declaration with the same name, such that the module can be loaded both with and without the explicit symbol name. */ #define CMPP_MODULE_DECL(CNAME) \ extern const cmpp_module * cmpp_module__##CNAME /** @def CMPP_MODULE_IMPL Intended to be used to implement module declarations. If a module has both C and H files, CMPP_MODULE_DECL(CNAME) should be used in the H file and CMPP_MODULE_IMPL2() should be used in the C file. If the DLL has only a C file (or no public H file), CMPP_MODULE_DECL is unnecessary. If the module's human-use name is a legal C identifier, CMPP_MODULE_IMPL2() is slightly easier to use than this macro. Implements a static cmpp_module object named cmpp_module__#\#CNAME#\#_impl and a non-static (cmpp_module*) named cmpp_module__#\#CNAME which points to cmpp_module__#\#CNAME#\#_impl. (The latter symbol may optionally be declared in a header file via CMPP_MODULE_DECL.) NAME is used as the cmpp_module::name value. INIT_F must be a cmpp_module_init_f() function pointer. That function is called when cmpp_module_load() loads the module. This macro may be combined in a file with a single CMPP_MODULE_IMPL_SOLO() declaration using the same CNAME value, such that the module can be loaded both with and without the explicit symbol name. Example usage, in a module's header file, if any: ``` CMPP_MODULE_DECL(mymodule); ``` (The declaration is not strictly necessary - it is more of a matter of documentation.) And in the C file: ``` CMPP_MODULE_IMPL3(mymodule,"mymodule",mymodule_install); // OR: CMPP_MODULE_IMPL2(mymodule,mymodule_install); ``` If it will be the only module in the target DLL, one can also add this: ``` CMPP_MODULE_IMPL2(mymodule,mymodule_install); // _OR_ (every so slightly different): CMPP_MODULE_STANDALONE_IMPL2(mymodule,mymodule_install); ``` Which simplifies client-side module loading by allowing them to leave out the module name when loading, but that approach only works if modules are compiled one per DLL (as opposed to being packaged together in one DLL). @see CMPP_MODULE_DECL @see CMPP_MODULE_IMPL_SOLO */ #define CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F) \ static const cmpp_module \ cmpp_module__##CNAME##_impl = { NAME, INIT_F }; \ const cmpp_module * \ cmpp_module__##CNAME = &cmpp_module__##CNAME##_impl /** @def CMPP_MODULE_IMPL3 A simplier form of CMPP_MODULE_IMPL3() for cases where a module name is a legal C symbol name. */ #define CMPP_MODULE_IMPL2(CNAME,INIT_F) \ CMPP_MODULE_IMPL3(CNAME,#CNAME,INIT_F) /** @def CMPP_MODULE_IMPL_SOLO Implements a static cmpp_module symbol called cmpp_module1_impl and a non-static (cmpp_module*) named cmpp_module1 which points to cmpp_module1_impl INIT_F must be a cmpp_module_init_f. This macro must only be used in the C file for a loadable module when that module is to be the only one in the resuling DLL. Do not use it when packaging multiple modules into one DLL: use CMPP_MODULE_IMPL for those cases (CMPP_MODULE_IMPL can also be used together with this macro). @see CMPP_MODULE_IMPL @see CMPP_MODULE_DECL @see CMPP_MODULE_STANDALONE_IMPL */ #define CMPP_MODULE_IMPL_SOLO(NAME,INIT_F) \ static const cmpp_module \ cmpp_module1_impl = { NAME, INIT_F }; \ const cmpp_module * cmpp_module1 = &cmpp_module1_impl /** @def CMPP_MODULE_STANDALONE_IMPL CMPP_MODULE_STANDALONE_IMPL2() works like CMPP_MODULE_IMPL_SOLO() but is only fully expanded if the preprocessor variable CMPP_MODULE_STANDALONE is defined (to any value). If CMPP_MODULE_STANDALONE is not defined, this macro expands to a dummy placeholder which does nothing (but has to expand to something to avoid leaving a trailing semicolon in the C code, which upsets the compiler (the other alternative would be to not require a semicolon after the macro call, but that upsets emacs' sense of indentation (and keeping emacs happy is more important than keeping compilers happy (all of these parens are _not_ a reference to emacs lisp, by the way)))). This macro may be used in the same source file as CMPP_MODULE_IMPL. The intention is that DLLs prefer this option over CMPP_MODULE_IMPL_SOLO, to allow that the DLLs can be built as standalone DLLs, multi-plugin DLLs, and compiled directly into a project (in which case the code linking it in needs to resolve and call the cmpp_module entry for each built-in module). @see CMPP_MODULE_IMPL_SOLO @see CMPP_MODULE_REGISTER */ #if defined(CMPP_MODULE_STANDALONE) # define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \ CMPP_MODULE_IMPL_SOLO(NAME,INIT_F) //arguably too much magic in one place: //# if !defined(CMPP_API_THUNK) //# define CMPP_API_THUNK //# endif #else # define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \ extern void cmpp_module__dummy_does_not_exist__(void) #endif /** @def CMPP_MODULE_REGISTER3 Performs all the necessary setup for registering a loadable module, including declaration and definition. NAME is the stringified name of the module. This is normally called immediately after defining the plugin's init func (which is passed as the 3rd argument to this macro). See CMPP_MODULE_IMPL3() and CMPP_MODULE_STANDALONE_IMPL2() for the fine details. */ #define CMPP_MODULE_REGISTER3(CNAME,NAME,INIT_F) \ CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F); \ CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) /** Slight convenience form of CMPP_MODULE_REGISTER3() which assumes a registration function name of cpp_ext_${CNAME}_register(). */ #define CMPP_MODULE_REGISTER2(CNAME,NAME) \ CMPP_MODULE_REGISTER3(CNAME,NAME,cmpp_module__ ## CNAME ## _register) /** Slight convenience form of CMPP_MODULE_REGISTER2() for cases when CNAME and NAME are the same. */ #define CMPP_MODULE_REGISTER1(CNAME) \ CMPP_MODULE_REGISTER3(CNAME,#CNAME,cmpp_module__ ## CNAME ## _register) /** This looks for a DLL file named fname. If found, it is dlopen()ed (or equivalent) and searched for a symbol named symName. If found, it is assumed to be a cmpp_module instance and its init() method is invoked. If fname is NULL then the module is looked up in the currently-running program. If symName is NULL then the name "cmpp_module1" is assumed, which is the name used by CMPP_MODULE_IMPL_SOLO() and friends (for use when a module is the only one in its DLL). If no match is found, or there's a problem loading the DLL or resolving the name, non-0 is returned. Similarly, if the init() method fails, non-0 is returned. The file name is searched using the cmpp_module_dir_add() path, and if fname is an exact match, or an exact when the system's conventional DLL file extension is appended to it, that is used rather than any potential match from the search path. On error, pp's error state will contain more information. It's indeterminate which errors from this API are recoverable. This function is a no-op if called when pp's error state is set, returning that code. If built without module-loading support then this will always fail with CMPP_RC_UNSUPPORTED. */ CMPP_EXPORT int cmpp_module_load(cmpp * pp, char const * fname, char const * symName); /** Adds the directory or directories listed in zDirs to the search path used by cmpp_module_load(). The entries are expected to be either colon- or semicolon-delimited, depending on the platform the library was built for. If zDirs is NULL and pp's library path is empty then it looks for the environment variable CMPP_MODULE_PATH. If that is set, it is used in place of zDirs, otherwise the library's compile-time default is used (as set by the CMPP_MODULE_PATH compile-time value, which defaults to ".:$prefix/lib/cmpp" in the canonical builds). This should only be done once per cmpp instance, as the path will otherwise be extended each time. (The current list structure does not make it easy to recognize duplicates.) Returns 0 on success or if zDirs is empty. Returns CMPP_RC_OOM on allocation error (ostensibly recoverable - see cmpp_err_set()). This is a no-op if called when pp has error state, returning that code without other side-effects. If modules are not enabled then this function is a no-op and always returns CMPP_RC_UNSUPPORTED _without_ setting pp's error state (as it's not an error, per se). That can typically be ignored as a non-error. */ CMPP_EXPORT int cmpp_module_dir_add(cmpp *pp, const char * zDirs); /** State for a cmpp_dx_pimpl which we need in order to snapshot the parse position for purposes of restoring it later. This is basically to support that #query can contain other #query directives, but this same capability is required by any directives which want to both process directives in their content block and loop over the content block. */ struct cmpp_dx_pos { /** Current parse pos. */ unsigned char const *z; /** Current line number. */ cmpp_size_t lineNo; }; typedef struct cmpp_dx_pos cmpp_dx_pos; #define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}//,.dline=CmppDLine_empty_m} /** Stores dx's current input position into pos. pos gets completely initialized by this routine - it need not (in contrast to many other functions in this library) be cleanly initialized by the caller first. */ CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos); /** Restores dx's input position from pos. Results are undefined if pos is not populated with the result of having passed the same dx/pos pointer combination to cmpp_dx_pos_save(). */ CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos); /** A "thunk" for use with loadable modules, encapsulating all of the functions from the public cmpp API into an object. This allows loadable modules to call into the cmpp API if the binary which loads them not built in such a way that it exports libcmpp's symbols to the DLL. (On Linux systems, that means if it's not linked with -rdynamic.) For every public cmpp function, this struct has a member with the same signature and name, minus the "cmpp_" name prefix. Thus cmpp_foo(...) is accessible as api->foo(...). Object-type exports, e.g. cmpp_b_empty, are exposed here as pointers instead of objects. The CMPP_API_THUNK-installed API wrapper macros account for that. There is only one instance of this class and it gets passed into cmpp_module_init_f() methods. It is also assigned to the cmpp_dx::api member of cmpp_dx instances which get passed to cmpp_dx_f() implementations. Loadable modules "should" use this interface to access the API, rather than the global symbols. If they don't then the module may, depending on how the loading application was linked, throw unresolved symbols errors when loading. */ struct cmpp_api_thunk { #define A(VER) #define V(N,T,VER) T N; #define F(N,T,P) T (*N)P; #define O(N,T) T * const N; cmpp_api_thunk_map(A,V,F,O) #undef F #undef O #undef V #undef A }; /** For loadable modules to be able portably access the cmpp API, without requiring that their loading binary be linked with -rdynamic, we need a "thunk". The API exposes cmpp_api_thunk for that purpose. The following macros set up the thunk for a given compilation unit. They are intended to only be used by loadable modules, not generic client code. Before including this header, define CMPP_API_THUNK with no value and/or define CMPP_API_THUNK_NAME to a C symbol name. The latter macro implies the former and defines the name of the static symbol to be the local cmpp_api_thunk instance, defaulting to cmppApi. The first line of a module's registration function should then be: cmpp_api_init(pp); where pp is the name of the sole argument to the registration callback. After that is done, the cmpp_...() APIs may be used via the macros defined below, all of which route through the thunk object. */ #if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME) # if !defined(CMPP_API_THUNK) # define CMPP_API_THUNK # endif # if !defined(CMPP_API_THUNK_NAME) # define CMPP_API_THUNK_NAME cmppApi # endif # if !defined(CMPP_API_THUNK__defined) # define CMPP_API_THUNK__defined static cmpp_api_thunk const * CMPP_API_THUNK_NAME = 0; # endif /** cmpp_api_init() must be invoked from the module's registration function, passed the only argument to that function. It sets the global symbol CMPP_API_THUNK_NAME to its argument. From that point on, the thunk's API is accessible via cmpp_foo macros which proxy theThunk->foo. It is safe to call this from, e.g. a cmpp_dx_f() implementation, as it will always have the same pointer, so long as it is not passed NULL, which would make the next cmpp_...() call segfault. */ # if !defined(CMPP_API_THUNK__assigned) # define CMPP_API_THUNK__assigned # define cmpp_api_init(PP) CMPP_API_THUNK_NAME = (PP)->api # else # define cmpp_api_init(PP) (void)(PP)/*CMPP_API_THUNK_NAME*/ # endif /* What follows is generated code from c-pp's (#pragma api-thunk). */ /* Thunk APIs which follow are available as of version 0... */ #define cmpp_mrealloc CMPP_API_THUNK_NAME->mrealloc #define cmpp_malloc CMPP_API_THUNK_NAME->malloc #define cmpp_mfree CMPP_API_THUNK_NAME->mfree #define cmpp_ctor CMPP_API_THUNK_NAME->ctor #define cmpp_dtor CMPP_API_THUNK_NAME->dtor #define cmpp_reset CMPP_API_THUNK_NAME->reset #define cmpp_check_oom CMPP_API_THUNK_NAME->check_oom #define cmpp_is_legal_key CMPP_API_THUNK_NAME->is_legal_key #define cmpp_define_legacy CMPP_API_THUNK_NAME->define_legacy #define cmpp_define_v2 CMPP_API_THUNK_NAME->define_v2 #define cmpp_undef CMPP_API_THUNK_NAME->undef #define cmpp_define_shadow CMPP_API_THUNK_NAME->define_shadow #define cmpp_define_unshadow CMPP_API_THUNK_NAME->define_unshadow #define cmpp_process_string CMPP_API_THUNK_NAME->process_string #define cmpp_process_file CMPP_API_THUNK_NAME->process_file #define cmpp_process_stream CMPP_API_THUNK_NAME->process_stream #define cmpp_process_argv CMPP_API_THUNK_NAME->process_argv #define cmpp_err_get CMPP_API_THUNK_NAME->err_get #define cmpp_err_set CMPP_API_THUNK_NAME->err_set #define cmpp_err_set1 CMPP_API_THUNK_NAME->err_set1 #define cmpp_err_has CMPP_API_THUNK_NAME->err_has #define cmpp_is_safemode CMPP_API_THUNK_NAME->is_safemode #define cmpp_sp_begin CMPP_API_THUNK_NAME->sp_begin #define cmpp_sp_commit CMPP_API_THUNK_NAME->sp_commit #define cmpp_sp_rollback CMPP_API_THUNK_NAME->sp_rollback #define cmpp_output_f_FILE CMPP_API_THUNK_NAME->output_f_FILE #define cmpp_output_f_fd CMPP_API_THUNK_NAME->output_f_fd #define cmpp_input_f_FILE CMPP_API_THUNK_NAME->input_f_FILE #define cmpp_input_f_fd CMPP_API_THUNK_NAME->input_f_fd #define cmpp_flush_f_FILE CMPP_API_THUNK_NAME->flush_f_FILE #define cmpp_stream CMPP_API_THUNK_NAME->stream #define cmpp_slurp CMPP_API_THUNK_NAME->slurp #define cmpp_fopen CMPP_API_THUNK_NAME->fopen #define cmpp_fclose CMPP_API_THUNK_NAME->fclose #define cmpp_outputer_out CMPP_API_THUNK_NAME->outputer_out #define cmpp_outputer_flush CMPP_API_THUNK_NAME->outputer_flush #define cmpp_outputer_cleanup CMPP_API_THUNK_NAME->outputer_cleanup #define cmpp_outputer_cleanup_f_FILE CMPP_API_THUNK_NAME->outputer_cleanup_f_FILE #define cmpp_delimiter_set CMPP_API_THUNK_NAME->delimiter_set #define cmpp_delimiter_get CMPP_API_THUNK_NAME->delimiter_get #define cmpp_chomp CMPP_API_THUNK_NAME->chomp #define cmpp_b_clear CMPP_API_THUNK_NAME->b_clear #define cmpp_b_reuse CMPP_API_THUNK_NAME->b_reuse #define cmpp_b_swap CMPP_API_THUNK_NAME->b_swap #define cmpp_b_reserve CMPP_API_THUNK_NAME->b_reserve #define cmpp_b_reserve3 CMPP_API_THUNK_NAME->b_reserve3 #define cmpp_b_append CMPP_API_THUNK_NAME->b_append #define cmpp_b_append4 CMPP_API_THUNK_NAME->b_append4 #define cmpp_b_append_ch CMPP_API_THUNK_NAME->b_append_ch #define cmpp_b_append_i32 CMPP_API_THUNK_NAME->b_append_i32 #define cmpp_b_append_i64 CMPP_API_THUNK_NAME->b_append_i64 #define cmpp_b_chomp CMPP_API_THUNK_NAME->b_chomp #define cmpp_output_f_b CMPP_API_THUNK_NAME->output_f_b #define cmpp_outputer_cleanup_f_b CMPP_API_THUNK_NAME->outputer_cleanup_f_b #define cmpp_version CMPP_API_THUNK_NAME->version #define cmpp_tt_cstr CMPP_API_THUNK_NAME->tt_cstr #define cmpp_dx_err_set CMPP_API_THUNK_NAME->dx_err_set #define cmpp_dx_next CMPP_API_THUNK_NAME->dx_next #define cmpp_dx_process CMPP_API_THUNK_NAME->dx_process #define cmpp_dx_consume CMPP_API_THUNK_NAME->dx_consume #define cmpp_dx_consume_b CMPP_API_THUNK_NAME->dx_consume_b #define cmpp_arg_parse CMPP_API_THUNK_NAME->arg_parse #define cmpp_arg_strdup CMPP_API_THUNK_NAME->arg_strdup #define cmpp_arg_to_b CMPP_API_THUNK_NAME->arg_to_b #define cmpp_errno_rc CMPP_API_THUNK_NAME->errno_rc #define cmpp_d_register CMPP_API_THUNK_NAME->d_register #define cmpp_dx_f_dangling_closer CMPP_API_THUNK_NAME->dx_f_dangling_closer #define cmpp_dx_out_raw CMPP_API_THUNK_NAME->dx_out_raw #define cmpp_dx_out_expand CMPP_API_THUNK_NAME->dx_out_expand #define cmpp_dx_outf CMPP_API_THUNK_NAME->dx_outf #define cmpp_dx_delim CMPP_API_THUNK_NAME->dx_delim #define cmpp_atpol_from_str CMPP_API_THUNK_NAME->atpol_from_str #define cmpp_atpol_get CMPP_API_THUNK_NAME->atpol_get #define cmpp_atpol_set CMPP_API_THUNK_NAME->atpol_set #define cmpp_atpol_push CMPP_API_THUNK_NAME->atpol_push #define cmpp_atpol_pop CMPP_API_THUNK_NAME->atpol_pop #define cmpp_unpol_from_str CMPP_API_THUNK_NAME->unpol_from_str #define cmpp_unpol_get CMPP_API_THUNK_NAME->unpol_get #define cmpp_unpol_set CMPP_API_THUNK_NAME->unpol_set #define cmpp_unpol_push CMPP_API_THUNK_NAME->unpol_push #define cmpp_unpol_pop CMPP_API_THUNK_NAME->unpol_pop #define cmpp_path_search CMPP_API_THUNK_NAME->path_search #define cmpp_args_parse CMPP_API_THUNK_NAME->args_parse #define cmpp_args_cleanup CMPP_API_THUNK_NAME->args_cleanup #define cmpp_dx_args_clone CMPP_API_THUNK_NAME->dx_args_clone #define cmpp_popen CMPP_API_THUNK_NAME->popen #define cmpp_popenv CMPP_API_THUNK_NAME->popenv #define cmpp_pclose CMPP_API_THUNK_NAME->pclose #define cmpp_popen_args CMPP_API_THUNK_NAME->popen_args #define cmpp_kav_each CMPP_API_THUNK_NAME->kav_each #define cmpp_d_autoloader_set CMPP_API_THUNK_NAME->d_autoloader_set #define cmpp_d_autoloader_take CMPP_API_THUNK_NAME->d_autoloader_take #define cmpp_isspace CMPP_API_THUNK_NAME->isspace #define cmpp_isnl CMPP_API_THUNK_NAME->isnl #define cmpp_issnl CMPP_API_THUNK_NAME->issnl #define cmpp_skip_space CMPP_API_THUNK_NAME->skip_space #define cmpp_skip_snl CMPP_API_THUNK_NAME->skip_snl #define cmpp_skip_space_trailing CMPP_API_THUNK_NAME->skip_space_trailing #define cmpp_skip_snl_trailing CMPP_API_THUNK_NAME->skip_snl_trailing #define cmpp_array_reserve CMPP_API_THUNK_NAME->array_reserve #define cmpp_module_load CMPP_API_THUNK_NAME->module_load #define cmpp_module_dir_add CMPP_API_THUNK_NAME->module_dir_add #define cmpp_outputer_FILE (*CMPP_API_THUNK_NAME->outputer_FILE) #define cmpp_outputer_b (*CMPP_API_THUNK_NAME->outputer_b) #define cmpp_outputer_empty (*CMPP_API_THUNK_NAME->outputer_empty) #define cmpp_b_empty (*CMPP_API_THUNK_NAME->b_empty) /* Thunk APIs which follow are available as of version 20251116... */ #define cmpp_next_chunk CMPP_API_THUNK_NAME->next_chunk /* Thunk APIs which follow are available as of version 20251118... */ #define cmpp_atdelim_get CMPP_API_THUNK_NAME->atdelim_get #define cmpp_atdelim_set CMPP_API_THUNK_NAME->atdelim_set #define cmpp_atdelim_push CMPP_API_THUNK_NAME->atdelim_push #define cmpp_atdelim_pop CMPP_API_THUNK_NAME->atdelim_pop /* Thunk APIs which follow are available as of version 20251224... */ #define cmpp_dx_pos_save CMPP_API_THUNK_NAME->dx_pos_save #define cmpp_dx_pos_restore CMPP_API_THUNK_NAME->dx_pos_restore /* Thunk APIs which follow are available as of version 20260130... */ #define cmpp_dx_is_call CMPP_API_THUNK_NAME->dx_is_call /* Thunk APIs which follow are available as of version 20260206... */ #define cmpp_b_borrow CMPP_API_THUNK_NAME->b_borrow #define cmpp_b_return CMPP_API_THUNK_NAME->b_return #else /* not CMPP_API_THUNK */ /** cmpp_api_init() is a no-op when not including a file-local API thunk. */ # define cmpp_api_init(PP) (void)0 #endif /* CMPP_API_THUNK */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* include guard */ #endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */ #if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED) #define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED /** This file houses declarations and macros for the private/internal libcmpp APIs. */ #include "sqlite3.h" #include #include #include #include #include #include /* write() and friends */ #if defined(_WIN32) || defined(WIN32) # include # include # ifndef access # define access(f,m) _access((f),(m)) # endif #else # include # include #endif #ifndef CMPP_DEFAULT_DELIM #define CMPP_DEFAULT_DELIM "##" #endif #ifndef CMPP_ATSIGN #define CMPP_ATSIGN (unsigned char)'@' #endif #ifndef CMPP_MODULE_PATH #define CMPP_MODULE_PATH "." #endif #if defined(NDEBUG) #define cmpp__staticAssert(NAME,COND) (void)1 #else #define cmpp__staticAssert(NAME,COND) \ static const char staticAssert_ ## NAME[ (COND) ? 1 : -1 ] = {0}; \ (void)staticAssert_ ## NAME #endif #if defined(CMPP_OMIT_ALL_UNSAFE) #undef CMPP_OMIT_D_PIPE #define CMPP_OMIT_D_PIPE #undef CMPP_OMIT_D_DB #define CMPP_OMIT_D_DB #undef CMPP_OMIT_D_INCLUDE #define CMPP_OMIT_D_INCLUDE #undef CMPP_OMIT_D_MODULE #define CMPP_OMIT_D_MODULE #endif #if !defined(CMPP_VERSION) #error "exporting CMPP_VERSION to have been set up" #endif #define CMPP__DB_MAIN_NAME "cmpp" #if defined(CMPP_AMALGAMATION) #define CMPP_PRIVATE static #else #define CMPP_PRIVATE #endif #if CMPP_PLATFORM_IS_WASM # define CMPP_PLATFORM_IS_WINDOWS 0 # define CMPP_PLATFORM_IS_UNIX 0 # define CMPP_PLATFORM_PLATFORM "wasm" # define CMPP_PATH_SEPARATOR ':' # define CMPP__EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default"))) // See also: //__attribute__((export_name("theExportedName"), used, visibility("default"))) # define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */ # if !defined(CMPP_PLATFORM_EXT_DLL) # define CMPP_PLATFORM_EXT_DLL "" # endif #else //# define CMPP_WASM_EXPORT # define CMPP__EXPORT_NAMED(X) # if defined(_WIN32) || defined(WIN32) # define CMPP_PLATFORM_IS_WINDOWS 1 # define CMPP_PLATFORM_IS_UNIX 0 # define CMPP_PLATFORM_PLATFORM "windows" # define CMPP_PATH_SEPARATOR ';' //# include # elif defined(__MINGW32__) || defined(__MINGW64__) # define CMPP_PLATFORM_IS_WINDOWS 1 # define CMPP_PLATFORM_IS_UNIX 0 # define CMPP_PLATFORM_PLATFORM "windows" # define CMPP_PATH_SEPARATOR ':' /*?*/ # elif defined(__CYGWIN__) # define CMPP_PLATFORM_IS_WINDOWS 0 # define CMPP_PLATFORM_IS_UNIX 1 # define CMPP_PLATFORM_PLATFORM "unix" # define CMPP_PATH_SEPARATOR ':' # else # define CMPP_PLATFORM_IS_WINDOWS 0 # define CMPP_PLATFORM_IS_UNIX 1 # define CMPP_PLATFORM_PLATFORM "unix" # define CMPP_PATH_SEPARATOR ':' # endif #endif #define CMPP__EXPORT(RETTYPE,NAME) CMPP__EXPORT_NAMED(NAME) RETTYPE NAME #if !defined(CMPP_PLATFORM_EXT_DLL) # error "Expecting CMPP_PLATFORM_EXT_DLL to have been set by the auto-configured bits" # define CMPP_PLATFORM_EXT_DLL "???" #endif #if 1 # define CMPP_NORETURN __attribute__((noreturn)) #else # define CMPP_NORETURN #endif /** @def CMPP_HAVE_DLOPEN If set to true, use dlopen() and friends. Requires linking to -ldl on some platforms. Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be true. */ /** @def CMPP_HAVE_LTDLOPEN If set to true, use lt_dlopen() and friends. Requires linking to -lltdl on most platforms. Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be true. */ #if !defined(CMPP_HAVE_DLOPEN) # if defined(HAVE_DLOPEN) # define CMPP_HAVE_DLOPEN HAVE_DLOPEN # else # define CMPP_HAVE_DLOPEN 0 # endif #endif #if !defined(CMPP_HAVE_LTDLOPEN) # if defined(HAVE_LTDLOPEN) # define CMPP_HAVE_LTDLOPEN HAVE_LTDLOPEN # else # define CMPP_HAVE_LTDLOPEN 0 # endif #endif #if !defined(CMPP_ENABLE_DLLS) # define CMPP_ENABLE_DLLS (CMPP_HAVE_LTDLOPEN || CMPP_HAVE_DLOPEN) #endif #if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE) # define CMPP_D_MODULE 1 #else # define CMPP_D_MODULE 0 #endif /** Many years of practice have taught that it is literally impossible to safely close DLLs because simply opening one may trigger arbitrary code (at least for C++ DLLs) which "might" be used by the application. e.g. some classloaders use DLL initialization to inject new classes into the application without the app having to do anything more than open the DLL. (That's precisely what the cmpp port of this code is doing except that we don't call it classloading here.) So cmpp does not close DLLs. Except (...sigh...) to please valgrind. When CMPP_CLOSE_DLLS is true then this API will keep track of DLL handles so that they can be closed, and offers the ability for higher-level clients to close them (all at once, not individually). */ #if !defined(CMPP_CLOSE_DLLS) #define CMPP_CLOSE_DLLS 1 #endif /** Proxy for cmpp_malloc() which (A) is a no-op if ppCode and (B) sets pp->err on OOM. */ CMPP_PRIVATE void * cmpp__malloc(cmpp* pp, cmpp_size_t n); /** Internal-use-only flags for use with cmpp_d::flags. These supplement the ones from the public API's cmpp_d_e. */ enum cmpp_d_ext_e { /** Mask of flag bits from this enum and cmpp_d_e which are for internal use only and are disallowed in client-side directives. */ cmpp_d_F_MASK_INTERNAL = ~cmpp_d_F_MASK, /** If true, and if cmpp_d_F_ARGS_LIST is set, then cmpp_args_parse() will pass its results to cmpp_args__not_simplify(). Only directives which eval cmpp_arg expressions need this, and the library does not expose the pieces for evaluating such expressions. As such, this flag is for internal use only. This only has an effect if cmpp_d_F_ARGS_LIST is also used. */ cmpp_d_F_NOT_SIMPLIFY = 0x10000, /** Most directives are inert when they are seen in the "falsy" part of an if/else. The callbacks for such directives are skipped, as opposed to requiring each directive's callback to check whether they should be skipping. This flag indicates that a directive must always be run, even when skipping content (e.g. inside of an #if 0 block). Only flow-control directives may have the FLOW_CONTROL bit set. The library API does not expose enough of its internals for client-defined directives to make flow-control decisions. i really want to get rid of this flag but it seems to be a necessary evil. */ cmpp_d_F_FLOW_CONTROL = 0x20000 }; /** A single directive line from an input stream. */ struct CmppDLine { /** Line number in the source input. */ cmpp_size_t lineNo; /** Start of the line within its source input. */ unsigned char const * zBegin; /** One-past-the-end byte of the line. A virtual EOF. It will only actually be NUL-terminated if it is the last line of the input and that input has no trailing newline. */ unsigned char const * zEnd; }; typedef struct CmppDLine CmppDLine; #define CmppDLine_empty_m {0U,0,0} /** A snippet from a string. */ struct CmppSnippet { /* Start of the range. */ unsigned char const *z; /* Number of bytes. */ unsigned int n; }; typedef struct CmppSnippet CmppSnippet; #define CmppSnippet_empty_m {0,0} /** CmppLvl represents one "level" of parsing, pushing one level for each of `#if` and popping one for each `#/if`. These pieces are ONLY for use with flow-control directives. It's not proven that they can be of any use to more than a single flow-control directive. e.g. if we had a hypothetical #foreach, we may need to extend this. */ struct CmppLvl { #if 0 /** The directive on whose behalf this level was opened. */ cmpp_d const * d; /** Opaque directive-specific immutable state. It's provided as a way for a directive to see whether the top of the stack is correct after it processes inner directives. */ void const * state; #endif /** Bitmask of CmppLvl_F_... */ cmpp_flag32_t flags; /** The directive line number which started this level. This is used for reporting the starting lines of erroneously unclosed block constructs. */ cmpp_size_t lineNo; }; typedef struct CmppLvl CmppLvl; #define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U} /** Declares struct T as a container for a list-of-MT. MT may be pointer-qualified. cmpp__ListType_impl() with the same arguments implements T_reserve() for basic list allocation. Cleanup, alas, is MT-dependent. */ #define cmpp__ListType_decl(T,MT) \ struct T { \ MT * list; \ cmpp_size_t n; \ cmpp_size_t nAlloc; \ }; \ typedef struct T T; \ int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min) #define CMPP__MAX(X,Y) ((X)<=(Y) ? (X) : (Y)) #define cmpp__ListType_impl(T,MT) \ int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \ return cmpp_array_reserve(pp, (void**)&li->list, min, \ &li->nAlloc, sizeof(MT)); \ } #define cmpp__LIST_T_empty_m {.list=0,.n=0,.nAlloc=0} /** A dynamically-allocated list of CmppLvl objects. */ cmpp__ListType_decl(CmppLvlList,CmppLvl*); #define CmppLvlList_empty_m cmpp__LIST_T_empty_m CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx); CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx); CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl); CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on); CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl); CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx); /** A dynamically-allocated list of cmpp_b objects. */ cmpp__ListType_decl(cmpp_b_list,cmpp_b*); #define cmpp_b_list_empty_m cmpp__LIST_T_empty_m extern const cmpp_b_list cmpp_b_list_empty; CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li); //CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li); //CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li); /** cmpp_b_list sorting policies. NULL entries must always sort last. */ enum cmpp_b_list_e { cmpp_b_list_UNSORTED, /* Smallest first. */ cmpp_b_list_ASC, /* Largest first. */ cmpp_b_list_DESC }; /** A dynamically-allocated list of cmpp_arg objects. Used by cmmp_args. */ cmpp__ListType_decl(CmppArgList,cmpp_arg); #define CmppArgList_empty_m cmpp__LIST_T_empty_m /** Allocate a new arg, owned by li, and return it (cleanly zeroed out). Returns NULL and updates pp->err on error. */ CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li); /** The internal part of the cmpp_args interface. */ struct cmpp_args_pimpl { /** We need(?) a (cmpp*) here for finalization/recycling purposes. */ cmpp *pp; bool isCall; /** Next entry in the free-list. */ cmpp_args_pimpl * nextFree; /** Version 3 of the real args memory. */ CmppArgList argli; /** cmpp_args_parse() copies each argument's bytes into here, each one NUL-terminated. */ cmpp_b argOut; }; #define cmpp_args_pimpl_empty_m { \ .pp = 0, \ .isCall = false, \ .nextFree = 0, \ .argli = CmppArgList_empty_m, \ .argOut = cmpp_b_empty_m \ } extern const cmpp_args_pimpl cmpp_args_pimpl_empty; void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p); /** The internal part of the cmpp_dx interface. */ struct cmpp_dx_pimpl { /** Start of input. */ unsigned const char * zBegin; /** One-after-the-end of input. */ unsigned const char * zEnd; /** Current input position. Generally speaking, only cmpp_dx_delim_search() should update this, but it turns out that the ability to rewind the input is necessary for looping constructs, like #query, when they want to be able to include other directives in their bodies. */ cmpp_dx_pos pos; /** Currently input line. */ CmppDLine dline; /** Number of active #savepoints. */ unsigned nSavepoint; /** Current directive's args. */ cmpp_args args; /** A stack of state used by #if and friends to inform the innards that they must not generate output. This is largely historical and could have been done differently had this code started as a library instead of a monolithic app. TODO is to figure out how best to move this state completely into the #if handler, rather than fiddle with this all throughout the processing. We could maybe move this stack into CmppIfState? */ CmppLvlList dxLvl; struct { /** A copy of this->d's input line which gets translated slightly from its native form for futher processing. */ cmpp_b line; /** Holds the semi-raw input line, stripped only of backslash-escaped newlines and leading spaces. This is primarily for debug output but also for custom arg parsing for some directives. */ cmpp_b argsRaw; } buf; /** Record IDs for/from cmpp_[un]define_shadow(). */ struct { /** ID for __FILE__. */ int64_t sidFile; /** Rowid for #include path entry. */ int64_t ridInclPath; } shadow; struct { /** Set when we're searching for directives so that we know whether cmpp_out_expand() should count newlines. */ unsigned short countLines; /** True if the next directive is the start of a [call]. */ bool nextIsCall; } flags; }; /** Initializes or resets a. Returns non-0 on OOM. */ CMPP_PRIVATE int cmpp_args__init(cmpp *pp, cmpp_args *a); /** If a has state then it's recycled for reuse, else this zeroes out a except for a->pimpl, which is retained (but may be NULL). */ CMPP_PRIVATE void cmpp_args_reuse(cmpp_args *a); #define cmpp_dx_pimpl_empty_m { \ .zBegin=0, .zEnd=0, \ .pos=cmpp_dx_pos_empty_m, \ .dline=CmppDLine_empty_m, \ .nSavepoint=0, \ .args = cmpp_args_empty_m, \ .dxLvl = CmppLvlList_empty_m, \ .buf = { \ cmpp_b_empty_m, \ cmpp_b_empty_m \ }, \ .shadow = { \ .sidFile = 0, \ .ridInclPath = 0 \ }, \ .flags = { \ .countLines = 0, \ .nextIsCall = false \ } \ } /** A level of indirection for CmppDList in order to be able to manage ownership of their name (string) lifetimes. */ struct CmppDList_entry { /** this->d.name.z points to this, which is owned by the CmppDList which manages this object. */ char * zName; /* Potential TODO: move d->id into here. That doesn't eliminate our dependency on it, though. */ cmpp_d d; //cmpp_d_reg reg; }; typedef struct CmppDList_entry CmppDList_entry; #define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/} /** A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs. */ cmpp__ListType_decl(CmppDList,CmppDList_entry*); #define CmppDList_empty_m cmpp__LIST_T_empty_m /** State for keeping track of DLL handles, a.k.a. shared-object handles, a.k.a. "soh". Instances of this must be either cleanly initialized by bitwise copying CmppSohList_empty, memset() (or equivalent) them to 0, or allocating them with CmppSohList_new(). */ cmpp__ListType_decl(CmppSohList,void*); #define CmppSohList_empty_m cmpp__LIST_T_empty_m /** Closes all handles which have been CmppSohList_append()ed to soli and frees any memory it owns, but does not free soli (which might be stack-allocated or part of another struct). Special case: if built without DLL-closing support then this is no-op. */ CMPP_PRIVATE void CmppSohList_close(CmppSohList *soli); /** Operators and operator policies for use with X=Y-format keys. This is legacy stuff, actually, but some of the #define management still needs it. */ #define CmppKvp_op_map(E) \ E(none,"") \ E(eq1,"=") enum CmppKvp_op_e { #define E(N,S) CmppKvp_op_ ## N, CmppKvp_op_map(E) #undef E }; typedef enum CmppKvp_op_e CmppKvp_op_e; /** Result type for CmppKvp_parse(). */ struct CmppKvp { /* Key part of the kvp. */ CmppSnippet k; /* Key part of the kvp. Might be empty. */ CmppSnippet v; /* Operator part of kvp, if any. */ CmppKvp_op_e op; }; typedef struct CmppKvp CmppKvp; extern const CmppKvp CmppKvp_empty; /** Parses X or X=Y into p. Sets pp's error state on error. If nKey is negative then strlen() is used to calculate it. The third argument specifies whether/how to permit/treat the '=' part of X=Y. */ CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey, cmpp_ssize_t nKey, CmppKvp_op_e opPolicy); /** Stack of POD values. Intended for use with cmpp at-token and undefined key policies. */ #define cmpp__PodList_decl(ST,ET) \ struct ST { \ /* current stack index */ \ cmpp_size_t n; \ cmpp_size_t na; \ ET * stack; \ }; typedef struct ST ST; \ void ST ## _wipe(ST * s, ET v); \ int ST ## _push(cmpp *pp, ST * s, ET v); \ void ST ## _set(ST * s, ET v); \ void ST ## _finalize(ST * s); \ void ST ## _pop(ST *s); \ int ST ## _reserve(cmpp *, ST *, cmpp_size_t min) #define cmpp__PodList_impl(ST,ET) \ void ST ## _wipe(ST * const s, ET v){ \ if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na); \ s->n = 0; \ } \ int ST ## _reserve(cmpp * const pp, ST * const s, \ cmpp_size_t min){ \ return cmpp_array_reserve(pp, (void**)&s->stack, min>0 \ ? min : (s->n \ ? (s->n==s->na-1 \ ? s->na*2 : s->n+1) \ : 8), \ &s->na, sizeof(ET)); \ } \ int ST ## _push(cmpp * const pp, ST * s, ET v){ \ if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v; \ return ppCode; \ } \ void ST ## _set(ST * s, ET v){ \ assert(s->n); \ if( 0== ST ## _reserve(NULL, s, 0) ){ \ s->stack[s->n] = v; \ } \ } \ ET ST ## _get(ST const * const s){ \ assert(s->na && s->na >=s->n); \ return s->stack[s->n]; \ } \ void ST ## _pop(ST *s){ \ assert(s->n); \ if(s->n) --s->n; \ } \ void ST ## _finalize(ST *s){ \ cmpp_mfree(s->stack); \ s->stack = NULL; \ s->n = s->na = 0; \ } cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e); cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e); #define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH #define cmpp__policy(PP,WHICH) \ cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n] /** A "delimiter" object. That is, the "#" referred to in the libcmpp docs. It's also outfitted for a second delimiter so that it can be used for the opening/closing delimiters of @tokens@. */ struct cmpp__delim { /** Bytes of the directive delimiter/prefix or the @token@ opening delimiter. Owned elsewhere but often points at this->zOwns. */ CmppSnippet open; /** Closing @token@ delimiter. This has no meaning for the directive delimiter. */ CmppSnippet close; /** Memory, owned by this object, for this->open and this->close. In the latter case, it's one string with both delimiters encoded in it. */ unsigned char * zOwns; }; typedef struct cmpp__delim cmpp__delim; #define cmpp__delim_empty_m { \ .open={ \ .z=(unsigned char*)CMPP_DEFAULT_DELIM, \ .n=sizeof(CMPP_DEFAULT_DELIM)-1 \ }, \ .close=CmppSnippet_empty_m, \ .zOwns=0 \ } extern const cmpp__delim cmpp__delim_empty; void cmpp__delim_cleanup(cmpp__delim *d); /** A dynamically-allocated list of cmpp__delim objects. */ cmpp__ListType_decl(cmpp__delim_list,cmpp__delim); #define cmpp__delim_list_empty_m {0,0,0} extern const cmpp__delim_list cmpp__delim_list_empty; CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li); static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){ return li->n ? li->list+(li->n-1) : NULL; } static inline void cmpp__delim_list_pop(cmpp__delim_list *li){ assert(li->n); if( li->n ) cmpp__delim_cleanup(li->list + --li->n); } static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){ while( li->n ) cmpp__delim_cleanup(li->list + --li->n); } /** An untested experiment: an output buffer proxy. Integrating this fully would require some surgery, but it might also inspire me to do the same with input and stream it rather than slurp it all at once. */ #define CMPP__OBUF 0 typedef struct cmpp__obuf cmpp__obuf; #if CMPP__OBUF /** An untested experiment. */ struct cmpp__obuf { /** Start of the output buffer. */ unsigned char * begin; /** One-after-the-end of this->begin. Virtual EOF. */ unsigned char const * end; /** Current write position. Must initially be this->begin. */ unsigned char * cursor; /** True if this object owns this->begin, which must have been allocated using cmpp_malloc() or cmpp_realloc(). */ bool ownsMemory; /** Propagating result code. */ int rc; /** The output channel to buffer for. Flushing */ cmpp_outputer dest; }; #define cmpp__obuf_empty_m { \ .begin=0, .end=0, .cursor=0, .ownsMemory=false, \ .rc=0, .dest=cmpp_outputer_empty_m \ } extern const cmpp__obuf cmpp__obuf_empty; extern const cmpp_outputer cmpp_outputer_obuf; #endif /* CMPP__OBUF */ /** The main public-API context type for this library. */ struct cmpp_pimpl { /* Internal workhorse. */ struct { sqlite3 * dbh; /** Optional filename. Memory is owned by this object. */ char * zName; } db; /** Current directive context. It's const, primarily to help protect cmpp_dx_f()'s from inadvertent side effects of changes which lower-level APIs might make to it. Maybe it shouldn't be: if it were not then we could update dx->zDelim from cmpp__delimiter_set(). */ cmpp_dx const * dx; /* Output channel. */ cmpp_outputer out; /** Delimiters version 2. */ struct { /** Directive delimiter. */ cmpp__delim_list d; /** @token@ delimiters. */ cmpp__delim_list at; } delim; struct { #define CMPP__SEL_V_FROM(N) \ "(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \ " ORDER BY source LIMIT 1)" /** One entry for each distinct query used by cmpp: E(X,SQL), where X is the member's name and SQL is its SQL. */ #define CMPP_SAVEPOINT_NAME "_cmpp_" #define CmppStmt_map(E) \ E(sdefIns, \ "INSERT INTO " \ CMPP__DB_MAIN_NAME ".sdef" \ "(t,k,v) VALUES(?1,?2,?3) RETURNING id") \ E(defIns, \ "INSERT OR REPLACE INTO " \ CMPP__DB_MAIN_NAME ".def" \ "(t,k,v) VALUES(?1,?2,?3)") \ E(defDel, \ "DELETE FROM " \ CMPP__DB_MAIN_NAME ".def" \ " WHERE k GLOB ?1") \ E(sdefDel, \ "DELETE FROM " \ CMPP__DB_MAIN_NAME ".sdef" \ " WHERE k=?1 AND id>=?2") \ E(defHas, \ "SELECT 1 FROM " \ CMPP__DB_MAIN_NAME ".vdef" \ " WHERE k = ?1") \ E(defGet, \ "SELECT source,t,k,v FROM " \ CMPP__DB_MAIN_NAME ".vdef" \ " WHERE k = ?1 ORDER BY source LIMIT 1") \ E(defGetBool, \ "SELECT cmpp_truthy(v) FROM " \ CMPP__DB_MAIN_NAME ".vdef" \ " WHERE k = ?1" \ " ORDER BY source LIMIT 1") \ E(defGetInt, \ "SELECT CAST(v AS INTEGER)" \ " FROM " CMPP__DB_MAIN_NAME ".vdef" \ " WHERE k = ?1" \ " ORDER BY source LIMIT 1") \ E(defSelAll, "SELECT t,k,v" \ " FROM " CMPP__DB_MAIN_NAME ".vdef" \ " ORDER BY source, k") \ E(inclIns," INSERT OR FAIL INTO " \ CMPP__DB_MAIN_NAME ".incl(" \ " file,srcFile, srcLine" \ ") VALUES(?,?,?)") \ E(inclDel, "DELETE FROM " \ CMPP__DB_MAIN_NAME ".incl WHERE file=?") \ E(inclHas, "SELECT 1 FROM " \ CMPP__DB_MAIN_NAME ".incl WHERE file=?") \ E(inclPathAdd, "INSERT INTO " \ CMPP__DB_MAIN_NAME ".inclpath(priority,dir) " \ "VALUES(coalesce(?1,0),?2) " \ "ON CONFLICT DO NOTHING " \ "RETURNING rowid /*xlates to 0 on conflict*/") \ E(inclPathRmId, "DELETE FROM " \ CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 " \ "RETURNING rowid") \ E(inclSearch, \ "SELECT ?1 fn WHERE cmpp_file_exists(fn) " \ "UNION ALL SELECT fn FROM (" \ " SELECT replace(dir||'/'||?1, '//','/') AS fn " \ " FROM " CMPP__DB_MAIN_NAME ".inclpath" \ " WHERE cmpp_file_exists(fn) " \ " ORDER BY priority DESC, rowid LIMIT 1" \ ")") \ E(cmpVV, "SELECT cmpp_compare(?1,?2)") \ E(cmpDV, \ "SELECT cmpp_compare(" \ CMPP__SEL_V_FROM(1) ", ?2" \ ")") \ E(cmpVD, \ "SELECT cmpp_compare(" \ "?1," CMPP__SEL_V_FROM(2) \ ")") \ E(cmpDD, \ "SELECT cmpp_compare(" \ CMPP__SEL_V_FROM(1) \ "," \ CMPP__SEL_V_FROM(2) \ ")") \ E(dbAttach, \ "ATTACH ?1 AS ?2") \ E(dbDetach, \ "DETACH ?1") \ E(spBegin, "SAVEPOINT " CMPP_SAVEPOINT_NAME) \ E(spRollback, \ "ROLLBACK TO SAVEPOINT " CMPP_SAVEPOINT_NAME) \ E(spRelease, \ "RELEASE SAVEPOINT " CMPP_SAVEPOINT_NAME) \ E(insTtype, \ "INSERT INTO " CMPP__DB_MAIN_NAME ".ttype" \ "(t,n,s) VALUES(?1,?2,?3)") \ E(selPathSearch, \ /* sqlite.org/forum/forumpost/840c98a8e87c2207 */ \ "WITH path(basename, sep, ext, path) AS (\n" \ " select\n" \ " ?1 basename,\n" \ " ?2 sep,\n" \ " ?3 ext,\n" \ " ?4 path\n" \ "),\n" \ "pathsplit(i, l, c, r) AS (\n" \ "-- i = sequential ID\n" \ "-- l = Length remaining\n" \ "-- c = text remaining\n" \ "-- r = current unpacked value\n" \ " SELECT 1,\n" \ " length(p.path)+length(p.sep),\n" \ " p.path||p.sep, ''\n" \ " FROM path p\n" \ " UNION ALL\n" \ " SELECT i+1, instr( c, p.sep ) l,\n" \ " substr( c, instr( c, p.sep ) + 1) c,\n" \ " trim( substr( c, 1,\n" \ " instr( c, p.sep) - 1) ) r\n" \ " FROM pathsplit, path p\n" \ " WHERE l > 0\n" \ "),\n" \ "thefile (f) AS (\n" \ " select basename f FROM path\n" \ " union all\n" \ " select basename||ext\n" \ " from path where ext is not null\n" \ ")\n" \ "select 0 i, replace(f,'//','/') AS fn\n" \ "from thefile where cmpp_file_exists(fn)\n" \ "union all\n" \ "select i, replace(r||'/'||f,'//','/') fn\n" \ "from pathsplit, thefile\n" \ "where r<>'' and cmpp_file_exists(fn)\n" \ "order by i\n" \ "limit 1;") /* trivia: selPathSearch (^^^) was generated using cmpp's #c-code directive. */ #define E(N,S) sqlite3_stmt * N; CmppStmt_map(E) #undef E } stmt; /** Error state. */ struct { /** Result code. */ int code; /** Error string owned by this object. */ char * zMsg; /** Either this->zMsg or an external error string. */ char const * zMsgC; } err; /** State for SQL tracing. */ struct { bool expandSql; cmpp_size_t counter; cmpp_outputer out; } sqlTrace; struct { /** If set properly, cmpp_dtor() will free this object, else it will not. */ void const * allocStamp; /** How many dirs we believe are in the #include search list. We only do this for the sake of the historical "if no path was added, assume '.'" behavior. This really ought to go away. */ unsigned nIncludeDir; /** The current depth of cmpp_process_string() calls. We do this so the directory part of #include'd files can get added to the #include path and be given a higher priority than previous include path entries in the stack. */ int nDxDepth; /* Number of active #savepoints. */ unsigned nSavepoint; /* If >0, enables certain debugging output. */ char doDebug; /* If true, chomp() files read via -Fx=file. */ unsigned char chompF; /* Flags passed to cmpp_ctor(). */ cmpp_flag32_t newFlags; /** An ugly hack for getting cmpp_d_register() to get syntactically-illegal directive names, like "@policy", to register. */ bool isInternalDirectiveReg; /** True if the next directive is the start of a [call]. This is used for: 1) To set cmpp_dx::isCall, which is useful in certain directives. 2) So that the cmpp_dx object created for the call can inherit the line number from its parent context. That's significant for error reporting. 3) So that #2's cmpp_dx object can communicate that flag to cmpp_dx_next(). */ bool nextIsCall; /** True until the cmpp's (sometimes) lazy init has been run. This is essentially a kludge to work around a wrench cmpp_reset() throws into cmpp state. Maybe we should just remove cmpp_reset() from the interface, since error recovery in this context is not really a thing. */ bool needsLazyInit; } flags; /** Policies. */ struct { /** @token@-parsing policy. */ PodList__atpol at; /** Policy towards referencing undefined symbols. */ PodList__unpol un; } policy; /** Directive state. */ struct { /** Runtime-installed directives. */ CmppDList list; /** Directive autoloader/auto-registerer. */ cmpp_d_autoloader autoload; } d; struct { /** List of DLL handles opened by cmpp_module_extract(). */ CmppSohList sohList; /** Search path for DLLs, delimited by this->pathSep. */ cmpp_b path; /** File extension for DLLs. */ char const * soExt; /** Separator char for this->path. */ char pathSep; } mod; struct { /** Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return(). */ cmpp_b_list buf; /** How/whether this->list is sorted. */ enum cmpp_b_list_e bufSort; /** Head of the free-list. */ cmpp_args_pimpl * argPimpl; } recycler; }; /** IDs Distinct for each cmpp::stmt member. */ enum CmppStmt_e { CmppStmt_none = 0, #define E(N,S) CmppStmt_ ## N, CmppStmt_map(E) #undef E }; static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){ return cmpp__delim_list_get(&pp->pimpl->delim.d); } static inline char const * cmpp__pp_zdelim(cmpp const *pp){ cmpp__delim const * const d = cmpp__pp_delim(pp); return d ? (char const *)d->open.z : NULL; } #define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp) #define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp) /** Emit [z,(char*)z+n) to the given output channel if (A) pOut->out is not NULL and (B) pp has no error state and (C) n>0. On error, pp's error state is updated. Returns pp->err.code. Skip level is not honored. */ CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n); CMPP_PRIVATE void cmpp__err_clear(cmpp *pp); /** Initialize pp->db.dbh. If it's already open or ppCode!=0 then ppCode is returned. */ int cmpp__db_init(cmpp *pp); /** Returns the pp->pimpl->stmt.X corresponding to `which`, initializing it if needed. If it returns NULL then either this was called when pp has its error state set or this function will set the error state. If prepEvenIfErr is true then the ppCode check is bypassed, but it will still fail if pp->pimpl->db is not opened or if the preparation itself fails. */ sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which, bool prepEvenIfErr); /** Reminder to self: this must return an SQLITE_... code, not a CMPP_RC_... code. On success it returns 0, SQLITE_ROW, or SQLITE_DONE. On error it returns another non-0 SQLITE_... code and updates pp->pimpl->err. This is a no-op if called when pp has an error set, returning SQLITE_ERROR. If resetIt is true, q is passed to cmpp__stmt_reset(), else the caller must eventually reset it. */ int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt); /** Resets and clear bindings from q (if q is not NULL). */ void cmpp__stmt_reset(sqlite3_stmt * const q); /** Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err is updated with pp->db's current error state. zMsgSuffix is an optional prefix for the error message. */ int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsgSuffix); /* Proxy for sqlite3_bind_int64(). */ int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); /** Proxy for cmpp__bind_text() which encodes val as a string. For queries which compare values, it's important that they all have the same type, so some cases where we might want an int needs to be bound as text instead. See #query for one such case. */ int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); /* Proxy for sqlite3_bind_null(). */ int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col); /* Proxy for sqlite3_bind_text() which updates pp->err on error. */ int cmpp__bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col, unsigned const char * zStr); /* Proxy for sqlite3_bind_text() which updates pp->err on error. */ int cmpp__bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col, unsigned const char *zStr, cmpp_ssize_t len); /** Adds zDir to the include path, using the given priority value (use 0 except for the implicit cwd path which #include should (but does not yet) set). If pRowid is not NULL then *pRowid gets set to either 0 (if zDir was already in the path) or the row id of the newly-inserted record, which can later be used to delete just that entry. If this returns a non-zero value via pRowid, the caller is obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(), even if pp is in an error state. TODO: normalize zDir (at least remove trailing slashes) before insertion to avoid that both a/b and a/b/ get be inserted. */ int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid); /** Deletes the include path entry with the given rowid. This will make make the attempt even if pp is in an error state but also retains any existing error rather than overwriting it if this operation somehow fails. Returns pp's error code. It is not an error for the given entry to not exist. */ int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid); #if 0 /** Proxy for sqlite3_bind_text(). It uses sqlite3_str_vappendf() so supports all of its formatting options. */ int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col, const char * zFmt, ...); #endif /** Proxy for sqlite3_str_finish() which updates pp's error state if s has error state. Returns s's string on success and NULL on error. The returned string must eventualy be passed to cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so callers must check pp->err to see if NULL is an error. If n is not NULL then on success it is set to the byte length of the returned string. */ char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n); /** Searches pp's list of directives. If found, return it else return NULL. See cmpp__d_search3(). */ cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName); /** Flags for use with the final argument to cmpp__d_search3(). */ enum cmpp__d_search3_e { /** Internal delayed-registered directives. */ cmpp__d_search3_F_DELAYED = 0x01, /** Directive autoloader. */ cmpp__d_search3_F_AUTOLOADER = 0x02, /** Search for a DLL. */ cmpp__d_search3_F_DLL = 0x04, /** Options which do not trigger DLL lookup. */ cmpp__d_search3_F_NO_DLL = 0 | cmpp__d_search3_F_DELAYED | cmpp__d_search3_F_AUTOLOADER, /** All options. */ cmpp__d_search3_F_ALL = 0 | cmpp__d_search3_F_DELAYED | cmpp__d_search3_F_AUTOLOADER | cmpp__d_search3_F_DLL }; /** Like cmpp__d_search() but if no match is found then it will search through its other options and, if found, register it. The final argument specifies where to search. cmpp__d_search() always checked first. After that, depending on "what", the search order is: (1) internal delayed-load modules, (2) autoloader, (3) DLL. This may update pp's error state, in which case it will return NULL. */ cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName, cmpp_flag32_t what); /** Sets pp's error state (A) if it's not set already and (B) if !cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is legal (to support legacy CLI pieces). Returns ppCode. */ int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey, cmpp_ssize_t nKey, bool permitEqualSign); /** Appends DLL handle soh to soli. Returns 0 on success, CMPP_RC_OOM on error. If pp is not NULL then its error state is updated as well. Results are undefined if soli was not cleanly initialized (by copying CmppSohList_empty or using CmppSohList_new()). Special case: if built without DLL-closing support, this is a no-op returning 0. */ int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh); /** True if arg is of type cmpp_TT_Word and it looks like it _might_ be a filename or flag argument. Might. */ bool cmpp__arg_wordIsPathOrFlag(cmpp_arg const * const arg); /** Helper for #query and friends. Binds aVal's value to column bindNdx of q. It expands cmpp_TT_StringAt and cmpp_TT_Word aVal. cmpp_TT_String and cmpp_TT_Int are bound as strings. A cmpp_TT_GroupParen aVal is eval'ed as an integer and that int gets bound as a string. This function strictly binds everything as strings, even if the value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that comparison queries will work as expected. Returns ppCode. */ int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * q, int bindNdx, cmpp_arg const * aVal); /** Helper for #query's bind X part, where aGroup is that X. A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have non-empty content. cmpp_TT_GroupBrace treats it as a list of values to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt column in one of these forms: :bindName -> value $bindName -> value Each LHS refers to a same-named binding in q's SQL, including the ':' or '$' prefix. (SQLite supports an '@' prefix but we don't allow it here to avoid confusion with cmpp_TT_StringAt tokens.) Each bound value is passed to cmpp__bind_arg() for processing. On success, each aGroup entry is bound to q. On error q's state is unspecified. Returns ppCode. See cmpp__bind_arg() for notes about the bind data type. */ int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q, cmpp_arg const * const aGroup); /** Returns true if the given key is already in the `#define` list, else false. Sets pp's error state on db error. nName is the length of the key part of zName (which might have a following =y part. If it's negative, strlen() is used to calculate it. */ int cmpp_has(cmpp *pp, const char * zName, cmpp_ssize_t nName); /** Returns true if the given key is already in the `#define` list, and it has a truthy value (is not empty and not equal to '0'), else false. If zName contains an '=' then only the part preceding that is used as the key. nName is the length of zName, or <0 to use strlen() to figure it out. Updates ppCode on error. */ int cmpp__get_bool(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName); /** Fetches the given define. If found, sets *pOut to it, else pOut is unmodified. Returns pp->err.code. If bRequire is true and no entry is found p->err.code is updated. */ int cmpp__get_int(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, int *pOut); /** Searches for a define where (k GLOB zName). If one is found, a copy of it is assigned to *zVal (the caller must eventually db_free() it), *nVal (if nVal is not NULL) is assigned its strlen, and returns non-0. If no match is found, 0 is returned and neither *zVal nor *nVal are modified. If more than one result matches, a fatal error is triggered. It is legal for *zVal to be NULL (and *nVal to be 0) if it returns non-0. That just means that the key was defined with no value part. Fixme: return 0 on success and set output *gotOne=0|1. */ int cmpp__get(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, unsigned char **zVal, unsigned int *nVal); /** Like cmp__get() but puts its output in os. */ int cmpp__get_b(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, cmpp_b * os, bool enforceUndefPolicy); /** Helper for #query and friends. It expects that q has just been stepped. For each column in the row, it sets a define named after the column. If q has row data then the values come from there. If q has no row then: if defineIfNoRow is true then it defines each column name to an empty value else it defines nothing. */ int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, bool defineIfNoRow); /** Start a new savepoint for dx. */ int cmpp__dx_sp_begin(cmpp_dx * const dx); /** Commit and close dx's current savepoint. */ int cmpp__dx_sp_commit(cmpp_dx * const dx); /** Roll back and close dx's current savepoint. */ int cmpp__dx_sp_rollback(cmpp_dx * const dx); /** Append's dx's file/line information to sstr. It returns void because that's how sqlite3_str_appendf() and friends work. */ void cmpp__dx_append_script_info(cmpp_dx const * dx, sqlite3_str * sstr); /** If zName matches one of the delayed-load directives, that directive is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if no match is found, but pp's error state is not updated in that case. If a match is found and registration fails, that result code will propagate via pp. */ int cmpp__d_delayed_load(cmpp *pp, char const *zName); void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent); /** Like cmpp_tt_cstr(), but if bSymbolName is false then it returns the higher-level token name, which is NULL for most token types. */ char const * cmpp__tt_cstr(int tt, bool bSymbolName); /** Expects **zPos to be one of ('(' '{' '[' '"' '\'') and zEnd to be the logical EOF for *zPos. This looks for a matching closing token, accounting for nesting. On success, returns 0 and sets *zPos to the closing character. On error it update's pp's error state and returns that code. pp may be NULL. If pNl is not NULL then *pNl is incremented for each '\n' character seen while looking for the closing character. */ int cmpp__find_closing2(cmpp *pp, unsigned char const **zPos, unsigned char const *zEnd, cmpp_size_t *pNl); #define cmpp__find_closing(PP,Z0,Z1) \ cmpp__find_closing2(PP, Z0, Z1, NULL) static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_ssize_t n){ return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n; } static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_ssize_t n){ return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n; } /** If ppCode is not set and pol resolves to cmpp_atpol_OFF then this updates ppCode with a message about the lack of support for at-strings. If cmpp_atpol_CURRENT==pol then pp's current policy is checked. Returns ppCode. */ int cmpp__StringAtIsOk(cmpp * const pp, cmpp_atpol_e pol); /** "define"s zKey to zVal, recording the value type as tType. */ int cmpp__define2(cmpp *pp, unsigned char const * zKey, cmpp_ssize_t nKey, unsigned char const *zVal, cmpp_ssize_t nVal, cmpp_tt tType); /** Evals pArgs's arguments as an integer expression. On success, sets *pResult to the value. Returns ppCode. */ int cmpp__args_evalToInt(cmpp_dx * dx, cmpp_args const *pArgs, int * pResult); /** Passes the contents of arg through to cmpp__args_evalToInt(). */ int cmpp__arg_evalSubToInt(cmpp_dx *dx, cmpp_arg const *arg, int * pResult); /** Evaluated arg as an integer/bool, placing the result in *pResult and setting *pNext to the first argument to arg's right which this routine did not consume. Non-0 on error and all that. */ int cmpp__arg_toBool(cmpp_dx * dx, cmpp_arg const *arg, int * pResult, cmpp_arg const **pNext); /** If thisTtype==cmpp_TT_AnyType or thisTtype==arg->ttype and arg->z looks like it might contain an at-string then os is re-used to hold the @token@-expanded version of arg's content. os is unconditionally passed to cmpp_b_reuse() before it begines work. It uses the given atPolicy to determine whether or not the content is expanded, as per cmpp_dx_out_expand(). Returns 0 on success. If it expands content then *pExp is set to os->z, else *pExp is set to arg->z. If nExp is not NULL then *nExp gets set to the length of *pExp (geither os->n or arg->n). Returns ppCode. Much later: what does this give us that cmpp_arg_to_b() doesn't? Oh - that one calls into this one. i.e. this one is lower-level. */ int cmpp__arg_expand_ats(cmpp_dx const * const dx, cmpp_b * os, cmpp_atpol_e atPolicy, cmpp_arg const * const arg, cmpp_tt thisTtype, unsigned char const **pExp, cmpp_size_t * nExp); typedef struct cmpp_argOp cmpp_argOp; typedef void (*cmpp_argOp_f)(cmpp_dx *dx, cmpp_argOp const *op, cmpp_arg const *vLhs, cmpp_arg const **pvRhs, int *pResult); struct cmpp_argOp { int ttype; /* 1 or 2 */ unsigned char arity; /* 0=none/left, 1=right (unary ops only) */ signed char assoc; cmpp_argOp_f xCall; }; cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt); bool cmpp__is_int(unsigned char const *z, unsigned n, int *pOut); bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut); char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p); char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p); /** Uncerimoniously bitwise-replaces pp's output channel with oNew. It does _not_ clean up the previous channel, on the assumption that the caller is taking any necessary measures. Apropos necessary measures for cleanup: if oPrev is not NULL, *oPrev is set to a bitwise copy of the previous channel. Intended usage: ``` cmpp_outputer oMine = cmpp_outputer_b; cmpp_b bMine = cmpp_b_empty; cmpp_outputer oOld = {0}; oMine.state = &bMine; cmpp_outputer_swap(pp, &myOut, &oOld); ...do some work then ALWAYS do... cmpp_outputer_swap(pp, &oOld, &oMine); ``` Because this involves bitwise copying, care must be taken with stream state, e.g. bMine.z (above) can be reallocated, so we have to be sure to swap it back before using bMine again. */ void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew, cmpp_outputer *oPrev); /** Init code which is usually run as part of the ctor but may have to be run later, after cmpp_reset(). We can't run it from cmpp_reset() because that could leave post-reset in an error state, which is icky. This call is a no-op after the first. */ int cmpp__lazy_init(cmpp *pp); CMPP_NORETURN void cmpp__fatalv_base(char const *zFile, int line, char const *zFmt, va_list); #define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__) CMPP_NORETURN void cmpp__fatal_base(char const *zFile, int line, char const *zFmt, ...); #define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__) /** Outputs a printf()-formatted message to stderr. */ void g_stderr(char const *zFmt, ...); #define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__) #define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg) #if 0 #define g_debug(PP,lvl,pfexpr) (void)0 #else #define g_debug(PP,lvl,pfexpr) \ if(lvl<=(PP)->pimpl->flags.doDebug) { \ if( (PP)->pimpl->dx ){ \ g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ", \ (PP)->pimpl->dx->sourceName, \ (PP)->pimpl->dx->pimpl->dline.lineNo); \ } \ g_stderr("%s():%d: ", \ __func__,__LINE__); \ g_stderr pfexpr; \ } (void)0 #endif /** Returns true if zFile is readable, else false. */ bool cmpp__file_is_readable(char const *zFile); #define ustr_c(X) ((unsigned char const *)X) #define ustr_nc(X) ((unsigned char *)X) #define ppCode pp->pimpl->err.code #define dxppCode dx->ppCode #define cmpp__pi(PP) cmpp_pimpl * const pi = PP->pimpl #define cmpp__dx_pi(DX) cmpp_dx_pimpl * const dpi = DX->pimpl #define serr(...) cmpp_err_set(pp, CMPP_RC_SYNTAX, __VA_ARGS__) #define dxserr(...) cmpp_err_set(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__) #define cmpp__li_reserve1_size(li,nInitial) \ (li->n ? (li->n==li->nAlloc ? li->nAlloc * 2 : li->n+1) : nInitial) #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) #endif /* include guard */ /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the core of libcmpp (it used to house all of it, ** but the library grew beyond the confines of a single file). ** ** See the accompanying c-pp.h and README.md and/or c-pp.h for more ** details. */ #include "sqlite3.h" char const * cmpp_version(void){ return CMPP_VERSION; } const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m; const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m; const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m; const cmpp_outputer cmpp_outputer_FILE = { .out = cmpp_output_f_FILE, .flush = cmpp_flush_f_FILE, .cleanup = cmpp_outputer_cleanup_f_FILE, .state = NULL }; const cmpp_b_list cmpp_b_list_empty = cmpp_b_list_empty_m; const cmpp_outputer cmpp_outputer_b = { .out = cmpp_output_f_b, .flush = 0, .cleanup = cmpp_outputer_cleanup_f_b, .state = NULL }; /** Default delimiters for @tokens@. */ static const cmpp__delim delimAtDefault = { .open = { .z = ustr_c("@"), .n = 1 }, .close = { .z = ustr_c("@"), .n = 1 }, .zOwns = NULL }; static const cmpp_api_thunk cmppApiMethods = { #define A(V) #define V(N,T,V) .N = V, #define F(N,T,P) .N = cmpp_ ##N, #define O(N,T) .N = &cmpp_ ##N, cmpp_api_thunk_map(A,V,F,O) #undef F #undef O #undef V #undef A }; /* Fatally exits the app with the given printf-style message. */ CMPP__EXPORT(bool, cmpp_isspace)(int ch){ return ' '==ch || '\t'==ch; } //CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){} static inline int cmpp_isnl(unsigned char const * z, unsigned char const *zEnd){ //assert(z= zBegin ); unsigned char const * z = *p; while( z>zBegin && cmpp_isspace(z[-1]) ) --z; *p = z; } CMPP__EXPORT(void, cmpp_skip_snl_trailing)( unsigned char const *zBegin, unsigned char const **p ){ assert( *p >= zBegin ); unsigned char const * z = *p; /* FIXME: CRNL. */ while( z>zBegin && cmpp_issnl(*z) ) --z; *p = z; } /* Set pp's error state. */ static int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list); /** Sets pp's error state. */ CMPP__EXPORT(int, cmpp_err_set)(cmpp *pp, int rc, char const *zFmt, ...); #define cmpp__err cmpp_err_set #define cmpp_dx_err cmpp_dx_err_set /* Open/close pp's output channel. */ static int cmpp__out_fopen(cmpp *pp, const char *zName); static void cmpp__out_close(cmpp *pp); #define CmppKvp_empty_m \ {CmppSnippet_empty_m,CmppSnippet_empty_m,CmppKvp_op_none} const CmppKvp CmppKvp_empty = CmppKvp_empty_m; /* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just supported cmpp_FILE input. */ typedef struct FileWrapper FileWrapper; struct FileWrapper { /* File's name. */ char const *zName; /* cmpp_FILE handle. */ cmpp_FILE * pFile; /* Where FileWrapper_slurp() stores the file's contents. */ unsigned char * zContent; /* Size of this->zContent, as set by FileWrapper_slurp(). */ cmpp_size_t nContent; }; #define FileWrapper_empty_m {0,0,0,0} static const FileWrapper FileWrapper_empty = FileWrapper_empty_m; /** Proxy for cmpp_fclose() and frees all memory owned by p. It is not an error if p is already closed. */ static void FileWrapper_close(FileWrapper * p); /** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */ static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode); /* Populates p->zContent and p->nContent from p->pFile. */ //static int FileWrapper_slurp(FileWrapper * p, int bCloseFile ); /** If p->zContent ends in \n or \r\n, that part is replaced with 0 and p->nContent is adjusted. Returns true if it chomps, else false. */ static bool FileWrapper_chomp(FileWrapper * p); /* ** Outputs a printf()-formatted message to stderr. */ static void g_stderrv(char const *zFmt, va_list); CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){ switch((cmpp_rc_e)rc){ #define E(N,V,H) case N: return # N; cmpp_rc_e_map(E) #undef E } return NULL; } CMPP__EXPORT(void, cmpp_mfree)(void *p){ /* This MUST be a proxy for sqlite3_free() because allocate memory exclusively using sqlite3_malloc() and friends. */ sqlite3_free(p); } CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){ return sqlite3_realloc64(p, n); } CMPP__EXPORT(void *, cmpp_malloc)(size_t n){ #if 1 return sqlite3_malloc64(n); #else void * p = sqlite3_malloc64(n); if( p ) memset(p, 0, n); return p; #endif } cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){ cmpp_FILE *f; if(zName && ('-'==*zName && !zName[1])){ f = (strchr(zMode, 'w') || strchr(zMode,'+')) ? stdout : stdin ; }else{ f = fopen(zName, zMode); } return f; } void cmpp_fclose( cmpp_FILE * f ){ if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){ fclose(f); } } int cmpp_slurp(cmpp_input_f fIn, void *sIn, unsigned char **pOut, cmpp_size_t * nOut){ unsigned char zBuf[1024 * 16]; unsigned char * pDest = 0; unsigned nAlloc = 0; unsigned nOff = 0; int rc = 0; cmpp_size_t nr = 0; while( 0==rc ){ nr = sizeof(zBuf); if( (rc = fIn(sIn, zBuf, &nr)) ){ break; } if(nr>0){ if(nAlloc < nOff + nr + 1){ nAlloc = nOff + nr + 1; pDest = cmpp_mrealloc(pDest, nAlloc); } memcpy(pDest + nOff, zBuf, nr); nOff += nr; }else{ break; } } if( 0==rc ){ if(pDest) pDest[nOff] = 0; *pOut = pDest; *nOut = nOff; }else{ cmpp_mfree(pDest); } return rc; } void FileWrapper_close(FileWrapper * p){ if(p->pFile) cmpp_fclose(p->pFile); if(p->zContent) cmpp_mfree(p->zContent); *p = FileWrapper_empty; } int FileWrapper_open(FileWrapper * p, const char * zName, const char * zMode){ FileWrapper_close(p); if( (p->pFile = cmpp_fopen(zName, zMode)) ){ p->zName = zName; return 0; }else{ return cmpp_errno_rc(errno, CMPP_RC_IO); } } int FileWrapper_slurp(FileWrapper * p, int bCloseFile){ assert(!p->zContent); assert(p->pFile); int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile, &p->zContent, &p->nContent); if( bCloseFile ){ cmpp_fclose(p->pFile); p->pFile = 0; } return rc; } CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){ if( *n && '\n'==z[*n-1] ){ z[--*n] = 0; if( *n && '\r'==z[*n-1] ){ z[--*n] = 0; } return true; } return false; } bool FileWrapper_chomp(FileWrapper * p){ return cmpp_chomp(p->zContent, &p->nContent); } #if 0 /** Returns the number newline characters between the given starting point and inclusive ending point. Results are undefined if zFrom is greater than zTo. */ static unsigned cmpp__count_lines(unsigned char const * zFrom, unsigned char const *zTo); unsigned cmpp__count_lines(unsigned char const * zFrom, unsigned char const *zTo){ unsigned ln = 0; assert(zFrom && zTo); assert(zFrom <= zTo); for(; zFrom < zTo; ++zFrom){ if((unsigned char)'\n' == *zFrom) ++ln; } return ln; } #endif char const * cmpp__tt_cstr(int tt, bool bSymbolName){ switch(tt){ #define E(N,TOK) case cmpp_TT_ ## N: \ return bSymbolName ? "cmpp_TT_" #N : TOK; cmpp_tt_map(E) #undef E } return NULL; } char const * cmpp_tt_cstr(int tt){ return cmpp__tt_cstr(tt, true); } /** Flags and constants related to CmppLvl. */ enum CmppLvl_e { /** Flag indicating that all ostensible output for a CmpLevel should be elided. This also suppresses non-flow-control directives from being processed. */ CmppLvl_F_ELIDE = 0x01, /** Mask of CmppLvl::flags which are inherited when CmppLvl_push() is used. */ CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE }; //static const CmppDLine CmppDLine_empty = CmppDLine_empty_m; /** Free all memory owned by li but does not free li. */ static void CmppLvlList_cleanup(CmppLvlList *li); /** Allocate a list entry, owned by li, and return it (cleanly zeroed out). Returns NULL and updates pp->err on error. It is expected that the caller will populate the entry's zName using sqlite3_mprintf() or equivalent. */ static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li); /** Returns the most-recently-appended element of li back to li's free-list. It expects to receive that value as a sanity-checking measure and may fail fatally of that's not upheld. */ static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl); static const cmpp_dx_pimpl cmpp_dx_pimpl_empty = cmpp_dx_pimpl_empty_m; #define cmpp_dx_empty_m { \ .pp=0, \ .d=0, \ .sourceName=0, \ .args={ \ .z=0, .nz=0, \ .argc=0, .arg0=0 \ }, \ .pimpl = 0 \ } const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m; #define cmpp_d_empty_m {{0,0},0,0,cmpp_d_impl_empty_m} //static const cmpp_d cmpp_d_empty = cmpp_d_empty_m; static const CmppDList_entry CmppDList_entry_empty = CmppDList_entry_empty_m; /** Free all memory owned by li but does not free li. */ static void CmppDList_cleanup(CmppDList *li); /** Allocate a list entry, owned by li, and return it (cleanly zeroed out). Returns NULL and updates pp->err on error. It is expected that the caller will populate the entry's zName using sqlite3_mprintf() or equivalent. */ static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li); /** Returns the most-recently-appended element of li back to li's free-list. */ static void CmppDList_unappend(CmppDList *li); /** Resets li's list for re-use but does not free it. Returns li. */ //static CmppDList * CmppDList_reuse(CmppDList *li); static CmppDList_entry * CmppDList_search(CmppDList const * li, char const *zName); /** Reset dx and free any memory it may own. */ static void cmpp_dx_cleanup(cmpp_dx * const dx); /** Reset some of dx's parsing-related state in prep for fetching the next line. */ static void cmpp_dx__reset(cmpp_dx * const dx); /* Returns dx's current directive. */ static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){ return dx->d; } static const cmpp_pimpl cmpp_pimpl_empty = { .db = { .dbh = 0, .zName = 0 }, .dx = 0, .out = cmpp_outputer_empty_m, .delim = { .d = cmpp__delim_list_empty_m, .at = cmpp__delim_list_empty_m }, .stmt = { #define E(N,S) .N = 0, CmppStmt_map(E) #undef E }, .err = { .code = 0, .zMsg = 0, .zMsgC = 0 }, .sqlTrace = { .expandSql = false, .counter = 0, .out = cmpp_outputer_empty_m }, .flags = { .allocStamp = 0, .nIncludeDir = 0, .nDxDepth = 0, .nSavepoint = 0, .doDebug = 0, .chompF = 0, .newFlags = 0, .isInternalDirectiveReg = false, .nextIsCall = false, .needsLazyInit = true }, .policy = { .at = {0,0,0}, .un = {0,0,0} }, .d = { .list = CmppDList_empty_m, .autoload = cmpp_d_autoloader_empty_m }, .mod = { .sohList = CmppSohList_empty_m, .path = cmpp_b_empty_m, .soExt = CMPP_PLATFORM_EXT_DLL, /* Yes, '*'. It makes sense in context. */ .pathSep = '*' // 0x1e /* "record separator" */ doesn't work. Must be non-ctrl. }, .recycler = { .buf = cmpp_b_list_empty_m, .bufSort = cmpp_b_list_UNSORTED, .argPimpl = NULL } }; #if 0 static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){ return cmpp__out2(pp, &pp->out, z, n); } #endif /** Returns an approximate cmpp_tt for the given SQLITE_... value from sqlite3_column_type() or sqlite3_value_type(). */ static cmpp_tt cmpp__tt_for_sqlite(int sqType); /** Init code which is usually run as part of the ctor but may have to be run later, after cmpp_reset(). We can't run it from cmpp_reset() because that could leave post-reset in an error state, which is icky. */ int cmpp__lazy_init(cmpp *pp){ if( !ppCode && pp->pimpl->flags.needsLazyInit ){ pp->pimpl->flags.needsLazyInit = false; cmpp__delim_list * li = &pp->pimpl->delim.d; if( !li->n ) cmpp_delimiter_push(pp, NULL); li = &pp->pimpl->delim.at; if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL); #if defined(CMPP_CTOR_INSTANCE_INIT) if( !ppCode ){ extern int CMPP_CTOR_INSTANCE_INIT(cmpp*); int const rc = CMPP_CTOR_INSTANCE_INIT(pp); if( rc && !ppCode ){ cmpp__err(pp, rc, "Initialization via CMPP_CTOR_INSTANCE_INIT() failed " "with code %d/%s.", rc, cmpp_rc_cstr(rc) ); } } #endif } return ppCode; } static void cmpp__wipe_policies(cmpp *pp){ if( 0==ppCode ){ PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0); PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0); if( 0==ppCode ){ PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT); PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT); } } } CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_cfg const * cfg){ cmpp_pimpl * pi = 0; cmpp * pp = 0; void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi)); if( mv ){ if( !cfg ){ static const cmpp_ctor_cfg dfltCfg = {0}; cfg = &dfltCfg; } cmpp const x = { .api = &cmppApiMethods, .pimpl = (cmpp_pimpl*)((unsigned char *)mv + sizeof(cmpp)) /* ^^^ (T const * const) members */ }; memcpy(mv, &x, sizeof(x)) /* FWIW, i'm convinced that this is a legal way to transfer these const-pointers-to-const. If not, we'll need to change those cmpp members from (T const * const) to (T const *). */; pp = mv; assert(pp->api == &cmppApiMethods); assert(pp->pimpl); pi = pp->pimpl; *pOut = pp; *pi = cmpp_pimpl_empty; assert( pi->flags.needsLazyInit ); pi->flags.newFlags = cfg->flags; pi->flags.allocStamp = &cmpp_pimpl_empty; if( cfg->dbFile ){ pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile); cmpp_check_oom(pp, pi->db.zName); } if( 0==ppCode ){ cmpp__wipe_policies(pp); cmpp__lazy_init(pp); } } return pp ? ppCode : CMPP_RC_OOM; } CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){ cmpp__pi(pp); cmpp_outputer_cleanup(&pi->sqlTrace.out); pi->sqlTrace.out = cmpp_outputer_empty; if( pi->d.autoload.dtor ){ pi->d.autoload.dtor(pi->d.autoload.state); } pi->d.autoload = cmpp_pimpl_empty.d.autoload; cmpp_b_clear(&pi->mod.path); if( pi->stmt.spRelease && pi->stmt.spRollback ){ /* Cleanly kill all savepoint levels. This is truly superfluous, as they'll all be rolled back (if the db is persistent) or nuked (if using a :memory: db) momentarily. However, we'll eventually need this for a partial-clear operation which leaves the db and custom directives intact. For now it lives here but will eventually move to wherever that ends up being. 2025-11-16: or not. It's fine here, really. */ sqlite3_reset(pi->stmt.spRelease); while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){ sqlite3_reset(pi->stmt.spRollback); sqlite3_step(pi->stmt.spRollback); sqlite3_reset(pi->stmt.spRelease); } } cmpp__out_close(pp); CmppDList_cleanup(&pi->d.list); #define E(N,S) \ if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;} CmppStmt_map(E) (void)0; #undef E if( pi->db.dbh ){ if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){ sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL) /* ignoring error */; } sqlite3_close(pi->db.dbh); pi->db.dbh = 0; } cmpp__delim_list_reuse(&pi->delim.d); cmpp__delim_list_reuse(&pi->delim.at); //why? cmpp_b_list_reuse(&pi->cache.buf); cmpp__err_clear(pp); {/* Zero out pi but save some pieces for later, when pp is cmpp_dtor()'d */ cmpp_pimpl const tmp = *pi; *pi = cmpp_pimpl_empty; pi->db = tmp.db /* restore db.zName */; pi->recycler = tmp.recycler; pi->policy = tmp.policy; pi->delim = tmp.delim; pi->mod.sohList = tmp.mod.sohList; cmpp__wipe_policies(pp); pi->flags.allocStamp = tmp.flags.allocStamp; pi->flags.newFlags = tmp.flags.newFlags; pi->flags.needsLazyInit = true; } } static void cmpp__delim_list_cleanup(cmpp__delim_list *li); CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){ if( pp ){ cmpp__pi(pp); cmpp_reset(pp); cmpp_mfree(pi->db.zName); PodList__atpol_finalize(&cmpp__epol(pp,at)); assert(!cmpp__epol(pp,at).na); PodList__unpol_finalize(&cmpp__epol(pp,un)); assert(!cmpp__epol(pp,un).na); cmpp_b_list_cleanup(&pi->recycler.buf); cmpp__delim_list_cleanup(&pi->delim.d); cmpp__delim_list_cleanup(&pi->delim.at); for( cmpp_args_pimpl * apNext = 0, * ap = pi->recycler.argPimpl; ap; ap = apNext ){ apNext = ap->nextFree; ap->nextFree = 0; cmpp_args_pimpl_cleanup(ap); cmpp_mfree(ap); } CmppSohList_close(&pi->mod.sohList); if( &cmpp_pimpl_empty==pi->flags.allocStamp ){ pi->flags.allocStamp = 0; cmpp_mfree(pp); } } } CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){ return pp ? 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) : false; } /** Sets ppCode if m is NULL. Returns ppCode. */ CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){ int rc; if( pp ){ if( !m ){ //assert(!"oom"); cmpp__err(pp, CMPP_RC_OOM, 0); } rc = ppCode; }else{ rc = m ? 0 : CMPP_RC_OOM; } return rc; } //CxMPP_WASM_EXPORT void *cmpp__malloc(cmpp *pp, cmpp_size_t n){ void *p = 0; if( 0==ppCode ){ p = cmpp_malloc(n); cmpp_check_oom(pp, p); } return p; } /** If ppCode is not 0 then it flushes pp's output channel. If that fails, it sets ppCode. Returns ppCode. */ static int cmpp__flush(cmpp *pp){ if( !ppCode && pp->pimpl->out.flush ){ int const rc = pp->pimpl->out.flush(pp->pimpl->out.state); if( rc && !ppCode ){ cmpp_err_set(pp, rc, "Flush failed."); } } return ppCode; } void cmpp__out_close(cmpp *pp){ cmpp__flush(pp)/*ignoring result*/; cmpp_outputer_cleanup(&pp->pimpl->out); pp->pimpl->out = cmpp_pimpl_empty.out; } int cmpp__out_fopen(cmpp *pp, const char *zName){ cmpp__out_close(pp); if( !ppCode ){ cmpp_FILE * const f = cmpp_fopen(zName, "wb"); if( f ){ ppCode = 0; pp->pimpl->out = cmpp_outputer_FILE; pp->pimpl->out.state = f; pp->pimpl->out.name = zName; }else{ ppCode = cmpp__err( pp, cmpp_errno_rc(errno, CMPP_RC_IO), "Error opening file %s", zName ); } } return ppCode; } static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw, const char * zName, const char * zMode){ int const rc = FileWrapper_open(fw, zName, zMode); if( rc ){ cmpp__err(pp, rc, "Error %s opening file [%s] " "with mode [%s]", cmpp_rc_cstr(rc), zName, zMode); } return ppCode; } static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){ assert( fw->pFile ); int const rc = FileWrapper_slurp(fw, 1); if( rc ){ cmpp__err(pp, rc, "Error %s slurping file %s", cmpp_rc_cstr(rc), fw->zName); } return ppCode; } void g_stderrv(char const *zFmt, va_list va){ vfprintf(0 ? stdout : stderr, zFmt, va); } void g_stderr(char const *zFmt, ...){ va_list va; va_start(va, zFmt); g_stderrv(zFmt, va); va_end(va); } CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){ return (char const *)cmpp__dx_zdelim(dx); } int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n){ assert( pOut ); if( !ppCode && pOut->out && n ){ int const rc = pOut->out(pOut->state, z, n); if( rc ){ cmpp__err(pp, rc, "Write of %" CMPP_SIZE_T_PFMT " bytes to output stream failed.", n); } } return ppCode; } CMPP__EXPORT(int, cmpp_dx_out_raw)(cmpp_dx * dx, void const *z, cmpp_size_t n){ if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode; return cmpp__out2(dx->pp, &dx->pp->pimpl->out, z, n); } CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){ assert( out ); if( !ppCode && zFmt && *zFmt && out->out ){ char * s = sqlite3_vmprintf(zFmt, va); if( 0==cmpp_check_oom(pp, s) ){ cmpp__out2(pp, out, s, cmpp__strlen(s, -1)); } cmpp_mfree(s); } return ppCode; } CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){ assert( out ); if( !ppCode && zFmt && *zFmt && out->out ){ va_list va; va_start(va, zFmt); cmpp_outfv2(pp, out, zFmt, va); va_end(va); } return ppCode; } CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){ return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va); } CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){ if( !ppCode ){ va_list va; va_start(va, zFmt); cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va); va_end(va); } return ppCode; } CMPP__EXPORT(int, cmpp_dx_outf)(cmpp_dx *dx, char const *zFmt, ...){ if( !dxppCode && zFmt && *zFmt && dx->pp->pimpl->out.out ){ va_list va; va_start(va, zFmt); cmpp_outfv(dx->pp, zFmt, va); va_end(va); } return dxppCode; } static int cmpp__affirm_undef_policy(cmpp *pp, unsigned char const *zName, cmpp_size_t nName){ if( 0==ppCode && cmpp_unpol_ERROR==cmpp__policy(pp,un) ){ cmpp__err(pp, CMPP_RC_NOT_DEFINED, "Key '%.*s' was not found and the undefined-value " "policy is 'error'.", (int)nName, zName); } return ppCode; } static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut, unsigned char const * zFrom, cmpp_size_t n, cmpp_atpol_e atPolicy){ enum state_e { /* looking for @token@ opening @ */ state_opening, /* looking for @token@ closing @ */ state_closing }; cmpp__pi(pp); if( ppCode ) return ppCode; if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at); assert( cmpp_atpol_invalid!=atPolicy ); unsigned char const *zLeft = zFrom; unsigned char const * const zEnd = zFrom + n; unsigned char const *z = (cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy) ? zEnd : zLeft; unsigned char const chEol = (unsigned char)'\n'; cmpp__delim const * delim = cmpp__delim_list_get(&pp->pimpl->delim.at); if( !delim && zout; } assert( pi->dx ? !cmpp_dx_is_eliding(pi->dx) : 1 ); #define tflush \ if(z>zEnd) z=zEnd; \ if(zLeftpimpl->dx ? pp->pimpl->dx->pimpl : NULL; for( ; zflags.countLines ){ ++dxp->lineNo; } #endif state = state_opening; continue; } if( state_opening==state ){ if( z + delim->open.n < zEnd && 0==memcmp(z, delim->open.z, delim->open.n) ){ tflush; z += delim->open.n; if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft); if( 0 ){ g_warn("\nzLeft..=[%s]\nz=[%s]", zLeft, z); } state = state_closing; #if 1 /* Handle call of @[directive ...args]@ i'm not a huge fan of this syntax, but that may go away if we replace the single-char separator with a pair of opening/closing delimiters. */ if( z>", (int)(zEnd-zb), zb); if( cmpp__find_closing2(pp, &zb, zEnd, &nl) ){ break; } //g_warn("Found: <<%.*s>>", (int)(zb+1-z), z); if( zb + delim->close.n >= zEnd || 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){ serr("Expecting '%s' after closing ']'.", delim->close.z); break; } if( nl && dxp && dxp->flags.countLines ){ dxp->pos.lineNo +=nl; } cmpp_call_str(pp, z+delim->open.n, (zb - z - delim->open.n), cmpp_b_reuse(bCall), 0); if( 0==ppCode ){ cmpp__out2(pp, pOut, bCall->z, bCall->n); state = state_opening; zLeft = z = zb + delim->close.n + 1; //g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z); } } #endif if( z>=zEnd ) break; goto again /* avoid adjusting z again */; } }else{/*we're looking for delim->closer*/ assert( state_closing==state ); if( z + delim->close.n <= zEnd && 0==memcmp(z, delim->close.z, delim->close.n ) ){ /* process the ... part of @...@ */ assert( state_closing==state ); assert( zLeftopen.z, delim->open.n) ); unsigned char const *zKey = zLeft + delim->open.n; cmpp_ssize_t const nKey = z - zLeft - delim->open.n; if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey); assert( nKey>= 0 ); if( !nKey ){ serr("Empty key is not permitted in %s...%s.", delim->open.z, delim->close.z); break; } if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){ if(0){ g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n, (int)bVal->n, bVal->z); } if( bVal->n ){ cmpp__out2(pp, pOut, bVal->z, bVal->n); }else{ /* Elide it */ } zLeft = z + delim->close.n; assert( zLeft<=zEnd ); }else if( !ppCode ){ assert( !bVal->n ); /* No matching define . */ switch( atPolicy ){ case cmpp_atpol_ELIDE: zLeft = z + delim->close.n; break; case cmpp_atpol_RETAIN: tflush; break; case cmpp_atpol_ERROR: cmpp__err(pp, CMPP_RC_NOT_DEFINED, "Undefined %skey%s: %.*s", delim->open.z, delim->close.z, nKey, zKey); break; case cmpp_atpol_invalid: case cmpp_atpol_CURRENT: case cmpp_atpol_OFF: assert(!"this shouldn't be reachable" ); cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d", atPolicy); break; } }/* process @...@ */ state = state_opening; assert( z<=zEnd ); }/*matched a closer*/ }/*state_closer==state*/ assert( z<=zEnd ); }/*per-line loop*/ }/*outer loop*/ #if 0 if( 0==ppCode && state_closer==state ){ serr("Opening '%s' found without a closing '%s'.", delim->open.z, delim->close.z); } #endif tflush; #undef tflush cmpp_b_return(pp, bCall); cmpp_b_return(pp, bVal); return ppCode; } CMPP__EXPORT(int, cmpp_dx_out_expand)(cmpp_dx const * const dx, cmpp_outputer * pOut, unsigned char const * zFrom, cmpp_size_t n, cmpp_atpol_e atPolicy){ if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode; return cmpp__out_expand(dx->pp, pOut, zFrom, n, atPolicy); } CmppLvl * CmppLvl_get(cmpp_dx const *dx){ return dx->pimpl->dxLvl.n ? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1] : 0; } static const CmppLvl CmppLvl_empty = CmppLvl_empty_m; CmppLvl * CmppLvl_push(cmpp_dx *dx){ CmppLvl * p = 0; if( !dxppCode ){ CmppLvl * const pPrev = CmppLvl_get(dx); p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl); if( p ){ *p = CmppLvl_empty; p->lineNo = dx->pimpl->dline.lineNo; //p->d = dx->d; if( pPrev ){ p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags); //if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE; } } } return p; } void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){ CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl); } void CmppLvl_elide(CmppLvl *lvl, bool on){ if( on ) lvl->flags |= CmppLvl_F_ELIDE; else lvl->flags &= ~CmppLvl_F_ELIDE; } bool CmppLvl_is_eliding(CmppLvl const *lvl){ return lvl && !!(lvl->flags & CmppLvl_F_ELIDE); } #if 0 void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){ CmppLvl_elide(CmppLvl_get(dx), on); } #endif bool cmpp_dx_is_eliding(cmpp_dx const *dx){ return CmppLvl_is_eliding(CmppLvl_get(dx)); } char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n){ char * z = 0; int const rc = sqlite3_str_errcode(s); cmpp__db_rc(pp, rc, "sqlite3_str_errcode()"); if(0==rc){ int const nStr = sqlite3_str_length(s); if(n) *n = nStr; z = sqlite3_str_finish(s); if( !z ){ assert( 0==nStr && "else rc!=0" ); } }else{ cmpp_mfree( sqlite3_str_finish(s) ); } return z; } int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val){ return ppCode ? ppCode : cmpp__db_rc(pp, sqlite3_bind_int64(pStmt, col, val), "from cmpp__bind_int()"); } int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val){ unsigned char buf[32]; snprintf((char *)buf, sizeof(buf), "%" PRIi64, val); return cmpp__bind_textn(pp, pStmt, col, buf, -1); } int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col){ return ppCode ? ppCode : cmpp__db_rc(pp, sqlite3_bind_null(pStmt, col), "from cmpp__bind_null()"); } static int cmpp__bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr, cmpp_ssize_t n, void (*dtor)(void *)){ if( 0==ppCode ){ cmpp__db_rc( pp, (zStr && n) ? sqlite3_bind_text(pStmt, col, (char const *)zStr, (int)n, dtor) : sqlite3_bind_null(pStmt, col), sqlite3_sql(pStmt) ); } return ppCode; } int cmpp__bind_textn(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr, cmpp_ssize_t n){ return cmpp__bind_textx(pp, pStmt, col, zStr, (int)n, SQLITE_TRANSIENT); } int cmpp__bind_text(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr){ return cmpp__bind_textn(pp, pStmt, col, zStr, -1); } #if 0 int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col, const char * zFmt, ...){ if( 0==p->err.code ){ int rc; sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh); int n = 0; char * z; va_list va; if( !str ) return ppCode; va_start(va,zFmt); sqlite3_str_vappendf(str, zFmt, va); va_end(va); z = cmpp_str_finish(str, &n); cmpp__db_rc( pp, z ? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free) : sqlite3_bind_null(pStmt, col), sqlite3_sql(pStmt) ); cmpp_mfree(z); } return p->err.code; } #endif void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName){ cmpp__pi(pp); cmpp_outputer_cleanup(&pi->out); if( out ) pi->out = *out; else pi->out = cmpp_outputer_empty; pi->out.name = zName; } void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew, cmpp_outputer *oPrev){ if( oPrev ){ *oPrev = pp->pimpl->out; } pp->pimpl->out = *oNew; } #if 0 static void delim__list_dump(cmpp const *pp){ cmpp__delim_list const *li = &pp->pimpl->delim.d; if( li->n ){ g_warn0("delimiter stack:"); for(cmpp_size_t i = 0; i < li->n; ++i ){ g_warn("#%d: %s", (int)i, li->list[i].z); } } } #endif static bool cmpp__valid_delim(cmpp * const pp, char const *z, char const *zEnd){ char const * const zB = z; for( ; z < zEnd; ++z ){ if( *z<33 || 127==*z ){ cmpp_err_set(pp, CMPP_RC_SYNTAX, "Delimiters may not contain " "control characters."); return false; } } if( zB==z ){ cmpp_err_set(pp, CMPP_RC_SYNTAX, "Delimiters may not be empty."); } return z>zB; } CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){ if( ppCode ) return ppCode; unsigned n; if( zDelim ){ n = cmpp__strlen(zDelim, -1); if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){ return ppCode; }else if( n>12 /* arbitrary but seems sensible enough */ ){ return cmpp__err(pp, CMPP_RC_MISUSE, "Invalid delimiter (too long): %s", zDelim); } } cmpp__pi(pp); if( pi->delim.d.n ){ cmpp__delim * const delim = cmpp__pp_delim(pp); if( !cmpp_check_oom(pp, delim) ){ cmpp__delim_cleanup(delim); if( zDelim ){ delim->open.n = n; delim->open.z = delim->zOwns = (unsigned char*)sqlite3_mprintf("%.*s", n, zDelim); cmpp_check_oom(pp, delim->zOwns); }else{ assert( delim->open.z ); assert( !delim->zOwns ); assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 ); } } }else{ assert(!"Cannot set delimiter on an empty stack!"); cmpp_err_set(pp, CMPP_RC_MISUSE, "Directive delimter stack is empty."); } return ppCode; } CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){ cmpp__delim const * d = cmpp__pp_delim(pp); if( !d ) d = &cmpp__delim_empty; *zDelim = (char const *)d->open.z; } CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){ cmpp__delim * const d = cmpp__delim_list_push(pp, &pp->pimpl->delim.d); if( d && cmpp_delimiter_set(pp, zDelim) ){ cmpp__delim_list_pop(&pp->pimpl->delim.d); } return ppCode; } CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){ cmpp__delim_list * const li = &pp->pimpl->delim.d; if( li->n ){ //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp)); cmpp__delim_list_pop(li); if( 0 && li->n ){ g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp)); } }else if( !ppCode ){ assert(!"Attempt to pop an empty delimiter stack."); cmpp_err_set(pp, CMPP_RC_MISUSE, "Cannot pop an empty delimiter stack."); } return ppCode; } CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp, char const *zOpen, char const *zClose){ if( 0==ppCode ){ cmpp__pi(pp); cmpp__delim * const d = pi->delim.at.n ? &pi->delim.at.list[pi->delim.at.n-1] : NULL; assert( d ); if( !d ){ return cmpp__err(pp, CMPP_RC_MISUSE, "@token@ delimiter stack is currently empty."); } if( 0==zOpen ){ zOpen = (char const *)delimAtDefault.open.z; zClose = (char const *)delimAtDefault.close.z; }else if( 0==zClose ){ zClose = zOpen; } cmpp_size_t const nO = cmpp__strlen(zOpen, -1); cmpp_size_t const nC = cmpp__strlen(zClose, -1); assert( zOpen && zClose ); if( !cmpp__valid_delim(pp, zOpen, zOpen+nO) || !cmpp__valid_delim(pp, zClose, zClose+nC) ){ return ppCode; } cmpp_b b = cmpp_b_empty /* Don't use cmpp_b_borrow() here because we'll unconditionally transfer ownership of b.z to d. */; if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){ #ifndef NDEBUG unsigned char const * const zReallocCheck = b.z; #endif /* Copy the open/close tokens to a single string to simplify management. */ cmpp_b_append4(pp, &b, zOpen, nO); cmpp_b_append_ch(&b, '\0'); cmpp_b_append4(pp, &b, zClose, nC); assert( zReallocCheck==b.z && "Else buffer was not properly pre-sized" ); cmpp__delim_cleanup(d); d->open.z = b.z; d->open.n = nO; d->close.z = d->open.z + nO + 1/*NUL*/; d->close.n = nC; d->zOwns = b.z; b = cmpp_b_empty /* transfer memory ownership */; } } return ppCode; } CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen, char const *zClose){ cmpp__delim * const d = cmpp__delim_list_push(pp, &pp->pimpl->delim.at); if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){ cmpp__delim_list_pop(&pp->pimpl->delim.at); } return ppCode; } CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){ cmpp__delim_list * const li = &pp->pimpl->delim.at; if( li->n ){ //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp)); cmpp__delim_list_pop(li); }else if( !ppCode ){ assert(!"Attempt to pop an empty @token@ delim stack."); cmpp_err_set(pp, CMPP_RC_MISUSE, "Cannot pop an empty @token@ delimiter stack."); } return ppCode; } CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp, char const **zOpen, char const **zClose){ cmpp__delim const * d = cmpp__delim_list_get(&pp->pimpl->delim.at); assert( d ); if( !d ) d = &delimAtDefault; if( zClose ) *zClose = (char const *)d->close.z; if( zOpen ) *zOpen = (char const *)d->open.z; } #define cmpp__scan_int2(SZ,PFMT,Z,N,TGT) \ (Npimpl->flags.chompF ){ FileWrapper_chomp(&fw); } if( fw.nContent ){ cmpp__bind_textx(pp, q, 3, fw.zContent, (cmpp_ssize_t)fw.nContent, sqlite3_free); fw.zContent = 0 /* transferred ownership */; fw.nContent = 0; }else{ cmpp__bind_null(pp, q, 2); } cmpp__step(pp, q, true); g_debug(pp,2,("define: %s%s%s\n", kvp.k.z, kvp.v.z ? " with value " : "", kvp.v.z ? (char const *)kvp.v.z : "")); } FileWrapper_close(&fw); return ppCode; } CMPP__EXPORT(int, cmpp_has)(cmpp *pp, const char * zName, cmpp_ssize_t nName){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false); if( q ){ nName = cmpp__strlen(zName, nName); cmpp__bind_textn(pp, q, 1, ustr_c(zName), nName); if(SQLITE_ROW == cmpp__step(pp, q, true)){ rc = 1; }else{ rc = 0; } g_debug(pp,1,("has [%s] ?= %d\n",zName, rc)); } return rc; } int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_ssize_t nName){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp__bind_textn(pp, q, 1, zName, nName); assert(0==ppCode); if(SQLITE_ROW == cmpp__step(pp, q, false)){ rc = sqlite3_column_int(q, 0); }else{ rc = 0; cmpp__affirm_undef_policy(pp, zName, nName); } cmpp__stmt_reset(q); } return rc; } int cmpp__get_int(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, int *pOut ){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp__bind_textn(pp, q, 1, zName, nName); assert(0==ppCode); if(SQLITE_ROW == cmpp__step(pp, q, false)){ *pOut = sqlite3_column_int(q,0); }else{ cmpp__affirm_undef_policy(pp, zName, nName); } cmpp__stmt_reset(q); } return ppCode; } int cmpp__get_b(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, cmpp_b * os, bool enforceUndefPolicy){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp__bind_textn(pp, q, 1, zName, nName); int n = 0; if(SQLITE_ROW == cmpp__step(pp, q, false)){ const unsigned char * z = sqlite3_column_text(q, 3); n = sqlite3_column_bytes(q, 3); cmpp_b_append4(pp, os, z, (cmpp_size_t)n); rc = 1; }else{ if( enforceUndefPolicy ){ cmpp__affirm_undef_policy(pp, zName, nName); } rc = 0; } cmpp__stmt_reset(q); g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n", nName, zName, rc, os->n, os->z)); } return rc; } int cmpp__get(cmpp *pp, unsigned const char * zName, cmpp_ssize_t nName, unsigned char **zVal, unsigned int *nVal){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp__bind_textn(pp, q, 1, zName, nName); int n = 0; if(SQLITE_ROW == cmpp__step(pp, q, false)){ const unsigned char * z = sqlite3_column_text(q, 3); n = sqlite3_column_bytes(q, 3); if( nVal ) *nVal = (unsigned)n; *zVal = ustr_nc(sqlite3_mprintf("%.*s", n, z)) /* TODO? Return NULL for the n==0 case? */; if( n && cmpp_check_oom(pp, *zVal) ){ assert(!*zVal); }else{ rc = 1; } }else{ cmpp__affirm_undef_policy(pp, zName, nName); rc = 0; } cmpp__stmt_reset(q); g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n", nName, zName, rc, *zVal ? n : 0, *zVal ? (char const *)*zVal : "")); } return rc; } CMPP__EXPORT(int, cmpp_undef)(cmpp *pp, const char * zKey, unsigned int *nRemoved){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDel, false); if( q ){ unsigned int const n = strlen(zKey); cmpp__bind_textn(pp, q, 1, ustr_c(zKey), (cmpp_ssize_t)n); cmpp__step(pp, q, true); if( nRemoved ){ *nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh); } g_debug(pp,2,("undefine: %.*s\n",n, zKey)); } return ppCode; } int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid){ if( pRowid ) *pRowid = 0; if( !ppCode && zDir && *zDir ){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false); if( q ){ /* TODO: normalize zDir before insertion so that a/b and a/b/ are equivalent. The relavent code is in another tree, awaiting a decision on whether to import it or re-base cmpp on top of that library (which would, e.g., replace cmpp_b with that one, which is more mature). */ cmpp__bind_int(pp, q, 1, priority); cmpp__bind_textn(pp, q, 2, ustr_c(zDir), -1); int const rc = cmpp__step(pp, q, false); if( SQLITE_ROW==rc ){ ++pp->pimpl->flags.nIncludeDir; if( pRowid ){ *pRowid = sqlite3_column_int64(q, 0); } } cmpp__stmt_reset(q); /*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s", rc, pRowid ? *pRowid : 0, priority, zDir);*/ g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir)); } } return ppCode; } CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){ return cmpp__include_dir_add(pp, zDir, 0, NULL); } int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true); if( q ){ /* Hoop-jumping to allow this to work even if pp's in an error state. */ int rc = sqlite3_bind_int64(q, 1, rowid); if( 0==rc ){ rc = sqlite3_step(q); if( SQLITE_ROW==rc ){ --pp->pimpl->flags.nIncludeDir; rc = 0; }else if( SQLITE_DONE==rc ){ rc = 0; } } if( rc && !ppCode ){ cmpp__db_rc(pp, rc, sqlite3_sql(q)); } cmpp__stmt_reset(q); g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid)); } return ppCode; } CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){ #if CMPP_ENABLE_DLLS if( !ppCode ){ cmpp_b * const ob = &pp->pimpl->mod.path; if( !zDirs && !ob->n ){ zDirs = getenv("CMPP_MODULE_PATH"); if( !zDirs ){ zDirs = CMPP_MODULE_PATH; } } if( !zDirs || !*zDirs ) return 0; char const * z = zDirs; char const * const zEnd = zDirs + strlen(zDirs); if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){ unsigned char * zo = ob->z + ob->n; unsigned i = 0; for( ; z < zEnd && !ppCode; ++z ){ switch( *z ){ case CMPP_PATH_SEPARATOR: *zo++ = pp->pimpl->mod.pathSep; break; default: if( 1==++i && ob->n ){ cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep); } *zo++ = *z; break; } } *zo = 0; ob->n = (zo - ob->z); } } return ppCode; #else return CMPP_RC_UNSUPPORTED; #endif } CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){ if( 0==ppCode ){ cmpp__pi(pp); if( pi->db.dbh ){ return cmpp__err(pp, CMPP_RC_MISUSE, "DB name cannot be set after db initialization."); } if( zName ){ char * const z = sqlite3_mprintf("%s", zName); if( 0==cmpp_check_oom(pp, z) ){ cmpp_mfree(pi->db.zName); pi->db.zName = z; } }else{ cmpp_mfree(pi->db.zName); pi->db.zName = 0; } } return ppCode; } bool cmpp__is_legal_key(unsigned char const *zName, cmpp_size_t n, unsigned char const **zErrPos, bool equalIsLegal){ if( !n || n>64/*arbitrary*/ ){ if( zErrPos ) *zErrPos = 0; return false; } unsigned char const * z = zName; unsigned char const * const zEnd = zName + n; for( ; z='a' && *z<='z') || (*z>='A' && *z<='Z') || (z>zName && ('-'==*z /* This is gonna bite us if we extend the expresions to support +/-. Expressions currently parse X=Y (no spaces) as the three tokens X = Y, but we'd need to require a space between X-Y in expressions because '-' is a legal symbol character. i've looked at making '-' illegal but it's just too convenient for use in define keys. Once one is used to tcl-style-naming of stuff, it's painful to have to go back to snake_case. */ || (*z>='0' && *z<='9'))) || (*z>='.' && *z<='/') || (*z==':') || (*z=='_') || (equalIsLegal && z>zName && '='==*z) || (*z & 0x80) ) ){ if( zErrPos ) *zErrPos = z; return false; } } return true; } bool cmpp_is_legal_key(unsigned char const *zName, cmpp_size_t n, unsigned char const **zErrPos){ return cmpp__is_legal_key(zName, n, zErrPos, false); } int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey, cmpp_ssize_t nKey, bool permitEqualSign){ if( !ppCode ){ unsigned char const *zAt = 0; nKey = cmpp__strlenu(zKey, nKey); if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){ cmpp__err(pp, CMPP_RC_SYNTAX, "Illegal character 0x%02x in key [%.*s]", (int)*zAt, nKey, zKey); } } return ppCode; } CMPP__EXPORT(bool, cmpp_next_chunk)(unsigned char const **zPos, unsigned char const *zEnd, unsigned char chSep, cmpp_size_t *pCounter){ assert( zPos ); assert( *zPos ); assert( zEnd ); if( *zPos >= zEnd ) return false; unsigned char const * z = *zPos; while( zpimpl state, which "doesn't happen". */ //static bool cmpp__dx_next_line(cmpp_dx * const dx, CmppDLine *ln){ assert( !dxppCode ); cmpp_dx_pimpl * const dxp = dx->pimpl; if(!dxp->pos.z) dxp->pos.z = dxp->zBegin; assert( dxp->zEnd ); if( dxp->pos.z>=dxp->zEnd ){ return false; } assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n') && "Else we've mismanaged something."); cmpp__dx_pi(dx); ln->lineNo = dpi->pos.lineNo; ln->zBegin = dpi->pos.z; ln->zEnd = ln->zBegin; return cmpp_next_chunk(&ln->zEnd, dpi->zEnd, (unsigned char)'\n', &dpi->pos.lineNo); } /** Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any non-delimiter output found along the way to dx->pp's output channel. This updates dx->pimpl->pos.z and dx->pimpl->pos.lineNo as it goes. If a delimiter is found, it sets *gotOne to true and updates dx->pimpl->dline to point to the remainder of that line. On no match *gotOne will be false and EOF will have been reached. Returns dxppCode. If it returns non-0 then the state of dx's tokenization pieces are unspecified. i.e. it's illegal to call this again without a reset. */ static int cmpp_dx_delim_search(cmpp_dx * const dx, bool * gotOne){ if( dxppCode ) return dxppCode; cmpp_dx_pimpl * const dxp = dx->pimpl; if(!dxp->pos.z) dxp->pos.z = dxp->zBegin; if( dxp->pos.z>=dxp->zEnd ){ *gotOne = false; return 0; } assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n') && "Else we've mismanaged something."); cmpp__pi(dx->pp); cmpp__delim const * const delim = cmpp__dx_delim(dx); if(!delim) { return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "The directive delimiter stack is empty."); } unsigned char const * const zD = delim->open.z; unsigned short const nD = delim->open.n; unsigned char const * const zEnd = dxp->zEnd; unsigned char const * zLeft = dxp->pos.z; unsigned char const * z = zLeft; assert(zD); assert(nD); #if 0 assert( 0==*zEnd && "Else we'll misinteract with strcspn()" ); if( *zEnd ){ return cmpp_dx_err(dx, CMPP_RC_RANGE, "Input must be NUL-terminated."); } #endif ++dxp->flags.countLines; while( zpos.lineNo; ++z; } #define tflush \ if( z>zEnd ) z=zEnd; \ if( z>zLeft && cmpp_dx_out_expand(dx, &pi->out, zLeft, \ (cmpp_size_t)(z-zLeft), \ cmpp_atpol_CURRENT) ){ \ --dxp->flags.countLines; \ return dxppCode; \ } zLeft = z CmppDLine * const dline = &dxp->dline; bool atBOL = true /* At the start of a line? Successful calls to this always end at either BOL or EOF. */; if( 0 ){ g_warn("scanning... <<%.*s...>>", (zEnd-z)>20?20:(zEnd-z), z); } while( zpos.lineNo; z += 1 + ('\r'==*z); atBOL = true; } if( atBOL ){ break; } ++z; } if( !atBOL ) break; } if( 0 ){ g_warn("at BOL... <<%.*s...>>", (zEnd-z) > 20 ? 20 : (zEnd-z), z); } /* We're at BOL. Check for a delimiter with optional leading spaces. */ tflush; cmpp_skip_space(&z, zEnd); int const skip = cmpp_isnl(z, zEnd); if( skip ){ /* Special case: a line comprised solely of whitespace. If we don't catch this here, we won't recognize a delimiter which starts on the next line. */ tflush; z += skip; ++dxp->pos.lineNo; continue; } if( 0 ){ g_warn("at BOL... <<%.*s...>>", (zEnd-z) > 20 ? 20 : (zEnd-z), z); } if( z + nD>zEnd ){ /* Too short for a delimiter. We'll catch the z+nD==zEnd corner case in a moment. */ z = zEnd; break; } if( memcmp(z, zD, nD) ){ /* Not a delimiter. Keep trying. */ atBOL = false; ++z; continue; } /* z now points to a delimiter which sits at the start of a line (ignoring leading spaces). */ z += nD /* skip the delimiter */; cmpp_skip_space(&z, zEnd) /* skip spaces immediately following the delimiter. */; if( z>=zEnd || cmpp_isnl(z, zEnd) ){ dxserr("No directive name found after %s.", zD); /* We could arguably treat this as no match and pass this line through as-is but that currently sounds like a pothole. */ break; } /* Set up dx->pimpl->dline to encompass the whole directive line sans delimiter and leading spaces. */ dline->zBegin = z /* dx->pimpl->dline starts at the directive name and extends until the next EOL/EOF. We don't yet know if it's a legal directive name - cmpp_dx_next() figures that part out. */; dline->lineNo = dxp->pos.lineNo; /* Now find the end of the line or EOF, accounting for backslash-escaped newlines and _not_ requiring backslashes to escape newlines inside of {...}, (...), or [...]. We could also add the double-quotes to this, but let's start without that. */ bool keepGoing = true; zLeft = z; while( keepGoing && zpp, &z, zEnd, &dxp->pos.lineNo) ){ --dxp->flags.countLines; return dxppCode; } ++z /* group-closing character */; /* Sidebar: this only checks top-level groups. It is possible that an inner group is malformed, e.g.: { ( } It's also possible that that's perfectly legal for a specific use case. Such cases will, if they're indeed syntax errors, be recognized as such in the arguments-parsing steps. Catching them here would require that we recursively validate all of [zLeft,z) for group constructs, whereas that traversal happens as a matter of course in argument parsing. It would also require the assumption that such constructs are not legal, which is invalid once we start dealing with free-form input like #query SQL. */ break; } case '\n': assert( z!=dline->zBegin && "Checked up above" ); if( '\\'==z[-1] || (z>zLeft+1 && '\r'==z[-1] && '\\'==z[-2]) ){ /* Backslash-escaped newline. */ ++z; }else{ /* EOL for this directive. */ keepGoing = false; } ++dxp->pos.lineNo; break; default: ++z; } } assert( z==zEnd || '\n'==*z ); dline->zEnd = z; dxp->pos.z = dline->zEnd + 1 /* For the next call to this function, skip the trailing newline or EOF */; assert( dline->zBegin < dline->zEnd && "Was checked above" ); if( 0 ){ g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin), (dline->zEnd-dline->zBegin), dline->zBegin); } *gotOne = true; assert( !dxppCode ); --dxp->flags.countLines; return 0; } /* No directives found. We're now at EOL or EOF. Flush any pending LHS content. */ tflush; dx->pimpl->pos.z = z; *gotOne = false; return dxppCode; #undef tflush } int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey, cmpp_ssize_t nKey, CmppKvp_op_e opPolicy){ if(ppCode) return ppCode; char chEq = 0; char opLen = 0; *p = CmppKvp_empty; p->k.z = zKey; p->k.n = cmpp__strlenu(zKey, nKey); switch( opPolicy ){ case CmppKvp_op_none:// break; case CmppKvp_op_eq1: chEq = '='; opLen = 1; break; default: assert(!"don't use these"); /* no longer todo: ==, !=, <=, <, >, >= */ chEq = '='; opLen = 1; break; } assert( chEq ); p->op = CmppKvp_op_none; unsigned const char * const zEnd = p->k.z + p->k.n; for(unsigned const char * zPos = p->k.z ; *zPos && zPosop = CmppKvp_op_eq1; p->k.n = (unsigned)(zPos - ustr_c(zKey)); zPos += opLen; assert( zPos <= zEnd ); p->v.z = zPos; p->v.n = (unsigned)(zEnd - zPos); } break; } } cmpp__legal_key_check(pp, p->k.z, p->k.n, false); return ppCode; } int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired, cmpp_size_t * nAlloc, unsigned sizeOfEntry){ int rc = pp ? ppCode : 0; if( 0==rc && nDesired > *nAlloc ){ cmpp_size_t const nA = nDesired < 10 ? 10 : nDesired; void * const p = cmpp_mrealloc(*list, sizeOfEntry * nA); rc = cmpp_check_oom(pp, p); if( p ){ memset((unsigned char *)p + (sizeOfEntry * *nAlloc), 0, sizeOfEntry * (nA - *nAlloc)); *list = p; *nAlloc = nA; } } return rc; } CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li){ CmppLvl * p = 0; assert( li->list ? li->nAlloc : 0==li->nAlloc ); if( 0==ppCode && 0==CmppLvlList_reserve(pp, li, cmpp__li_reserve1_size(li,5)) ){ p = li->list[li->n]; if( !p ){ p = cmpp__malloc(pp, sizeof(*p)); } if( p ){ li->list[li->n++] = p; *p = CmppLvl_empty; } } return p; } void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li, CmppLvl * const lvl){ assert( li->n ); if( li->n ){ if( lvl==li->list[li->n-1] ){ *lvl = CmppLvl_empty; cmpp_mfree(lvl); li->list[--li->n] = 0; }else{ if( pp ){ cmpp_err_set(pp, CMPP_RC_ASSERT, "Misuse of %s(): not passed the top of the stack. " "The CmppLvl stack is now out of whack.", __func__); }else{ cmpp__fatal("Misuse of %s(): not passed the top of the stack", __func__); } /* do not free it - CmppLvlList_cleanup() will catch it. */ } } } void CmppLvlList_cleanup(CmppLvlList *li){ const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m; while( li->nAlloc ){ cmpp_mfree( li->list[--li->nAlloc] ); } cmpp_mfree(li->list); *li = CmppLvlList_empty; } static inline void CmppDList_entry_clean(CmppDList_entry * const e){ if( e->d.impl.dtor ){ e->d.impl.dtor( e->d.impl.state ); } cmpp_mfree(e->zName); *e = CmppDList_entry_empty; } #if 0 CmppDList * CmppDList_reuse(CmppDList *li){ while( li->n ){ CmppDList_entry_clean( li->list[--li->n] ); } return li; } #endif void CmppDList_cleanup(CmppDList *li){ static const CmppDList CmppDList_empty = CmppDList_empty_m; while( li->n ){ CmppDList_entry_clean( li->list[--li->n] ); cmpp_mfree( li->list[li->n] ); li->list[li->n] = 0; } cmpp_mfree(li->list); *li = CmppDList_empty; } void CmppDList_unappend(CmppDList *li){ assert( li->n ); if( li->n ){ CmppDList_entry_clean(li->list[--li->n]); } } /** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */ static int CmppDList_entry_cmp_pp(const void *p1, const void *p2){ CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1; CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2; return eL->d.name.n==eR->d.name.n ? memcmp(eL->d.name.z, eR->d.name.z, eL->d.name.n) : strcmp((char const *)eL->d.name.z, (char const *)eR->d.name.z); } static void CmppDList_sort(CmppDList * const li){ if( li->n>1 ){ qsort(li->list, li->n, sizeof(CmppDList_entry*), CmppDList_entry_cmp_pp); } } CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){ CmppDList_entry * p = 0; assert( li->list ? li->nAlloc : 0==li->nAlloc ); if( 0==ppCode && 0==cmpp_array_reserve(pp, (void **)&li->list, cmpp__li_reserve1_size(li, 15), &li->nAlloc, sizeof(p)) ){ p = li->list[li->n]; if( !p ){ li->list[li->n] = p = cmpp__malloc(pp, sizeof(*p)); } if( p ){ ++li->n; *p = CmppDList_entry_empty; } } return p; } CmppDList_entry * CmppDList_search(CmppDList const * li, char const *zName){ if( li->n > 2 ){ CmppDList_entry const key = { .d = { .name = { .z = zName, .n = strlen(zName) } } }; CmppDList_entry const * pKey = &key; CmppDList_entry ** pRv = bsearch(&pKey, li->list, li->n, sizeof(li->list[0]), CmppDList_entry_cmp_pp); //g_warn("search in=%s out=%s", zName, (pRv ? (*pRv)->d.name.z : "")); return pRv ? *pRv : 0; }else{ cmpp_size_t const nName = cmpp__strlen(zName, -1); for( cmpp_size_t i = 0; i < li->n; ++i ){ CmppDList_entry * const e = li->list[i]; if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){ //g_warn("search in=%s out=%s", zName, e->d.name.z); return e; } } return 0; } } void cmpp__delim_cleanup(cmpp__delim *d){ cmpp__delim const dd = cmpp__delim_empty_m; cmpp_mfree(d->zOwns); *d = dd; assert(!d->zOwns); assert(d->open.z); assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM)); assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1); } cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){ cmpp__delim * p = 0; assert( li->list ? li->nAlloc : 0==li->nAlloc ); if( 0==ppCode && 0==cmpp_array_reserve(pp, (void **)&li->list, cmpp__li_reserve1_size(li,4), &li->nAlloc, sizeof(cmpp__delim)) ){ p = &li->list[li->n++]; *p = cmpp__delim_empty; } return p; } void cmpp__delim_list_cleanup(cmpp__delim_list *li){ while( li->nAlloc ) cmpp__delim_cleanup(li->list + --li->nAlloc); cmpp_mfree(li->list); *li = cmpp__delim_list_empty; } CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){ if( dxppCode ) return dxppCode; CmppDLine * const tok = &dx->pimpl->dline; if( !dx->pimpl->zBegin ){ *pGotOne = false; return 0; } assert(dx->pimpl->zEnd); assert(dx->pimpl->zEnd > dx->pimpl->zBegin); *pGotOne = false; cmpp_dx__reset(dx); bool foundDelim = false; if( cmpp_dx_delim_search(dx, &foundDelim) || !foundDelim ){ return dxppCode; } if( cmpp_args__init(dx->pp, &dx->pimpl->args) ){ return dxppCode; } cmpp_skip_space( &tok->zBegin, tok->zEnd ); g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n", tok->lineNo, (int)(tok->zEnd-tok->zBegin), tok->zBegin)); /* Normalize the directive's line and parse arguments */ const unsigned lineLen = (unsigned)(tok->zEnd - tok->zBegin); if(!lineLen){ return cmpp_dx_err(dx, CMPP_RC_SYNTAX, "Line #%u has no directive after %s", tok->lineNo, cmpp_dx_delim(dx)); } unsigned char const * zi = tok->zBegin /* Start of input */; unsigned char const * ziEnd = tok->zEnd /* Input EOF */; cmpp_b * const bufLine = cmpp_b_reuse(&dx->pimpl->buf.line) /* Slightly-transformed copy of the input. */; if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){ return dxppCode; } unsigned char * zo = bufLine->z /* Start of output */; unsigned char const * const zoEnd = zo + bufLine->nAlloc /* Output EOF. */; g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n", tok->lineNo, lineLen, lineLen, tok->zBegin)); //memset(bufLine->z, 0, bufLine->nAlloc); #define out(CH) if(zo==zoEnd) break; (*zo++)=CH /* bufLine is now populated with a copy of the whole input line. Now normalize that buffer a bit before trying to parse it. */ unsigned char const * zEsc = 0; cmpp_dx_pimpl * const pimpl = dx->pimpl; for( ; zi=zoEnd ){ return cmpp_dx_err(dx, CMPP_RC_RANGE, "Ran out of argument-processing space."); } *zo = 0; #undef out bufLine->n = (cmpp_size_t)(zo - bufLine->z); if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z); /* Line has now been normalized into bufLine->z. */ for( zo = bufLine->z; zoz; dx->d = cmpp__d_search3(dx->pp, (char const *)zDirective, cmpp__d_search3_F_ALL); if( dxppCode ){ return dxppCode; }else if(!dx->d){ return cmpp_dx_err(dx, CMPP_RC_NOT_FOUND, "Unknown directive at line %" CMPP_SIZE_T_PFMT ": %.*s\n", (unsigned)tok->lineNo, (int)bufLine->n, bufLine->z); } assert( zDirective == bufLine->z ); const bool isCall = dx->pimpl->args.pimpl->isCall = dx->pimpl->flags.nextIsCall; dx->pimpl->flags.nextIsCall = false; if( isCall ){ if( cmpp_d_F_NO_CALL & dx->d->flags ){ return cmpp_dx_err(dx, CMPP_RC_SYNTAX, "%s%s cannot be used in a [call] context.", cmpp_dx_delim(dx), dx->d->name.z); } }else if( cmpp_d_F_CALL_ONLY & dx->d->flags ){ return cmpp_dx_err(dx, CMPP_RC_TYPE, "'%s' is a call-only directive, " "not legal here.", dx->d->name.z); } if( bufLine->n > dx->d->name.n ){ dx->args.z = zDirective + dx->d->name.n + 1; assert( dx->args.z > bufLine->z ); assert( dx->args.z <= bufLine->z+bufLine->n ); dx->args.nz = cmpp__strlenu(dx->args.z, -1); assert( bufLine->nAlloc > dx->args.nz ); }else{ dx->args.z = ustr_c("\0"); dx->args.nz = 0; } if( 0 ){ g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>", bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z); } cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz); if(0){ g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz, (int)dx->args.nz, dx->args.z); } assert( !pimpl->buf.argsRaw.n ); if( dx->args.nz ){ if( 0 ){ g_warn("lineLen=%u zargs len=%u: [%.*s]\n", (unsigned)lineLen, (int)dx->args.nz, (int)dx->args.nz, dx->args.z ); } if( cmpp_b_append4(dx->pp, &pimpl->buf.argsRaw, dx->args.z, dx->args.nz) ){ return dxppCode; } } assert( !pimpl->args.arg0 ); assert( !pimpl->args.argc ); assert( !pimpl->args.pimpl->argOut.n ); assert( !pimpl->args.pimpl->argli.n ); assert( dx->args.z ); if( //1 || //pleases valgrind. Well, it did at one point. !cmpp_dx_is_eliding(dx) || 0!=(cmpp_d_F_FLOW_CONTROL & dx->d->flags) ){ if( cmpp_d_F_ARGS_LIST & dx->d->flags ){ cmpp_dx_args_parse(dx, &pimpl->args); }else if( cmpp_d_F_ARGS_RAW & dx->d->flags ){ /* Treat rest of line as one token */ cmpp_arg * const arg = CmppArgList_append(dx->pp, &pimpl->args.pimpl->argli); if( !arg ) return dxppCode; pimpl->args.arg0 = arg; pimpl->args.argc = 1; arg->ttype = cmpp_TT_RawLine; arg->z = pimpl->buf.argsRaw.z; arg->n = pimpl->buf.argsRaw.n; //g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z); } } if( 0==dxppCode ){ dx->args.arg0 = pimpl->args.arg0; dx->args.argc = pimpl->args.argc; } *pGotOne = true; return dxppCode; } CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx){ return dx->pimpl->args.pimpl->isCall; } CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r, cmpp_d ** dOut){ CmppDList_entry * e1 = 0, * e2 = 0; bool const isCallOnly = (cmpp_d_F_CALL_ONLY & r->opener.flags); if( ppCode ){ goto end; } if( (cmpp_d_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags)) && (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) ){ cmpp__err(pp, CMPP_RC_ACCESS, "Directive %s%s flag cmpp_d_F_NOT_IN_SAFE_MODE is set " "and the preprocessor is running in safe mode.", cmpp__pp_zdelim(pp), r->name); goto end; } if( isCallOnly && r->closer.f ){ cmpp__err(pp, CMPP_RC_MISUSE, "Call-only directives may not have a closing directive."); goto end; } #if 0 if( pp->pimpl->dx ){ cmpp__err(pp, CMPP_RC_MISUSE, "Directives may not be added while a " "directive is running." /* because that might reallocate being-run directives. 2025-10-25: that's since been resolved but we need a use case before enabling this. */); goto end; } #endif if( !pp->pimpl->flags.isInternalDirectiveReg && !cmpp_is_legal_key(ustr_c(r->name), cmpp__strlen(r->name,-1), NULL) ){ cmpp__err(pp, CMPP_RC_RANGE, "\"%s\" is not a legal directive name.", r->name); goto end; } if( cmpp__d_search(pp, r->name) ){ cmpp__err(pp, CMPP_RC_ALREADY_EXISTS, "Directive name '%s' is already in use.", r->name); goto end; } e1 = CmppDList_append(pp, &pp->pimpl->d.list); if( !e1 ) goto end; e1->d.impl.callback = r->opener.f; e1->d.impl.state = r->state; e1->d.impl.dtor = r->dtor; if( pp->pimpl->flags.isInternalDirectiveReg ){ e1->d.flags = r->opener.flags; }else{ e1->d.flags = r->opener.flags & cmpp_d_F_MASK; } e1->zName = sqlite3_mprintf("%s", r->name); if( 0==cmpp_check_oom(pp, e1->zName) ){ //e1->reg = *r; e1->reg.zName = e1->zName; e1->d.name.z = e1->zName; e1->d.name.n = strlen(e1->zName); if( r->closer.f && (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){ e2->d.impl.callback = r->closer.f; e2->d.impl.state = r->state; if( pp->pimpl->flags.isInternalDirectiveReg ){ e2->d.flags = r->closer.flags; }else{ e2->d.flags = r->closer.flags & cmpp_d_F_MASK; } e1->d.closer = &e2->d; e2->zName = sqlite3_mprintf("/%s", r->name); if( 0==cmpp_check_oom(pp, e2->zName) ){ e2->d.name.z = e2->zName; e2->d.name.n = e1->d.name.n + 1; } } } end: if( ppCode ){ if( e2 ) CmppDList_unappend(&pp->pimpl->d.list); if( e1 ) CmppDList_unappend(&pp->pimpl->d.list); else if( r->dtor ){ r->dtor( r->state ); } }else{ CmppDList_sort(&pp->pimpl->d.list); if( dOut ){ *dOut = &e1->d; } if( 0 ){ g_warn("Registered: %s%s%s", e1->zName, e2 ? " and " : "", e2 ? e2->zName : ""); } } return ppCode; } CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os, cmpp_d const * const * const dClosers, unsigned nClosers, cmpp_flag32_t flags){ assert( !dxppCode ); bool gotOne = false; cmpp_outputer const oldOut = dx->pp->pimpl->out; bool const allowOtherDirectives = (flags & cmpp_dx_consume_F_PROCESS_OTHER_D); cmpp_d const * const d = cmpp_dx_d(dx); cmpp_size_t const lineNo = dx->pimpl->dline.lineNo; bool const pushAt = (cmpp_dx_consume_F_RAW & flags); if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){ return dxppCode; } if( os ){ dx->pp->pimpl->out = *os; } while( 0==dxppCode && 0==cmpp_dx_next(dx, &gotOne) /* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){ if( !gotOne ){ dxserr("No closing directive found for " "%s%s opened on line %" CMPP_SIZE_T_PFMT ".", cmpp_dx_delim(dx), d->name.z, lineNo); }else{ cmpp_d const * const d2 = cmpp_dx_d(dx); gotOne = false; for( unsigned i = 0; !gotOne && i < nClosers; ++i ){ gotOne = d2==dClosers[i]; } //g_warn("gotOne=%d d2=%s", gotOne, d2->name.z); if( gotOne ) break; else if( !allowOtherDirectives ){ dxserr("%s%s at line %" CMPP_SIZE_T_PFMT " may not contain %s%s.", cmpp_dx_delim(dx), d->name.z, lineNo, cmpp_dx_delim(dx), d2->name.z); }else{ cmpp_dx_process(dx); } } } if( pushAt ){ cmpp_atpol_pop(dx->pp); } if( os ){ dx->pp->pimpl->out = oldOut; } return dxppCode; } CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b, cmpp_d const * const * dClosers, unsigned nClosers, cmpp_flag32_t flags){ cmpp_outputer oss = cmpp_outputer_b; oss.state = b; return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags); } char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){ again: switch(p){ case cmpp_atpol_CURRENT:{ if( pp ){ assert( p!=cmpp__policy(pp, at) ); p = cmpp__policy(pp, at); pp = 0; goto again; } return NULL; } case cmpp_atpol_invalid: return NULL; case cmpp_atpol_OFF: return "off"; case cmpp_atpol_RETAIN: return "retain"; case cmpp_atpol_ELIDE: return "elide"; case cmpp_atpol_ERROR: return "error"; } return NULL; } cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){ cmpp_atpol_e rv = cmpp_atpol_invalid; if( 0==strcmp(z, "retain") ) rv = cmpp_atpol_RETAIN; else if( 0==strcmp(z, "elide") ) rv = cmpp_atpol_ELIDE; else if( 0==strcmp(z, "error") ) rv = cmpp_atpol_ERROR; else if( 0==strcmp(z, "off") ) rv = cmpp_atpol_OFF; if( pp ){ if( cmpp_atpol_invalid==rv && 0==strcmp(z, "current") ){ rv = cmpp__policy(pp,at); }else if( cmpp_atpol_invalid==rv ){ cmpp__err(pp, CMPP_RC_RANGE, "Invalid @ policy value: %s." " Try one of retain|elide|error|off|current.", z); }else{ cmpp__policy(pp,at) = rv; } } return rv; } int cmpp__StringAtIsOk(cmpp * pp, cmpp_atpol_e pol){ if( 0==ppCode ){ if( pol==cmpp_atpol_CURRENT ) pol=cmpp__policy(pp,at); if(cmpp_atpol_OFF==pol ){ cmpp_err_set(pp, CMPP_RC_UNSUPPORTED, "@policy is \"off\", so cannot use @\"strings\"."); } } return ppCode; } cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e) cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e) int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){ if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at); assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." ); if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol) && 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){ PodList__atpol_pop(&cmpp__epol(pp,at)); } return ppCode; } void cmpp_atpol_pop(cmpp * pp){ assert( cmpp__epol(pp,at).n ); if( cmpp__epol(pp,at).n ){ PodList__atpol_pop(&cmpp__epol(pp,at)); }else if( !ppCode ){ cmpp_err_set(pp, CMPP_RC_MISUSE, "%s() called when no cmpp_atpol_push() is active.", __func__); } } int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){ if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol) && cmpp_unpol_set(pp, pol)/*for validation*/ ){ PodList__unpol_pop(&cmpp__epol(pp,un)); } return ppCode; } void cmpp_unpol_pop(cmpp * pp){ assert( cmpp__epol(pp,un).n ); if( cmpp__epol(pp,un).n ){ PodList__unpol_pop(&cmpp__epol(pp,un)); }else if( !ppCode ){ cmpp_err_set(pp, CMPP_RC_MISUSE, "%s() called when no cmpp_unpol_push() is active.", __func__); } } CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){ return cmpp__epol(pp,at).na ? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT; } CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){ if( 0==ppCode ){ switch(pol){ case cmpp_atpol_OFF: case cmpp_atpol_RETAIN: case cmpp_atpol_ELIDE: case cmpp_atpol_ERROR: assert(cmpp__epol(pp,at).na); cmpp__policy(pp,at) = pol; break; case cmpp_atpol_CURRENT: break; default: cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d", (int)pol); } } return ppCode; } char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){ (void)pp; switch(p){ case cmpp_unpol_NULL: return "null"; case cmpp_unpol_ERROR: return "error"; case cmpp_unpol_invalid: return NULL; } return NULL; } cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp, char const *z){ cmpp_unpol_e rv = cmpp_unpol_invalid; if( 0==strcmp(z, "null") ) rv = cmpp_unpol_NULL; else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR; if( pp ){ if( cmpp_unpol_invalid==rv && 0==strcmp(z, "current") ){ rv = cmpp__policy(pp,un); }else if( cmpp_unpol_invalid==rv ){ cmpp__err(pp, CMPP_RC_RANGE, "Invalid undefined key policy value: %s." " Try one of null|error.", z); }else{ cmpp_unpol_set(pp, rv); } } return rv; } CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){ return cmpp__epol(pp,un).na ? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT; } CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){ if( 0==ppCode ){ switch(pol){ case cmpp_unpol_NULL: case cmpp_unpol_ERROR: cmpp__policy(pp,un) = pol; break; default: cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d", (int)pol); } } return ppCode; } /** Reminders to self re. savepoint tracking: cmpp_dx tracks per-input-source savepoints. We always want savepoints which are created via scripts to be limited to that script. cmpp instances, on the other hand, don't care about that. Thus we have two different APIs for starting/ending savepoints. */ CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){ if( 0==ppCode ){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true); assert( q || !"db init would have otherwise failed"); if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ ++pp->pimpl->flags.nSavepoint; } } return ppCode; } int cmpp__dx_sp_begin(cmpp_dx * const dx){ if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){ ++dx->pimpl->nSavepoint; } return dxppCode; } CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){ /* Remember that rollback must (mostly) ignore the pending error state. */ if( !pp->pimpl->flags.nSavepoint ){ if( 0==ppCode ){ cmpp__err(pp, CMPP_RC_MISUSE, "Cannot roll back: no active savepoint"); } }else{ sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true); assert( q || !"db init would have otherwise failed"); if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ q = cmpp__stmt(pp, CmppStmt_spRelease, true); if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ --pp->pimpl->flags.nSavepoint; } } } return ppCode; } int cmpp__dx_sp_rollback(cmpp_dx * const dx){ /* Remember that rollback must (mostly) ignore the pending error state. */ if( !dx->pimpl->nSavepoint ){ if( 0==dxppCode ){ cmpp_dx_err(dx, CMPP_RC_MISUSE, "Cannot roll back: no active savepoint"); } }else{ cmpp_sp_rollback(dx->pp); --dx->pimpl->nSavepoint; } return dxppCode; } CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){ if( 0==ppCode ){ if( !pp->pimpl->flags.nSavepoint ){ cmpp__err(pp, CMPP_RC_MISUSE, "Cannot commit: no active savepoint"); }else{ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true); assert( q || !"db init would have otherwise failed"); if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ --pp->pimpl->flags.nSavepoint; } } }else{ cmpp_sp_rollback(pp); } return ppCode; } int cmpp__dx_sp_commit(cmpp_dx * const dx){ if( 0==dxppCode ){ if( !dx->pimpl->nSavepoint ){ cmpp_dx_err(dx, CMPP_RC_MISUSE, "Cannot commit: no active savepoint"); }else if( 0==cmpp_sp_commit(dx->pp) ){ --dx->pimpl->nSavepoint; } } return dxppCode; } static void cmpp_dx_pimpl_reuse(cmpp_dx_pimpl *p){ #if 0 /* no: we need most of the state to remain intact. */ cmpp_dx_pimpl const tmp = *p; *p = cmpp_dx_pimpl_empty; p->buf = tmp.buf; p->args = tmp.args; #endif cmpp_b_reuse(&p->buf.line); cmpp_b_reuse(&p->buf.argsRaw); cmpp_args_reuse(&p->args); } void cmpp_dx_pimpl_cleanup(cmpp_dx_pimpl *p){ cmpp_b_clear(&p->buf.line); cmpp_b_clear(&p->buf.argsRaw); cmpp_args_cleanup(&p->args); *p = cmpp_dx_pimpl_empty; } void cmpp_dx__reset(cmpp_dx * const dx){ dx->args = cmpp_dx_empty.args; cmpp_dx_pimpl_reuse(dx->pimpl); dx->d = 0; //no: dx->sourceName = 0; } void cmpp_dx_cleanup(cmpp_dx * const dx){ unsigned prev = 0; CmppLvlList_cleanup(&dx->pimpl->dxLvl); while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){ prev = dx->pimpl->nSavepoint; cmpp__dx_sp_rollback(dx); } cmpp_dx_pimpl_cleanup(dx->pimpl); memset(dx, 0, sizeof(*dx)); } int cmpp__find_closing2(cmpp *pp, unsigned char const **zPos, unsigned char const *zEnd, cmpp_size_t * pNl){ unsigned char const * z = *zPos; unsigned char const opener = *z; unsigned char closer = 0; switch(opener){ case '(': closer = ')'; break; case '[': closer = ']'; break; case '{': closer = '}'; break; case '"': case '\'': closer = opener; break; default: return cmpp__err(pp, CMPP_RC_MISUSE, "Invalid starting char (0x%x) for %s()", (int)opener, __func__); } int count = 1; for( ++z; z < zEnd; ++z ){ if( closer == *z && 0==--count ){ /* Have to check this first for the case of "" and ''. */ break; }else if( opener == *z ){ ++count; }else if( pNl && '\n'==*z ){ ++*pNl; } } if( closer!=*z ){ if( 0 ){ g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z, (zEnd - *zPos), *zPos); } //assert(!"here"); cmpp__err(pp, CMPP_RC_SYNTAX, "Unbalanced %c%c: %.*s", opener, closer, (int)(z-*zPos), *zPos); }else{ if( 0 ){ g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos); } *zPos = z; } return ppCode; } cmpp_tt cmpp__tt_for_sqlite(int sqType){ cmpp_tt rv; switch( sqType ){ case SQLITE_INTEGER: rv = cmpp_TT_Int; break; case SQLITE_NULL: rv = cmpp_TT_Null; break; default: rv = cmpp_TT_String; break; } return rv; } int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, bool defineIfNoRow){ if( 0==ppCode ){ int const nCol = sqlite3_column_count(q); assert( sqlite3_data_count(q)>0 || defineIfNoRow); /* Create a #define for each column */ bool const hasRow = sqlite3_data_count(q)>0; for( int i = 0; !ppCode && i < nCol; ++i ){ char const * const zCol = sqlite3_column_name(q, i); if( hasRow ){ unsigned char const * const zVal = sqlite3_column_text(q, i); int const nVal = sqlite3_column_bytes(q, i); cmpp_tt const ttype = cmpp__tt_for_sqlite(sqlite3_column_type(q,i)); cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype); }else if(defineIfNoRow){ cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_TT_Null); }else{ break; } } } return ppCode; } cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){ cmpp_d const * d = 0;//cmpp__d_search(zName); if( !d ){ CmppDList_entry const * e = CmppDList_search(&pp->pimpl->d.list, zName); if( e ) d = &e->d; } return d; } cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName, cmpp_flag32_t what){ cmpp_d const * d = cmpp__d_search(pp, zName); if( !d ){ CmppDList_entry const * e = 0; if( cmpp__d_search3_F_DELAYED & what ){ int rc = cmpp__d_delayed_load(pp, zName); if( 0==rc ){ e = CmppDList_search(&pp->pimpl->d.list, zName); }else if( CMPP_RC_NO_DIRECTIVE!=rc ){ assert( ppCode ); return NULL; } } if( !e && (cmpp__d_search3_F_AUTOLOADER & what) && pp->pimpl->d.autoload.f && 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){ e = CmppDList_search(&pp->pimpl->d.list, zName); } #if CMPP_D_MODULE if( !e && !ppCode && (cmpp__d_search3_F_DLL & what) ){ char * z = sqlite3_mprintf("libcmpp-d-%s", zName); cmpp_check_oom(pp, z); int rc = cmpp_module_load(pp, z, NULL); sqlite3_free(z); if( rc ){ if( CMPP_RC_NOT_FOUND==rc ){ cmpp__err_clear(pp); } return NULL; } e = CmppDList_search(&pp->pimpl->d.list, zName); } #endif if( e ) d = &e->d; } return d; } int cmpp_dx_process(cmpp_dx * const dx){ if( 0==dxppCode ){ cmpp_d const * const d = cmpp_dx_d(dx); assert( d ); if( !cmpp_dx_is_eliding(dx) || (d->flags & cmpp_d_F_FLOW_CONTROL) ){ if( (cmpp_d_F_NOT_IN_SAFEMODE & d->flags) && (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.newFlags) ){ cmpp_dx_err(dx, CMPP_RC_ACCESS, "Directive %s%s is disabled by safe mode.", cmpp_dx_delim(dx), dx->d->name.z); }else{ assert(d->impl.callback); d->impl.callback(dx); } } } return dxppCode; } static void cmpp_dx__setup_include_path(cmpp_dx * dx){ /* Add the leading dir part of dx->sourceName as the highest-priority include path. It gets removed in cmpp_dx__teardown(). */ assert( dx->sourceName ); enum { BufSize = 512 * 4 }; unsigned char buf[BufSize] = {0}; unsigned char *z = &buf[0]; cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1); if( n > (unsigned)BufSize-1 ) return; memcpy(z, dx->sourceName, n); buf[n] = 0; cmpp_ssize_t i = n - 1; for( ; i > 0; --i ){ if( '/'==z[i] || '\\'==z[i] ){ z[i] = 0; n = i; break; } } if( n>(cmpp_size_t)i ){ /* No path separator found. Assuming '.'. This is intended to replace the historical behavior of automatically adding '.' if no -I flags are used. Potential TODO is getcwd() here instead of using '.' */ n = 1; buf[0] = '.'; buf[1] = 0; } int64_t rowid = 0; cmpp__include_dir_add(dx->pp, (char const*)buf, dx->pp->pimpl->flags.nDxDepth, &rowid); if( rowid ){ //g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z); dx->pimpl->shadow.ridInclPath = rowid; } } static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx, unsigned char const * zIn, cmpp_ssize_t nIn){ if( 0==ppCode ){ assert( dx->sourceName ); assert( dx->pimpl ); assert( pp==dx->pp ); nIn = cmpp__strlenu(zIn, nIn); if( !nIn ) return 0; pp->pimpl->dx = dx; dx->pimpl->zBegin = zIn; dx->pimpl->zEnd = zIn + nIn; cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName, &dx->pimpl->shadow.sidFile); ++dx->pp->pimpl->flags.nDxDepth; cmpp_dx__setup_include_path(dx); } return ppCode; } static void cmpp_dx__teardown(cmpp_dx *dx){ if( dx->pimpl->shadow.ridInclPath>0 ){ cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath); dx->pimpl->shadow.ridInclPath = 0; } if( dx->pimpl->shadow.sidFile ){ cmpp_define_unshadow(dx->pp, "__FILE__", dx->pimpl->shadow.sidFile); } --dx->pp->pimpl->flags.nDxDepth; cmpp_dx_cleanup(dx); } CMPP__EXPORT(int, cmpp_process_string)( cmpp *pp, const char * zName, unsigned char const * zIn, cmpp_ssize_t nIn ){ if( !zName ) zName = ""; if( 0==cmpp__db_init(pp) ){ cmpp_dx const * const oldDx = pp->pimpl->dx; cmpp_dx_pimpl dxp = cmpp_dx_pimpl_empty; cmpp_dx dx = { .pp = pp, .sourceName = ustr_c(zName), .args = cmpp_dx_empty.args, .pimpl = &dxp }; dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall; pp->pimpl->flags.nextIsCall = false; if( dxp.flags.nextIsCall ){ assert( pp->pimpl->dx ); dxp.pos.lineNo = pp->pimpl->dx->pimpl->pos.lineNo; } bool gotOne = false; (void)cmpp__stmt(pp, CmppStmt_sdefIns, true); (void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true); (void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true); (void)cmpp__stmt(pp, CmppStmt_sdefDel, true) /* hack: ensure that those queries are allocated now, as an error in processing may keep them from being created later. We might want to rethink the prepare-no-statements-on-error bits, but will have to go back and fix routines which currently rely on that. */; cmpp_dx__setup(pp, &dx, zIn, nIn); while(0==ppCode && 0==cmpp_dx_next(&dx, &gotOne) && gotOne){ cmpp_dx_process(&dx); } if(0==ppCode && 0!=dx.pimpl->dxLvl.n){ CmppLvl const * const lv = CmppLvl_get(&dx); cmpp_dx_err(&dx, CMPP_RC_SYNTAX, "Input ended inside an unterminated nested construct " "opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName, lv ? lv->lineNo : (cmpp_size_t)0); } cmpp_dx__teardown(&dx); pp->pimpl->dx = oldDx; } if( !ppCode ){ cmpp_outputer_flush(&pp->pimpl->out) /* We're going to ignore a result code just this once. */; } return ppCode; } int cmpp_process_file(cmpp *pp, const char * zName){ if( 0==ppCode ){ FileWrapper fw = FileWrapper_empty; if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb") && 0==cmpp__FileWrapper_slurp(pp, &fw) ){ cmpp_process_string(pp, zName, fw.zContent, fw.nContent); } FileWrapper_close(&fw); } return ppCode; } CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName, cmpp_input_f src, void * srcState){ if( 0==ppCode ){ cmpp_b * const os = cmpp_b_borrow(pp); int const rc = os ? cmpp_stream(src, srcState, cmpp_output_f_b, os) : ppCode; if( 0==rc ){ cmpp_process_string(pp, zName, os->z, os->n); }else{ cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName); } cmpp_b_return(pp, os); } return ppCode; } CMPP__EXPORT(int, cmpp_call_str)( cmpp *pp, unsigned char const * z, cmpp_ssize_t n, cmpp_b * dest, cmpp_flag32_t flags ){ if( ppCode ) return ppCode; cmpp_args args = cmpp_args_empty; cmpp_b * const b = cmpp_b_borrow(pp); cmpp_b * const bo = cmpp_b_borrow(pp); cmpp_outputer oB = cmpp_outputer_b; if( !b || !bo ) return ppCode; cmpp__pi(pp); oB.state = bo; oB.name = pi->out.name;//"[call]"; n = cmpp__strlenu(z, n); //g_warn("calling: <<%.*s>>", (int)n, z); unsigned char const * zEnd = z+n; cmpp_skip_snl(&z, zEnd); cmpp_skip_snl_trailing(z, &zEnd); n = (zEnd-z); if( !n ){ cmpp_err_set(pp, CMPP_RC_SYNTAX, "Empty [call] is not permitted."); goto end; } //g_warn("calling: <<%.*s>>", (int)n, z); cmpp__delim const * const delim = cmpp__pp_delim(pp); assert(delim); if( (cmpp_size_t)n<=delim->open.n || 0!=memcmp(z, delim->open.z, delim->open.n) ){ /* If it doesn't start with the current delimiter, prepend one. */ cmpp_b_reserve3(pp, b, delim->open.n + n + 2); cmpp_b_append4(pp, b, delim->open.z, delim->open.n); } cmpp_b_append4(pp, b, z, n); if( !ppCode ){ cmpp_outputer oOld = cmpp_outputer_empty; pi->flags.nextIsCall = true /* Convey (indirectly) that the first cmpp_dx_next() call made via cmpp_process_string() is a call context. */; cmpp__outputer_swap(pp, &oB, &oOld); cmpp_process_string(pp, (char*)b->z, b->z, b->n); cmpp__outputer_swap(pp, &oOld, &oB); assert( !pi->flags.nextIsCall || ppCode ); pi->flags.nextIsCall = false; } if( !ppCode ){ unsigned char const * zz = bo->z; unsigned char const * zzEnd = bo->z + bo->n; if( cmpp_call_F_TRIM_ALL & flags ){ cmpp_skip_snl(&zz, zzEnd); cmpp_skip_snl_trailing(zz, &zzEnd); }else if( 0==(cmpp_call_F_NO_TRIM & flags) ){ cmpp_b_chomp(bo); zzEnd = bo->z + bo->n; } if( (zzEnd-zz) ){ cmpp_b_append4(pp, dest, zz, (zzEnd-zz)); } } end: cmpp_b_return(pp, b); cmpp_b_return(pp, bo); cmpp_args_cleanup(&args); return ppCode; } CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){ switch(errNo){ /* Please expand on this as tests/use cases call for it... */ case 0: return 0; case EINVAL: return CMPP_RC_MISUSE; case ENOMEM: return CMPP_RC_OOM; case EROFS: case EACCES: case EBUSY: case EPERM: case EDQUOT: case EAGAIN: case ETXTBSY: return CMPP_RC_ACCESS; case EISDIR: case ENOTDIR: return CMPP_RC_TYPE; case ENAMETOOLONG: case ELOOP: case ERANGE: return CMPP_RC_RANGE; case ENOENT: case ESRCH: return CMPP_RC_NOT_FOUND; case EEXIST: case ENOTEMPTY: return CMPP_RC_ALREADY_EXISTS; case EIO: return CMPP_RC_IO; default: return dflt; } } int cmpp_flush_f_FILE(void * _FILE){ return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0; } int cmpp_output_f_FILE( void * state, void const * src, cmpp_size_t n ){ return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout)) ? 0 : CMPP_RC_IO; } int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){ int const fd = *((int*)state); ssize_t const wn = write(fd, src, n); return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0; } int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){ cmpp_FILE * f = state; cmpp_size_t const rn = *n; *n = (cmpp_size_t)fread(dest, 1, rn, f); return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO); } int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){ int const fd = *((int*)state); ssize_t const rn = read(fd, dest, *n); if( rn<0 ){ return cmpp_errno_rc(errno, CMPP_RC_IO); }else{ *n = (cmpp_size_t)rn; return 0; } } void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){ if( self->state ){ cmpp_fclose( self->state ); self->name = NULL; self->state = NULL; } } CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){ if( self->state ) cmpp_b_clear(self->state); } CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){ return o->out ? o->out(o->state, p, n) : 0; } CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){ return o->flush ? o->flush(o->state) : 0; } CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){ if( o->cleanup ){ o->cleanup( o ); } } CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState, cmpp_output_f outF, void * outState ){ int rc = 0; enum { BufSize = 1024 * 4 }; unsigned char buf[BufSize]; cmpp_size_t rn = BufSize; while( 0==rc && (rn==BufSize) && (0==(rc=inF(inState, buf, &rn))) ){ if(rn) rc = outF(outState, buf, rn); } return rc; } void cmpp__fatalv_base(char const *zFile, int line, char const *zFmt, va_list va){ cmpp_FILE * const fp = stderr; fflush(stdout); fprintf(fp, "\n%s:%d: ", zFile, line); if(zFmt && *zFmt){ vfprintf(fp, zFmt, va); fputc('\n', fp); } fflush(fp); exit(1); } void cmpp__fatal_base(char const *zFile, int line, char const *zFmt, ...){ va_list va; va_start(va, zFmt); cmpp__fatalv_base(zFile, line, zFmt, va); va_end(va); } CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){ if( zMsg && ppCode ) *zMsg = pp->pimpl->err.zMsg; return ppCode; } CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){ int const rc = ppCode; if( rc ){ *zMsg = pp->pimpl->err.zMsg; pp->pimpl->err = cmpp_pimpl_empty.err; } return rc; } //CMPP_WASM_EXPORT void cmpp__err_clear(cmpp *pp){ cmpp_mfree(pp->pimpl->err.zMsg); pp->pimpl->err = cmpp_pimpl_empty.err; } CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){ return pp ? pp->pimpl->err.code : 0; } CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){ *pos = dx->pimpl->pos; } CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){ dx->pimpl->pos = *pos; } //CMPP_WASM_EXPORT void cmpp__dx_append_script_info(cmpp_dx const * dx, sqlite3_str * const sstr){ sqlite3_str_appendf( sstr, "%s%s@ %s line %" CMPP_SIZE_T_PFMT, dx->d ? dx->d->name.z : "", dx->d ? " " : "", (dx->sourceName && 0==strcmp("-", (char const *)dx->sourceName)) ? "" : (char const *)dx->sourceName, dx->pimpl->dline.lineNo ); } int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list va){ if( pp ){ cmpp__err_clear(pp); ppCode = rc; if( 0==rc ) return rc; if( CMPP_RC_OOM==rc ){ oom: pp->pimpl->err.zMsgC = "An allocation failed."; return pp->pimpl->err.code = CMPP_RC_OOM; } assert( !pp->pimpl->err.zMsg ); if( pp->pimpl->dx || (zFmt && *zFmt) ){ sqlite3_str * sstr = 0; sstr = sqlite3_str_new(pp->pimpl->db.dbh); if( pp->pimpl->dx ){ cmpp__dx_append_script_info(pp->pimpl->dx, sstr); sqlite3_str_append(sstr, ": ", 2); } if( zFmt && *zFmt ){ sqlite3_str_vappendf(sstr, zFmt, va); }else{ sqlite3_str_appendf(sstr, "No error info provided."); } pp->pimpl->err.zMsgC = pp->pimpl->err.zMsg = sqlite3_str_finish(sstr); if( !pp->pimpl->err.zMsg ){ goto oom; } }else{ pp->pimpl->err.zMsgC = "No error info provided."; } rc = ppCode; } return rc; } //CMPP_WASM_EXPORT no - variadic int cmpp_err_set(cmpp *pp, int rc, char const *zFmt, ...){ if( pp ){ va_list va; va_start(va, zFmt); rc = cmpp__errv(pp, rc, zFmt, va); va_end(va); } return rc; } const cmpp_d_autoloader cmpp_d_autoloader_empty = cmpp_d_autoloader_empty_m; CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){ if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state); if( pNew ) pp->pimpl->d.autoload = *pNew; else pp->pimpl->d.autoload = cmpp_d_autoloader_empty; } CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){ *pOld = pp->pimpl->d.autoload; pp->pimpl->d.autoload = cmpp_d_autoloader_empty; } //CMPP_WASM_EXPORT no - variadic int cmpp_dx_err_set(cmpp_dx *dx, int rc, char const *zFmt, ...){ va_list va; va_start(va, zFmt); rc = cmpp__errv(dx->pp, rc, zFmt, va); va_end(va); return rc; } CMPP__EXPORT(int, cmpp_err_set1)(cmpp *pp, int rc, char const *zMsg){ return cmpp_err_set(pp, rc, (zMsg && *zMsg) ? "%s" : 0, zMsg); } //no: CMPP_WASM_EXPORT char * cmpp_path_search(cmpp *pp, char const *zPath, char pathSep, char const *zBaseName, char const *zExt){ char * zrc = 0; if( !ppCode ){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_selPathSearch, false); if( q ){ unsigned char sep[2] = {pathSep, 0}; cmpp__bind_text(pp, q, 1, ustr_c(zBaseName)); cmpp__bind_text(pp, q, 2, sep); cmpp__bind_text(pp, q, 3, ustr_c((zExt ? zExt : ""))); cmpp__bind_text(pp, q, 4, ustr_c((zPath ? zPath: ""))); int const dbrc = cmpp__step(pp, q, false); if( SQLITE_ROW==dbrc ){ unsigned char const * s = sqlite3_column_text(q, 1); zrc = sqlite3_mprintf("%s", s); cmpp_check_oom(pp, zrc); } cmpp__stmt_reset(q); } } return zrc; } #if CMPP__OBUF int cmpp__obuf_flush(cmpp__obuf * b){ if( 0==b->rc && b->cursor > b->begin ){ if( b->dest.out ){ b->rc = b->dest.out(b->dest.state, b->begin, b->cursor-b->begin); } b->cursor = b->begin; } if( 0==b->rc && b->dest.flush ){ b->rc = b->dest.flush(b->dest.state); } return b->rc; } void cmpp__obuf_cleanup(cmpp__obuf * b){ if( b ){ cmpp__obuf_flush(b);/*ignoring result*/; if( b->ownsMemory ){ cmpp_mfree(b->begin); } *b = cmpp__obuf_empty; } } int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n){ assert( b ); if( n && !b->rc && b->dest.out ){ assert( b->end ); assert( b->cursor ); assert( b->cursor <= b->end ); assert( b->end>b->begin ); if( b->cursor + n >= b->end ){ if( 0==cmpp_flush_f_obuf(b) ){ if( b->cursor + n >= b->end ){ /* Go ahead and write it all */ b->rc = b->dest.out(b->dest.state, src, n); }else{ goto copy_it; } } }else{ copy_it: memcpy(b->cursor, src, n); b->cursor += n; } } return b->rc; } int cmpp_flush_f_obuf(void * b){ return cmpp__obuf_flush(b); } int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n){ return cmpp__obuf_write(state, src, n); } void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o){ cmpp__obuf_cleanup(o->state); } #endif /* CMPP__OBUF */ //cmpp__ListType_impl(cmpp__delim_list,cmpp__delim) //cmpp__ListType_impl(CmppDList,CmppDList_entry*) //cmpp__ListType_impl(CmppSohList,void*) cmpp__ListType_impl(CmppArgList,cmpp_arg) cmpp__ListType_impl(cmpp_b_list,cmpp_b*) cmpp__ListType_impl(CmppLvlList,CmppLvl*) /** Expects that *ndx points to the current argv entry and that it is a flag which expects a value. This function checks for --flag=val and (--flag val) forms. If a value is found then *ndx is adjusted (if needed) to point to the next argument after the value and *zVal is * pointed to the value. If no value is found then it returns false. */ static bool get_flag_val(int argc, char const * const * argv, int * ndx, char const **zVal){ char const * zEq = strchr(argv[*ndx], '='); if( zEq ){ *zVal = zEq+1; return 1; }else if(*ndx+1>=argc){ return 0; }else{ *zVal = argv[++*ndx]; return 1; } } static bool cmpp__arg_is_flag( char const *zFlag, char const *zArg, char const **zValIfEqX ); bool cmpp__arg_is_flag( char const *zFlag, char const *zArg, char const **zValIfEqX ){ if( zValIfEqX ) *zValIfEqX = 0; if( 0==strcmp(zFlag, zArg) ) return true; char const * z = strchr(zArg,'='); if( z && z>zArg ){ /* compare the part before the '=' */ if( 0==strncmp(zFlag, zArg, z-zArg) ){ if( !zFlag[z-zArg] ){ if( zValIfEqX ) *zValIfEqX = z+1; return true; } /* Else it was a prefix match. */ } } return false; } void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false); if( q ){ while( SQLITE_ROW==sqlite3_step(q) ){ int const tt = sqlite3_column_int(q, 0); unsigned char const * zK = sqlite3_column_text(q, 1); unsigned char const * zV = sqlite3_column_text(q, 2); int const nK = sqlite3_column_bytes(q, 1); int const nV = sqlite3_column_bytes(q, 2); char const * zTt = cmpp__tt_cstr(tt, true); if( tt && zTt ) zTt += 3; else zTt = "String"; fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "", nK, zK, zTt, nV, zV); } cmpp__stmt_reset(q); } } /** This is what was originally the main() of cmpp v1, back when it was a monolithic app. It still serves as the driver for main() but is otherwise unused. */ CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc, char const * const * argv){ if( ppCode ) return ppCode; int nFile = 0 /* number of files/-e scripts seen */; #define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \ cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \ argv[i]); \ break; \ } #define M(X) cmpp__arg_is_flag(X, zArg, &zVal) #define ISFLAG(X) else if(M(X)) #define ISFLAG2(X,Y) else if(M(X) || M(Y)) #define NOVAL if( zVal ){ \ cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \ break; \ } (void)0 #define open_output_if_needed \ if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break cmpp__staticAssert(TT_None,0==(int)cmpp_TT_None); cmpp__staticAssert(Mask1, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_FLOW_CONTROL); cmpp__staticAssert(Mask2, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_NOT_SIMPLIFY); cmpp__staticAssert(Mask3, 0==(cmpp_d_F_MASK_INTERNAL & cmpp_d_F_MASK)); for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){ /** Loop through the flags twice. The first time we just validate and look for --help/-?. The second time we process the flags. This approach allows us to easily chain multiple files and flags: ./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y Which, it turns out, is a surprisingly useful way to work. */ #define DOIT if(1==doIt) for(int i = 0; i < argc && 0==ppCode; ++i){ char const * zVal = 0; int isNoFlag = 0; char const * zArg = argv[i]; //g_stderr("i=%d zArg=%s\n", i, zArg); zVal = 0; while('-'==*zArg) ++zArg; if(zArg==argv[i]/*not a flag*/){ zVal = zArg; goto do_infile; } //g_warn("zArg=%s", zArg); if( 0==strncmp(zArg,"no-",3) ){ zArg += 3; isNoFlag = 1; } if( M("?") || M("help") ){ NOVAL; cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]); break; }else if('D'==*zArg){ ++zArg; if(!*zArg){ cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D"); }else DOIT { cmpp_define_legacy(pp, zArg, 0); } }else if('F'==*zArg){ ++zArg; if(!*zArg){ cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F"); }else DOIT { cmpp__set_file(pp, ustr_c(zArg), -1); } } ISFLAG("e"){ ARGVAL; DOIT { ++nFile; open_output_if_needed; cmpp_process_string(pp, "-e script", (unsigned char const *)zVal, -1); } }else if('U'==*zArg){ ++zArg; if(!*zArg){ cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U"); }else DOIT { cmpp_undef(pp, zArg, NULL); } }else if('I'==*zArg){ ++zArg; if(!*zArg){ cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I"); }else DOIT { cmpp_include_dir_add(pp, zArg); } }else if('L'==*zArg){ ++zArg; if(!*zArg){ cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L"); }else DOIT { cmpp_module_dir_add(pp, zArg); } } ISFLAG2("o","outfile"){ ARGVAL; DOIT { cmpp__out_fopen(pp, zVal); } } ISFLAG2("f","file"){ ARGVAL; do_infile: DOIT { if( !pp->pimpl->mod.path.z ){ cmpp_module_dir_add(pp, NULL); } ++nFile; if( 0 && !pp->pimpl->flags.nIncludeDir && cmpp_include_dir_add(pp, ".") ){ break; } open_output_if_needed; cmpp_process_file(pp, zVal); } } ISFLAG("@"){ NOVAL; DOIT { assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF ); cmpp_atpol_set(pp, isNoFlag ? cmpp_atpol_OFF : cmpp_atpol_DEFAULT_FOR_FLAG); } } ISFLAG("@policy"){ ARGVAL; cmpp_atpol_from_str(pp, zVal); } ISFLAG("debug"){ NOVAL; DOIT { pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1; } } ISFLAG2("u","undefined-policy"){ ARGVAL; cmpp_unpol_from_str(pp, zVal); } ISFLAG("sql-trace"){ NOVAL; /* Needs to be set before the start of the second pass, when the db is inited. */ DOIT { pp->pimpl->sqlTrace.expandSql = false; do_trace_flag: cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out); if( isNoFlag ){ pp->pimpl->sqlTrace.out = cmpp_outputer_empty; }else{ pp->pimpl->sqlTrace.out = cmpp_outputer_FILE; pp->pimpl->sqlTrace.out.state = stderr; } } } ISFLAG("sql-trace-x"){ NOVAL; DOIT { pp->pimpl->sqlTrace.expandSql = true; goto do_trace_flag; } } ISFLAG("chomp-F"){ NOVAL; DOIT pp->pimpl->flags.chompF = !isNoFlag; } ISFLAG2("d","delimiter"){ ARGVAL; DOIT { cmpp_delimiter_set(pp, zVal); } } ISFLAG2("dd", "dump-defines"){ DOIT { cmpp_FILE * const fp = /* tcl's exec treats output to stderr as failure. If we use [exec -ignorestderr] then it instead replaces stderr's output with its own message, invalidating test expectations. */ 1 ? stdout : stderr; fprintf(fp, "All %sdefine entries:\n", cmpp__pp_zdelim(pp)); cmpp__dump_defines(pp, fp, 1); } } #if !defined(CMPP_OMIT_D_DB) ISFLAG2("db", "db-file"){ /* Undocumented flag used for testing purposes. */ ARGVAL; DOIT { cmpp_db_name_set(pp, zVal); } } #endif ISFLAG("version"){ NOVAL; #if !defined(CMPP_OMIT_FILE_IO) fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n", cmpp_version(), sqlite3_libversion(), sqlite3_sourceid()); #endif doIt = 100; break; } #if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE) ISFLAG("safe-mode"){ if( i>0 ){ cmpp_err_set(pp, CMPP_RC_MISUSE, "--%s, if used, must be the first argument.", zArg); break; } } #endif else{ cmpp__err(pp,CMPP_RC_MISUSE, "Unhandled flag: %s", argv[i]); } } DOIT { if(!nFile){ /* We got no file arguments, so read from stdin. */ if(0 && !pp->pimpl->flags.nIncludeDir && cmpp_include_dir_add(pp, ".") ){ break; } open_output_if_needed; cmpp_process_file(pp, "-"); } } #undef DOIT } return ppCode; #undef ARGVAL #undef M #undef ISFLAG #undef ISFLAG2 #undef NOVAL #undef open_output_if_needed } void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){ #if defined(CMPP_OMIT_FILE_IO) (void)zAppName; (void)fOut; #else fprintf(fOut, "%s version %s\nwith SQLite %s %s\n", zAppName ? zAppName : "c-pp", cmpp_version(), sqlite3_libversion(), sqlite3_sourceid()); fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName); fprintf(fOut, "Flags and filenames may be in any order and " "they are processed in that order.\n" "\nFlags:\n"); #define GAP " " #define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D) #if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE) arg("--safe-mode", "Disables preprocessing directives which use the filesystem " "or invoke external processes. If used, it must be the first " "argument."); #endif arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n" GAP "Because arguments are processed in order, this should\n" GAP "normally be given before -f."); arg("-f|--file FILE","Process FILE (default=- (stdin)).\n" GAP "All non-flag arguments are assumed to be the input files."); arg("-e SCRIPT", "Treat SCRIPT as a complete c-pp input and process it.\n" GAP "Doing anything marginally useful with this requires\n" GAP "using it several times, once per directive. It will not\n" GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for " CMPP_DEFAULT_DELIM "expr, " CMPP_DEFAULT_DELIM "assert, and " CMPP_DEFAULT_DELIM "define."); arg("-DXYZ[=value]","Define XYZ to the given value (default=1)."); arg("-UXYZ","Undefine all defines matching glob XYZ."); arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path."); arg("-LXYZ","Add dir XYZ to the loadable module search path."); arg("-FXYZ=filename", "Define XYZ to the raw contents of the given file.\n" GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n" GAP "Maybe it should be. Or maybe we need a new flag for that."); arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE " "(default=" CMPP_DEFAULT_DELIM ")."); arg("--@policy retain|elide|error|off", "Specifies how to handle @tokens@ (default=off).\n" GAP "off = do not look for @tokens@\n" GAP "retain = parse @tokens@ and retain any undefined ones\n" GAP "elide = parse @tokens@ and elide any undefined ones\n" GAP "error = parse @tokens@ and error out for any undefined ones" ); arg("-u|--undefined-policy NAME", "Sets the policy for how to handle references to undefined key:\n" GAP "null = treat them as empty/falsy. This is the default.\n" GAP "error = trigger an error. This should probably be " "the default." ); arg("-@", "Equivalent to --@policy=error."); arg("-no-@", "Equivalent to --@policy=off (the default)."); arg("--sql-trace", "Send a trace of all SQL to stderr."); arg("--sql-trace-x", "Like --sql-trace but expand all bound values in the SQL."); arg("--no-sql-trace", "Disable SQL tracing (default)."); arg("--chomp-F", "One trailing newline is trimmed from files " "read via -FXYZ=filename."); arg("--no-chomp-F", "Disable --chomp-F (default)."); #undef arg #undef GAP fputs("\nFlags which require a value accept either " "--flag=value or --flag value. " "The exceptions are that the -D... and -F... flags " "require their '=' to be part of the flag (because they " "are parsed elsewhere).\n\n",fOut); #endif /*CMPP_OMIT_FILE_IO*/ } #if defined(CMPP_MAIN) /* add main() */ int main(int argc, char const * const * argv){ int rc = 0; cmpp * pp = 0; cmpp_flag32_t newFlags = 0 #if defined(CMPP_MAIN_SAFEMODE) | cmpp_ctor_F_SAFEMODE #endif ; cmpp_b bArgs = cmpp_b_empty; sqlite3_config(SQLITE_CONFIG_URI,1); { /* Copy argv to a string so we can #define it. This has proven helpful in testing, debugging, and output validation. */ for( int i = 0; i < argc; ++i ){ if( i ) cmpp_b_append_ch(&bArgs,' '); cmpp_b_append(&bArgs, argv[i], strlen(argv[i])); } if( (rc = bArgs.errCode) ) goto end; if( argc>1 && cmpp__arg_is_flag("--safe-mode", argv[1], NULL) ){ newFlags |= cmpp_ctor_F_SAFEMODE; --argc; ++argv; } } cmpp_ctor_cfg const cfg = { .flags = newFlags }; rc = cmpp_ctor(&pp, &cfg); if( rc ) goto end; /** Define CMPP_MAIN_INIT to the name of a function with the signature int (*)(cmpp*) to have it called here. The intent is that custom directives can be installed this way without having to edit this code. */ #if defined(CMPP_MAIN_INIT) extern int CMPP_MAIN_INIT(cmpp*); if( 0!=(rc = CMPP_MAIN_INIT(pp)) ){ g_warn0("Initialization via CMPP_MAIN_INIT() failed"); goto end; } #endif #if defined(CMPP_MAIN_AUTOLOADER) { extern int CMPP_MAIN_AUTOLOADER(cmpp*,char const *,void*); cmpp_d_autoloader al = cmpp_d_autoloader_empty; al.f = CMPP_MAIN_AUTOLOADER; cmpp_d_autoloader_set(pp, &al); } #endif if( cmpp_define_v2(pp, "c-pp::argv", (char*)bArgs.z) ) goto end; cmpp_b_clear(&bArgs); rc = cmpp_process_argv(pp, argc-1, argv+1); switch( rc ){ case 0: break; case CMPP_RC_HELP: rc = 0; cmpp_process_argv_usage(argv[0], stdout); break; default: break; } end: cmpp_b_clear(&bArgs); if( pp ){ char const *zErr = 0; rc = cmpp_err_get(pp, &zErr); if( rc && CMPP_RC_HELP!=rc ){ g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr); } cmpp_dtor(pp); }else if( rc && CMPP_RC_HELP!=rc ){ g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc)); } sqlite3_shutdown(); return rc ? EXIT_FAILURE : EXIT_SUCCESS; } #endif /* CMPP_MAIN */ /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the cmpp_b-related parts of libcmpp. */ const cmpp_b cmpp_b_empty = cmpp_b_empty_m; CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp, cmpp_b * const os, void const * src, cmpp_size_t n){ if( !ppCode && cmpp_b_append(os, src, n) ){ cmpp_check_oom(pp, 0); } return ppCode; } CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp, cmpp_b * const os, cmpp_size_t n){ if( !ppCode && cmpp_b_reserve(os, n) ){ cmpp_check_oom(pp, 0); } return ppCode; } CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){ if( s->z ) cmpp_mfree(s->z); *s = cmpp_b_empty; } CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){ if( s->z ){ #if 1 memset(s->z, 0, s->nAlloc) /* valgrind pushes for this, which is curious because cmpp_b_reserve[3]() memset()s new space to 0. Try the following without this block using one commit after [5f9c31d1da1d] (that'll be the commit that this comment and #if block were added): ##define foo ##if not defined a ##/if ##query define {select ?1 a} bind [1] There's a misuse complaint about a jump depending on uninitialized memory deep under cmpp__is_int(), in strlen(), on the "define" argument of the ##query. It does not appear if the lines above it are removed, which indicates that it's at least semi-genuine. gcc v13.3.0, if it matters. */; #else s->z[0] = 0; #endif s->n = 0; } s->errCode = 0; return s; } CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){ if( l!=r ){ cmpp_b const x = *l; *l = *r; *r = x; } } CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){ if( 0==s->errCode && s->nAlloc < n ){ void * const m = cmpp_mrealloc(s->z, s->nAlloc + n); if( m ){ memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc)) /* valgrind convincingly recommends this. */; s->z = m; s->nAlloc += n; }else{ s->errCode = CMPP_RC_OOM; } } return s->errCode; } CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src, cmpp_size_t n){ if(0==os->errCode){ cmpp_size_t const nNeeded = os->n + n + 1; if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){ assert( CMPP_RC_OOM==os->errCode ); return os->errCode; } memcpy(os->z + os->n, src, n); os->n += n; os->z[os->n] = 0; if( 0 ) { g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n, (char const*) src, (int)os->n); } } return os->errCode; } CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){ if( 0==os->errCode && (os->n+1nAlloc || 0==cmpp_b_reserve(os, os->n+2)) ){ os->z[os->n++] = (unsigned char)ch; os->z[os->n] = 0; } return os->errCode; } CMPP__EXPORT(int, cmpp_b_append_i32)(cmpp_b * os, int32_t d){ if( 0==os->errCode ){ char buf[16] = {0}; int const n = snprintf(buf, sizeof(buf), "%" PRIi32, d); cmpp_b_append(os, buf, (unsigned)n); } return os->errCode; } CMPP__EXPORT(int, cmpp_b_append_i64)(cmpp_b * os, int64_t d){ if( 0==os->errCode ){ char buf[32] = {0}; int const n = snprintf(buf, sizeof(buf), "%" PRIi64, d); cmpp_b_append(os, buf, (unsigned)n); } return os->errCode; } CMPP__EXPORT(bool, cmpp_b_chomp)(cmpp_b * b){ return cmpp_chomp(b->z, &b->n); } CMPP__EXPORT(void, cmpp_b_list_cleanup)(cmpp_b_list *li){ while( li->nAlloc ){ cmpp_b * const b = li->list[--li->nAlloc]; if(b){ cmpp_b_clear(b); cmpp_mfree(b); } } cmpp_mfree(li->list); *li = cmpp_b_list_empty; } CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){ while( li->n ){ cmpp_b * const b = li->list[li->n--]; if(b) cmpp_b_reuse(b); } } static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){ cmpp_b * p = 0; assert( li->list ? li->nAlloc : 0==li->nAlloc ); if( !cmpp_b_list_reserve(NULL, li, cmpp__li_reserve1_size(li, 20)) ){ p = li->list[li->n]; if( p ){ cmpp_b_reuse(p); }else{ p = cmpp_malloc(sizeof(*p)); if( p ){ li->list[li->n++] = p; *p = cmpp_b_empty; } } } return p; } /** bsearch()/qsort() comparison for (cmpp_b**), sorting by size, largest first and empty slots last. */ static int cmpp_b__cmp_desc(const void *p1, const void *p2){ cmpp_b const * const eL = *(cmpp_b const **)p1; cmpp_b const * const eR = *(cmpp_b const **)p2; if( eL==eR ) return 0; else if( !eL ) return 1; else if (!eR ) return -1; return (int)(/*largest first*/eL->nAlloc - eR->nAlloc); } /** bsearch()/qsort() comparison for (cmpp_b**), sorting by size, smallest first and empty slots last. */ static int cmpp_b__cmp_asc(const void *p1, const void *p2){ cmpp_b const * const eL = *(cmpp_b const **)p1; cmpp_b const * const eR = *(cmpp_b const **)p2; if( eL==eR ) return 0; else if( !eL ) return 1; else if (!eR ) return -1; return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc); } /** Sort li's buffer list using the given policy. NULL entries always sort last. This is a no-op of how == cmpp_b_list_UNSORTED or li->n<2. */ static void cmpp_b_list__sort(cmpp_b_list * const li, enum cmpp_b_list_e how){ switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){ case cmpp_b_list_UNSORTED: break; case cmpp_b_list_DESC: qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc); break; case cmpp_b_list_ASC: qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc); break; } } CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){ cmpp__pi(pp); cmpp_b_list * const li = &pi->recycler.buf; cmpp_b * b = 0; if( cmpp_b_list_UNSORTED==pi->recycler.bufSort ){ pi->recycler.bufSort = cmpp_b_list_DESC; cmpp_b_list__sort(li, pi->recycler.bufSort); assert( cmpp_b_list_UNSORTED!=pi->recycler.bufSort || pi->recycler.buf.n<2 ); } for( cmpp_size_t i = 0; i < li->n; ++i ){ b = li->list[i]; if( b ){ li->list[i] = 0; assert( !b->n && "Someone wrote to a buffer after giving it back" ); if( i < li->n-1 ){ pi->recycler.bufSort = cmpp_b_list_UNSORTED; } return cmpp_b_reuse(b); } } /** Allocate the list entry now and then remove the buffer from it to "borrow" it. We allocate now, instead of in cmpp_b_return(), so that that function has no OOM condition (handling it properly in higher-level code would be a mess). */ b = cmpp_b_list_push(li); if( 0==cmpp_check_oom(pp, b) ) { assert( b==li->list[li->n-1] ); li->list[li->n-1] = 0; } return b; } CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){ if( !b ) return; cmpp__pi(pp); cmpp_b_list * const li = &pi->recycler.buf; for( cmpp_size_t i = 0; i < li->n; ++i ){ if( !li->list[i] ){ li->list[i] = cmpp_b_reuse(b); pi->recycler.bufSort = cmpp_b_list_UNSORTED; return; } } assert( !"This shouldn't be possible - no slot in recycler.buf" ); cmpp_b_clear(b); cmpp_mfree(b); } CMPP__EXPORT(int, cmpp_output_f_b)( void * state, void const * src, cmpp_size_t n ){ if( state ){ return cmpp_b_append(state, src, n); } return 0; } #if CMPP__OBUF int cmpp__obuf_flush(cmpp__obuf * b); int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n); void cmpp__obuf_cleanup(cmpp__obuf * b); int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n); int cmpp_flush_f_obuf(void * state); void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o); const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m; const cmpp_outputer cmpp_outputer_obuf = { .out = cmpp_output_f_obuf, .flush = cmpp_flush_f_obuf, .cleanup = cmpp_outputer_cleanup_f_obuf }; #endif /* ** 2025-11-07: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the db-related pieces of libcmpp. */ /** A proxy for sqlite3_prepare() which updates pp->pimpl->err on error. */ static int cmpp__prepare(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, ...){ /* We need for pp->pimpl->stmt.sp* to work regardless of pending errors so that we can, when appropriate, create the rollback statements. */ sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh); char * z = 0; int n = 0; va_list va; assert( pp->pimpl->db.dbh ); va_start(va, zSql); sqlite3_str_vappendf(str, zSql, va); va_end(va); z = cmpp_str_finish(pp, str, &n); if( z ){ int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, z, n, pStmt, 0); cmpp__db_rc(pp, rc, z); sqlite3_free(z); } return ppCode; } sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which, bool prepEvenIfErr){ if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL; sqlite3_stmt ** q = 0; char const * zSql = 0; switch(which){ default: cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which); return NULL; #define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break; CmppStmt_map(E) #undef E } assert( q ); assert( zSql && *zSql ); if( !*q && (!ppCode || prepEvenIfErr) ){ cmpp__prepare(pp, q, "%s", zSql); } return *q; } void cmpp__stmt_reset(sqlite3_stmt * const q){ if( q ){ sqlite3_clear_bindings(q); sqlite3_reset(q); } } static inline int cmpp__stmt_is_sp(cmpp const * const pp, sqlite3_stmt const * const q){ return q==pp->pimpl->stmt.spBegin || q==pp->pimpl->stmt.spRelease || q==pp->pimpl->stmt.spRollback; } int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){ int rc = SQLITE_ERROR; assert( q ); if( !ppCode || cmpp__stmt_is_sp(pp,q) ){ rc = sqlite3_step(q); cmpp__db_rc(pp, rc, sqlite3_sql(q)); } if( resetIt /* even if ppCode!=0 */ ) cmpp__stmt_reset(q); assert( 0!=rc ); return rc; } /** Expects an SQLITE_... result code and returns an approximate match from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE as non-errors, returning 0 for those. */ static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode); int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){ (void)db; int rc = 0; switch(sqliteCode & 0xff){ case SQLITE_ROW: case SQLITE_DONE: case SQLITE_OK: rc = 0; break; case SQLITE_NOMEM: rc = CMPP_RC_OOM; break; case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break; case SQLITE_TOOBIG: case SQLITE_FULL: case SQLITE_RANGE: rc = CMPP_RC_RANGE; break; case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break; case SQLITE_PERM: case SQLITE_AUTH: case SQLITE_BUSY: case SQLITE_LOCKED: case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break; case SQLITE_CANTOPEN: case SQLITE_IOERR: rc = CMPP_RC_IO; break; case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break; default: //MARKER(("sqlite3_errcode()=0x%04x\n", rc)); rc = CMPP_RC_DB; break; } return rc; } int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsg){ switch(dbRc){ case 0: case SQLITE_DONE: case SQLITE_ROW: return 0; default: return cmpp_err_set( pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc), "SQLite error #%d: %s%s%s", dbRc, pp->pimpl->db.dbh ? sqlite3_errmsg(pp->pimpl->db.dbh) : "", zMsg ? ": " : "", zMsg ? zMsg : "" ); } } /** The base "define" impl. Requires q to be an INSERT for one of the define tables and have the (t,k,v) columns set up to bind to ?1, ?2, and ?3. */ static int cmpp__define_impl(cmpp * const pp, sqlite3_stmt * const q, unsigned char const * zKey, cmpp_ssize_t nKey, unsigned char const *zVal, cmpp_ssize_t nVal, int tType, bool resetStmt){ if( 0==ppCode){ assert( q ); nKey = cmpp__strlenu(zKey, nKey); nVal = cmpp__strlenu(zVal, nVal); if( 0==cmpp__bind_textn(pp, q, 2, zKey, (int)nKey) && 0==cmpp__bind_int(pp, q, 1, tType) ){ //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq); /* TODO? if tType==cmpp_TT_Blob, bind it as a blob */ if( zVal ){ if( nVal ){ cmpp__bind_textn(pp, q, 3, zVal, (int)nVal); }else{ /* Arguable */ cmpp__bind_null(pp, q, 3); } }else{ cmpp__bind_int(pp, q, 3, 1); } cmpp__step(pp, q, resetStmt); g_debug(pp,2,("define: %s [%s]=[%.*s]\n", cmpp_tt_cstr(tType), zKey, (int)nVal, zVal)); } } return ppCode; } int cmpp__define2(cmpp *pp, unsigned char const * zKey, cmpp_ssize_t nKey, unsigned char const *zVal, cmpp_ssize_t nVal, cmpp_tt tType){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false); if( q ){ cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true); } return ppCode; } /** The legacy variant of define() which accepts X=Y in zKey. This continues to exist because it's convenient for passing args from main(). */ static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal, cmpp_tt ttype ){ if(ppCode) return ppCode; CmppKvp kvp = CmppKvp_empty; if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1, zVal ? CmppKvp_op_none : CmppKvp_op_eq1) ) { return ppCode; } if( kvp.v.z ){ if( zVal ){ assert(!"cannot happen - CmppKvp_op_none will prevent it"); return cmpp_err_set(pp, CMPP_RC_MISUSE, "Cannot assign two values to [%.*s] [%.*s] [%s]", kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal); } }else{ kvp.v.z = (unsigned char const *)zVal; kvp.v.n = zVal ? (int)strlen(zVal) : 0; } sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false); if( !q ) return ppCode; int64_t intCheck = 0; switch( ttype ){ case cmpp_TT_Unknown: if(kvp.v.n){ if( cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ ttype = cmpp_TT_Int; if( '+'==*kvp.v.z ){ ++kvp.v.z; --kvp.v.n; } }else{ ttype = cmpp_TT_String; } }else if( kvp.v.z ){ ttype = cmpp_TT_String; }else{ ttype = cmpp_TT_Int; intCheck = 1 /* No value ==> value of 1. */; } break; case cmpp_TT_Int: if( !cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ ttype = cmpp_TT_String; } break; default: break; } if( 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, kvp.k.n) && 0==cmpp__bind_int(pp, q, 1, ttype) ){ //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq); switch( ttype ){ case cmpp_TT_Int: cmpp__bind_int(pp, q, 3, intCheck); break; case cmpp_TT_Null: cmpp__bind_null(pp, q, 3); break; default: cmpp__bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n); break; } cmpp__step(pp, q, true); g_debug(pp,2,("define: [%.*s]=[%.*s]\n", kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z)); } return ppCode; } CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){ return cmpp__define_legacy(pp, zKey, zVal, cmpp_TT_Unknown); } CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){ return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1, cmpp_TT_String); } static int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey, cmpp_ssize_t nKey, unsigned char const *zVal, cmpp_ssize_t nVal, int ttype, int64_t * pId){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false); if( q ){ if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false) && pId ){ *pId = sqlite3_column_int64(q, 0); assert( *pId ); } cmpp__stmt_reset(q); } return ppCode; } CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey, char const *zVal, int64_t *pId){ assert( pId ); return cmpp__define_shadow(pp, ustr_c(zKey), -1, ustr_c(zVal), -1, cmpp_TT_String, pId); } static int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey, cmpp_ssize_t nKey, int64_t id){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false); if( q ){ cmpp__bind_textn(pp, q, 1, zKey, (int)nKey); cmpp__bind_int(pp, q, 2, id); cmpp__step(pp, q, true); } return ppCode; } CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){ return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id); } /* ** This sqlite3_trace_v2() callback outputs tracing info using ** ((cmpp*)c)->sqlTrace.pFile. */ static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){ switch(dx){ case SQLITE_TRACE_STMT:{ char const * const zSql = x; cmpp * const pp = c; cmpp__pi(pp); if(pi->sqlTrace.out.out){ char * const zExp = pi->sqlTrace.expandSql ? sqlite3_expanded_sql((sqlite3_stmt*)p) : 0; sqlite3_str * const s = sqlite3_str_new(pi->db.dbh); if( pi->dx ){ cmpp__dx_append_script_info(pi->dx, s); sqlite3_str_appendchar(s, 1, ':'); sqlite3_str_appendchar(s, 1, ' '); } sqlite3_str_appendall(s, zExp ? zExp : zSql); sqlite3_str_appendchar(s, 1, '\n'); int const n = sqlite3_str_length(s); if( n ){ char * const z = sqlite3_str_finish(s); if( z ){ cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n); sqlite3_free(z); } } sqlite3_free(zExp); } break; } } return 0; } #include /* ** sqlite3 UDF which returns true if its argument refers to an ** accessible file, else false. */ static void cmpp__udf_file_exists( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; (void)(argc); /* Unused parameter */ zName = (const char*)sqlite3_value_text(argv[0]); if( 0!=zName ){ struct stat sb; sqlite3_result_int(context, stat(zName, &sb) ? 0 : S_ISREG(sb.st_mode)); } } static void cmpp__udf_truthy( sqlite3_context *context, int argc, sqlite3_value **argv ){ (void)(argc); /* Unused parameter */ assert(1==argc); int buul = 0; sqlite3_value * const sv = argv[0]; switch( sqlite3_value_type(sv) ){ case SQLITE_NULL: break; case SQLITE_FLOAT: buul = 0.0!=sqlite3_value_double(sv); break; case SQLITE_INTEGER: buul = 0!=sqlite3_value_int(sv); break; case SQLITE_TEXT: case SQLITE_BLOB:{ int const n = sqlite3_value_bytes(sv); if( n>1 ) buul = 1; else if( 1==n ){ const char *z = (const char*)sqlite3_value_text(sv); buul = z ? 0!=strcmp(z,"0") : 0; } } } sqlite3_result_int(context, buul); } /** SQLite3 UDF which compares its two arguments using memcmp() semantics. NULL will compare equal to NULL, but less than anything else. */ static void cmpp__udf_compare( sqlite3_context *context, int argc, sqlite3_value **argv ){ (void)(argc); /* Unused parameter */ assert(2==argc); sqlite3_value * const v1 = argv[0]; sqlite3_value * const v2 = argv[1]; unsigned char const * const z1 = sqlite3_value_text(v1); unsigned char const * const z2 = sqlite3_value_text(v2); int const n1 = sqlite3_value_bytes(v1); int const n2 = sqlite3_value_bytes(v2); int rv; if( !z1 ){ rv = z2 ? -1 : 0; }else if( !z2 ){ rv = 1; }else{ rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2); } if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv); sqlite3_result_int(context, rv); } int cmpp__db_init(cmpp *pp){ cmpp__pi(pp); if( pi->db.dbh || ppCode ) return ppCode; int rc; char * zErr = 0; const char * zDrops = "BEGIN EXCLUSIVE;" "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;" "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;" "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;" "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;" "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;" "DROP VIEW IF EXISTS " CMPP__DB_MAIN_NAME ".vdef;" "COMMIT;" ; const char * zSchema = "BEGIN EXCLUSIVE;" "CREATE TABLE " CMPP__DB_MAIN_NAME ".def(" /* ^^^ defines */ "t INTEGER DEFAULT NULL," /*^^ type: cmpp_tt or NULL */ "k TEXT PRIMARY KEY NOT NULL," "v TEXT DEFAULT NULL" ") WITHOUT ROWID;" "CREATE TABLE " CMPP__DB_MAIN_NAME ".incl(" /* ^^^ files currently being included */ "file TEXT PRIMARY KEY NOT NULL," "srcFile TEXT DEFAULT NULL," "srcLine INTEGER DEFAULT 0" ") WITHOUT ROWID;" "CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath(" /* ^^^ include path. We use (ORDER BY priority DESC, rowid) to make their priority correct. priority should only be set by the #include directive for its cwd entry. */ "priority INTEGER DEFAULT 0," /* higher sorts first */ "dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE" ");" "CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath(" /* ^^^ module path. We use ORDER BY ROWID to make their priority correct. */ "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE" ");" "CREATE TABLE " CMPP__DB_MAIN_NAME ".predef(" /* ^^^ pre-defines */ "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */ "k TEXT PRIMARY KEY NOT NULL," "v TEXT DEFAULT NULL" ") WITHOUT ROWID;" "INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)" " VALUES(NULL,'cmpp::version','" CMPP_VERSION "')" ";" /** sdefs - "scoped defines" or "shadow defines". The problem these solve is the one of supporting a __FILE__ define in cmpp input sources, such that it remains valid both before and after an #include, but has a new name in the scope of an #include. We can't use savepoints for that because they're a nuclear option affecting _all_ #defines in the #include'd file, whereas we normally want #defines to stick around across files. See cmpp_define_shadow() and cmpp_define_unshadow(). */ "CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef(" "id INTEGER PRIMARY KEY AUTOINCREMENT," "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */ "k TEXT NOT NULL," "v TEXT DEFAULT NULL" ");" /** vdef is a view consolidating the various #define stores. It's intended to be used for all general-purpose fetching of defines and it orders the results such that the library's defines supercede all others, then scoped keys, then client-level defines. To push a new sdef we simply insert into sdef. Then vdef will order the newest sdef before any entry from the def table. */ "CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS" " SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef" /* ------^^^^ sorts before numbers */ " UNION ALL" " SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef" /* ^^^^ sorts newest of matching keys first */ " UNION ALL" " SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def" " ORDER BY 1, 3" ";" #if 0 "CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype(" /* ^^^ token types */ "t INTEGER PRIMARY KEY NOT NULL," /*^^ type: cmpp_tt */ "n TEXT NOT NULL," /*^^ cmpp_TT_... name. */ "s TEXT DEFAULT NULL" /* Symbolic or directive name, if any. */ ");" #endif "COMMIT;" "BEGIN EXCLUSIVE;" ; cmpp__err_clear(pp); int openFlags = SQLITE_OPEN_READWRITE; if( pi->db.zName ){ openFlags |= SQLITE_OPEN_CREATE; } rc = sqlite3_open_v2( pi->db.zName ? pi->db.zName : ":memory:", &pi->db.dbh, openFlags, 0); if(rc){ cmpp__db_rc(pp, rc, pi->db.zName ? pi->db.zName : ":memory:"); sqlite3_close(pi->db.dbh); pi->db.dbh = 0; assert(ppCode); return rc; } sqlite3_busy_timeout(pi->db.dbh, 5000); sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME, CMPP__DB_MAIN_NAME); rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT, cmpp__db_sq3TraceV2, pp); if( cmpp__db_rc(pp, rc, "Installing tracer failed") ){ goto end; } //g_warn("Schema:\n%s\n",zSchema); struct { /* SQL UDFs */ char const * const zName; void (*xUdf)(sqlite3_context *,int,sqlite3_value **); int arity; int flags; } aFunc[] = { { .zName = "cmpp_file_exists", .xUdf = cmpp__udf_file_exists, .arity = 1, .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY }, { .zName = "cmpp_truthy", .xUdf = cmpp__udf_truthy, .arity = 1, .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC }, { .zName = "cmpp_compare", .xUdf = cmpp__udf_compare, .arity = 2, .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC } }; assert( 0==rc ); for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){ rc = sqlite3_create_function( pi->db.dbh, aFunc[i].zName, aFunc[i].arity, aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0 ); } if( cmpp__db_rc(pp, rc, "UDF registration failed.") ){ return ppCode; } if( pi->db.zName ){ /* Drop all cmpp tables when using a persistent db so that we are not beholden to a given structure. TODO: a config flag to toggle this. */ rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr); } if( !rc ){ rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr); } if( !rc ){ extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *); rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL); } if(rc){ if( zErr ){ cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc), "SQLite error #%d initializing DB: %s", rc, zErr); sqlite3_free(zErr); }else{ cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc), "SQLite error #%d initializing DB", rc); } goto end; } while(0){ /* Insert the ttype mappings. We don't yet make use of this but only for lack of a use case ;). */ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false); if( !q ) goto end; #define E(N,STR) \ cmpp__bind_int(pp, q, 1, cmpp_TT_ ## N); \ cmpp__bind_textn(pp, q, 2, \ ustr_c("cmpp_TT_ " # N), sizeof("cmpp_TT_" # N)-1); \ if( STR ) cmpp__bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \ else cmpp__bind_null(pp, q, 3); \ if( SQLITE_DONE!=cmpp__step(pp, q, true) ) return ppCode; cmpp_tt_map(E) #undef E sqlite3_finalize(q); pi->stmt.insTtype = 0; break; } end: if( !ppCode ){ /* ** Keep us from getting in the situation later that delayed ** preparation if one of the savepoint statements fails (e.g. due ** to OOM or memory corruption). */ cmpp__stmt(pp, CmppStmt_spBegin, false); cmpp__stmt(pp, CmppStmt_spRelease, false); cmpp__stmt(pp, CmppStmt_spRollback, false); cmpp__lazy_init(pp); } return ppCode; } /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the core cmpp_dx_f() implementations of libcmpp. */ static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){ return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' may only be used once.", arg->z); } /* No-op cmpp_dx_f() impl. */ static void cmpp_dx_f_noop(cmpp_dx *dx){ (void)dx; } /** cmpp_kav_each_f() impl for use by #define {k->v}. */ static int cmpp_kav_each_f_define__group( cmpp_dx *dx, unsigned char const *zKey, cmpp_size_t nKey, unsigned char const *zVal, cmpp_size_t nVal, void* callbackState ){ if( (callbackState==dx) && cmpp_has(dx->pp, (char const*)zKey, nKey) ){ return dxppCode; } return cmpp__define2(dx->pp, zKey, nKey, zVal, nVal, cmpp_TT_String); } /* #error impl. */ static void cmpp_dx_f_error(cmpp_dx *dx){ const char *zBegin = (char const *)dx->args.z; unsigned n = (unsigned)dx->args.nz; if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){ ++zBegin; n -= 2; } if( n ){ cmpp_dx_err_set(dx, CMPP_RC_ERROR, "%.*s", n, zBegin); }else{ cmpp_dx_err_set(dx, CMPP_RC_ERROR, "(no additional info)"); } } /* Impl. for #define. */ static void cmpp_dx_f_define(cmpp_dx *dx){ cmpp_d const * const d = dx->d; assert(d); if( !dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp_arg const * aKey = 0; int argNdx = 0; int nChomp = 0; unsigned nHeredoc = 0; unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */; bool ifNotDefined = false /* true if '?' arg */; cmpp_arg const *aAppend = 0; #define checkIsDefined(ARG) \ if(ifNotDefined && (cmpp_has(dx->pp, (char const*)ARG->z, ARG->n) \ || dxppCode)) break for( cmpp_arg const * arg = dx->args.arg0; 0==dxppCode && arg; arg = arg->next, ++argNdx ){ //g_warn("arg=%s", arg->z); if( 0==argNdx && cmpp_arg_equals(arg, "?") ){ /* Only set the key if it's not already defined. */ ifNotDefined = true; continue; } switch( arg->ttype ){ case cmpp_TT_ShiftL3: ++nChomp; /* fall through */ case cmpp_TT_ShiftL: if( arg->next || argNdx<1 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Ill-placed '%s'.", arg->z); }else if( arg->n >= sizeof(acHeredoc)-1 ){ cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Heredoc name is too large."); }else if( !aKey ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing key before %s.", cmpp__tt_cstr(cmpp_TT_ShiftL, false)); }else{ assert( aKey ); nHeredoc = aKey->n; memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/); } break; case cmpp_TT_OpEq: if( 1 /*seenEq || argNdx!=1 || !arg->next*/ ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Ill-placed '%s'.", arg->z); break; } continue; case cmpp_TT_StringAt: if( cmpp__StringAtIsOk(dx->pp, cmpp_atpol_CURRENT) ){ break; } /* fall through */ case cmpp_TT_Int: case cmpp_TT_String: case cmpp_TT_Word: if( cmpp_arg_isflag(arg,"-chomp") ){ ++nChomp; break; } if( cmpp_arg_isflag(arg,"-append") || cmpp_arg_isflag(arg,"-a") ){ aAppend = arg->next; if( !aAppend ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting argument for %s", arg->z); } arg = aAppend; break; } if( aKey ){ /* This is the second arg - the value */ checkIsDefined(aKey); cmpp_b * const os = cmpp_b_borrow(dx->pp); cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0; while( os ){ if( ba ){ cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false); if( dxppCode ) break; if( 0 ){ g_warn("key=%s\n", aKey->z); g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z); } } if( cmpp_arg_to_b(dx, arg, os, cmpp_arg_to_b_F_BRACE_CALL) ) break; cmpp_b * const which = (ba && ba->n) ? ba : os; if( which==ba && os->n ){ if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n); cmpp_b_append4(dx->pp, ba, os->z, os->n); } cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n, arg->ttype); if( 0 ){ g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z); g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z); } break; } cmpp_b_return(dx->pp, os); cmpp_b_return(dx->pp, ba); aKey = 0; }else if( cmpp_TT_Word!=arg->ttype ){ cmpp_dx_err_set(dx, CMPP_RC_TYPE, "Expecting a define-name token here."); }else if( arg->next ){ aKey = arg; }else{ /* No value = a value of 1. */ checkIsDefined(arg); cmpp__define2(dx->pp, arg->z, arg->n, ustr_c("1"), 1, cmpp_TT_Int); } break; case cmpp_TT_GroupSquiggly: assert( !acHeredoc[0] ); if( (ifNotDefined ? argNdx>1 : argNdx>0) || arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "{...} must be the only argument.") /* This is for simplicity's sake. */; }else{ cmpp_kav_each(dx, arg->z, arg->n, cmpp_kav_each_f_define__group, ifNotDefined ? dx : NULL, cmpp_kav_each_F_NOT_EMPTY | cmpp_kav_each_F_CALL_VAL | cmpp_kav_each_F_PARENS_EXPR //TODO cmpp_kav_each_F_IF_UNDEF ); } aKey = 0; break; case cmpp_TT_GroupParen:{ if( !aKey ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "(...) is not permitted as a key."); break; } checkIsDefined(aKey); int d = 0; if( 0==cmpp__arg_evalSubToInt(dx, arg, &d) ){ char exprBuf[32] = {0}; cmpp_size_t nVal = (cmpp_size_t)snprintf(&exprBuf[0], sizeof(exprBuf), "%d", d); assert(nVal>0); cmpp__define2(dx->pp, aKey->z, aKey->n, ustr_c(&exprBuf[0]), nVal, cmpp_TT_Int); } break; } case cmpp_TT_GroupBrace:{ if( !aKey ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "[...] is not permitted as a key."); break; } checkIsDefined(aKey); cmpp_b * const b = cmpp_b_borrow(dx->pp); if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){ cmpp__define2(dx->pp, aKey->z, aKey->n, b->z, b->n, cmpp_TT_AnyType); } cmpp_b_return(dx->pp, b); break; } default: // TODO: treat (...) as an expression cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg type %s: %s", cmpp__tt_cstr(arg->ttype, true), arg->z); break; } } if( 0==nHeredoc && nChomp ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "-chomp can only be used with <<."); } if( 0==dxppCode && nHeredoc ){ // Process (#define KEY <<) cmpp_b * const os = cmpp_b_borrow(dx->pp); assert( dx->d->closer ); if( os && 0==cmpp_dx_consume_b(dx, os, &dx->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D) ){ while( nChomp-- && cmpp_b_chomp(os) ){} g_debug(dx->pp,2,("define heredoc: [%s]=[%.*s]\n", acHeredoc, (int)os->n, os->z)); if( !ifNotDefined || !cmpp_has(dx->pp, (char const*)acHeredoc, nHeredoc) ){ cmpp__define2( dx->pp, acHeredoc, nHeredoc, os->z, os->n, cmpp_TT_String ); } } cmpp_b_return(dx->pp, os); } #undef checkIsDefined return; } /* Impl. for #undef */ static void cmpp_dx_f_undef(cmpp_dx *dx){ if( !dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp_d const * const d = dx->d; for( cmpp_arg const * arg = dx->args.arg0; 0==dxppCode && arg; arg = arg->next ){ if( 0 ){ g_stderr(" %s: %s %p n=%d %.*s\n", d->name.z, cmpp__tt_cstr(arg->ttype, true), arg->z, (int)arg->n, (int)arg->n, arg->z); } if( cmpp_TT_Word==arg->ttype ){ #if 0 /* Too strict? */ if( 0==cmpp__legal_key_check(dx->pp, arg->z, (cmpp_ssize_t)arg->n, false) ) { cmpp_undef(dx->pp, (char const *)arg->z); } #else cmpp_undef(dx->pp, (char const *)arg->z, NULL); #endif }else{ cmpp_err_set(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s", d->name.z, arg->z); } } } /* Impl. for #once. */ static void cmpp_dx_f_once(cmpp_dx *dx){ cmpp_d const * const d = dx->d; assert(d); assert(d->closer); if( dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting no arguments"); return; } cmpp_dx_pimpl * const dxp = dx->pimpl; cmpp_b * const b = cmpp_b_borrow(dx->pp); if( !b ) return; cmpp_b_append_ch(b, '#'); cmpp_b_append4(dx->pp, b, d->name.z, d->name.n); cmpp_b_append_ch(b, ':'); cmpp__get_b(dx->pp, ustr_c("__FILE__"), 8, b, true) /* Wonky return semantics. */; if( b->errCode || dxppCode || cmpp_b_append_ch(b, ':') || cmpp_b_append_i32(b, (int)dxp->pos.lineNo) ){ goto end; } //g_debug(dx->pp,1,("#once key: %s", b->z)); int const had = cmpp_has(dx->pp, (char const *)b->z, b->n); if( dxppCode ) goto end; else if( had ){ CmppLvl * const lvl = CmppLvl_push(dx); if( lvl ){ CmppLvl_elide(lvl, true); cmpp_outputer devNull = cmpp_outputer_empty; cmpp_dx_consume(dx, &devNull, &d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D); CmppLvl_pop(dx, lvl); } }else if( !cmpp_define_v2(dx->pp, (char const*)b->z, "1") ){ cmpp_dx_consume(dx, NULL, &d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D); } end: cmpp_b_return(dx->pp, b); return; } /* Impl. for #/define, /#query, /#pipe. */ CMPP__EXPORT(void, cmpp_dx_f_dangling_closer)(cmpp_dx *dx){ cmpp_d const * const d = dx->d; char const * const zD = cmpp_dx_delim(dx); dxserr("%s%s used without its opening directive.", zD, d->name.z); } #ifndef CMPP_OMIT_D_INCLUDE static int cmpp__including_has(cmpp *pp, unsigned const char * zName){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false); if( q && 0==cmpp__bind_text(pp, q, 1, zName) ){ if(SQLITE_ROW == cmpp__step(pp, q, true)){ rc = 1; }else{ rc = 0; } g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc)); } return rc; } /** Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of the `#include` dirs (cmpp_include_dir_add()). If no file match is found, NULL is returned. Memory must eventually be passed to cmpp_mfree() to free it. */ static char * cmpp__include_search(cmpp *pp, unsigned const char * zKey, int * nVal){ char * zName = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false); if( nVal ) *nVal = 0; if( q && 0==cmpp__bind_text(pp, q, 1, zKey) ){ int const rc = cmpp__step(pp, q, false); if(SQLITE_ROW==rc){ const unsigned char * z = sqlite3_column_text(q, 0); int const n = sqlite3_column_bytes(q,0); zName = n ? sqlite3_mprintf("%.*s", n, z) : 0; if( n ) cmpp_check_oom(pp, zName); if( nVal ) *nVal = n; } cmpp__stmt_reset(q); } return zName; } /** Removes zKey from the currently-being-`#include`d list list. */ static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false); if( q ){ cmpp__bind_text(pp, q, 1, ustr_c(zKey)); cmpp__step(pp, q, true); g_debug(pp,2,("incl rm [%s]\n", zKey)); } return ppCode; } #if 0 /* ** Sets pp's error state if the `#include` list contains the given ** key. */ static int cmpp__including_check(cmpp *pp, const char * zKey); int cmpp__including_check(cmpp *pp, const char * zName){ if( !ppCode ){ if(cmpp__including_has(pp, zName)){ cmpp__err(pp, CMPP_RC_MISUSE, "Recursive include detected: %s\n", zName); } } return ppCode; } #endif /** Adds the given filename to the list of being-`#include`d files, using the given source file name and line number of error reporting purposes. If recursion is later detected. */ static int cmpp__including_add(cmpp *pp, unsigned const char * zKey, unsigned const char * zSrc, cmpp_size_t srcLine){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false); if( q ){ cmpp__bind_text(pp, q, 1, zKey); cmpp__bind_text(pp, q, 2, zSrc); cmpp__bind_int(pp, q, 3, srcLine); cmpp__step(pp, q, true); g_debug(pp,2,("is-including-file add [%s] from [%s]:%" CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine)); } return ppCode; } /* Impl. for #include. */ static void cmpp_dx_f_include(cmpp_dx *dx){ char * zResolved = 0; int nResolved = 0; cmpp_b * const ob = cmpp_b_borrow(dx->pp); bool raw = false; cmpp_args args = cmpp_args_empty; if( !ob || cmpp_dx_args_clone(dx, &args) ){ goto end; } assert(args.pimpl && args.pimpl->pp==dx->pp); cmpp_arg const * arg = args.arg0; for( ; arg; arg = arg->next){ #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) FLAG("-raw"){ raw = true; continue; } break; #undef FLAG } if( !arg ){ cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting at least one filename argument."); } for( ; !dxppCode && arg; arg = arg->next ){ cmpp_flag32_t a2bf = cmpp_arg_to_b_F_BRACE_CALL; if( cmpp_TT_Word==arg->ttype && cmpp__arg_wordIsPathOrFlag(arg) ){ a2bf |= cmpp_arg_to_b_F_NO_DEFINES; } if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(ob), a2bf) ){ break; } //g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved); if(!raw && cmpp__including_has(dx->pp, ob->z)){ /* Note that different spellings of the same filename ** will elude this check, but that seems okay, as different ** spellings means that we're not re-running the exact same ** invocation. We might want some other form of multi-include ** protection, rather than this, however. There may well be ** sensible uses for recursion. */ cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Recursive include of file: %s", ob->z); break; } cmpp_mfree(zResolved); nResolved = 0; zResolved = cmpp__include_search(dx->pp, ob->z, &nResolved); if(!zResolved){ if( !dxppCode ){ cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "file not found: %s", ob->z); } break; } if( raw ){ if( !dx->pp->pimpl->out.out ) break; FILE * const fp = cmpp_fopen(zResolved, "r"); if( fp ){ int const rc = cmpp_stream(cmpp_input_f_FILE, fp, dx->pp->pimpl->out.out, dx->pp->pimpl->out.state); if( rc ){ cmpp_dx_err_set(dx, rc, "Unknown error streaming file %s.", arg->z); } cmpp_fclose(fp); }else{ cmpp_dx_err_set(dx, cmpp_errno_rc(errno, CMPP_RC_IO), "Unknown error opening file %s.", arg->z); } }else{ cmpp__including_add(dx->pp, ob->z, ustr_c(dx->sourceName), dx->pimpl->dline.lineNo); cmpp_process_file(dx->pp, zResolved); cmpp__include_rm(dx->pp, ob->z); } } end: cmpp_mfree(zResolved); cmpp_args_cleanup(&args); cmpp_b_return(dx->pp, ob); } #endif /* #ifndef CMPP_OMIT_D_INCLUDE */ /** cmpp_dx_f() callback state for cmpp_dx_f_if(): pointers to the various directives of that family. */ struct CmppIfState { cmpp_d * dIf; cmpp_d * dElif; cmpp_d * dElse; cmpp_d const * dEndif; }; typedef struct CmppIfState CmppIfState; /* Version 2 of #if. */ static void cmpp_dx_f_if(cmpp_dx *dx){ /* Reminder to self: We need to be able to recurse, even in skip mode, for #if nesting to work. That's not great because it means we are evaluating stuff we ideally should be skipping over, but it's keeping the current tests working as-is. We can/do, however, avoid evaluating expressions and such when recursing via skip mode. If we can eliminate that here, by keeping track of the #if stack depth, then we can possibly eliminate the whole CmppLvl_F_ELIDE flag stuff. The more convoluted version 1 #if (which this replaced not hours ago) kept track of the skip state across a separate directive function for #if and #/if. That was more complex but did avoid having to recurse into #if in order to straighten out #elif and #else. Update: tried a non-recursive variant involving moving this function's gotTruth into the CmppLvl object() and managing the CmppLvl stack here, but it just didn't want to work for me and i was too tired to figure out why. */ int gotTruth = 0 /*expr result*/; CmppIfState const * const cis = dx->d->impl.state; cmpp_d const * dClosers[] = { cis->dElif, cis->dElse, cis->dEndif }; CmppLvl * lvl = 0; CmppDLine const dline = dx->pimpl->dline; cmpp_args args = cmpp_args_empty; char delim[20] = {0}; #define skipOn CmppLvl_elide((lvl), true) #define skipOff CmppLvl_elide((lvl), false) assert( dx->d==cis->dIf ); if( !dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an expression."); return; } snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx)); delim[sizeof(delim)-1] = 0; lvl = CmppLvl_push(dx); if( !lvl ) goto end; if( cmpp_dx_is_eliding(dx) ){ gotTruth = 1; }else if( cmpp__args_evalToInt(dx, &dx->pimpl->args, &gotTruth) ){ goto end; }else if( !gotTruth ){ skipOn; } cmpp_d const * dPrev = dx->d; cmpp_outputer devNull = cmpp_outputer_empty; while( !dxppCode ){ dPrev = dx->d; bool const isFinal = dPrev==cis->dElse /* true if expecting an #/if. */; if( cmpp_dx_consume(dx, CmppLvl_is_eliding(lvl) ? &devNull : NULL, isFinal ? &cis->dEndif : dClosers, isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]), cmpp_dx_consume_F_PROCESS_OTHER_D) ){ break; } cmpp_d const * const d2 = dx->d; if( !d2 ){ dxserr("Reached end of input in an untermined %s%s opened " "at line %" CMPP_SIZE_T_PFMT ".", delim, cis->dIf->name.z, dline.lineNo); } if( d2==cis->dEndif ){ break; }else if( isFinal ){ assert(!"cannot happen - caught by consume()"); dxserr("Expecting %s%s to close %s%s.", delim, cis->dEndif->name.z, delim, dPrev->name.z); break; }else if( gotTruth ){ skipOn; continue; }else if( d2==cis->dElif ){ if( 0==cmpp_dx_args_parse(dx, &args) && 0==cmpp__args_evalToInt(dx, &args, &gotTruth) ){ if( gotTruth ) skipOff; else skipOn; } continue; }else{ assert( d2==cis->dElse && "Else (haha!) we cannot have gotten here" ); skipOff; continue; } assert(!"unreachable"); } #undef skipOff #undef skipOn end: cmpp_args_cleanup(&args); if( lvl ){ bool const lvlIsOk = CmppLvl_get(dx)==lvl; CmppLvl_pop(dx, lvl); if( !lvlIsOk && !dxppCode ){ assert(!"i naively believe that this is not possible"); cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Mis-terminated %s%s opened at line " "%" CMPP_SIZE_T_PFMT ".", delim, cis->dIf->name.z, dline.lineNo); } } return; } /* Version 2 of #elif, #else, and #/if. */ static void cmpp_dx_f_if_dangler(cmpp_dx *dx){ CmppIfState const * const cis = dx->d->impl.state; char const *zDelim = cmpp_dx_delim(dx); cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "%s%s with no matching %s%s", zDelim, dx->d->name.z, zDelim, cis->dIf->name.z); } static void cmpp__dump_sizeofs(cmpp_dx*dx){ (void)dx; #define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X)) SO(cmpp); SO(cmpp_api_thunk); SO(cmpp_arg); SO(cmpp_args); SO(cmpp_args_pimpl); SO(cmpp_b); SO(cmpp_d); SO(cmpp_d_reg); SO(cmpp__delim); SO(cmpp__delim_list); SO(cmpp_dx); SO(cmpp_dx_pimpl); SO(cmpp_outputer); SO(cmpp_pimpl); SO(((cmpp_pimpl*)0)->stmt); SO(((cmpp_pimpl*)0)->policy); SO(CmppArgList); SO(CmppDLine); SO(CmppDList); SO(CmppDList_entry); SO(CmppLvl); SO(CmppSnippet); SO(PodList__atpol); printf("cmpp_TT__last = %d\n", cmpp_TT__last); #undef SO } /* Impl. for #pragma. */ static void cmpp_dx_f_pragma(cmpp_dx *dx){ cmpp_arg const * arg = dx->args.arg0; if(!arg){ cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting an argument"); return; }else if(arg->next){ cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Too many arguments"); return; } const char * const zArg = (char const *)arg->z; #define M(X) 0==strcmp(zArg,X) if(M("defines")){ cmpp__dump_defines(dx->pp, stderr, 1); }else if(M("sizeof")){ cmpp__dump_sizeofs(dx); }else if(M("chomp-F")){ dx->pp->pimpl->flags.chompF = 1; }else if(M("no-chomp-F")){ dx->pp->pimpl->flags.chompF = 0; }else if(M("api-thunk")){ /* Generate macros for CMPP_API_THUNK and friends from cmpp_api_thunk_map. */ char const * zName = "CMPP_API_THUNK_NAME"; char buf[256]; #define out(FMT,...) snprintf(buf, sizeof(buf), FMT,__VA_ARGS__); \ cmpp_dx_out_raw(dx, buf, strlen(buf)) if( 0 ){ out("/* libcmpp API thunk. */\n" "static cmpp_api_thunk const * %s = 0;\n" "#define cmpp_api_init(PP) %s = (PP)->api\n", zName, zName); } #define A(V) \ if(V<=cmpp_api_thunk_version) { \ out("/* Thunk APIs which follow are available as of " \ "version %d... */\n",V); \ } #define V(N,T,V) #define F(N,T,P) out("#define cmpp_%s %s->%s\n", # N, zName, # N); #define O(N,T) out("#define cmpp_%s (*%s->%s)\n", # N, zName, # N); cmpp_api_thunk_map(A,V,F,O) #undef V #undef F #undef O #undef A #undef out }else{ cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg); } #undef M } /* Impl. for #savepoint. */ static void cmpp_dx_f_savepoint(cmpp_dx *dx){ if(!dx->args.arg0 || dx->args.arg0->next){ cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting one argument"); }else{ const char * const zArg = (const char *)dx->args.arg0->z; #define M(X) else if( 0==strcmp(zArg,X) ) if( 0 ){} M("begin"){ cmpp__dx_sp_begin(dx); } M("rollback"){ cmpp__dx_sp_rollback(dx); }M("commit"){ cmpp__dx_sp_commit(dx); }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unknown savepoint option: %s", zArg); } } #undef M } /* #stderr impl. */ static void cmpp_dx_f_stderr(cmpp_dx *dx){ if(dx->args.z){ g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName, dx->pimpl->dline.lineNo, (int)dx->args.nz, dx->args.z); }else{ cmpp_d const * d = dx->d; g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n", dx->sourceName, dx->pimpl->dline.lineNo, cmpp_dx_delim(dx), d->name.z); } } /** Manages both the @token@ policy and the delimiters. #@ ?push? policy NAME ?<args.arg0; if( !arg ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting arguments."); return; } enum ops { op_none, op_set, op_push, op_pop, op_heredoc }; enum popWhichE { pop_policy = 0x01, pop_delim = 0x02, pop_both = pop_policy | pop_delim }; enum ops op = op_none /* what to do */; int popWhich = 0 /* what to pop */; bool gotPolicy = false; bool checkedCallForm = !cmpp_dx_is_call(dx); cmpp_arg const * argDelimO = 0 /* @token@ opener */; cmpp_arg const * argDelimC = 0 /* @token@ closer */; cmpp__pi(dx->pp); cmpp_atpol_e polNew = cmpp_atpol_get(dx->pp); for( ; arg; arg = arg ? arg->next : NULL ){ //g_warn("arg=%s", arg->z); if( !checkedCallForm ){ assert( cmpp_dx_is_call(dx) ); checkedCallForm = true; if( cmpp_arg_equals(arg, "policy") ){ char const * z = cmpp__atpol_name(dx->pp, cmpp__policy(dx->pp,at)); if( z ){ cmpp_dx_out_raw(dx, z, strlen(z)); } }else if( cmpp_arg_equals(arg, "delimiter") ){ char const * zO = 0; char const * zC = 0; cmpp_atdelim_get(dx->pp, &zO, &zC); if( zC ){ cmpp_dx_out_raw(dx, zO, strlen(zO)); cmpp_dx_out_raw(dx, " ", 1); cmpp_dx_out_raw(dx, zC, strlen(zC)); } goto end; }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "In call form, '%s' expects one of " "'policy' or delimiter'."); } goto end; }/* checkedCallForm */ if( !argDelimC && op_none==op ){ /* Look for push|pop. */ if( cmpp_arg_equals(arg, "pop") ){ arg = arg->next; if( !arg ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'pop' expects arguments of 'policy' " "and/or 'delimiter' and/or 'both'."); goto end; } for( ; arg; arg = arg->next ){ if( 0==(pop_policy & popWhich) && cmpp_arg_equals(arg, "policy") ){ popWhich |= pop_policy; }else if( 0==(pop_delim & popWhich) && cmpp_arg_equals(arg, "delimiter") ){ popWhich |= pop_delim; }else if( 0==(pop_both & popWhich) && cmpp_arg_equals(arg, "both") ){ popWhich |= pop_both; }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Invalid argument to 'pop': ", arg->z); goto end; } } assert( !arg ); op = op_pop; break; }/* pop */ if( cmpp_arg_equals(arg, "push") ){ op = op_push; continue; } if( cmpp_arg_equals(arg, "set") ){ /* set is implied if neither of push/pop are and we get a policy name. */ op = op_set; continue; } /* Fall through */ }/* !argDelimC && op_none==op */ if( !gotPolicy && cmpp_arg_equals(arg, "policy") ){ arg = arg->next; if( !arg ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'policy' requires a policy name argument."); goto end; } polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z); if( cmpp_atpol_invalid==polNew ){ cmpp_atpol_from_str(dx->pp, (char const*)arg->z) /* Will set the error state to something informative. */; goto end; } if( op_none==op ) op = op_set; gotPolicy = true; continue; } if( !argDelimC && cmpp_arg_equals(arg, "delimiter") ){ assert( !argDelimO && !argDelimC ); argDelimO = arg->next; argDelimC = argDelimO ? argDelimO->next : NULL; if( !argDelimC ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'delimiter' requires two arguments."); goto end; } arg = argDelimC->next; continue; } if( op_pop!=op ){ if( cmpp_arg_equals(arg,"<<") ){ if( arg->next ) { cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' must be the final argument.", arg->z); goto end; } op = op_heredoc; break; } } cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); return; }/*arg collection*/ assert( !dxppCode ); assert( cmpp_atpol_invalid!=polNew ); #define popcheck(LIST) \ if(dxppCode) goto end; \ if(!LIST.n) goto bad_pop if( op_pop==op ){ assert( popWhich>0 && popWhich<=3 ); if( pop_policy & popWhich ){ popcheck(pi->policy.at); cmpp_atpol_pop(dx->pp); } if( pop_delim & popWhich ){ popcheck(pi->delim.at); cmpp_atdelim_pop(dx->pp); } goto end; } assert( op_set==op || op_push==op || op_heredoc==op ); if( argDelimC ){ /* Push or set the @token@ delimiters */ if( 0 ){ g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push", argDelimO->z, argDelimC->z); } if( op_push==op || op_heredoc==op ){ if( cmpp_atdelim_push(dx->pp, (char const*)argDelimO->z, (char const*)argDelimC->z) ){ goto end; } argDelimO = 0 /* Re-use argDelimC as a flag in case we need to roll this back on an error below. */; }else{ assert( op_set==op ); if( cmpp_atdelim_set(dx->pp, (char const*)argDelimO->z, (char const*)argDelimC->z) ){ goto end; } argDelimO = argDelimC = 0; } } assert( !dxppCode ); assert( !argDelimO ); if( op_heredoc==op ){ if( cmpp_atpol_push(dx->pp, polNew) ){ if( argDelimC ){ popcheck(pi->delim.at); cmpp_atdelim_pop(dx->pp); } }else{ bool const pushedDelim = NULL!=argDelimC; assert( dx->d->closer ); cmpp_dx_consume(dx, NULL, &dx->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D) /* !Invalidates argDelimO and argDelimC! */; popcheck(pi->policy.at); cmpp_atpol_pop(dx->pp); if( pushedDelim ) cmpp_atdelim_pop(dx->pp); } }else if( op_push==op ){ if( cmpp_atpol_push(dx->pp, polNew) && argDelimC ){ /* Roll back delimiter push */ cmpp_atdelim_pop(dx->pp); } }else{ assert( op_set==op ); if( cmpp__policy(dx->pp,at)!=polNew ){ cmpp_atpol_set(dx->pp, polNew); } } end: return; bad_pop: cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Cannot pop an empty stack."); #undef popcheck } static void cmpp_dx_f_expr(cmpp_dx *dx){ int rv = 0; assert( dx->args.z ); if( 0 ){ g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc, dx->args.arg0->n, dx->args.arg0->z); g_stderr("%s() dx->args.z [%.*s]\n", __func__, (int)dx->args.nz, dx->args.z); } if( !dx->args.argc ){ dxserr("An empty expression is not permitted."); return; } #if 0 for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){ g_stderr("got type=%s n=%u z=%.*s\n", cmpp__tt_cstr(a->ttype, true), (unsigned)a->n, (int)a->n, a->z); } #endif if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){ if( 'a'==dx->d->name.z[0] ){ if( !rv ){ cmpp_dx_err_set(dx, CMPP_RC_ASSERT, "Assertion failed: %s", dx->pimpl->buf.argsRaw.z); } }else{ char buf[60]; snprintf(buf, sizeof(buf), "%d\n", rv); cmpp_dx_out_raw(dx, buf, strlen(buf)); } } } static void cmpp_dx_f_undef_policy(cmpp_dx *dx){ cmpp_unpol_e up = cmpp_unpol_invalid; int nSeen = 0; cmpp_arg const * arg = dx->args.arg0; enum ops { op_set, op_push, op_pop }; enum ops op = op_set; if( !dx->args.argc ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting one of: error, null"); return; } again: ++nSeen; if( cmpp_arg_equals(arg,"error") ) up = cmpp_unpol_ERROR; else if( cmpp_arg_equals(arg,"null") ) up = cmpp_unpol_NULL; else if( 1==nSeen ){ if( cmpp_arg_equals(arg, "push") ){ if( !arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting argument to 'push'."); return; } op = op_push; arg = arg->next; goto again; }else if( cmpp_arg_equals(arg, "pop") ){ if( arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Extra argument after 'pop': %s", arg->next->z); return; } op = op_pop; } } if( op_pop!=op && cmpp_unpol_invalid==up ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled undefined-policy '%s'." " Try one of: error, null", arg->z); }else if( op_set==op ){ cmpp_unpol_set(dx->pp, up); }else if( op_push==op ){ cmpp_unpol_push(dx->pp, up); }else{ assert( op_pop==op ); if( !cmpp__epol(dx->pp,un).n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "No %s%s push is active.", cmpp_dx_delim(dx), dx->d->name.z); }else{ cmpp_unpol_pop(dx->pp); } } } #ifndef CMPP_OMIT_D_DB /* Impl. for #attach. */ static void cmpp_dx_f_attach(cmpp_dx *dx){ if( 3!=dx->args.argc ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s expects: STRING as NAME", dx->d->name.z); return; } cmpp_arg const * pNext = 0; cmpp_b osDbFile = cmpp_b_empty; cmpp_b osSchema = cmpp_b_empty; for( cmpp_arg const * arg = dx->args.arg0; 0==dxppCode && arg; arg = pNext ){ pNext = arg->next; if( !osDbFile.n ){ if( 0==cmpp_arg_to_b(dx, arg, &osDbFile, cmpp_arg_to_b_F_BRACE_CALL) && !osDbFile.n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Empty db file name is not permitted. " "If '%s' is intended as a value, " "it should be quoted.", arg->z); break; } assert( pNext ); if( !pNext || !cmpp_arg_equals(pNext, "as") ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting 'as' after db file name."); break; } pNext = pNext->next; }else if( !osSchema.n ){ if( 0==cmpp_arg_to_b(dx, arg, &osSchema, cmpp_arg_to_b_F_BRACE_CALL) && !osSchema.n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Empty db schema name is not permitted." "If '%s' is intended as a value, " "it should be quoted.", arg->z); break; } } } if( dxppCode ) goto end; if( !osSchema.n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing schema name."); goto end; } sqlite3_stmt * const q = cmpp__stmt(dx->pp, CmppStmt_dbAttach, false); if( q ){ cmpp__bind_textn(dx->pp, q, 1, osDbFile.z, osDbFile.n); cmpp__bind_textn(dx->pp, q, 2, osSchema.z, osSchema.n); cmpp__step(dx->pp, q, true); } end: cmpp_b_clear(&osDbFile); cmpp_b_clear(&osSchema); } /* Impl. for #detach. */ static void cmpp_dx_f_detach(cmpp_dx *dx){ cmpp_d const * d = dx->d; if( 1!=dx->args.argc ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s expects: NAME", d->name.z); return; } cmpp_arg const * const arg = dx->args.arg0; cmpp_b os = cmpp_b_empty; if( cmpp_arg_to_b(dx, arg, &os, cmpp_arg_to_b_F_BRACE_CALL) ){ goto end; } if( !os.n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Empty db schema name is not permitted."); goto end; } sqlite3_stmt * const q = cmpp__stmt(dx->pp, CmppStmt_dbDetach, false); if( q ){ cmpp__bind_textn(dx->pp, q, 1, os.z, os.n); cmpp__step(dx->pp, q, true); } end: cmpp_b_clear(&os); } #endif /* #ifndef CMPP_OMIT_D_DB */ static void cmpp_dx_f_delimiter(cmpp_dx *dx){ cmpp_arg const * arg = dx->args.arg0; enum ops { op_none, op_set, op_push, op_pop }; enum ops op = op_none; cmpp_arg const * argD = 0; bool doHeredoc = false; bool const isCall = cmpp_dx_is_call(dx); for( ; arg; arg = arg->next ){ if( op_none==op ){ /* Look for push|pop. */ if( cmpp_arg_equals(arg, "push") ){ op = op_push; continue; }else if( cmpp_arg_equals(arg, "pop") ){ if( arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'pop' expects no arguments."); return; } op = op_pop; break; } /* Fall through */ } if( !argD ){ if( op_none==op ) op = op_set; argD = arg; continue; }else if( !doHeredoc && cmpp_arg_equals(arg,"<<") ){ if( isCall ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' is not legal in [call] form.", arg->z); return; }else if( arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' must be the final argument.", arg->z); return; } op = op_push; doHeredoc = true; continue; } cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); return; } if( op_pop==op ){ cmpp_delimiter_pop(dx->pp); }else if( !argD ){ if( isCall ){ cmpp__delim const * const del = cmpp__dx_delim(dx); if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n); }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "No delimiter specified."); } return; }else{ char const * const z = (0==strcmp("default",(char*)argD->z)) ? NULL : (char const*)argD->z; if( op_push==op ){ cmpp_delimiter_push(dx->pp, (char const*)argD->z); }else{ assert( op_set==op ); if( doHeredoc ) cmpp_delimiter_push(dx->pp, z); else cmpp_delimiter_set(dx->pp, z); } } if( !cmpp_dx_err_check(dx) ){ if( isCall ){ cmpp__delim const * const del = cmpp__dx_delim(dx); if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n); }else if( doHeredoc ){ assert( op_push==op ); cmpp_dx_consume(dx, NULL, &dx->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D); cmpp_delimiter_pop(dx->pp); } } } #ifndef NDEBUG /* Experimenting grounds. */ static void cmpp_dx_f_experiment(cmpp_dx *dx){ void * st = dx->d->impl.state; (void)st; g_warn("raw args: %s", dx->pimpl->buf.argsRaw.z); g_warn("argc=%u", dx->args.argc); g_warn("isCall=%d\n", cmpp_dx_is_call(dx)); if( 1 ){ for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){ g_stderr("got type=%s n=%u z=%.*s\n", cmpp__tt_cstr(a->ttype, true), (unsigned)a->n, (int)a->n, a->z); } } if( 0 ){ int rv = 0; if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){ g_stderr("expr result: %d\n", rv); } } if( 0 ){ char const * zIn = "a strspn test @# and @"; g_stderr("strlen : %u\n", (unsigned)strlen(zIn)); g_stderr("strspn 1: %u, %u\n", (unsigned)strspn(zIn, "#@"), (unsigned)strspn(zIn, "@#")); g_stderr("strcspn 2: %u, %u\n", (unsigned)strcspn(zIn, "#@"), (unsigned)strcspn(zIn, "@#")); g_stderr("strcspn 3: %u, %u\n", (unsigned)strcspn(zIn, "a strspn"), (unsigned)strcspn(zIn, "nope")); } if( 1 ){ cmpp__dump_sizeofs(dx); } } #endif /* #ifndef NDEBUG */ #ifndef CMPP_OMIT_D_DB /** Helper for #query and friends. Expects arg to be an SQL value. If arg->next is "bind" then this consumes the following two arguments( "bind" BIND_ARG), where BIND_ARG must be one of either cmpp_TT_GroupSquiggly or cmpp_TT_GroupBrace. If it returns 0 then: - If "bind" was found then *pBind is set to the BIND_ARG argument and *pNext is set to the one after that. - Else *pBind is set to NULL and and *pNext is set to arg->next. In either case, *pNext may be set to NULL. */ static int cmpp__consume_sql_args(cmpp *pp, cmpp_arg const *arg, cmpp_arg const **pBind, cmpp_arg const **pNext){ if( 0==ppCode ){ *pBind = 0; cmpp_arg const *pN = arg->next; if( pN && cmpp_arg_equals(pN, "bind") ){ pN = pN->next; if( !pN || ( cmpp_TT_GroupSquiggly!=pN->ttype && cmpp_TT_GroupBrace!=pN->ttype ) ){ return serr("Expecting {...} or [...] after 'bind'."); } *pBind = pN; *pNext = pN->next; } else { *pBind = 0; *pNext = pN; } } return ppCode; } /** cmpp_kav_each_f() impl for used by #query's `bind {...}` argument. */ static int cmpp_kav_each_f_query__bind( cmpp_dx * const dx, unsigned char const * const zKey, cmpp_size_t nKey, unsigned char const * const zVal, cmpp_size_t nVal, void * const callbackState ){ /* Expecting: :bindName -> bindValue */ if( ':'!=zKey[0] && '$'!=zKey[0] /*&& '@'!=zKey[0]*/ ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Bind keys must start with ':' or '$'."); }else{ sqlite3_stmt * const q = callbackState; assert( q ); int const bindNdx = sqlite3_bind_parameter_index(q, (char const*)zKey); if( bindNdx ){ cmpp__bind_textn(dx->pp, q, bindNdx, zVal, nVal); }else{ cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Invalid bind name: %.*s", (int)nKey, zKey); } } return dxppCode; } int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q, cmpp_arg const * const aGroup){ if( dxppCode ) return dxppCode; if( cmpp_TT_GroupSquiggly==aGroup->ttype ){ return cmpp_kav_each( dx, aGroup->z, aGroup->n, cmpp_kav_each_f_query__bind, q, cmpp_kav_each_F_NOT_EMPTY | cmpp_kav_each_F_CALL_VAL | cmpp_kav_each_F_PARENS_EXPR ); } if( cmpp_TT_GroupBrace!=aGroup->ttype ){ return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting {...} or [...] " "for SQL binding list."); } int bindNdx = 0; cmpp_args args = cmpp_args_empty; cmpp_args_parse(dx, &args, aGroup->z, aGroup->n, 0); if( !args.argc && !dxppCode ){ cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Empty SQL bind list is not permitted."); /* Keep going so we can clean up a partially-parsed args. */ } for( cmpp_arg const * aVal = args.arg0; !dxppCode && aVal; aVal = aVal->next ){ ++bindNdx; if( 0 ){ g_warn("bind #%d %s <<%s>>", bindNdx, cmpp__tt_cstr(aVal->ttype, true), aVal->z); } cmpp__bind_arg(dx, q, bindNdx, aVal); } cmpp_args_cleanup(&args); return dxppCode; } /** #query impl */ static void cmpp_dx_f_query(cmpp_dx *dx){ //cmpp_d const * d = cmpp_dx_d(dx); if( !dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp * const pp = dx->pp; sqlite3_stmt * q = 0; cmpp_b * const obBody = cmpp_b_borrow(dx->pp); cmpp_b * const sql = cmpp_b_borrow(dx->pp); cmpp_outputer obNull = cmpp_outputer_empty; //cmpp_b obBindArgs = cmpp_b_empty; cmpp_args args = cmpp_args_empty /* We need to copy the args or do some arg-type-specific work to copy the memory for specific cases. */; int nChomp = 0; bool spStarted = false; bool seenDefine = false; bool batchMode = false; cmpp_arg const * pNext = 0; cmpp_arg const * aBind = 0; cmpp_d const * const dNoRows = dx->d->impl.state; cmpp_d const * const dClosers[2] = {dx->d->closer, dNoRows}; if( !obBody || !sql ) goto cleanup; assert( dNoRows ); if( cmpp_dx_args_clone(dx, &args) ){ goto cleanup; } //g_warn("args.argc=%d", args.argc); for( cmpp_arg const * arg = args.arg0; 0==dxppCode && arg; arg = pNext ){ //g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z); pNext = arg->next; if( cmpp_arg_equals(arg, "define") ){ if( seenDefine ){ cmpp__dx_err_just_once(dx, arg); goto cleanup; } seenDefine = true; continue; } if( cmpp_arg_equals(arg, "-chomp") ){ ++nChomp; continue; } if( cmpp_arg_equals(arg, "-batch") ){ if( batchMode ){ cmpp__dx_err_just_once(dx, arg); goto cleanup; } batchMode = true; continue; } if( !sql->n ){ if( cmpp__consume_sql_args(pp, arg, &aBind, &pNext) ){ goto cleanup; } if( cmpp_arg_to_b(dx, arg, sql, cmpp_arg_to_b_F_BRACE_CALL) ){ goto cleanup; } //g_warn("SQL: <<%s>>", sql->z); continue; } cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); goto cleanup; } if( ppCode ) goto cleanup; if( seenDefine ){ if( nChomp ){ serr("-chomp and define may not be used together."); goto cleanup; }else if( batchMode ){ serr("-batch and define may not be used together."); goto cleanup; } } if( !sql->n ){ serr("Expecting an SQL-string argument."); goto cleanup; } if( batchMode ){ if( aBind ){ serr("Bindable values may not be used with -batch."); goto cleanup; } char *zErr = 0; cmpp__pi(dx->pp); int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr); rc = cmpp__db_rc(dx->pp, rc, zErr); sqlite3_free(zErr); goto cleanup; } if( cmpp__db_rc(pp, sqlite3_prepare_v2( pp->pimpl->db.dbh, (char const *)sql->z, (int)sql->n, &q, 0), 0) ){ goto cleanup; }else if( !q ){ cmpp_err_set(pp, CMPP_RC_RANGE, "Empty SQL is not permitted."); goto cleanup; } //g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q)); int const nCol = sqlite3_column_count(q); if( !nCol ){ cmpp_err_set(pp, CMPP_RC_RANGE, "SQL does not have any result columns."); goto cleanup; } if( !seenDefine ){ if( cmpp_sp_begin(pp) ) goto cleanup; spStarted = true; } if( aBind && cmpp__bind_group(dx, q, aBind) ){ goto cleanup; } bool gotARow = false; cmpp_dx_pos dxPosStart; cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D; cmpp_dx_pos_save(dx, &dxPosStart); int const nChompOrig = nChomp; while( 0==ppCode ){ int const dbrc = cmpp__step(pp, q, false); if( SQLITE_ROW==dbrc ){ nChomp = nChompOrig; gotARow = true; if( cmpp__define_from_row(pp, q, false) ) break; if( seenDefine ) break; cmpp_dx_pos_restore(dx, &dxPosStart); cmpp_b_reuse(obBody); /* If it weren't for -chomp, we wouldn't need to buffer this. */ if( cmpp_dx_consume_b(dx, obBody, dClosers, sizeof(dClosers)/sizeof(dClosers[0]), consumeFlags) ){ goto cleanup; } assert( dx->d == dClosers[0] || dx->d == dClosers[1] ); while( nChomp-- && cmpp_b_chomp(obBody) ){} if( obBody->n && cmpp_dx_out_raw(dx, obBody->z, obBody->n) ) break; if( dx->d == dNoRows ){ if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/, consumeFlags) ){ goto cleanup; } assert( dx->d == dClosers[0] ); /* TODO? chomp? */ } continue; } if( 0==ppCode && seenDefine ){ /* If we got here, there was no result row. */ cmpp__define_from_row(pp, q, true); } break; }/*result row loop*/ cmpp__stmt_reset(q); if( ppCode ) goto cleanup; while( !seenDefine && !gotARow ){ /* No result rows. Skip past the body, emitting the #query:no-rows content, if any. We disable @token processing for that first step because (A) the output is not going anywhere, so no need to expand it (noting that expanding may have side effects via @[call...]@) and (B) the @tokens@ referring to this query's results will not have been set because there was no row to set them from, so @expanding@ them would fail. */ cmpp_atpol_e const atpol = cmpp_atpol_get(dx->pp); if( cmpp_atpol_set(dx->pp, cmpp_atpol_OFF) ) break; cmpp_dx_consume(dx, &obNull, dClosers, sizeof(dClosers)/sizeof(dClosers[0]), consumeFlags); cmpp_atpol_set(dx->pp, atpol); if( dxppCode ) break; assert( dx->d == dClosers[0] || dx->d == dClosers[1] ); if( dx->d == dNoRows ){ if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/, consumeFlags) ){ break; } assert( dx->d == dClosers[0] ); /* TODO? chomp? */ } break; } cleanup: cmpp_args_cleanup(&args); cmpp_b_return(dx->pp, obBody); cmpp_b_return(dx->pp, sql); sqlite3_finalize(q); if( spStarted ) cmpp_sp_rollback(pp); } #endif /* #ifndef CMPP_OMIT_D_DB */ #ifndef CMPP_OMIT_D_PIPE /** #pipe impl. */ static void cmpp_dx_f_pipe(cmpp_dx *dx){ //cmpp_d const * d = cmpp_dx_d(dx); unsigned char const * zArgs = dx->args.z; assert( dx->args.arg0->n == dx->args.nz ); unsigned char const * const zArgsEnd = zArgs + dx->args.nz; if( zArgs==zArgsEnd ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting a command and arguments to pipe."); return; } cmpp_FILE * fpToChild = 0; int nChompIn = 0, nChompOut = 0; cmpp_b * const chout = cmpp_b_borrow(dx->pp); cmpp_b * const cmd = cmpp_b_borrow(dx->pp); cmpp_b * const body = cmpp_b_borrow(dx->pp); cmpp_b * const bArg = cmpp_b_borrow(dx->pp) /* arg parsing and the initial command name part of the external command. */; cmpp_args cmdArgs = cmpp_args_empty; /* TODOs and FIXMEs: We need flags to optionally @token@-parse before and/or after filtering. */ bool seenDD = false /* true if seen "--" or [...] */; bool doCapture = true /* true if we need a closing /pipe */; bool argsAsGroup = false /* true if args is [...] */; bool dumpDebug = false; cmpp_flag32_t popenFlags = 0; cmpp_popen_t po = cmpp_popen_t_empty; if( cmpp_b_reserve3(dx->pp, cmd, zArgsEnd-zArgs + 1) || cmpp_b_reserve3(dx->pp, bArg, cmd->nAlloc) ){ goto cleanup; } unsigned char * zOut = bArg->z; unsigned char const * const zOutEnd = bArg->z + bArg->nAlloc - 1; while( 0==dxppCode ){ cmpp_arg arg = cmpp_arg_empty; zOut = bArg->z; if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd, &zOut, zOutEnd) ){ goto cleanup; } if( cmpp_arg_equals(&arg, "--") ){ zOut = bArg->z; if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd, &zOut, zOutEnd) ){ goto cleanup; } if( !arg.n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting external command name " "or [...] after --."); goto cleanup; } do_arg_list: seenDD = true; cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; if( cmpp_TT_GroupBrace==arg.ttype ){ argsAsGroup = true; a2bFlags |= cmpp_arg_to_b_F_NO_BRACE_CALL; }else if( cmpp__arg_wordIsPathOrFlag(&arg) ){ /* If it looks like it is a path, do not expand it as a word. */ arg.ttype = cmpp_TT_String; } if( cmpp_arg_to_b(dx, &arg, cmd, a2bFlags) || (!argsAsGroup && cmpp_b_append_ch(cmd, ' ')) ){ goto cleanup; } //g_warn("command: [%s]=>%s", arg.z, cmd->z); if( cmd->n<2 ){ cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Command name '%s' resolves to empty. " "This is most commonly caused by not " "quoting it but it can also mean that it " "is an unknown define key.", arg.z); goto cleanup; } //g_warn("arg=%s", arg.z); //g_warn("cmd=%s", cmd->z); break; } if( cmpp_TT_GroupBrace==arg.ttype ){ goto do_arg_list; } #define FLAG(X)if( cmpp_arg_isflag(&arg, X) ) FLAG("-no-input"){ doCapture = false; continue; } FLAG("-chomp-output"){ ++nChompOut; continue; } FLAG("-chomp"){ ++nChompIn; continue; } FLAG("-exec-direct"){ popenFlags |= cmpp_popen_F_DIRECT; continue; } FLAG("-path"){ popenFlags |= cmpp_popen_F_PATH; continue; } FLAG("-debug"){ dumpDebug = true; continue; } #undef FLAG cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s. %s%s requires -- " "before its external command name.", arg.z, cmpp_dx_delim(dx), dx->d->name.z); goto cleanup; } if( !seenDD ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s requires a -- before the name of " "its external app.", cmpp_dx_delim(dx), dx->d->name.z); goto cleanup; } //g_warn("zArgs n=%u zArgs=%s", (unsigned)(zArgsEnd-zArgs), zArgs); /* dx->pimpl->args gets overwritten by cmpp_dx_consume(), so we have to copy the args. */ if( argsAsGroup ){ assert( cmd->z ); if( cmpp_args_parse(dx, &cmdArgs, cmd->z, cmd->n, 0) ){ goto cleanup; } }else{ /* zArgs can have newlines in it. We need to strip those out before passing it on. We elide them entirely, as opposed to replacing them with a space. */ cmpp_skip_snl(&zArgs, zArgsEnd); if( cmpp_b_reserve3(dx->pp, cmd, cmd->n + (zArgsEnd-zArgs) + 1) ){ goto cleanup; } unsigned char * zo = cmd->z + cmd->n; unsigned char const *zi = zArgs; #if !defined(NDEBUG) unsigned char const * zoEnd = cmd->z + cmd->nAlloc; #endif for( ; zi zo ); *zo = 0; cmd->n = zo - cmd->z; } assert( !dxppCode ); if( doCapture ){ assert( dx->d->closer ); if( cmpp_dx_consume_b(dx, body, &dx->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D) ){ goto cleanup; } while( nChompIn-- && cmpp_b_chomp(body) ){} po.fpToChild = &fpToChild; } if( dumpDebug ){ g_warn("%s%s -debug: cmd argsAsGroup=%d n=%u z=%s", cmpp_dx_delim(dx), dx->d->name.z, (int)argsAsGroup, (unsigned)cmd->n, cmd->z); } if( argsAsGroup ){ cmpp_popen_args(dx, &cmdArgs, &po); }else{ unsigned char const * z = cmd->z; //cmpp_skip_snl(&z, cmd->z + cmd->n); cmpp_popen(dx->pp, z, popenFlags, &po); } if( dxppCode ) goto cleanup; int rc = 0; if( doCapture ){ /* Bug: if body is too bug (no idea how much that is), this will block while waiting on input from the child. This can easily happen with #include -raw. */ #if 0 /* Failed attempt to work around it. */ assert( fpToChild ); enum { BufSize = 128 }; unsigned char buf[BufSize]; cmpp_size_t nLeft = body->n; unsigned char const * z = body->z; while( nLeft>0 && !dxppCode ){ cmpp_size_t nWrite = nLeft < BufSize ? nLeft : BufSize; g_warn("writing %u to child...", (unsigned)nWrite); rc = cmpp_output_f_FILE(fpToChild, z, nWrite); if( rc ){ cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process."); break; } z += nWrite; nLeft -= nWrite; fflush(fpToChild); cmpp_size_t nRead = BufSize; rc = cmpp_input_f_fd(&po.fdFromChild, &buf[0], &nRead); if( rc ) goto err_reading; cmpp_b_append4(dx->pp, &chout, buf, nRead);\ } if( !dxppCode ){ g_warn0("reading from child..."); rc = cmpp_stream( cmpp_input_f_fd, &po.fdFromChild, cmpp_output_f_b, chout ); if( rc ) goto err_reading; } g_warn0("I/O done"); #else //g_warn("writing %u bytes to child...", (unsigned)body->n); rc = cmpp_output_f_FILE(fpToChild, body->z, body->n); if( rc ){ cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process."); goto cleanup; } //g_warn("wrote %u bytes to child.", (unsigned)body->n); fclose(fpToChild); fpToChild = 0; if( dxppCode ) goto cleanup; goto stream_chout; #endif }else{ stream_chout: //g_warn0("waiting on child..."); rc = cmpp_stream(cmpp_input_f_fd, &po.fdFromChild, cmpp_output_f_b, chout); //g_warn0("I/O done"); if( rc ){ //err_reading: cmpp_dx_err_set(dx, rc, "Error reading stdout from piped process."); goto cleanup; } } while( nChompOut-- && cmpp_b_chomp(chout) ){} //g_warn("Read in:\n%.*s", (int)chout->n, chout->z); cmpp_dx_out_raw(dx, chout->z, chout->n); cleanup: cmpp_args_cleanup(&cmdArgs); cmpp_b_return(dx->pp, chout); cmpp_b_return(dx->pp, cmd); cmpp_b_return(dx->pp, body); cmpp_b_return(dx->pp, bArg); cmpp_pclose(&po); } #endif /* #ifndef CMPP_OMIT_D_PIPE */ /** #sum ...args Emits the sum of its arguments, treating each as an integer. Non-integer arguments are silently skipped. */ static void cmpp_dx_f_sum(cmpp_dx *dx){ int64_t n = 0, i = 0; cmpp_b b = cmpp_b_empty; for( cmpp_arg const * arg = dx->args.arg0; arg && !cmpp_dx_err_check(dx); arg = arg->next ){ if( 0==cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&b), cmpp_arg_to_b_F_BRACE_CALL) && cmpp__is_int64(b.z, b.n, &i) ){ n += i; } } cmpp_b_append_i64(cmpp_b_reuse(&b), n); cmpp_dx_out_raw(dx, b.z, b.n); cmpp_b_clear(&b); } /** #arg ?flags? the-arg -trim-left -trim-right -trim: trim both sides It sends its arg to cmpp_arg_to_b() to expand it, optionally trims the result, and emits that value. This directive is not expected to be useful except, perhaps in testing cmpp itself. Its trim flags, in particular, aren't commonly useful because #arg is only useful in a function call context and those unconditionally trim their output. */ static void cmpp_dx_f_arg(cmpp_dx *dx){ cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; bool trimL = false, trimR = false; cmpp_arg const * arg = dx->args.arg0; for( ; arg && !cmpp_dx_err_check(dx); arg = arg->next ){ #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) FLAG("-raw") { a2bFlags = cmpp_arg_to_b_F_FORCE_STRING; continue; } FLAG("-trim-left") { trimL=true; continue; } FLAG("-trim-right") { trimR=true; continue; } FLAG("-trim") { trimL=trimR=true; continue; } #undef FLAG break; } if( arg ){ cmpp_b * const b = cmpp_b_borrow(dx->pp); if( b && 0==cmpp_arg_to_b(dx, arg, b, a2bFlags) ){ unsigned char const * zz = b->z; unsigned char const * zzEnd = b->z + b->n; if( trimL ) cmpp_skip_snl(&zz, zzEnd); if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd); if( zzEnd-zz ){ cmpp_dx_out_raw(dx, zz, zzEnd-zz); } } cmpp_b_return(dx->pp, b); }else if( !cmpp_dx_err_check(dx) ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument."); } } /** #join ?flags? ...args -s SEPARATOR: sets the separator for its RHS arguments. Default=space. -nl: append a newline (will be stripped by [call]s!). This is the default when !cmpp_dx_is_call(dx). -nonl: do not append a newline. Default when dx->isCall. */ static void cmpp_dx_f_join(cmpp_dx *dx){ cmpp_b * const b = cmpp_b_borrow(dx->pp); cmpp_b * const bSep = cmpp_b_borrow(dx->pp); cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; bool addNl = !cmpp_dx_is_call(dx); int n = 0; if( !b || !bSep ) goto end; if( !dx->args.argc ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s expects ?flags? ...args", cmpp_dx_delim(dx), dx->d->name.z); goto end; } cmpp_b_append_ch(bSep, ' '); cmpp_check_oom(dx->pp, bSep->z); for( cmpp_arg const * arg = dx->args.arg0; arg && !b->errCode && !bSep->errCode && !cmpp_dx_err_check(dx); arg = arg->next ){ #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) FLAG("-s"){ if( !arg->next ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing SEPARATOR argument to -s."); break; } cmpp_arg_to_b(dx, arg->next, cmpp_b_reuse(bSep), cmpp_arg_to_b_F_BRACE_CALL); arg = arg->next; continue; } //FLAG("-nl"){ addNl=true; continue; } FLAG("-nonl"){ addNl=false; continue; } #undef FLAG if( n++ && cmpp_dx_out_raw(dx, bSep->z, bSep->n) ){ break; } if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b), a2bFlags) ){ break; } cmpp_dx_out_raw(dx, b->z, b->n); } if( !cmpp_dx_err_check(dx) ){ if( !n ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting at least one argument."); }else if( addNl ){ cmpp_dx_out_raw(dx, "\n", 1); } } end: cmpp_b_return(dx->pp, b); cmpp_b_return(dx->pp, bSep); } /* Impl. for #file */ static void cmpp_dx_f_file(cmpp_dx *dx){ if( !dx->args.arg0 ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp_d const * const d = dx->d; enum e_op { op_none, op_exists, op_join }; cmpp_b * const b0 = cmpp_b_borrow(dx->pp); if( !b0 ) goto end; enum e_op op = op_none; cmpp_arg const * opArg = 0; cmpp_arg const * arg = 0; for( arg = dx->args.arg0; 0==dxppCode && arg; arg = arg->next ){ if( op_none==op ){ if( cmpp_arg_equals(arg, "exists") ){ op = op_exists; opArg = arg->next; arg = opArg->next; if( arg ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s exists: too many arguments", cmpp_dx_delim(dx), d->name.z); goto end; } break; }else if( cmpp_arg_equals(arg, "join") ){ op = op_join; if( !arg->next ) goto missing_arg; arg = arg->next; break; }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unknown %s%s command: %s", cmpp_dx_delim(dx), d->name.z, arg->z); goto end; } cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s unhandled argument: %s", cmpp_dx_delim(dx), d->name.z, arg->z); goto end; } } switch( op ){ case op_none: goto missing_arg; case op_join: { int i = 0; cmpp_flag32_t const bFlags = cmpp_arg_to_b_F_BRACE_CALL; for( ; arg; arg = arg->next, ++i ){ if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b0), bFlags) || (i && cmpp_dx_out_raw(dx, "/", 1)) || (b0->n && cmpp_dx_out_raw(dx, b0->z, b0->n)) ){ break; } } cmpp_dx_out_raw(dx, "\n", 1); break; } case op_exists: { assert( opArg ); bool const b = cmpp__file_is_readable((char const *)opArg->z); cmpp_dx_out_raw(dx, b ? "1\n" : "0\n", 2); break; } } end: cmpp_b_return(dx->pp, b0); return; missing_arg: if( arg ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s %s: missing argument", cmpp_dx_delim(dx), d->name.z, arg->z ); }else{ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s: missing subcommand", cmpp_dx_delim(dx), d->name.z); } goto end; } /** #cmp LHS op RHS */ static void cmpp_dx_f_cmp(cmpp_dx *dx){ cmpp_b * const bL = cmpp_b_borrow(dx->pp); cmpp_b * const bR = cmpp_b_borrow(dx->pp); cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; if( !bL || !!bR ) goto end; for( cmpp_arg const * arg = dx->args.arg0; arg && !cmpp_dx_err_check(dx); arg = arg->next ){ if( !bL->z ){ cmpp_arg_to_b(dx, arg, bL, a2bFlags); continue; } if( !bR->z ){ cmpp_arg_to_b(dx, arg, bR, a2bFlags); continue; } goto usage; } if( cmpp_dx_err_check(dx) ) goto end; if( !bL->z || !bR->z ){ usage: cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Usage: LHS RHS"); goto end; } assert( bL->z ); assert( bR->z ); char cbuf[20]; int const cmp = strcmp((char*)bL->z, (char*)bR->z); int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp); assert(n>0); cmpp_dx_out_raw(dx, cbuf, (cmpp_size_t)n); end: cmpp_b_return(dx->pp, bL); cmpp_b_return(dx->pp, bR); } #if 0 /* Impl. for dummy placeholder. */ static void cmpp_dx_f_todo(cmpp_dx *dx){ cmpp_d const * d = cmpp_dx_d(dx); g_warn("TODO: directive handler for %s", d->name.z); } #endif /** If zName matches one of the delayed-load directives, that directive is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if no match is found, but pp's error state is not updated in that case. If a match is found and registration fails, that result code will propagate via pp. */ int cmpp__d_delayed_load(cmpp *pp, char const *zName){ if( ppCode ) return ppCode; int rc = CMPP_RC_NO_DIRECTIVE; unsigned const nName = strlen(zName); pp->pimpl->flags.isInternalDirectiveReg = true; #define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME)) #define M_OC(NAME) (M(NAME) || M("/" NAME)) #define M_IF(NAME) if( M(NAME) ) #define CF(X) cmpp_d_F_ ## X #define F_A_RAW CF(ARGS_RAW) #define F_A_LIST CF(ARGS_LIST) #define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY) #define F_UNSAFE cmpp_d_F_NOT_IN_SAFEMODE #define F_NC cmpp_d_F_NO_CALL #define F_CALL cmpp_d_F_CALL_ONLY #define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \ cmpp_d_reg SYMNAME = { \ .name = NAME, \ .opener = { \ .f = OPENER, \ .flags = OFLAGS \ }, \ .closer = { \ .f = CLOSER, \ .flags = CFLAGS \ }, \ .dtor = 0, \ .state = 0 \ } #define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS ) \ DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \ rc = cmpp_d_register(pp, &rReg, NULL); \ goto end /* The #if family requires some hand-holding... */ if( M_OC("if") || M("elif") || M("else") ) { DREG0(rIf, "if", cmpp_dx_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL), cmpp_dx_f_if_dangler, 0); DREG0(rElif, "elif", cmpp_dx_f_if_dangler, F_NC, 0, 0); DREG0(rElse, "else", cmpp_dx_f_if_dangler, F_NC, 0, 0); CmppIfState * const cis = cmpp__malloc(pp, sizeof(*cis)); if( !cis ) goto end; memset(cis, 0, sizeof(*cis)); rIf.state = cis; rIf.dtor = cmpp_mfree; if( cmpp_d_register(pp, &rIf, &cis->dIf) /* rIf must be first to avoid leaking cis on error */ || cmpp_d_register(pp, &rElif, &cis->dElif) || cmpp_d_register(pp, &rElse, &cis->dElse) ){ rc = ppCode; }else{ assert( cis->dIf && cis->dElif && cis->dElse ); assert( !cis->dEndif ); assert( cis == cis->dIf->impl.state ); assert( cmpp_mfree==cis->dIf->impl.dtor ); cis->dElif->impl.state = cis->dElse->impl.state = cis; cis->dElif->closer = cis->dElse->closer = cis->dEndif = cis->dIf->closer; rc = 0; } goto end; }/* #if and friends */ /* Basic core directives... */ #define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS) \ if( M_OC(N) ){ \ DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS); \ } (void)0 M_IF_CORE("@", cmpp_dx_f_at, F_A_LIST, cmpp_dx_f_dangling_closer, 0); M_IF_CORE("arg", cmpp_dx_f_arg, F_A_LIST, 0, 0); M_IF_CORE("assert", cmpp_dx_f_expr, F_EXPR, 0, 0); M_IF_CORE("cmp", cmpp_dx_f_cmp, F_A_LIST, 0, 0); M_IF_CORE("define", cmpp_dx_f_define, F_A_LIST, cmpp_dx_f_dangling_closer, 0); M_IF_CORE("delimiter", cmpp_dx_f_delimiter, F_A_LIST, cmpp_dx_f_dangling_closer, 0); M_IF_CORE("error", cmpp_dx_f_error, F_A_RAW, 0, 0); M_IF_CORE("expr", cmpp_dx_f_expr, F_EXPR, 0, 0); M_IF_CORE("join", cmpp_dx_f_join, F_A_LIST, 0, 0); M_IF_CORE("once", cmpp_dx_f_once, F_A_LIST | F_NC, cmpp_dx_f_dangling_closer, 0); M_IF_CORE("pragma", cmpp_dx_f_pragma, F_A_LIST, 0, 0); M_IF_CORE("savepoint", cmpp_dx_f_savepoint, F_A_LIST, 0, 0); M_IF_CORE("stderr", cmpp_dx_f_stderr, F_A_RAW, 0, 0); M_IF_CORE("sum", cmpp_dx_f_sum, F_A_LIST, 0, 0); M_IF_CORE("undef", cmpp_dx_f_undef, F_A_LIST, 0, 0); M_IF_CORE("undefined-policy", cmpp_dx_f_undef_policy, F_A_LIST, 0, 0); M_IF_CORE("//", cmpp_dx_f_noop, F_A_RAW, 0, 0); M_IF_CORE("file", cmpp_dx_f_file, F_A_LIST | F_UNSAFE, 0, 0); #undef M_IF_CORE /* Directives which can be disabled via build flags or flags to cmpp_ctor()... */ #define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \ M_IF(NAME) { \ if( 0==(FLAG & pp->pimpl->flags.newFlags) ) { \ DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \ } \ goto end; \ } #ifndef CMPP_OMIT_D_INCLUDE M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE, cmpp_dx_f_include, F_A_LIST | F_UNSAFE, 0, 0); #endif #ifndef CMPP_OMIT_D_PIPE M_IF_FLAGGED("pipe", cmpp_ctor_F_NO_PIPE, cmpp_dx_f_pipe, F_A_RAW | F_UNSAFE, cmpp_dx_f_dangling_closer, 0); #endif #ifndef CMPP_OMIT_D_DB M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB, cmpp_dx_f_attach, F_A_LIST | F_UNSAFE, 0, 0); M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB, cmpp_dx_f_detach, F_A_LIST | F_UNSAFE, 0, 0); if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.newFlags) && (M_OC("query") || M("query:no-rows")) ){ DREG0(rQ, "query", cmpp_dx_f_query, F_A_LIST | F_UNSAFE, cmpp_dx_f_dangling_closer, 0); cmpp_d * dQ = 0; rc = cmpp_d_register(pp, &rQ, &dQ); if( 0==rc ){ /* It would be preferable to delay registration of query:no-rows until we need it, but doing so causes an error when: |#if 0 |#query |... |#query:no-rows HERE |... |#/query |#/if Because query:no-rows won't have been registered, and unknown directives are an error even in skip mode. Maybe they shouldn't be. Maybe we should just skip them in skip mode. That's only been an issue since doing delayed registration of directives, so it's not come up until recently (as of 2025-10-27). i was so hoping to be able to get _rid_ of skip mode at some point. */ cmpp_d * dNoRows = 0; cmpp_d_reg const rNR = { .name = "query:no-rows", .opener = { .f = cmpp_dx_f_dangling_closer, .flags = F_NC } }; rc = cmpp_d_register(pp, &rNR, &dNoRows); if( 0==rc ){ dNoRows->closer = dQ->closer; assert( !dQ->impl.state ); dQ->impl.state = dNoRows; } } goto end; } #endif /*CMPP_OMIT_D_DB*/ #if CMPP_D_MODULE extern void cmpp_dx_f_module(cmpp_dx *); M_IF_FLAGGED("module", cmpp_ctor_F_NO_MODULE, cmpp_dx_f_module, F_A_LIST | F_UNSAFE, 0, 0); #endif #undef M_IF_FLAGGED #ifndef NDEBUG M_IF("experiment"){ DREG("experiment", cmpp_dx_f_experiment, F_A_LIST | F_UNSAFE, 0, 0); } #endif end: #undef DREG #undef DREG0 #undef F_EXPR #undef F_A_RAW #undef F_A_LIST #undef F_UNSAFE #undef F_NC #undef F_CALL #undef CF #undef M #undef M_OC #undef M_IF pp->pimpl->flags.isInternalDirectiveReg = false; return ppCode ? ppCode : rc; } /* ** 2026-02-07: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses filesystem-related APIs libcmpp. */ #include /** There are APIs i'd _like_ to have here, but the readily-available code for them BSD license, so can't be pasted in here. Examples: - Filename canonicalization. - Cross-platform getcwd() (see below). - Windows support. This requires, in addition to the different filesystem APIs, converting strings into something it can use. All of that adds up to infrastructure... which already exists elsewhere but can't be copied here while retaining this project's license. */ bool cmpp__file_is_readable(char const *zFile){ return 0==access(zFile, R_OK); } #if 0 FILE *cmpp__fopen(const char *zName, const char *zMode){ FILE *f; if(zName && ('-'==*zName && !zName[1])){ f = (strchr(zMode, 'w') || strchr(zMode,'+')) ? stdout : stdin ; }else{ f = fopen(zName, zMode); } return f; } void cmpp__fclose( FILE * f ){ if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){ fclose(f); } } #endif /* ** 2025-11-07: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the arguments-handling-related pieces for libcmpp. */ const cmpp_args_pimpl cmpp_args_pimpl_empty = cmpp_args_pimpl_empty_m; const cmpp_args cmpp_args_empty = cmpp_args_empty_m; const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m; //just in case these ever get dynamic state void cmpp_arg_cleanup(cmpp_arg *arg){ if( arg ) *arg = cmpp_arg_empty; } //just in case these ever get dynamic state void cmpp_arg_reuse(cmpp_arg *arg){ if( arg ) *arg = cmpp_arg_empty; } /** Resets li's list for re-use but does not free it. Returns li. */ static CmppArgList * CmppArgList_reuse(CmppArgList *li){ for(cmpp_size_t n = li->nAlloc; n; ){ cmpp_arg_reuse( &li->list[--n] ); assert( !li->list[n].next ); } li->n = 0; return li; } /** Free all memory owned by li but does not free li. */ void CmppArgList_cleanup(CmppArgList *li){ const CmppArgList CmppArgList_empty = CmppArgList_empty_m; while( li->nAlloc ){ cmpp_arg_cleanup( &li->list[--li->nAlloc] ); } cmpp_mfree(li->list); *li = CmppArgList_empty; } /** Returns the most-recently-appended arg of li back to li's free-list. */ static void CmppArgList_unappend(CmppArgList *li){ assert( li->n ); if( li->n ){ cmpp_arg_reuse( &li->list[--li->n] ); } } cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){ cmpp_arg * p = 0; assert( li->list ? li->nAlloc : 0==li->nAlloc ); if( 0==ppCode && 0==CmppArgList_reserve(pp, li, cmpp__li_reserve1_size(li,10)) ){ p = &li->list[li->n++]; cmpp_arg_reuse( p ); } return p; } void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p){ assert( !p->nextFree ); cmpp_b_clear(&p->argOut); CmppArgList_cleanup(&p->argli); *p = cmpp_args_pimpl_empty; } static void cmpp_args_pimpl_reuse(cmpp_args_pimpl *p){ assert( !p->nextFree ); cmpp_b_reuse(&p->argOut); CmppArgList_reuse(&p->argli); assert( !p->argOut.n ); assert( !p->argli.n ); } static void cmpp_args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){ if( p ){ assert( p->pp ); cmpp__pi(pp); assert( !p->nextFree ); cmpp_args_pimpl_reuse(p); p->nextFree = pi->recycler.argPimpl; pi->recycler.argPimpl = p; } } static cmpp_args_pimpl * cmpp_args_pimpl_borrow(cmpp *pp){ cmpp__pi(pp); cmpp_args_pimpl * p = 0; if( pi->recycler.argPimpl ){ p = pi->recycler.argPimpl; pi->recycler.argPimpl = p->nextFree; p->nextFree = 0; p->pp = pp; assert( !p->argOut.n && "Buffer was used when not borrowed" ); }else{ p = cmpp__malloc(pp, sizeof(*p)); if( 0==cmpp_check_oom(pp, p) ) { *p = cmpp_args_pimpl_empty; p->pp = pp; } } return p; } CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){ if( a ){ if( a->pimpl ){ cmpp * const pp = a->pimpl->pp; assert( pp ); if( pp ){ cmpp_args_pimpl_return(pp, a->pimpl); }else{ cmpp_args_pimpl_cleanup(a->pimpl); cmpp_mfree(a->pimpl); } } *a = cmpp_args_empty; } } CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a){ cmpp_args_pimpl * const p = a->pimpl; if( p ) cmpp_args_pimpl_reuse(p); *a = cmpp_args_empty; a->pimpl = p; } int cmpp_args__init(cmpp * pp, cmpp_args * a){ if( 0==ppCode ){ if( a->pimpl ){ assert( a->pimpl->pp == pp ); cmpp_args_reuse(a); assert(! a->pimpl->argOut.n ); assert( a->pimpl->pp == pp ); }else{ a->pimpl = cmpp_args_pimpl_borrow(pp); assert( !a->pimpl || a->pimpl->pp==pp ); } } return ppCode; } /** Declare cmpp_argOp_f_NAME(). */ #define cmpp_argOp_decl(NAME) \ static void cmpp_argOp_f_ ## NAME (cmpp_dx *dx, \ cmpp_argOp const *op, \ cmpp_arg const *vLhs, \ cmpp_arg const **pvRhs, \ int *pResult) cmpp_argOp_decl(compare); #if 0 cmpp_argOp_decl(logical1); cmpp_argOp_decl(logical2); cmpp_argOp_decl(defined); #endif static const struct { const cmpp_argOp opAnd; const cmpp_argOp opOr; const cmpp_argOp opGlob; const cmpp_argOp opNotGlob; const cmpp_argOp opNot; const cmpp_argOp opDefined; #define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge) #define E(NAME) const cmpp_argOp op ## NAME; cmpp_argOps_cmp_map(E) #undef E } cmpp_argOps = { .opAnd = { .ttype = cmpp_TT_OpAnd, .arity = 2, .assoc = 0, .xCall = 0//cmpp_argOp_f_logical2 }, .opOr = { .ttype = cmpp_TT_OpOr, .arity = 2, .assoc = 0, .xCall = 0//cmpp_argOp_f_logical2 }, .opGlob = { .ttype = cmpp_TT_OpGlob, .arity = 2, .assoc = 0, .xCall = 0//cmpp_argOp_f_glob }, .opNotGlob = { .ttype = cmpp_TT_OpNotGlob, .arity = 2, .assoc = 0, .xCall = 0//cmpp_argOp_f_glob }, .opNot = { .ttype = cmpp_TT_OpNot, .arity = 1, .assoc = 1, .xCall = 0//cmpp_argOp_f_logical1 }, .opDefined = { .ttype = cmpp_TT_OpDefined, .arity = 1, .assoc = 1, .xCall = 0//cmpp_argOp_f_defined }, /* Comparison ops... */ #define E(NAME) .op ## NAME = { \ .ttype = cmpp_TT_Op ## NAME, .arity = 2, .assoc = 0, \ .xCall = cmpp_argOp_f_compare }, cmpp_argOps_cmp_map(E) #undef E }; cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt){ switch(tt){ case cmpp_TT_OpAnd: return &cmpp_argOps.opAnd; case cmpp_TT_OpOr: return &cmpp_argOps.opOr; case cmpp_TT_OpGlob: return &cmpp_argOps.opGlob; case cmpp_TT_OpNot: return &cmpp_argOps.opNot; case cmpp_TT_OpDefined: return &cmpp_argOps.opDefined; #define E(NAME) case cmpp_TT_Op ## NAME: return &cmpp_argOps.op ## NAME; cmpp_argOps_cmp_map(E) #undef E default: return NULL; } } #define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype) #if 0 cmpp_argOp_decl(logical1){ assert( cmpp_TT_OpNot==op->ttype ); assert( !vRhs ); assert( vLhs ); if( 0==cmpp__arg_toBool(dx, vLhs, pResult) ){ *pResult = !*pResult; } } cmpp_argOp_decl(logical2){ assert( vRhs ); assert( vLhs ); int vL = 0; int vR = 0; if( 0==cmpp__arg_toBool(dx, vLhs, &vL) && 0==cmpp__arg_toBool(dx, vRhs, &vR) ){ switch( op->ttype ){ case cmpp_TT_OpAnd: *pResult = vL && vR; break; case cmpp_TT_OpOr: *pResult = vL || vR; break; default: cmpp__fatal("Cannot happen: illegal op mapping"); } } } cmpp_argOp_decl(defined){ assert( cmpp_TT_OpDefined==op->ttype ); assert( !vRhs ); assert( vLhs ); if( cmpp_TT_Word==vLhs->ttype ){ *pResult = cmpp_has(pp, (char const *)vLhs->z, vLhs->n); if( !*pResult && vLhs->n>1 && '#'==vLhs->z[0] ){ *pResult = !!cmpp__d_search3(pp, vLhs->z+1, cmpp__d_search3_F_NO_DLL); } }else{ cmpp__err(pp, CMPP_RC_TYPE, "Invalid token type %s for %s", cmpp__tt_cstr(vLhs->ttype, true), cmpp__tt_cstr(op->ttype, false)); } } #endif #if 0 static cmpp_argOp const * cmpp_argOp_isCompare(cmpp_tt tt){ cmpp_argOp const * const p = cmpp_argOp_for_tt(tt); switch( p ? p->ttype : cmpp_TT_None ){ #define E(NAME) case cmpp_TT_Op ## NAME: return p; cmpp_argOps_cmp_map(E) #undef E return p; case cmpp_TT_None: default: return NULL; } } #endif /** An internal helper for cmpp_argOp_...(). It binds some value of *paArg to column bindNdx of query q and sets *paArg to the next argument to be consumed. This function expects that q is set up to do the right thing when *paArg is a Word-type value (see cmpp_argOp_f_compare()). */ static void cmpp_argOp__cmp_bind(cmpp_dx * const dx, sqlite3_stmt * const q, int bindNdx, cmpp_arg const ** paArg){ cmpp_arg const * const arg = *paArg; assert(arg); switch( dxppCode ? 0 : arg->ttype ){ case 0: break; case cmpp_TT_Word: /* In this case, q is supposed to be set up to use CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word and performs the expansion to its value in the query. */ cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, arg->n); *paArg = arg->next; break; case cmpp_TT_StringAt: case cmpp_TT_String: case cmpp_TT_Int:{ cmpp__bind_arg(dx, q, bindNdx, arg); *paArg = arg->next; break; } case cmpp_TT_OpNot: case cmpp_TT_OpDefined: case cmpp_TT_GroupParen:{ int rv = 0; if( 0==cmpp__arg_toBool(dx, arg, &rv, paArg) ){ cmpp__bind_int(dx->pp, q, bindNdx, rv); } *paArg = arg->next; break; } /* TODO? cmpp_TT_GroupParen */ default: cmpp_dx_err_set(dx, CMPP_RC_TYPE, "Invalid argument type (%s) for the comparison " "queries: %s", cmpp_tt_cstr(arg->ttype), arg->z); } } /** Internal helper for cmp_argOp_...(). Expects q to be a query with an integer in result column 0. This steps/resets the query and applies the given comparison operator's logic to column 0's value, placing the result of the operator in *pResult. If q has no result row, a default value of 0 is assumed. */ static void cmpp_argOp__cmp_apply(cmpp * const pp, cmpp_argOp const * const op, sqlite3_stmt * const q, int * const pResult){ if( 0==ppCode ){ int rc = cmpp__step(pp, q, false); assert( SQLITE_ROW==rc || ppCode ); if( SQLITE_ROW==rc ){ rc = sqlite3_column_int(q, 0); }else{ rc = 0; } switch( op->ttype ){ case 0: break; case cmpp_TT_OpEq: *pResult = 0==rc; break; case cmpp_TT_OpNeq: *pResult = 0!=rc; break; case cmpp_TT_OpLt: *pResult = rc<0; break; case cmpp_TT_OpLe: *pResult = rc<=0; break; case cmpp_TT_OpGt: *pResult = rc>0; break; case cmpp_TT_OpGe: *pResult = rc>=0; break; default: cmpp__fatal("Cannot happen: invalid arg mapping"); } } cmpp__stmt_reset(q); } /** Applies *paRhs as the RHS of an integer binary operator, the LHS of which is the lhs argument. The result is put in *pResult. On success *paRhs is set to the next argument for the expression to parse. */ static void cmpp_argOp_applyTo(cmpp_dx *dx, cmpp_argOp const * const op, int lhs, cmpp_arg const ** paRhs, int * pResult){ sqlite3_stmt * q = 0; cmpp_arg const * aRhs = *paRhs; assert(aRhs); q = cmpp_TT_Word==aRhs->ttype ? cmpp__stmt(dx->pp, CmppStmt_cmpVD, false) : cmpp__stmt(dx->pp, CmppStmt_cmpVV, false); if( q ){ char numbuf[32]; int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs); cmpp__bind_textn(dx->pp, q, 1, ustr_c(numbuf), nNum); cmpp_argOp__cmp_bind(dx, q, 2, paRhs); cmpp_argOp__cmp_apply(dx->pp, op, q, pResult); } } cmpp_argOp_decl(compare){ cmpp_arg const * const vRhs = *pvRhs; sqlite3_stmt * q = 0; /* Select which query to use, depending on whether each of the LHS/RHS are Word tokens. For Word tokens the corresponding query columns get bound to a subquery which resolves the word. Non-word tokens get bound as-is. */ if( cmpp_TT_Word==vLhs->ttype ){ q = cmpp_TT_Word==vRhs->ttype ? cmpp__stmt(dx->pp, CmppStmt_cmpDD, false) : cmpp__stmt(dx->pp, CmppStmt_cmpDV, false); if(0){ g_warn("\nvLhs=%s %s\nvRhs=%s %s\n", cmpp_tt_cstr(vLhs->ttype), vLhs->z, cmpp_tt_cstr(vRhs->ttype), vRhs->z); } }else if( cmpp_TT_Word==vRhs->ttype ){ q = cmpp__stmt(dx->pp, CmppStmt_cmpVD, false); }else{ q = cmpp__stmt(dx->pp, CmppStmt_cmpVV, false); } if( q ){ //cmpp__bind_textn(pp, q, 1, vLhs->z, vLhs->n); cmpp_argOp__cmp_bind(dx, q, 1, &vLhs); cmpp_argOp__cmp_bind(dx, q, 2, pvRhs); cmpp_argOp__cmp_apply(dx->pp, op, q, pResult); } } #undef cmpp_argOp_decl #if 0 static inline int cmpp_dxt_isBinOp(cmpp_tt tt){ cmpp_argOp const * const a = cmpp_argOp_for_tt(tt); return a ? 2==a->arity : 0; } static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){ return tt==cmpp_TT_OpNot || cmpp_TT_OpDefined; } static inline int cmpp_dxt_isGroup(cmpp_tt tt){ return tt==cmpp_TT_GroupParen || tt==cmpp_TT_GroupBrace || cmpp_TT_GroupSquiggly; } #endif int cmpp__arg_evalSubToInt(cmpp_dx *dx, cmpp_arg const *arg, int * pResult){ cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(dx, &sub, pResult); } cmpp_args_cleanup(&sub); return dxppCode; } int cmpp__args_evalToInt(cmpp_dx * const dx, cmpp_args const *pArgs, int * pResult){ if( dxppCode ) return dxppCode; cmpp_arg const * pNext = 0; cmpp_arg const * pPrev = 0; int result = *pResult; cmpp_b osL = cmpp_b_empty; cmpp_b osR = cmpp_b_empty; static int level = 0; ++level; #define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__) //lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z); for( cmpp_arg const *arg = pArgs->arg0; arg && 0==dxppCode; pPrev = arg, arg = pNext ){ pNext = arg->next; if( cmpp_TT_Noop==arg->ttype ){ arg = pPrev /* help the following arg to DTRT */; continue; } cmpp_argOp const * const thisOp = argOp(arg); cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0; if( 0 ){ lout("arg: %s @%p %s\n", cmpp__tt_cstr(arg->ttype, true), arg, arg->z); if(1){ if( pPrev ) lout(" prev arg: %s %s\n", cmpp__tt_cstr(pPrev->ttype, true), pPrev->z); if( pNext ) lout(" next arg: %s %s\n", cmpp__tt_cstr(pNext->ttype, true), pNext->z); } } if( thisOp ){ /* Basic validation */ if( !pNext ){ dxserr("Missing '%s' RHS.", cmpp__tt_cstr(thisOp->ttype, false)); break; }else if( !pPrev && 2==thisOp->arity ){ dxserr("Missing %s LHS.", cmpp__tt_cstr(thisOp->ttype, false)); break; } if( nextOp && nextOp->arity>1 ){ dxserr("Invalid '%s' RHS: %s", arg->z, pNext->z); break; } } switch( arg->ttype ){ case cmpp_TT_OpNot: case cmpp_TT_OpDefined: if( pPrev && !argOp(pPrev) ){ cmpp_dx_err_set(dx, CMPP_RC_CANNOT_HAPPEN, "We expected to have consumed '%s' by " "this point.", pPrev->z); }else{ cmpp__arg_toBool(dx, arg, &result, &pNext); } break; case cmpp_TT_OpAnd: case cmpp_TT_OpOr:{ assert( pNext ); assert( pPrev ); /* Reminder to self: we can't add short-circuiting of the RHS right now because the handling of chained unary ops on the RHS is handled via cmpp__arg_toBool(). */ int rv = 0; if( 0==cmpp__arg_toBool(dx, pNext, &rv, &pNext) ){ if( cmpp_TT_OpAnd==arg->ttype ) result = result && rv; else result = result || rv; } //g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0); break; } case cmpp_TT_OpNotGlob: case cmpp_TT_OpGlob:{ assert( pNext ); assert( pPrev ); assert( pNext!=arg ); assert( pPrev!=arg ); if( cmpp_arg_to_b(dx, pNext, &osL, 0) ){ break; } unsigned char const * const zGlob = osL.z; if( 0==cmpp_arg_to_b(dx, pPrev, &osR, 0) ){ if( 0 ){ g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z); } result = 0==sqlite3_strglob((char const *)zGlob, (char const *)osR.z); if( cmpp_TT_OpNotGlob==arg->ttype ){ result = !result; } //g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result); } pNext = pNext->next; break; } #define E(NAME) case cmpp_TT_Op ## NAME: cmpp_argOps_cmp_map(E) { cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0; if( prevOp ){ /* Chained operators */ cmpp_argOp_applyTo(dx, thisOp, result, &pNext, &result); }else{ assert( pNext ); assert( pPrev ); assert( thisOp ); assert( thisOp->xCall ); thisOp->xCall(dx, thisOp, pPrev, &pNext, &result); } break; } #undef E #define checkConsecutiveNonOps \ if( pPrev && !argOp(pPrev) ){ \ dxserr("Illegal consecutive non-operators: %s %s", \ pPrev->z, arg->z); \ break; \ }(void)0 case cmpp_TT_Int: case cmpp_TT_String: checkConsecutiveNonOps; if( !cmpp__is_int(arg->z, arg->n, &result) ){ /* This is mostly for and/or ops. glob will reach back and grab arg->z. */ result = 0; } break; case cmpp_TT_Word: checkConsecutiveNonOps; cmpp__get_int(dx->pp, arg->z, arg->n, &result); break; case cmpp_TT_GroupParen:{ checkConsecutiveNonOps; cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(dx, &sub, &result); } cmpp_args_cleanup(&sub); break; } case cmpp_TT_GroupBrace:{ checkConsecutiveNonOps; cmpp_b b = cmpp_b_empty; if( 0==cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0) ){ cmpp__is_int(b.z, b.n, &result); } cmpp_b_clear(&b); break; } #undef checkConsecutiveNonOps default: assert( arg->z ); dxserr("Illegal expression token %s: %s", cmpp__tt_cstr(arg->ttype, true), arg->z); }/*switch(arg->ttype)*/ }/* foreach arg */ if( 0 ){ lout("END %s() result=%d\n", __func__, result); } --level; if( !dxppCode ){ *pResult = result; } cmpp_b_clear(&osL); cmpp_b_clear(&osR); return dxppCode; #undef lout } #undef argOp #undef cmpp_argOp_decl static inline cmpp_tt cmpp_dxt_is_group(cmpp_tt ttype){ switch(ttype){ case cmpp_TT_GroupParen: case cmpp_TT_GroupBrace: case cmpp_TT_GroupSquiggly: return ttype; default: return cmpp_TT_None; } } int cmpp_args_parse(cmpp_dx * const dx, cmpp_args * const pArgs, unsigned char const * const zInBegin, cmpp_ssize_t nIn, cmpp_flag32_t flags){ assert( zInBegin ); unsigned char const * const zInEnd = zInBegin + cmpp__strlenu(zInBegin, nIn); if( cmpp_args__init(dx->pp, pArgs) ) return dxppCode; if( 0 ){ g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin), zInBegin); } unsigned char const * zPos = zInBegin; cmpp_size_t const nBuffer = /* Buffer size for our copy of the args. We need to know the size before we start so that we can have each arg reliably point back into this without it being reallocated during parsing. */ (cmpp_size_t)(zInEnd - zInBegin) /* Plus we need one final NUL and one NUL byte per argument, but we don't yet know how many arguments we will have, so let's estimate... */ + ((cmpp_size_t)(zInEnd - zInBegin))/3 + 5/*fudge room*/; cmpp_b * const buffer = &pArgs->pimpl->argOut; assert( !buffer->n ); if( cmpp_b_reserve3(dx->pp, buffer, nBuffer) ){ return dxppCode; } unsigned char * zOut = buffer->z; unsigned char const * const zOutEnd = zOut + buffer->nAlloc - 1; cmpp_arg * prevArg = 0; #if !defined(NDEBUG) unsigned char const * const zReallocCheck = buffer->z; #endif if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin), zInBegin); pArgs->arg0 = NULL; pArgs->argc = 0; for( int i = 0; zPospp, &pArgs->pimpl->argli); if( !arg ) return dxppCode; assert( pArgs->pimpl->argli.n ); if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos); if( cmpp_arg_parse(dx, arg, &zPos, zInEnd, &zOut, zOutEnd) ){ if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos); break; } if( 0 ){ g_warn("#%d zPos=<<%.*s>>", i, (int)(zInEnd-zPos), zPos); g_warn("#%d arg n=%u z=<<%.*s>> %s", i, (int)arg->n, (int)arg->n, arg->z, arg->z); } assert( zPos<=zInEnd ); if( 0 ){ g_stderr("ttype=%d %s n=%u z=%.*s\n", arg->ttype, cmpp__tt_cstr(arg->ttype, true), (unsigned)arg->n, (int)arg->n, arg->z); } if( cmpp_TT_Eof==arg->ttype ){ CmppArgList_unappend(&pArgs->pimpl->argli); break; } switch( 0==(flags & cmpp_args_F_NO_PARENS) ? cmpp_dxt_is_group( arg->ttype ) : 0 ){ case cmpp_TT_GroupParen:{ /* Sub-expression. We tokenize it here just to ensure that we can, so we can fail earlier rather than later. This is why we need a recycler for the cmpp_args buffer memory. */ cmpp_args sub = cmpp_args_empty; cmpp_args_parse(dx, &sub, arg->z, arg->n, flags); //g_stderr("Parsed sub-expr: %s\n", sub.buffer.z); cmpp_args_cleanup(&sub); break; } case cmpp_TT_GroupBrace: case cmpp_TT_GroupSquiggly: default: break; } if( dxppCode ) break; if( prevArg ){ assert( !prevArg->next ); prevArg->next = arg; } prevArg = arg; }/*foreach input char*/ //g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(dxppCode), pArgs->args.n); if( 0==dxppCode ){ pArgs->argc = pArgs->pimpl->argli.n; assert( !pArgs->arg0 ); if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list; if( zOutarg0; a; a = a->next ){ g_stderr(" got: %s %.*s\n", cmpp__tt_cstr(a->ttype, true), a->n, a->z); } } } assert(zReallocCheck==buffer->z && "Else buffer was reallocated, invalidating argN->z"); return dxppCode; } CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0, cmpp_args * const dest){ if( cmpp_args__init(pp, dest) || !a0 ) return ppCode; cmpp_b * const ob = &dest->pimpl->argOut; CmppArgList * const argli = &dest->pimpl->argli; unsigned int i = 0; cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */; assert( !ob->n ); assert( !dest->arg0 ); assert( !dest->argc ); assert( !argli->n ); /* Preallocate ob->z to fit a copy of a0's args. */ for( cmpp_arg const * a = a0; a; ++i, a = a->next ){ nReserve += a->n + 1/*NUL byte*/; } if( cmpp_b_reserve3(pp, ob, nReserve+1) || CmppArgList_reserve(pp, argli, i) ){ goto end; } assert( argli->nAlloc>=i ); i = 0; #ifndef NDEBUG unsigned char const * const zReallocCheck = ob->z; #endif for( cmpp_arg const * a = a0; a; ++i, a = a->next ){ cmpp_arg * const aNew = &argli->list[i]; aNew->n = a->n; aNew->z = ob->z + ob->n; aNew->ttype = a->ttype; if( i ) argli->list[i-1].next = aNew; assert( !a->z[a->n] && "Expecting a NUL byte there" ); cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/); if( 0 ){ g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype), (int)a->n, a->z, a->z); } assert( zReallocCheck==ob->z && "This cannot fail: ob->z was pre-allocated" ); } dest->argc = i; dest->arg0 = i ? &argli->list[0] : 0; end: if( ppCode ){ cmpp_args_reuse(dest); } return ppCode; } CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){ return cmpp_args_clone(dx->pp, dx->args.arg0, pOut); } char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg){ char * z = 0; if( 0==ppCode ){ z = sqlite3_mprintf("%s",arg->z); cmpp_check_oom(pp, z); } return z; } static cmpp_tt cmpp_tt_forWord(unsigned char const *z, unsigned n, cmpp_tt dflt){ static const struct { #define E(NAME,STR) struct CmppSnippet NAME; cmpp_tt_map(E) #undef E } ttStr = { #define E(NAME,STR) \ .NAME = {(unsigned char const *)STR,sizeof(STR)-1}, cmpp_tt_map(E) #undef E }; #define CASE(NAME) if( 0==memcmp(ttStr.NAME.z, z, n) ) return cmpp_TT_ ## NAME switch( n ){ case 1: CASE(OpEq); CASE(Plus); CASE(Minus); break; case 2: CASE(OpOr); CASE(ShiftL); //CASE(ShiftR); //CASE(ArrowL); CASE(ArrowR); CASE(OpNeq); CASE(OpLt); CASE(OpLe); CASE(OpGt); CASE(OpGe); break; case 3: CASE(OpAnd); CASE(OpNot); CASE(ShiftL3); break; case 4: CASE(OpGlob); break; case 7: CASE(OpDefined); break; #undef CASE } #if 0 bool b = cmpp__is_int(z, n, NULL); if( 1|| !b ){ g_warn("is_int(%s)=%d", z, b); } return b ? cmpp_TT_Int : dflt; #else return cmpp__is_int(z, n, NULL) ? cmpp_TT_Int : dflt; #endif } int cmpp_arg_parse(cmpp_dx * const dx, cmpp_arg *pOut, unsigned char const **pzIn, unsigned char const *zInEnd, unsigned char ** pzOut, unsigned char const * zOutEnd){ unsigned char const * zi = *pzIn; unsigned char * zo = *pzOut; cmpp_tt ttype = cmpp_TT_None; #if 0 // trying to tickle valgrind for(unsigned char const *x = zi; x < zInEnd; ++x ){ assert(*x); } #endif cmpp_arg_reuse( pOut ); cmpp_skip_snl( &zi, zInEnd ); if( zi>=zInEnd ){ *pzIn = zi; pOut->ttype = cmpp_TT_Eof; return 0; } #define out(CH) if(zo>=zOutEnd) goto notEnoughOut; *zo++ = CH #define eot_break if( cmpp_TT_None!=ttype ){ keepGoing = 0; break; } (void)0 pOut->z = zo; bool keepGoing = true; for( ; keepGoing && 0==dxppCode && zi'==zi[1] ){ ttype = cmpp_TT_ArrowR; out(*zi++); out(*zi++); keepGoing = false; }else{ goto do_word; } break; case '=': eot_break; keepGoing = false; ttype = cmpp_TT_OpEq; out(*zi++); break; #define opcmp(CH,TT,TTEQ,TTSHIFT,TTARROW) \ case CH: eot_break; keepGoing = false; ttype = TT; out(*zi++); \ if( zi',cmpp_TT_OpGt,cmpp_TT_OpGe,cmpp_TT_ShiftR,0) break; opcmp('<',cmpp_TT_OpLt,cmpp_TT_OpLe,cmpp_TT_ShiftL,cmpp_TT_ArrowL) if( cmpp_TT_ShiftL==ttype && zi= zInEnd || ('"'!=zi[1] && '\''!=zi[1]) ){ goto do_word; } //if( cmpp__StringAtIsOk(dx->pp) ) break; ttOverride = cmpp_TT_StringAt; ++zi /* consume opening '@' */; //g_stderr("@-string override\n"); /* fall through */ case '"': case '\'': { /* Parse a string. We do not support backslash-escaping of any sort here. Strings which themselves must contain quotes should use the other quote type. */ keepGoing = false; if( cmpp_TT_None!=ttype ){ cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Misplaced quote character near: %.*s", (int)(zi+1 - *pzIn), *pzIn); break; } unsigned char const * zQuoteAt = zi; if( cmpp__find_closing(dx->pp, &zQuoteAt, zInEnd) ){ break; } assert( zi+1 <= zQuoteAt ); assert( *zi == *zQuoteAt ); if( (zQuoteAt - zi - 2) >= (zOutEnd-zo) ){ goto notEnoughOut; } memcpy(zo, zi+1, zQuoteAt - zi - 1); //g_warn("string=<<%.*s>>", (zQuoteAt-zi-1), zo); zo += zQuoteAt - zi - 1; zi = zQuoteAt + 1/* closing quote */; ttype = (cmpp_TT_None==ttOverride ? cmpp_TT_String : ttOverride); break; } case '[': case '{': case '(': { /* Slurp these as a single token for later sub-parsing */ keepGoing = false; unsigned char const * zAt = zi; if( cmpp__find_closing(dx->pp, &zi, zInEnd) ) break; /* Transform the output, eliding the open/close characters and trimming spaces. We need to keep newlines intact, as the content may be free-form, intended for other purposes, e.g. the #pipe or #query directives. */ ttype = ('('==*zAt ? cmpp_TT_GroupParen : ('['==*zAt ? cmpp_TT_GroupBrace : cmpp_TT_GroupSquiggly)); ++zAt /* consume opening brace */; /* Trim leading and trailing space, but retain tabs and all but the first and last newline. */ cmpp_skip_space(&zAt, zi); if( zAt*pzOut && ' '==zo[-1] ) *--zo = 0; if( zo>*pzOut && '\n'==zo[-1] ){ *--zo = 0; if( zo>*pzOut && '\r'==zo[-1] ){ *--zo = 0; } } ++zi /* consume the closer */; break; } default: ; do_word: out(*zi++); ttype = cmpp_TT_Word; break; } //g_stderr("kg=%d char=%d %c\n", keepGoing, (int)*zi, *zi); } if( dxppCode ){ /* problem already reported */ }else if( zo>=zOutEnd-1 ){ notEnoughOut: cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Ran out of output space (%u bytes) while " "parsing an argument", (unsigned)(zOutEnd-*pzOut)); }else{ pOut->n = (zo - *pzOut); if( cmpp_TT_None==ttype ){ pOut->ttype = cmpp_TT_Eof; }else if( cmpp_TT_Word==ttype && pOut->n ){ pOut->ttype = cmpp_tt_forWord(pOut->z, pOut->n, ttype); }else{ pOut->ttype = ttype; } *zo++ = 0; *pzIn = zi; *pzOut = zo; switch( pOut->ttype ){ case cmpp_TT_Int: if( '+'==*pOut->z ){ /* strip leading + */ ++pOut->z; --pOut->n; } break; default: break; } if(0){ g_stderr("parse1: %s n=%u <<%.*s>>", cmpp__tt_cstr(pOut->ttype, true), pOut->n, pOut->n, pOut->z); } } #undef out #undef eot_break return dxppCode; } int cmpp__arg_toBool(cmpp_dx * const dx, cmpp_arg const *arg, int * pResult, cmpp_arg const **pNext){ switch( dxppCode ? 0 : arg->ttype ){ case 0: break; case cmpp_TT_Word: *pNext = arg->next; *pResult = cmpp__get_bool(dx->pp, arg->z, arg->n); break; case cmpp_TT_Int: *pNext = arg->next; cmpp__is_int(arg->z, arg->n, pResult)/*was already validated*/; break; case cmpp_TT_String: case cmpp_TT_StringAt:{ unsigned char const * z = 0; cmpp_size_t n = 0; cmpp_b os = cmpp_b_empty; if( 0==cmpp__arg_expand_ats(dx, &os, cmpp_atpol_CURRENT, arg, cmpp_TT_StringAt, &z, &n) ){ *pNext = arg->next; *pResult = n>0 && 0!=memcmp("0\0", z, 2); } cmpp_b_clear(&os); break; } case cmpp_TT_GroupParen:{ *pNext = arg->next; cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(dx, &sub, pResult); } cmpp_args_cleanup(&sub); break; } case cmpp_TT_OpDefined: if( !arg->next ){ dxserr("Missing '%s' RHS.", arg->z); }else if( cmpp_TT_Word!=arg->next->ttype ){ dxserr( "Invalid '%s' RHS: %s", arg->z, arg->next->z); }else{ cmpp_arg const * aOperand = arg->next; *pNext = aOperand->next; if( aOperand->n>1 && '#'==aOperand->z[0] && !!cmpp__d_search3(dx->pp, (char const*)aOperand->z+1, cmpp__d_search3_F_NO_DLL) ){ *pResult = 1; }else{ *pResult = cmpp_has(dx->pp, (char const *)aOperand->z, aOperand->n); } } break; case cmpp_TT_OpNot:{ assert( arg->next && "See cmpp_args__not_simplify()"); assert( cmpp_TT_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()"); if( 0==cmpp__arg_toBool(dx, arg->next, pResult, pNext) ){ *pResult = !*pResult; } break; } default: dxserr("Invalid token type %s for %s(): %s", cmpp__tt_cstr(arg->ttype, true), __func__, arg->z); break; } return dxppCode; } CMPP__EXPORT(int, cmpp_arg_to_b)(cmpp_dx * const dx, cmpp_arg const *arg, cmpp_b * ob, cmpp_flag32_t flags){ /** Reminder to self: this function specifically does not do any expression evaluation of its arguments. Please avoid the temptation to make it do so. Unless it proves necessary. Or useful. Even then, though, consider the implications deeply before doing so. */ switch( dxppCode ? 0 : ((cmpp_arg_to_b_F_FORCE_STRING & flags) ? cmpp_TT_String : arg->ttype) ){ case 0: break; case cmpp_TT_Word: if( 0==(flags & cmpp_arg_to_b_F_NO_DEFINES) ){ cmpp__get_b(dx->pp, arg->z, arg->n, ob, true); break; } goto theDefault; case cmpp_TT_StringAt:{ unsigned char const * z = 0; cmpp_size_t n = 0; if( 0 ){ g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z); } if( 0==cmpp__arg_expand_ats(dx, ob, cmpp_atpol_CURRENT, arg, cmpp_TT_StringAt, &z, &n) && 0 ){ g_warn("expanded at [%.*s] [%s]", (int)n, z, z); g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z); } break; } case cmpp_TT_GroupBrace: if( !(cmpp_arg_to_b_F_NO_BRACE_CALL & flags) && (cmpp_arg_to_b_F_BRACE_CALL & flags) ){ cmpp_call_str(dx->pp, arg->z, arg->n, ob, 0); break; } /* fall through */ default: { theDefault: ; cmpp_outputer oss = cmpp_outputer_b; oss.state = ob;//no: cmpp_b_reuse(ob); Append instead. cmpp__out2(dx->pp, &oss, arg->z, arg->n); break; } } return dxppCode; } int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * const q, int bindNdx, cmpp_arg const * const arg){ if( 0 ){ g_warn("bind #%d %s <<%.*s>>", bindNdx, cmpp__tt_cstr(arg->ttype, true), (int)arg->n, arg->z); } switch( arg->ttype ){ default: case cmpp_TT_Int: case cmpp_TT_String: cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, (int)arg->n); break; case cmpp_TT_Word: case cmpp_TT_StringAt:{ cmpp_b os = cmpp_b_empty; if( 0==cmpp_arg_to_b(dx, arg, &os, 0) ){ if( 0 ){ g_warn("bind #%d <<%s>> => <<%.*s>>", bindNdx, arg->z, (int)os.n, os.z); } cmpp__bind_textn(dx->pp, q, bindNdx, os.z, (int)os.n); } cmpp_b_clear(&os); break; } case cmpp_TT_GroupParen:{ cmpp_args sub = cmpp_args_empty; int i = 0; if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) && 0==cmpp__args_evalToInt(dx, &sub, &i) ){ /* See comment above about cmpp_TT_Int. */ cmpp__bind_int_text(dx->pp, q, bindNdx, i); } cmpp_args_cleanup(&sub); break; } case cmpp_TT_GroupBrace:{ cmpp_b b = cmpp_b_empty; cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0); cmpp__bind_textn(dx->pp, q, bindNdx, b.z, b.n); cmpp_b_clear(&b); break; } } return dxppCode; } /** If a is in li->list, return its non-const pointer from li->list (O(1)), else return NULL. */ static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){ if( li->nAlloc && a>=li->list && a<(li->list + li->nAlloc) ){ return li->list + (a - li->list); } return NULL; } /** To be called only by cmpp_dx_args_parse() and only if the current directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY. Filter chains of "not" operators from pArgs, removing unnecessary ones. Also collapse "not glob" into a single cmpp_TT_OpNotGlob argument. Performs some basic validation as well to simplify downstream operations. Returns p->err.code and is a no-op if that's set before this is called. */ static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){ cmpp_arg * pPrev = 0; cmpp_arg * pNext = 0; CmppArgList * const ali = &pArgs->pimpl->argli; pArgs->argc = 0; for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL; arg && !ppCode; pPrev=arg, arg = pNext ){ pNext = CmppArgList_arg_nc(ali, arg->next); assert( pNext || !arg->next ); if( cmpp_TT_OpNot==arg->ttype ){ if( !pNext ){ serr("Missing '%s' RHS", arg->z); break; } cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype); if( nop && nop->arity>1 && cmpp_TT_OpGlob!=nop->ttype ){ serr("Illegal '%s' RHS: binary '%s' operator", arg->z, pNext->z); break; } int bNeg = 1; if( '!'==*arg->z ){ /* odd number of ! == negate */ bNeg = arg->n & 1; } while( pNext && cmpp_TT_OpNot==pNext->ttype ){ bNeg = !bNeg; arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next); } if( pNext && cmpp_TT_OpGlob==pNext->ttype ){ /* Transform it to a cmpp_TT_OpNotGlob or cmpp_TT_OpGlob. */ assert( pNext->z > arg->z + arg->n ); arg->n = pNext->z + pNext->n - arg->z; arg->next = pNext->next; arg->ttype = bNeg ? cmpp_TT_OpNotGlob : pNext->ttype; ++pArgs->argc; }else if( pPrev ){ if( bNeg ){ ++pArgs->argc; }else{ /* Snip this node out. */ pPrev->next = pNext; } }else{ assert( 0==pArgs->argc ); ++pArgs->argc; if( !bNeg ){ arg->ttype = cmpp_TT_Noop; } } /* Potential bug in waiting/fixme: by eliding all nots we are ** changing the behavior from forced coercion to bool to ** coercion to whatever the LHS wants. */ }else{ ++pArgs->argc; } } pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL; return ppCode; } CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx, cmpp_args *args){ if( !dxppCode && 0==cmpp_args_parse(dx, args, dx->args.z, dx->args.nz, cmpp_args_F_NO_PARENS) && (cmpp_d_F_NOT_SIMPLIFY & dx->d->flags) ){ cmpp_args__not_simplify(dx->pp, args); } return dxppCode; } /* Helper for cmpp_kav_each() and friends. */ static int cmpp__each_parse_args(cmpp_dx *dx, cmpp_args *args, unsigned char const *zBegin, cmpp_ssize_t nz, cmpp_flag32_t flags){ if( 0==cmpp_args_parse(dx, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){ if( !args->argc && (cmpp_kav_each_F_NOT_EMPTY & flags) ){ cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Empty list is not permitted here."); } } return dxppCode; } /* Helper for cmpp_kav_each() and friends. */ static int cmpp__each_paren_expr(cmpp_dx *dx, cmpp_arg const * arg, unsigned char * pOut, size_t nOut){ cmpp_args sub = cmpp_args_empty; int rc = cmpp_args_parse(dx, &sub, arg->z, arg->n, 0); if( 0==rc ){ int d = 0; rc = cmpp__args_evalToInt(dx, &sub, &d); if( 0==rc ){ snprintf((char *)pOut, nOut, "%d", d); } } cmpp_args_cleanup(&sub); return rc; } CMPP__EXPORT(int, cmpp_kav_each)(cmpp_dx *dx, unsigned char const *zBegin, cmpp_ssize_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags){ if( dxppCode ) return dxppCode; /* Reminder to self: we cannot reuse internal buffers here because a callback could recurse into this or otherwise use APIs which use those same buffers. */ cmpp_b bKey = cmpp_b_empty; cmpp_b bVal = cmpp_b_empty; bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags); cmpp_args args = cmpp_args_empty; unsigned char exprBuf[32] = {0}; cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn); unsigned char const * const zEnd = zBegin + nz; cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_to_b() flags*/; assert( zBegin ); assert( zEnd ); assert( zEnd>=zBegin ); if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){ goto cleanup; }else if( reqArrow && 0!=args.argc%3 ){ cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Expecting a list of 3 tokens per entry: " "KEY -> VALUE"); }else if( !reqArrow && 0!=args.argc%2 ){ cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Expecting a list of 2 tokens per entry: " "KEY VALUE"); } if( cmpp_kav_each_F_CALL_KEY & flags ){ a2bK |= cmpp_arg_to_b_F_BRACE_CALL; flags |= cmpp_kav_each_F_EXPAND_KEY; } if( cmpp_kav_each_F_CALL_VAL & flags ){ a2bV |= cmpp_arg_to_b_F_BRACE_CALL; flags |= cmpp_kav_each_F_EXPAND_VAL; } cmpp_arg const * aNext = 0; for( cmpp_arg const * aKey = args.arg0; !dxppCode && aKey; aKey = aNext ){ aNext = aKey->next; cmpp_arg const * aVal = aKey->next; if( !aVal ){ dxserr("Expecting %s after key '%s'.", (reqArrow ? "->" : "a value"), aKey->z); break; } if( reqArrow ){ if( cmpp_TT_ArrowR!=aVal->ttype ){ dxserr("Expecting -> after key '%s'.", aKey->z); break; } aVal = aVal->next; if( !aVal ){ dxserr("Expecting a value after '%s' ->.", aKey->z); break; } } //g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z); /* Expand the key/value parts if needed... */ unsigned char const *zKey; unsigned char const *zVal; cmpp_size_t nKey, nVal; if( cmpp_kav_each_F_EXPAND_KEY & flags ){ if( cmpp_arg_to_b(dx, aKey, cmpp_b_reuse(&bKey), a2bK) ){ break; } zKey = bKey.z; nKey = bKey.n; }else{ zKey = aKey->z; nKey = aKey->n; } if( cmpp_TT_GroupParen==aVal->ttype && (cmpp_kav_each_F_PARENS_EXPR & flags) ){ if( cmpp__each_paren_expr(dx, aVal, &exprBuf[0], sizeof(exprBuf)-1) ){ break; } zVal = &exprBuf[0]; nVal = cmpp__strlenu(zVal, -1); }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){ if( cmpp_arg_to_b(dx, aVal, cmpp_b_reuse(&bVal), a2bV) ){ break; } zVal = bVal.z; nVal = bVal.n; }else{ zVal = aVal->z; nVal = aVal->n; } aNext = aVal->next; if( 0!=callback(dx, zKey, nKey, zVal, nVal, callbackState) ){ break; } } cleanup: cmpp_b_clear(&bKey); cmpp_b_clear(&bVal); cmpp_args_cleanup(&args); return dxppCode; } CMPP__EXPORT(int, cmpp_str_each)(cmpp_dx *dx, unsigned char const *zBegin, cmpp_ssize_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags){ g_warn0("UNTESTED!"); if( dxppCode ) return dxppCode; /* Reminder to self: we cannot reuse internal buffers here because a callback could recurse into this or otherwise use APIs which use those same buffers. */ cmpp_b ob = cmpp_b_empty; cmpp_args args = cmpp_args_empty; unsigned char exprBuf[32] = {0}; cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn); unsigned char const * const zEnd = zBegin + nz; assert( zBegin ); assert( zEnd ); assert( zEnd>=zBegin ); if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){ goto cleanup; } cmpp_arg const * aNext = 0; for( cmpp_arg const * arg = args.arg0; !dxppCode && arg; arg = aNext ){ aNext = arg->next; //g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z); /* Expand the key/value parts if needed... */ unsigned char const *zVal; cmpp_size_t nVal; if( cmpp_TT_GroupParen==arg->ttype && (cmpp_kav_each_F_PARENS_EXPR & flags) ){ if( cmpp__each_paren_expr(dx, arg, &exprBuf[0], sizeof(exprBuf)-1) ){ break; } zVal = &exprBuf[0]; nVal = cmpp__strlenu(zVal, -1); }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){ if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&ob), 0) ){ break; } zVal = ob.z; nVal = ob.n; }else{ zVal = arg->z; nVal = arg->n; } if( 0!=callback(dx, arg->z, arg->n, zVal, nVal, callbackState) ){ break; } } cleanup: cmpp_b_clear(&ob); cmpp_args_cleanup(&args); return dxppCode; } /** Returns true if z _might_ be a cmpp_TT_StringAt, else false. It may have false positives but won't have false negatives. This is only intended to be used on NUL-terminated strings, not a pointer into a cmpp input source. */ static bool cmpp__might_be_atstring(unsigned char const *z){ char const * const x = strchr((char const *)z, '@'); return x && !!strchr(x+1, '@'); } int cmpp__arg_expand_ats(cmpp_dx const * const dx, cmpp_b * os, cmpp_atpol_e atPolicy, cmpp_arg const * const arg, cmpp_tt thisTtype, unsigned char const **pExp, cmpp_size_t * nExp){ assert( os ); cmpp_b_reuse(os); if( 0==dxppCode && (cmpp_TT_AnyType==thisTtype || thisTtype==arg->ttype) && cmpp__might_be_atstring(arg->z) && 0==cmpp__StringAtIsOk(dx->pp, atPolicy) ){ #if 0 if( !os->nAlloc ){ cmpp_b_reserve3(os, 128); } #endif cmpp_outputer oos = cmpp_outputer_b; oos.state = os; assert( !os->n ); if( !cmpp_dx_out_expand(dx, &oos, arg->z, arg->n, atPolicy ) ){ *pExp = os->z; if( nExp ) *nExp = os->n; if( 0 ){ g_warn("os->n=%u os->z=[%.*s]\n", os->n, (int)os->n, os->z); } } }else if( !dxppCode ){ *pExp = arg->z; if( nExp ) *nExp = arg->n; } return dxppCode; } bool cmpp__arg_wordIsPathOrFlag( cmpp_arg const * const arg ){ return cmpp_TT_Word==arg->ttype && ('-'==(char)arg->z[0] || strchr((char*)arg->z, '.') || strchr((char*)arg->z, '-') || strchr((char*)arg->z, '/') || strchr((char*)arg->z, '\\')); } /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the cmpp_popen() pieces. */ #if !defined(_POSIX_C_SOURCE) # define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */ #endif #include const cmpp_popen_t cmpp_popen_t_empty = cmpp_popen_t_empty_m; #if CMPP_PLATFORM_IS_UNIX #include static int cmpp__err_errno(cmpp *pp, int errNo, char const *zContext){ return cmpp_err_set(pp, cmpp_errno_rc(errNo, CMPP_RC_ERROR), "errno #%d: %s", errNo, zContext); } #endif /** Uses fork()/exec() to run a command in a separate process and open a two-way stream to it. If azCmd is NULL then zCmd must contain the command to run and any flags. It is passed as the 4th argument to execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL). If azCmd is not NULL then it must be suitable for use as the 2nd argument to execv(2). execv(X, azCmd) is used in this case, where X is (zCmd ? zCmd : azCmd[0]). Flags: - cmpp_popen_F_DIRECT: if azCmd is NULL and flags has this bit set then zCmd is instead passed to execl(zCmd, zCmd, NULL). That can only work if zCmd is a single command without arguments. cmpp_popen_F_DIRECT has no effect if azCmd is not NULL. - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which performs path lookup of its initial argument. On success: - po->fdFromChild is the child's stdout. Read from it to read from the child. - If po->fpToChild is not NULL then *po->fpToChild is set to the child's stdin. Write to it to send the child stuff. Be sure to flush() and/or close() it to keep it from hanging forever. If po->fpToChild is NULL then the stdin of the child is closed. - po->childPid will be set to the PID of the child process. On error: you know the drill. After calling this, the caller is obligated to pass po to cmpp_pclose(). If the caller fcloses() *po->fpToChild then they must set it to NULL so that passing it to cmpp_pclose() knows not to close it. Bugs: because the command is run via /bin/sh -c ... we cannot tell if it's actually found. All we can tell is that /bin/sh ran. Also: this doesn't capture stderr, so commands should redirect stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is a potential TODO without a current use case. */ static int cmpp__popen_impl(cmpp *pp, unsigned char const *zCmd, char * const * azCmd, cmpp_flag32_t flags, cmpp_popen_t *po){ #if !CMPP_PLATFORM_IS_UNIX return cmpp__err(pp, CMPP_RC_UNSUPPORTED, "Piping is not supported in this build."); #else if( ppCode ) return ppCode; #define shut(P,N) close(P[N]) /** Attribution: this impl is derived from one found in the Fossil SCM. */ int pin[2]; int pout[2]; po->fdFromChild = -1; if( po->fpToChild ) *po->fpToChild = 0; if( pipe(pin)<0 ){ return cmpp__err_errno(pp, errno, "pipe(in) failed"); } if( pipe(pout)<0 ){ int const rc = cmpp__err_errno(pp, errno, "pipe(out) failed"); shut(pin,0); shut(pin,1); return rc; } po->childPid = fork(); if( po->childPid<0 ){ int const rc = cmpp__err_errno(pp, errno, "fork() failed"); shut(pin,0); shut(pin,1); shut(pout,0); shut(pout,1); return rc; } signal(SIGPIPE,SIG_IGN); if( po->childPid==0 ){ /* The child process. */ int fd; close(0); fd = dup(pout[0]); if( fd!=0 ) { cmpp__fatal("Error opening file descriptor 0."); }; shut(pout,0); shut(pout,1); close(1); fd = dup(pin[1]); if(fd!=1) { cmpp__fatal("Error opening file descriptor 1."); }; shut(pin,0); shut(pin,1); if( azCmd ){ if( pp->pimpl->flags.doDebug>1 ){ for( int i = 0; azCmd[i]; ++i ){ g_warn("execv arg[%d]=%s", i, azCmd[i]); } } int (*exc)(const char *, char *const []) = (cmpp_popen_F_PATH & flags) ? execvp : execv; exc(zCmd ? (char*)zCmd : azCmd[0], azCmd); cmpp__fatal("execv() failed"); }else{ g_debug(pp,2,("zCmd=%s\n", zCmd)); int (*exc)(const char *, char const *, ...) = (cmpp_popen_F_PATH & flags) ? execlp : execl; if( cmpp_popen_F_DIRECT & flags ){ exc((char*)zCmd, (char*)zCmd, (char*)0); }else{ exc("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0); } cmpp__fatal("execl() failed"); } /* not reached */ }else{ /* The parent process. */ //cmpp_outputer_flush(&pp->pimpl->out.ch); po->fdFromChild = pin[0]; shut(pin,1); shut(pout,0); if( po->fpToChild ){ *po->fpToChild = fdopen(pout[1], "w"); if( !*po->fpToChild ){ shut(pin,0); shut(pout,1); po->fdFromChild = -1; cmpp__err_errno(pp, errno, "Error opening child process's stdin " "FILE handle from its descriptor."); } }else{ shut(pout,1); } return ppCode; } #undef shut #endif } int cmpp_popen(cmpp *pp, unsigned char const *zCmd, cmpp_flag32_t flags, cmpp_popen_t *po){ return cmpp__popen_impl(pp, zCmd, NULL, flags, po); } int cmpp_popenv(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags, cmpp_popen_t *po){ return cmpp__popen_impl(pp, NULL, azCmd, flags, po); } int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args, cmpp_popen_t *po){ #if !CMPP_PLATFORM_IS_UNIX return cmpp__popen_impl(dx->pp, NULL, 0, po) /* will fail */; #else if( dxppCode ) return dxppCode; enum { MaxArgs = 128 }; char * argv[MaxArgs] = {0}; cmpp_size_t offsets[MaxArgs] = {0}; cmpp_b osAll = cmpp_b_empty; cmpp_b os1 = cmpp_b_empty; if( args->argc >= MaxArgs ){ return cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Too many arguments (%d). Max is %d.", args->argc, (int)MaxArgs); } int i = 0; for(cmpp_arg const * a = args->arg0; a; ++i, a = a->next ){ offsets[i] = osAll.n; cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; if( cmpp__arg_wordIsPathOrFlag(a) ){ a2bFlags |= cmpp_arg_to_b_F_FORCE_STRING; } if( cmpp_arg_to_b(dx, a, cmpp_b_reuse(&os1), a2bFlags) || cmpp_b_append4(dx->pp, &osAll, os1.z, os1.n+1/*NUL*/) ){ goto end; } assert( osAll.n > offsets[i] ); if( 0 ){ g_warn("execv arg[%d] = %s => %s", i, a->z, osAll.z+offsets[i]); } } argv[i] = 0; for( --i; i >= 0; --i ){ argv[i] = (char*)(osAll.z + offsets[i]); if( 0 ){ g_warn("execv arg[%d] = %s", i, argv[i]); } } end: if( 0==dxppCode ){ cmpp__popen_impl(dx->pp, NULL, argv, 0, po); } cmpp_b_clear(&osAll); cmpp_b_clear(&os1); return dxppCode; #endif } int cmpp_pclose(cmpp_popen_t *po){ #if CMPP_PLATFORM_IS_UNIX if( po->fdFromChild>=0 ) close(po->fdFromChild); if( po->fpToChild && *po->fpToChild ) fclose(*po->fpToChild); int const childPid = po->childPid; *po = cmpp_popen_t_empty; #if 1 int wp, rc = 0; if( childPid>0 ){ //kill(childPid, SIGINT); // really needed? do{ wp = waitpid(childPid, &rc, WNOHANG); if( wp>0 ){ if( WIFEXITED(rc) ){ rc = WEXITSTATUS(rc); }else if( WIFSIGNALED(rc) ){ rc = WTERMSIG(rc); }else{ rc = 0/*???*/; } } } while( wp>0 ); } return rc; #elif 0 while( waitpid(childPid, NULL, WNOHANG)>0 ){} #else if( childPid>0 ){ kill(childPid, SIGINT); // really needed? waitpid((pid_t)childPid, NULL, WNOHANG); }else{ while( waitpid( (pid_t)0, NULL, WNOHANG)>0 ){} } #endif #endif } /* ** 2022-11-12: ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** * May you do good and not evil. ** * May you find forgiveness for yourself and forgive others. ** * May you share freely, never taking more than you give. ** ************************************************************************ ** This file houses the module-loading pieces libcmpp. */ #if CMPP_ENABLE_DLLS static const CmppSohList CmppSohList_empty = CmppSohList_empty_m; #endif #if CMPP_ENABLE_DLLS /** If compiled without CMPP_ENABLE_DLLS defined to a true value then this function always returns CMPP_RC_UNSUPPORTED and updates the error state of its first argument with information about that code. Its first argument is the controlling cmpp. It can actually be NULL - it's only used for reporting error details. Its second argument is the name of a DLL file. Its third argument is the name of a symbol in the given DLL which resolves to a cmpp_module pointer. This name may be NULL, in which case a default symbol name of "cmpp_module1" is used (which is only useful when plugins are built one per DLL). The fourth argument is the output pointer to store the resulting module handle in. The fifth argument is an optional list to append the DLL's native handle to. It may be NULL. This function tries to open a DLL named fname using the system's DLL loader. If none is found, CMPP_RC_NOT_FOUND is returned and the cmpp's error state is populated with info about the error. If one is found, it looks for a symbol in the DLL: if symName is not NULL and is not empty then the symbol "cmpp_module_symName" is sought, else "cmpp_module". (e.g. if symName is "foo" then it searches for a symbol names "cmpp_module_foo".) If no such symbol is found then CMPP_RC_NOT_FOUND (again) is returned and the cmpp's error state is populated, else the symbol is assumed to be a (cmpp_module*) and *mod is assigned to it. All errors update pp's error state but all are recoverable. Returns 0 on success. On success: - `*mod` is set to the module object. Its ownship is kinda murky: it lives in memory made available via the module loader. It remains valid memory until the DLL is closed. The module might also actually be statically linked with the application, in which case it will live as long as the app. - If soli is not NULL then the native DLL handle is appended to it. Allocation errors when appending the DLL handle to the target list are ignored - failure to retain a DLL handle for closing later is not considered critical (and it would be extraordinarily rare (and closing them outside of late-/post-main() cleanup is ill-advised, anyway)). @see cmpp_module_load() @see CMPP_MODULE_DECL @see CMPP_MODULE_IMPL2 @see CMPP_MODULE_IMPL3 @see CMPP_MODULE_IMPL_SOLO @see CMPP_MODULE_REGISTER2 @see CMPP_MODULE_REGISTER3 */ static int cmpp__module_extract(cmpp * pp, char const * dllFileName, char const * symName, cmpp_module const ** mod); #endif #if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE) # define CMPP_D_MODULE 1 #else # define CMPP_D_MODULE 0 #endif #if CMPP_D_MODULE /** The #module directive: #module dll ?moduleName? Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a directive module. */ //static void cmpp_dx_f_module(cmpp_dx *dx) { cmpp_arg const * aName = 0; cmpp_b obDll = cmpp_b_empty; for( cmpp_arg const *arg = dx->args.arg0; arg; arg = arg->next ){ //MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z)); if( cmpp_dx_err_check(dx) ) goto end; else if( !obDll.z ){ cmpp_arg_to_b( dx, arg, &obDll, 0//cmpp_arg_to_b_F_NO_DEFINES ); continue; }else if( !aName ){ aName = arg; continue; } cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); goto end; } if( !obDll.z ){ cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting a DLL name argument."); goto end; } cmpp_module_load(dx->pp, (char const *)obDll.z, aName ? (char const *)aName->z : NULL); end: cmpp_b_clear(&obDll); return; #if 0 missing_arg: cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument after %s.", arg->z); return; #endif } #endif /* #module */ /** Module loader pedantic licensing note: Most of cmpp's module-loading code was copied verbatim from another project[^1], but was written by the same author who relicenses it in cmpp. [^1]: https://fossil.wanderinghorse.net/r/cwal */ #if CMPP_ENABLE_DLLS #if CMPP_HAVE_DLOPEN typedef void * cmpp_soh; # include /* this actually has a different name on some platforms! */ #elif CMPP_HAVE_LTDLOPEN # include typedef lt_dlhandle cmpp_soh; #elif CMPP_ENABLE_DLLS # error "We have no dlopen() impl for this configuration." #endif static cmpp_soh cmpp__dlopen(char const * fname, char const **errMsg){ static int once = 0; cmpp_soh soh = 0; if(!once && ++once){ #if CMPP_HAVE_DLOPEN dlopen( 0, RTLD_NOW | RTLD_GLOBAL ); #elif CMPP_HAVE_LTDLOPEN lt_dlinit(); lt_dlopen( 0 ); #endif } #if CMPP_HAVE_DLOPEN soh = dlopen(fname, RTLD_NOW | RTLD_GLOBAL); #elif CMPP_HAVE_LTDLOPEN soh = lt_dlopen(fname); #endif if(!soh && errMsg){ #if CMPP_HAVE_DLOPEN *errMsg = dlerror(); #elif CMPP_HAVE_LTDLOPEN *errMsg = lt_dlerror(); #endif } return soh; } static cmpp_module const * cmpp__dlsym(cmpp_soh soh, char const * mname){ cmpp_module const ** sym = #if CMPP_HAVE_DLOPEN dlsym(soh, mname) #elif CMPP_HAVE_LTDLOPEN lt_dlsym(soh, mname) #else NULL #endif ; return sym ? *sym : NULL; } static void cmpp__dlclose(cmpp_soh soh){ if( soh ) { #if CMPP_CLOSE_DLLS /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */ #if CMPP_HAVE_DLOPEN dlclose(soh); #elif CMPP_HAVE_LTDLOPEN lt_dlclose(soh); #endif #endif } } #endif /* CMPP_ENABLE_DLLS */ #define CmppSohList_works (CMPP_ENABLE_DLLS && CMPP_CLOSE_DLLS) int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){ #if CmppSohList_works int const rc = cmpp_array_reserve(pp, (void**)&soli->list, soli->n ? (soli->n==soli->nAlloc ? soli->nAlloc*2 : soli->n+1) : 8, &soli->nAlloc, sizeof(void*)); if( 0==rc ){ soli->list[soli->n++] = soh; } return rc; #else (void)pp; (void)soli; (void)soh; return 0; #endif } void CmppSohList_close(CmppSohList *s){ #if CmppSohList_works while( s->nAlloc ){ if( s->list[--s->nAlloc] ){ //MARKER(("closing soh %p\n", s->list[s->nAlloc])); cmpp__dlclose(s->list[s->nAlloc]); s->list[s->nAlloc] = 0; } } cmpp_mfree(s->list); *s = CmppSohList_empty; #else (void)s; #endif } #if 0 /** Passes soli to CmppSohList_close() then frees soli. Results are undefined if soli is not NULL but was not returned from CmppSohList_new(). Special case: if built without DLL-closing support, this is a no-op. */ //static void CmppSohList_free(CmppSohList *soli); void CmppSohList_free(CmppSohList *s){ if( s ){ #if CmppSohList_works CmppSohList_close(s); cmpp_mfree(s); #endif } } /** Returns a new, cleanly-initialized CmppSohList or NULL on allocation error. The returned instance must eventually be passed to CmppSohList_free(). Special case: if built without DLL-closing support, this returns a no-op singleton instance. */ //static CmppSohList * CmppSohList_new(void); CmppSohList * CmppSohList_new(void){ #if CmppSohList_works CmppSohList * s = cmpp_malloc(sizeof(*s)); if( s ) *s = CmppSohList_empty; return s; #else static CmppSohList soli = CmppSohList_empty; return &soli; #endif } #endif #undef CmppSohList_works #if CMPP_ENABLE_DLLS /** Default entry point symbol name for loadable modules. This must match the symbolic name defined by CMPP_MODULE_IMPL_SOLO(). */ static char const * const cmppModDfltSym = "cmpp_module1"; /** Looks for a symbol in the given DLL handle. If symName is NULL or empty, the symbol "cmpp_module" is used, else the symbols ("cmpp_module__" + symName) is used. If it finds one, it casts it to cmpp_module and returns it. On error it may update pp's error state with the error information if pp is not NULL. Errors: - symName is too long. - cmpp__dlsym() lookup failure. */ static cmpp_module const * cmpp__module_fish_out_entry_pt(cmpp * pp, cmpp_soh soh, char const * symName){ enum { MaxLen = 128 }; char buf[MaxLen] = {0}; cmpp_size_t const slen = symName ? strlen(symName) : 0; cmpp_module const * mod = 0; if(slen > (MaxLen-20)){ cmpp_err_set(pp, CMPP_RC_RANGE, "DLL symbol name '%.*s' is too long. Max is %d.", (int)slen, symName, (int)MaxLen-20); }else{ if(symName && *symName){ snprintf(buf, MaxLen,"cmpp_module__%s", symName); symName = &buf[0]; }else{ symName = cmppModDfltSym; } mod = cmpp__dlsym(soh, symName); } /*MARKER(("%s() [%s] ==> %p\n",__func__, symName, (void const *)mod));*/ return mod; } #endif/*CMPP_ENABLE_DLLS*/ #if CMPP_ENABLE_DLLS /** Tries to dlsym() the given cmpp_module symbol from the given DLL handle. On success, 0 is returned and *mod is assigned to the memory. On error, non-0 is returned and pp's error state may be updated. Ownership of the returned module ostensibly lies with the first argument, but that's not entirely true. If CMPP_CLOSE_DLLS is true then a copy of the module's pointer is stored in the engine for later closing. The memory itself is owned by the module loader, and "should" stay valid until the DLL is closed. */ static int cmpp__module_get_sym(cmpp * pp, cmpp_soh soh, char const * symName, cmpp_module const ** mod){ cmpp_module const * lm = 0; int rc = cmpp_err_has(pp); if( 0==rc ){ lm = cmpp__module_fish_out_entry_pt(pp, soh, symName); rc = cmpp_err_has(pp); } if(0==rc){ if(lm){ *mod = lm; }else{ cmpp__dlclose(soh); rc = cmpp_err_set(pp, CMPP_RC_NOT_FOUND, "Did not find module entry point symbol '%s'.", symName ? symName : cmppModDfltSym); } } return rc; } #endif/*CMPP_ENABLE_DLLS*/ #if !CMPP_ENABLE_DLLS static int cmpp__err_no_dlls(cmpp * const pp){ return cmpp_err_set(pp, CMPP_RC_UNSUPPORTED, "No dlopen() equivalent is installed " "for this build configuration."); } #endif #if CMPP_ENABLE_DLLS //no: CMPP_WASM_EXPORT int cmpp__module_extract(cmpp * pp, char const * fname, char const * symName, cmpp_module const ** mod){ int rc = cmpp_err_has(pp); if( rc ) return rc; else if( cmpp_is_safemode(pp) ){ return cmpp_err_set(pp, CMPP_RC_ACCESS, "Cannot use DLLs in safe mode."); }else{ cmpp_soh soh; char const * errMsg = 0; soh = cmpp__dlopen(fname, &errMsg); if(soh){ if( pp ){ CmppSohList_append(NULL/*alloc error here can be ignored*/, &pp->pimpl->mod.sohList, soh); } cmpp_module const * x = 0; rc = cmpp__module_get_sym(pp, soh, symName, &x); if(!rc && mod) *mod = x; return rc; }else{ return errMsg ? cmpp_err_set(pp, CMPP_RC_ERROR, "DLL open failed: %s", errMsg) : cmpp_err_set(pp, CMPP_RC_ERROR, "DLL open failed for unknown reason."); } } } #endif //no: CMPP_WASM_EXPORT int cmpp_module_load(cmpp * pp, char const * fname, char const * symName){ #if CMPP_ENABLE_DLLS if( ppCode ){ /* fall through */ }else if( cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags ){ cmpp_err_set(pp, CMPP_RC_ACCESS, "%s() is disallowed in safe-mode."); }else{ cmpp__pi(pp); char * zName = 0; if( fname ){ zName = cmpp_path_search(pp, (char const *)pi->mod.path.z, pi->mod.pathSep, fname, pi->mod.soExt); if( !zName ){ return cmpp_err_set(pp, CMPP_RC_NOT_FOUND, "Did not find [%s] or [%s%s] " "in search path [%s].", fname, fname, pi->mod.soExt, pi->mod.path.z); } } cmpp_module const * mod = 0; if( 0==cmpp__module_extract(pp, zName, symName, &mod) ){ assert(mod); assert(mod->init); int const rc = mod->init(pp); if( rc && !ppCode ){ cmpp_err_set(pp, CMPP_RC_ERROR, "Module %s::init() failed with code #%d/%s " "without providing additional info.", symName ? symName : "cmpp_module", rc, cmpp_rc_cstr(rc)); } cmpp_mfree(zName); } } return ppCode; #else (void)fname; (void)symName; return cmpp__err_no_dlls(pp); #endif } /* ** 2015-08-18, 2023-04-28 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** This file demonstrates how to create a table-valued-function using ** a virtual table. This demo implements the generate_series() function ** which gives the same results as the eponymous function in PostgreSQL, ** within the limitation that its arguments are signed 64-bit integers. ** ** Considering its equivalents to generate_series(start,stop,step): A ** value V[n] sequence is produced for integer n ascending from 0 where ** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 ) ** for each produced value (independent of production time ordering.) ** ** All parameters must be either integer or convertable to integer. ** The start parameter is required. ** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) ** The step parameter defaults to 1 and 0 is treated as 1. ** ** Examples: ** ** SELECT * FROM generate_series(0,100,5); ** ** The query above returns integers from 0 through 100 counting by steps ** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total ** of 21 rows. ** ** SELECT * FROM generate_series(0,100); ** ** Integers from 0 through 100 with a step size of 1. 101 rows. ** ** SELECT * FROM generate_series(20) LIMIT 10; ** ** Integers 20 through 29. 10 rows. ** ** SELECT * FROM generate_series(0,-100,-5); ** ** Integers 0 -5 -10 ... -100. 21 rows. ** ** SELECT * FROM generate_series(0,-1); ** ** Empty sequence. ** ** HOW IT WORKS ** ** The generate_series "function" is really a virtual table with the ** following schema: ** ** CREATE TABLE generate_series( ** value, ** start HIDDEN, ** stop HIDDEN, ** step HIDDEN ** ); ** ** The virtual table also has a rowid which is an alias for the value. ** ** Function arguments in queries against this virtual table are translated ** into equality constraints against successive hidden columns. In other ** words, the following pairs of queries are equivalent to each other: ** ** SELECT * FROM generate_series(0,100,5); ** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; ** ** SELECT * FROM generate_series(0,100); ** SELECT * FROM generate_series WHERE start=0 AND stop=100; ** ** SELECT * FROM generate_series(20) LIMIT 10; ** SELECT * FROM generate_series WHERE start=20 LIMIT 10; ** ** The generate_series virtual table implementation leaves the xCreate method ** set to NULL. This means that it is not possible to do a CREATE VIRTUAL ** TABLE command with "generate_series" as the USING argument. Instead, there ** is a single generate_series virtual table that is always available without ** having to be created first. ** ** The xBestIndex method looks for equality constraints against the hidden ** start, stop, and step columns, and if present, it uses those constraints ** to bound the sequence of generated values. If the equality constraints ** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step. ** xBestIndex returns a small cost when both start and stop are available, ** and a very large cost if either start or stop are unavailable. This ** encourages the query planner to order joins such that the bounds of the ** series are well-defined. ** ** Update on 2024-08-22: ** xBestIndex now also looks for equality and inequality constraints against ** the value column and uses those constraints as additional bounds against ** the sequence range. Thus, a query like this: ** ** SELECT value FROM generate_series($SA,$EA) ** WHERE value BETWEEN $SB AND $EB; ** ** Is logically the same as: ** ** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB)); ** ** Constraints on the value column can server as substitutes for constraints ** on the hidden start and stop columns. So, the following two queries ** are equivalent: ** ** SELECT value FROM generate_series($S,$E); ** SELECT value FROM generate_series WHERE value BETWEEN $S and $E; ** */ #if 0 #include "sqlite3ext.h" #else #include "sqlite3.h" #endif #include #include #include #include #ifndef SQLITE_OMIT_VIRTUALTABLE /* series_cursor is a subclass of sqlite3_vtab_cursor which will ** serve as the underlying representation of a cursor that scans ** over rows of the result. ** ** iOBase, iOTerm, and iOStep are the original values of the ** start=, stop=, and step= constraints on the query. These are ** the values reported by the start, stop, and step columns of the ** virtual table. ** ** iBase, iTerm, iStep, and bDescp are the actual values used to generate ** the sequence. These might be different from the iOxxxx values. ** For example in ** ** SELECT value FROM generate_series(1,11,2) ** WHERE value BETWEEN 4 AND 8; ** ** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7. ** Another example: ** ** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC; ** ** The cursor initialization for the above query is: ** ** iOBase = 1 iBase = 13 ** iOTerm = 15 iTerm = 1 ** iOStep = 3 iStep = 3 bDesc = 1 ** ** The actual step size is unsigned so that can have a value of ** +9223372036854775808 which is needed for querys like this: ** ** SELECT value ** FROM generate_series(9223372036854775807, ** -9223372036854775808, ** -9223372036854775808) ** ORDER BY value ASC; ** ** The setup for the previous query will be: ** ** iOBase = 9223372036854775807 iBase = -1 ** iOTerm = -9223372036854775808 iTerm = 9223372036854775807 ** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0 */ typedef unsigned char u8; typedef struct series_cursor series_cursor; struct series_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3_int64 iOBase; /* Original starting value ("start") */ sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ sqlite3_int64 iOStep; /* Original step value */ sqlite3_int64 iBase; /* Starting value to actually use */ sqlite3_int64 iTerm; /* Terminal value to actually use */ sqlite3_uint64 iStep; /* The step size */ sqlite3_int64 iValue; /* Current value */ u8 bDesc; /* iStep is really negative */ u8 bDone; /* True if stepped past last element */ }; /* ** Computed the difference between two 64-bit signed integers using a ** convoluted computation designed to work around the silly restriction ** against signed integer overflow in C. */ static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){ assert( a>=b ); return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b); } /* ** Add or substract an unsigned 64-bit integer from a signed 64-bit integer ** and return the new signed 64-bit integer. */ static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){ sqlite3_uint64 x = *(sqlite3_uint64*)&a; x += b; return *(sqlite3_int64*)&x; } static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){ sqlite3_uint64 x = *(sqlite3_uint64*)&a; x -= b; return *(sqlite3_int64*)&x; } /* ** The seriesConnect() method is invoked to create a new ** series_vtab that describes the generate_series virtual table. ** ** Think of this routine as the constructor for series_vtab objects. ** ** All this routine needs to do is: ** ** (1) Allocate the series_vtab object and initialize all fields. ** ** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the ** result set of queries against generate_series will look like. */ static int seriesConnect( sqlite3 *db, void *pUnused, int argcUnused, const char *const*argvUnused, sqlite3_vtab **ppVtab, char **pzErrUnused ){ sqlite3_vtab *pNew; int rc; /* Column numbers */ #define SERIES_COLUMN_ROWID (-1) #define SERIES_COLUMN_VALUE 0 #define SERIES_COLUMN_START 1 #define SERIES_COLUMN_STOP 2 #define SERIES_COLUMN_STEP 3 (void)pUnused; (void)argcUnused; (void)argvUnused; (void)pzErrUnused; rc = sqlite3_declare_vtab(db, "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); if( rc==SQLITE_OK ){ pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); } return rc; } /* ** This method is the destructor for series_cursor objects. */ static int seriesDisconnect(sqlite3_vtab *pVtab){ sqlite3_free(pVtab); return SQLITE_OK; } /* ** Constructor for a new series_cursor object. */ static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){ series_cursor *pCur; (void)pUnused; pCur = sqlite3_malloc( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); *ppCursor = &pCur->base; return SQLITE_OK; } /* ** Destructor for a series_cursor. */ static int seriesClose(sqlite3_vtab_cursor *cur){ sqlite3_free(cur); return SQLITE_OK; } /* ** Advance a series_cursor to its next row of output. */ static int seriesNext(sqlite3_vtab_cursor *cur){ series_cursor *pCur = (series_cursor*)cur; if( pCur->iValue==pCur->iTerm ){ pCur->bDone = 1; }else if( pCur->bDesc ){ pCur->iValue = sub64(pCur->iValue, pCur->iStep); assert( pCur->iValue>=pCur->iTerm ); }else{ pCur->iValue = add64(pCur->iValue, pCur->iStep); assert( pCur->iValue<=pCur->iTerm ); } return SQLITE_OK; } /* ** Return values of columns for the row at which the series_cursor ** is currently pointing. */ static int seriesColumn( sqlite3_vtab_cursor *cur, /* The cursor */ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ int i /* Which column to return */ ){ series_cursor *pCur = (series_cursor*)cur; sqlite3_int64 x = 0; switch( i ){ case SERIES_COLUMN_START: x = pCur->iOBase; break; case SERIES_COLUMN_STOP: x = pCur->iOTerm; break; case SERIES_COLUMN_STEP: x = pCur->iOStep; break; default: x = pCur->iValue; break; } sqlite3_result_int64(ctx, x); return SQLITE_OK; } #ifndef LARGEST_UINT64 #define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL) #define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL) #define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL) #endif /* ** The rowid is the same as the value. */ static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ series_cursor *pCur = (series_cursor*)cur; *pRowid = pCur->iValue; return SQLITE_OK; } /* ** Return TRUE if the cursor has been moved off of the last ** row of output. */ static int seriesEof(sqlite3_vtab_cursor *cur){ series_cursor *pCur = (series_cursor*)cur; return pCur->bDone; } /* True to cause run-time checking of the start=, stop=, and/or step= ** parameters. The only reason to do this is for testing the ** constraint checking logic for virtual tables in the SQLite core. */ #ifndef SQLITE_SERIES_CONSTRAINT_VERIFY # define SQLITE_SERIES_CONSTRAINT_VERIFY 0 #endif /* ** Return the number of steps between pCur->iBase and pCur->iTerm if ** the step width is pCur->iStep. */ static sqlite3_uint64 seriesSteps(series_cursor *pCur){ if( pCur->bDesc ){ assert( pCur->iBase >= pCur->iTerm ); return span64(pCur->iBase, pCur->iTerm)/pCur->iStep; }else{ assert( pCur->iBase <= pCur->iTerm ); return span64(pCur->iTerm, pCur->iBase)/pCur->iStep; } } #if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32) /* ** Case 1 (the most common case): ** The standard math library is available so use ceil() and floor() from there. */ static double seriesCeil(double r){ return ceil(r); } static double seriesFloor(double r){ return floor(r); } #elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) /* ** Case 2 (2nd most common): Use GCC/Clang builtins */ static double seriesCeil(double r){ return __builtin_ceil(r); } static double seriesFloor(double r){ return __builtin_floor(r); } #else /* ** Case 3 (rarely happens): Use home-grown ceil() and floor() routines. */ static double seriesCeil(double r){ sqlite3_int64 x; if( r!=r ) return r; if( r<=(-4503599627370496.0) ) return r; if( r>=(+4503599627370496.0) ) return r; x = (sqlite3_int64)r; if( r==(double)x ) return r; if( r>(double)x ) x++; return (double)x; } static double seriesFloor(double r){ sqlite3_int64 x; if( r!=r ) return r; if( r<=(-4503599627370496.0) ) return r; if( r>=(+4503599627370496.0) ) return r; x = (sqlite3_int64)r; if( r==(double)x ) return r; if( r<(double)x ) x--; return (double)x; } #endif /* ** This method is called to "rewind" the series_cursor object back ** to the first row of output. This method is always called at least ** once prior to any call to seriesColumn() or seriesRowid() or ** seriesEof(). ** ** The query plan selected by seriesBestIndex is passed in the idxNum ** parameter. (idxStr is not used in this implementation.) idxNum ** is a bitmask showing which constraints are available: ** ** 0x0001: start=VALUE ** 0x0002: stop=VALUE ** 0x0004: step=VALUE ** 0x0008: descending order ** 0x0010: ascending order ** 0x0020: LIMIT VALUE ** 0x0040: OFFSET VALUE ** 0x0080: value=VALUE ** 0x0100: value>=VALUE ** 0x0200: value>VALUE ** 0x1000: value<=VALUE ** 0x2000: value0, the value of the LIMIT */ sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */ (void)idxStrUnused; /* If any constraints have a NULL value, then return no rows. ** See ticket https://sqlite.org/src/info/fac496b61722daf2 */ for(i=0; i