/** This is the auto-generated "amalgamation build" of libcmpp. This C file contains both the header and source file for c-pp, a.k.a. libcmpp. It was amalgamated using: ./c-pp -I. -I./src -Dsrcdir=./src -o libcmpp.c ./tool/libcmpp.c-pp.c with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC */ #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 -o libcmpp.h ./tool/libcmpp.c-pp.h with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC */ #define CMPP_PACKAGE_NAME "libcmpp" #define CMPP_LIB_VERSION "2.0.x" #define CMPP_LIB_VERSION_HASH "4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46" #define CMPP_LIB_VERSION_TIMESTAMP "2026-09-30 09:12:44.230 UTC" #define CMPP_LIB_CONFIG_TIMESTAMP "2026-09-30 09:14 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 the library interface is likely still missing ** some pieces needed to make it more readily usable as a library. ** ** Author(s): ** ** - Stephan Beal ** ** Canonical home: ** ** - ** ** One may see mentions of %amalgamation and %lite in some comments in ** this file and its implementation files. Those are preprocessor ** directives for this very preprocessor, used by the build process to ** filter out certain pieces to produce its amalgamation build ** and (derived from that) the "lite" build. Their placement is ** significant, especially when they're embedded within a multi-line C ** macro or at the end of the line with a C directive. In normal ** builds they're innocuous, but when passed through the "lightener" ** process, they activate (see Makefile.in keyword "%lite" and ** tool/libcmpp.c-pp.[ch]). */ /** License note: the license headers have been stripped from other files in this amalgamation build in order to save space. They all have the same license as the one above but may have different dates. The canonical sources are authoritative with regards to the licenses of the code in each segment of this amalgamation. */ #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 #include "sqlite3.h" /* sqlite3_str */ /** For loadable modules to be able to 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 elsewhere in this header, 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 #endif #ifdef __cplusplus extern "C" { #endif typedef struct cmpp__pimpl cmpp__pimpl; typedef struct cmpp_api_thunk cmpp_api_thunk; typedef struct cmpp_outputer cmpp_outputer; typedef struct cmpp_b cmpp_b; typedef struct cmpp_arg cmpp_arg; typedef void cmpp_itch; /** 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; /** 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 terse informational 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.") \ 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_MODULE_VERSION, 143, \ "API version mismatch when loading a module.") \ 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); #define cmpp_rc_ucstr(RC) (unsigned char const*)cmpp_rc_cstr(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 ranges 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 "if this value is negative then use strlen(someOtherArg) to count it". Maintenance reminder: this type must remain 32-bits or risks breaking countless printf-style formatting uses, where it frequently ends up being used with "%.*s". */ typedef int32_t cmpp_strlen_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_strlen_t; #define CMPP_SIZE_T_PFMT PRIu64 #else #error "Invalid CMPP_BITNESS value. Expecting 32 or 64." #endif /** Semantically like cmpp_strlen_t but guranteed to be 32 bits. This is primarily provided as a reminder that some sources to which we pass negative values as a length are limited to 32-bit signed ranges. */ typedef int32_t cmpp_ssize32_t; /** 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 unless the cmpp_input_f() implementation says otherwise. 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_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); /** 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 and the library never exposes a cmpp object with a NULL api member. */ cmpp_api_thunk const * const api; /** Private internal state. Owned by this object. */ cmpp__pimpl * const pimpl; }; typedef struct cmpp cmpp; /** Flags for use with cmpp_ctor_opt::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_f_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, cmpp_ctor_F_DUMMY_TRAILING_COMMA_OCD }; /** A configuration object for cmpp_ctor(). This type may be extended as new construction-time customization opportunities are discovered. */ struct cmpp_ctor_opt { /** 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_opt cmpp_ctor_opt; /** 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 preprocessor 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_errf(). */ CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_opt 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); /** Proxy for cmpp_malloc() which (A) is a no-op returning NULL pp has error state and (B) sets pp's error state on OOM. */ void * cmpp_malloc2(cmpp* pp, cmpp_size_t n); /** free(3)-style 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 * 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_errf() 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_arg::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` name to the list of defines, overwriting any previous value (scoped to the current savepoint, if any). 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_undefine() See: cmpp_undef_legacy() */ 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` name from the list of defines. zKey is, in this case, treated as a GLOB pattern, and all matching defines are deleted. (This ugly historical behavior will be removed at some point, along with that argument!) 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_legacy(cmpp *pp, const char * zKey, unsigned int *nRemoved); /** Undefines the define with the given name (an exact match). Returns 0 on success. If pp has error state, this is a no-op returning the current error code, otherwise it returns non-0 only on allocation error or db-related errors (which, in practice, do not happen for in-memory databases unless they've run out of memory). */ CMPP_EXPORT int cmpp_undefine(cmpp *pp, const char * zKey, cmpp_strlen_t n); /** This works similarly to cmpp_define_v2() except that 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 (which must not be NULL). That value must be passed to cmpp_define_unshadow() to ensure that the "shadow stack" stays balanced. 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 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_strlen_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 or NULL if no error is set. 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. Naming note: this function predates the cmpp_errinfo class by a donkey's age, thus this apparent name collision. If it weren't for this function, that class would be called cmpp_err. */ 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 rc is non-0 and pp is currently processing a script, the resulting error string will be prefixed with error location information. 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. 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_errf(cmpp *pp, int rc, char const *zFmt, ...); /** va_list counterpart of cmpp_errf(). */ CMPP_EXPORT int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list vars); /** A variant of cmpp_errf() which is not variadic, primarily as a consolation for WASM builds. zMsg may be NULL. The second, third, and fourth parameters behave as documented for cmpp_errinfo_set(). If pp is not NULL, this returns either rc or CMPP_RC_OOM, as per cmpp_errinfo_set(). This permits a NULL pp to simplify certain uses but is a no-op in that case, returning rc. */ CMPP_EXPORT int cmpp_err(cmpp *pp, int rc, char const *zMsg, cmpp_strlen_t nMsg); #define cmpp_err_c(PP,RC,MSG) cmpp_err(PP, RC, "" MSG, sizeof(MSG)-1) static inline void cmpp_err_reset(cmpp *pp){ cmpp_err(pp,0,0,0); } #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 pass pp to one of cmpp_sp_commit() or cmpp_sp_rollback(). */ 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); /** _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 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; cmpp_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 stream 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); /** UNTESTED! Reads the entire contents of the given file, appending it to b. Returns 0 on success. If pp is not NULL then: 1) This is a no-op if pp has an error set, returning that code. 2) Any error triggered by this function will be set in its persistent error state. If pp is NULL then errors are considered transient. On error b may be partially populated. */ CMPP_EXPORT int cmpp_slurp_file(cmpp *pp, char const *fn, cmpp_b * b); /** 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 lines 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 32[^1], 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 (possibly 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_errf()). [^1]: the historical length limit was 12 but we actually hit that in this very tree when the "//%amalgamation" pieces were added to replace having to shell out to /usr/bin/sed. */ 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 for creating dynamic strings and as a target for cmpp_outputer_b to capture arbitrary output for later use. 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() or sqlite3_free(). This class's APIs strive to keep valid buffers NUL-terminated at byte this->z[this->n]. Any deviation from that is (A) unknown and (B) to be considered a bug. */ 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->z or 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; }; /** 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-const copy initialization. */ extern const cmpp_b cmpp_b_empty; /** A proxy for sqlite3_str_new() which returns NULL on OOM instead of returning a dummy object. If it returns NULL then pp's error state will be update with CMPP_RC_OOM. */ CMPP_EXPORT sqlite3_str * cmpp_sqlite3_str_new(cmpp *pp); /** 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, not including its NUL terminator. If this is called while pp has error state, this does not override that error state but will return 0. */ CMPP_EXPORT char * cmpp_sqlite3_str_finish(cmpp *pp, sqlite3_str *s, int * n); /** A class for holding error state. This class _should_ be called cmpp_err, but by the time it was added we already had a family of cmpp_err-named APIs and no good new names for them. Naming it cmpp_e would cause me confusion because `_e` is the conventional suffix for enums in this library. */ struct cmpp_errinfo { /** Error code. 0 = no error. */ int code; /** Buffer memory owned by this object. Updated by many different cmpp_errinfo APIs and freed by cmpp_errinfo_dtor(). */ cmpp_b b; /** Might point to this->b.z or it might point to a static/const error strings, in particular for use with OOM. Except on allocation errors, the cmpp_errinfo APIs which update this->b point this to this->b.z. */ char const *zStatic; }; typedef struct cmpp_errinfo cmpp_errinfo; #define cmpp_errinfo_empty_m {.code=0,.b=cmpp_b_empty_m,.zStatic=0} extern const cmpp_errinfo cmpp_errinfo_empty; /** Sets err's state to the given code and string and returns either rc or, if an allocation error happens while setting the message, CMPP_RC_OOM. If rc is 0 then zMsg is ignored, else the first nMsg bytes of zMsg are copied into err->b, or through zMsg's first NUL byte if nMsg is negative. To simplify certain uses, err may be NULL, in which case this simply returns rc. */ CMPP_EXPORT int cmpp_errinfo_set(cmpp_errinfo *err, int rc, char const *zMsg, cmpp_strlen_t nMsg); /** va_list form of cmpp_errinfo_set(), supporting sqlite3_str_vappendf()'s formatting options. */ CMPP_EXPORT int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt, va_list va); /** Variadic form of cmpp_errinfo_setf(). */ CMPP_EXPORT int cmpp_errinfo_setf(cmpp_errinfo *err, int rc, char const *zFmt, ...); /** Clears err and frees all memory it owns but does not free err. */ CMPP_EXPORT void cmpp_errinfo_dtor(cmpp_errinfo *err); /** Resets err's state a non-error state but retains any memory it has buffered. Returns err. */ CMPP_EXPORT cmpp_errinfo * cmpp_errinfo_reuse(cmpp_errinfo *err); /** Frees b->z and zeroes out s but does not free s. */ CMPP_EXPORT void cmpp_b_clear(cmpp_b *b); /** If b has content, b->nUsed is set to 0 and b->z is NUL-terminated at its first byte, else this is a no-op. b->errCode is set to 0. Returns s. */ CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *b); /** 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 b->errCode is 0 and b->nAlloc is less than n, b->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 *b, cmpp_size_t n); /** Works just like cmpp_b_reserve() but on allocation error it updates pp's error state. If called when pp has error state, that code is returned and this function has no side-effects. */ CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n); /** Appends n bytes from src to b, reallocating os as necessary. Returns 0 on succes, CMPP_RC_OOM on allocation error. This may modify b even if n is 0, to ensure that it always has a NUL terminator. src may only be NULL if n is 0. Errors from this function, and the other cmpp_b_append...() variants, are recoverable (see cmpp_errf()). */ CMPP_EXPORT int cmpp_b_append(cmpp_b * b, void const *src, cmpp_size_t n); /** Works like cmpp_b_append() except that if n is negative, strlen() is used to calculate it. */ CMPP_EXPORT int cmpp_b_append_str(cmpp_b * b, char const * str, cmpp_strlen_t n); /** Convenience form of cmpp_b_append_str() which requires a string literal. */ #define cmpp_b_append_c(B,STR) cmpp_b_append(B, STR "",sizeof(STR)-1) /** Works just like cmpp_b_append() but on allocation error it updates pp's error state. If called when pp has error state, that code is returned and this function has no side-effects. */ CMPP_EXPORT int cmpp_b_append4(cmpp * pp, cmpp_b * b, void const * src, cmpp_size_t n); /** Works like cmpp_b_append4() except that if n is negative, strlen() is used to calculate it. */ CMPP_EXPORT int cmpp_b_append4_str(cmpp * pp, cmpp_b * b, char const *str, cmpp_strlen_t n); /** Convenience form of cmpp_b_append4_str() which requires a string literal. */ #define cmpp_b_append4_c(PP,B,STR) \ cmpp_b_append4(PP, B, STR "", sizeof(STR)-1) /** 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); /** Compares b to [m,m+n) using memcmp() semantics as if b were the LHS. That is: returns 0 if b and z contain equivalent content, negative if b's content is "less than" z's, and positive if b's content is "greater than" z. If n<0 then m is assumed to be a NUL-terminated string and strlen() is used to calculate it. This function will misbehave with inputs having more than 31 bits of length. */ CMPP_EXPORT int cmpp_b_cmp(cmpp_b const * b, void const *m, cmpp_ssize32_t n); /** Unescapes b's contents (if any) inline, adjusting b->n accordingly (it will never grow but may stay the same size or shrink). Any sequences of (`\\`, `\b`, `\n`, `\t`, `\r`, `\"`, `\'`, `\``) are replaced with their traditional single-byte counterparts. Other bytes are passed through as-is. Other backslash sequences are left as-is. Similarly, `\uNNNN` and `\UNNNNNNNN`, where N is runs of hex digits, unescape to their corresponding Unicode code point, or are left as-is if they're invalid. We don't do `\f` or `\v` because they're historically only used in ugly hacks, like separators for bespoke formats, not "real inputs". We don't do `\0` because embedded nuls via unescaping are simply inappropriate in the context of this library. Potential TODO: add an argument for configuring unescaping or maybe erroring for unknown escape sequences (pass a cmpp_errinfo object). Potential TODO: an argument to tell it the byte offset to start at, to allow us to unescape being-appended-to buffers without re-unescaping the existing body. */ CMPP_EXPORT void cmpp_b_unescape(cmpp_b * b); /** A thin wrapper around cmpp_chomp() which chomps b->z. */ CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b); /** Counts the number of newlines between [zBegin,zPos). zBegin must be <= zEnd. zPos must be >= zBegin and <= zEnd. This function may assert() that the preconditions are met. The range need not be NUL-terminated and embedded NULs are counted as for other bytes. Returns the number of newlines and, if pCol is not NULL, sets the 0- and byte-based column number of zPos relative to the most recent newline or (if no newlines were found) zBegin. */ CMPP_EXPORT cmpp_size_t cmpp_count_nl(unsigned char const *zBegin, unsigned char const * const zEnd, unsigned char const * const zPos, cmpp_size_t *pCol); /** 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 b = cmpp_b_empty; int rc = cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_b, &b); ... cmpp_b_clear(&b); ``` */ 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); /** 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_out_expand(). */ cmpp_atpol_CURRENT, /** This isn't _really_ the default. It's the default for the --@ CLI flag. */ 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; /** Masks to embed some metadata into cmpp_tt_e token type IDs. */ enum cmpp_tt_mask_e { /** Mask of the "id" bits for a token ID. */ cmpp_tt_mask_id = 0x00fff, /** Mask of the "shape" bits for a token ID, describing the general data type so that we can group certain constructs together under a single mask for switch/case dispatching purposes. */ cmpp_tt_mask_shape = 0x0f000, /** Anything which does not have a closer match is a Word. */ cmpp_tt_shape_word = 0x01000, /** For numbers, optionally signed. Currently only decimal integers. */ cmpp_tt_shape_number = 0x02000, /** For "", '', and `` strings. */ cmpp_tt_shape_string = 0x04000, /** For {}, [], and () macro-tokens. */ cmpp_tt_shape_group = 0x08000, /** For expression operators. */ cmpp_tt_shape_op = 0x10000, /** For EOL, EOC, and EOF. */ cmpp_tt_shape_flow = 0x20000 }; /** cmpp token types, currently distinct from the public API's cmpp_tt_map as we consolidate the two. The first argument to E is the tail of the cmpp_tt_ ## TAIL part of the enum entry's name. The second is the cmpp_tt_mask_id part of its enum value (integer). The third is the mask/non-ID part of its value. The enum's value is the logical OR of arguments 2 and 3. Maintenance reminder: we need these even in %lite builds for use by cmpp_tizer. */ #define cmpp_tt_map(E) \ E(None, 0, 0) \ E(Error, 1, cmpp_tt_shape_flow) \ /** Anything with no more specific type. */ \ E(Word, 2, cmpp_tt_shape_word) \ E(StringSQ, 3, cmpp_tt_shape_string) \ E(StringDQ, 4, cmpp_tt_shape_string) \ /** Backtick string. */ \ E(StringBT, 5, cmpp_tt_shape_string) \ /** Decimal int, optionally signed but some contexts will elide a leading + when rendering then token. */ \ E(IntDec, 6, cmpp_tt_shape_number) \ E(RawLine, 7, 0) \ E(OpPlus, 100, cmpp_tt_shape_op) \ E(OpMinus, 101, cmpp_tt_shape_op) \ E(OpNot, 102, cmpp_tt_shape_op) \ E(OpAnd, 103, cmpp_tt_shape_op) \ E(OpOr, 104, cmpp_tt_shape_op) \ E(OpGlob, 105, cmpp_tt_shape_op) \ E(OpCmpEq, 106, cmpp_tt_shape_op) \ E(OpCmpNeq, 107, cmpp_tt_shape_op) \ E(OpCmpLt, 108, cmpp_tt_shape_op) \ E(OpCmpLe, 109, cmpp_tt_shape_op) \ E(OpCmpGt, 110, cmpp_tt_shape_op) \ E(OpCmpGe, 111, cmpp_tt_shape_op) \ E(OpShiftL, 112, cmpp_tt_shape_op) \ E(OpShiftL3, 113, cmpp_tt_shape_op) \ E(OpShiftR, 114, cmpp_tt_shape_op) \ E(OpBitwiseAnd, 115, cmpp_tt_shape_op) \ E(OpBitwiseOr, 116, cmpp_tt_shape_op) \ E(OpBitwiseNegate,117, cmpp_tt_shape_op) \ /*SQLite does have xor E(OpXOr, 118, cmpp_tt_shape_op)*/ \ E(OpMultiply, 119, cmpp_tt_shape_op) \ E(OpDivide, 120, cmpp_tt_shape_op) \ E(OpModulo, 121, cmpp_tt_shape_op) \ E(GroupBrace, 200, cmpp_tt_shape_group) \ E(GroupParen, 201, cmpp_tt_shape_group) \ E(GroupSquiggly, 202, cmpp_tt_shape_group) \ E(EOL, 253, cmpp_tt_shape_flow) \ E(EOC, 254, cmpp_tt_shape_flow) \ E(EOF, 255, cmpp_tt_shape_flow) \ /* Arg 2 MUST fit in cmpp_tt_mask_id. */ \ /* Refactoring crutches for legacy token type compatibility: */ \ E(OpDefined, 300, cmpp_tt_shape_op) \ E(OpNotGlob, 301, cmpp_tt_shape_op) \ E(Noop, 302, cmpp_tt_shape_op) \ E(Null, 303, cmpp_tt_shape_op) \ E(DbString, 304, cmpp_tt_shape_word) \ E(ThinArrowR, 305, cmpp_tt_shape_op) \ E(ThinArrowL, 306, cmpp_tt_shape_op) \ E(AnyType, 307, 0) /** Token type IDs and their metadata for use with cmpp_itch, namely cmpp__token::ttype. No entries have well-defined values except that cmpp_tt_None is guaranteed to be 0. cmpp_tt_EOF represents a _logical_ EOF: the end of the tokenizer's input. For sub-parsing this represent the end of a grouping token. */ enum cmpp_tt_e { #define E(NAME,ID,MASK) cmpp_tt_ ## NAME = ID | MASK, cmpp_tt_map(E) #undef E cmpp_tt__last }; typedef enum cmpp_tt_e cmpp_tt_e; /** If e is a cmpp_tt_e entry then a debugging-friendly string form of it is returned, else 0 is returned. The returned bytes are static and NUL-terminated. */ CMPP_EXPORT char const * cmpp_tt_cstr(int e); /** A snippet from a parent string, usually a parsed token and almost never NUL-terminated. This is the fundamental atomic unit for directive and command argument tokenization. */ struct cmpp_token { /** Start of the range. */ unsigned char const *z; /** Number of bytes in this->z. */ unsigned int n; /** Start of the inner range (for group tokens), else the same as this->z. Legacy compatibility quirk: see cmpp_args_F_NO_SQUIGGLY_HACK. */ unsigned char const *zInner; /** Number of bytes in this->zInner. */ unsigned int nInner; /** From enum cmpp_tt_e. */ int ttype; /* Potential TODO: add: cmpp_tizer const * tz; So that we can derive source location info given only a token. "The problem" with that is that it indirectly adds sizeof(void*) to each cmpp_arg object (via each embedding one of these). */ }; typedef struct cmpp_token cmpp_token; #define cmpp_token_empty_m {.z=0,.n=0,.zInner=0,.nInner=0,.ttype=0} extern const cmpp_token cmpp_token_empty; #define cmpp_token_isa(TOKPTR,TTYPE) ((TTYPE)==(TOKPTR)->ttype) typedef struct cmpp_tizer cmpp_tizer; /** Returns a pointer to t's string and assigns *n to its byte length. If inner is true and t->zInner differs from t->z then the range [t->zInner,t->nInner) is used, else [t->z,t->n) is used. Achtung: tokens are rarely NUL-terminated. */ char const * cmpp_token_cstr3(cmpp_token const * t, cmpp_size_t * n, bool inner); int cmpp_token_strcmp(cmpp_token const * t, char const *z, cmpp_strlen_t n); #define cmpp_token_ustrcmp(T,Z,N) \ cmpp_token_strcmp((T),(unsigned char const *)(Z),(N)) /** Convenience form of cmpp_token_cstr3() which casts the result to (unsigned char const *). */ #define cmpp_token_ucstr3(T,NP,I) \ (unsigned char const *)cmpp_token_cstr3((T),(NP),(I)) /** A basic tokenizer for argument/script-like tokens. */ struct cmpp_tizer { /** Parent of a sub-tokenizer. (To be) Used for error reporting: by knowing the whole input range we can provide better line/column info. */ cmpp_tizer const *parent; /** Optional error object through which to report detailed error info. It is owned by whoever sets it. */ cmpp_errinfo * err; /** Starting point. This memory must stay valid for the life of the tokenizer. */ unsigned char const * zBegin; /** One-after-the-end position. */ unsigned char const * zEnd; /** Initialize == zBegin. zPos is never > zEnd. */ unsigned char const * zPos; /** Bytes owned elsewhere. If NULL, inherit from this->parent (see cmpp_tizer_name()). */ char const * name; /** Info for current token. Only valid after a call to cmpp_tizer_next(). */ cmpp_token token; /** Put-back token. We probably don't need this, as any back-tracking would be done with call-local copies of tokens. It's overwritten at the start of cmpp_tizer_next() unless tz is at EOF at the time it's called (as opposed to hitting EOF in that call). */ cmpp_token pbToken; /** A place to mark location when an error is expected. If one does happen, and it channels through the proper magic, the location of this token will be included in the error message. The intent is not so much that this be set immediately before each potential error, but that it be set whenever a token is about to be processed, so that exceptions through that handling can have a sensible error position. It's not an exact science. This token's size is not currently used, only its z member, but we use a full token here for the sake of future experimentation. Some cases don't have one, in which case they can set this->errToken.z to a pointer _if_ it's known to live within [this->zBegin,this->zEnd). */ cmpp_token errToken; /** True when we're at the "begin-of-expression". i.e. we're looking for a command to run. This is true only when looking for the initial token in a given Itch command line (where a line ends an EOL or ';'). For subsequent searches in the same command line it must be false. This is generally managed by cmpp_tizer_next() but it wouldn't hurt for code which is responsible for finding command lines to set it at the start of that process. */ bool atBOX; }; /** An empty-initialized cmpp_tizer struct for use in const copy initialization. */ #define cmpp_tizer_empty_m { \ .parent=0, .err=0, \ .zBegin=0, .zEnd=0, .zPos=0, \ .token=cmpp_token_empty_m, \ .pbToken=cmpp_token_empty_m, \ .errToken=cmpp_token_empty_m, \ .atBOX=true \ } /** An empty-initialized cmpp_tizer struct for use in non-const copy initialization. */ extern const cmpp_tizer cmpp_tizer_empty; /** Sets up tz to tokenize the given half-open input range (zBegin,zBegin+nBytes), except that if nBytes is negative then strlen() is used to calculate it. The err arg may be NULL but it won't be able to report precise error location info via cmpp_tizer_next(). It is safe to pass the same tz instance multiple times. The instances own no dynamic state and this function completely overwrites tz. Results are specifically undefined if: - zBegin is NULL. - zBegin is invalidated during the lifetime of tz. - The contents of zBegin are modified during the lifetime of tz. i.e. don't do that. This function may assert() that its inputs look sane because this is a column upon which many internals are based and it need not tolerate bad inputs. If a call succeeds for any given instance, the caller is obligated to eventually pass pass tz to cmpp_tizer_dtor(), even if later calls to this fail. It is safe to pass it to cmpp_tizer_dtor() regardless of whether this fails, but an initial call is guaranteed not to leak resources, whereas a follow-up call without an intervening cmpp_tizer_dtor() call may leak resources if cmpp_tizer every gains any. The destructor requirement is to leave us the option of eventually giving cmpp_tizer dynamic state (plus, it zeroes out the object to help avoid misuse after its usable lifetime). */ CMPP_EXPORT void cmpp_tizer_init(cmpp_tizer * tz, cmpp_errinfo *err, unsigned char const *zBegin, cmpp_strlen_t nBytes); /** Copies "inherited" info from tzParent to tz, e.g. sets tz->parent to tzParent. If tz->err is NULL then it sets tz->err to tzParent->err, else tz->err is retained. Likewise for tz->name but uses cmpp_tizer_name(tzParent) to get it. Results are undefined if tz's input range is not fully contained within tzParent's, and this function may assert, or otherwise fail fatally, if that is the case (because it represents a serious footgun for any downstream code which relies on those semantics). i.e. don't do that. */ CMPP_EXPORT void cmpp_tizer_inherit(cmpp_tizer const * tzParent, cmpp_tizer * tz); /** Initializes a sub-tokenizer which is limited to the contents of tzParent->token. The token's inner range, if any, is used for the input, so the delimiters for strings and grouping tokens are not part of the input. If tok is NULL then tzParent->token is used. This also sets tzSub->parent to tzParent. */ CMPP_EXPORT void cmpp_tizer_init_sub(cmpp_tizer const *tzParent, cmpp_token const * tok, cmpp_tizer * tzSub); /** Returns the first name found in tz or one of tz->parent (recursively). */ CMPP_EXPORT char const * cmpp_tizer_name(cmpp_tizer const *tz); /** If tz holds any state, this relenquishes that state, then tz is zeroed out, available for discarding or passing it to cmpp_tizer_init() again. It is illegal to pass an uninitialized object to this function. Achtung: tz->err is owned by whoever put it there. This function zeroes out that member but does not modify that object. */ CMPP_EXPORT void cmpp_tizer_dtor(cmpp_tizer * tz); /** Calculates the whole input range of tz and its parents (if they share an encompassing byte range), for purposes of calculating line counts. Sets *zBegin and *zEnd to the half-open range. */ CMPP_EXPORT void cmpp_tizer_full_range( cmpp_tizer const * tz, unsigned char const **zBegin, unsigned char const **zEnd ); /** Returns true if tz->token.zPos is either at/past tz->zEnd or cmpp_tt_EOF==tz->token.ttype. */ CMPP_EXPORT bool cmpp_tizer_eof(cmpp_tizer const *tz); /** Returns true if z lies within, or at the end of, tz's complete input range, as per cmpp_tizer_full_range(). */ CMPP_EXPORT bool cmpp_tizer_contains(cmpp_tizer const * tz, unsigned char const *z); /** Rewinds the tokenizer to its initial tokenization position, such that the next call tp cmpp_tizer_next() will start at that position. This clears all cmpp_token-type members of tz and sets tz->atBOX to true. */ CMPP_EXPORT void cmpp_tizer_rewind(cmpp_tizer *tz); /** Flags for use with cmpp_tizer_next(). */ enum cmpp_tizer_next_e { /** Sentinel entry. */ cmpp_tizer_next_NONE = 0, /** Tells cmpp_tizer_next() to skip newlines, treating them as whitespace or comments. By default newlines are end-of-command distinct tokens reported with the type cmpp_tt_EOL. Sometimes newlines are EOL tokens, sometimes they're spaces, and it's up to the caller to specify which is which. */ cmpp_tizer_next_SKIP_NL = 0x01, /** Tells cmpp_tizer_next() to skip over C-style comments. They may span lines. For contexts where newlines end tokenization, the newlines within a comment are not considered newlines. */ cmpp_tizer_next_SKIP_C_COMMENTS = 0x02 /** TODO: a flag which triggers the workaround described arg.c:cmpp__squiggly_strip_lead(). */ }; /** Fetches the next token and updates tz->token. On success, returns 0, and tz->token contain the relevant information. On error tz->token.type==cmpp_tt_Error, tz->token.n will be 0, and tz->token.z will point to the best approximation of where the error is. At EOF, 0 is returned, tz->token.z points to the one-after-the-end position, tz->n==0, and tz->token.type==cmpp_tt_EOF. */ CMPP_EXPORT int cmpp_tizer_next(cmpp_tizer *tz, cmpp_flag32_t flags); /** Flags for use with cmpp_token_content(). */ enum cmpp_token_content_e { /** Sentinel entry. */ cmpp_token_content_NONE = 0, /** This returns the whole content of the token, regardless of its type and with no grouping wrappers or + signs removes. */ cmpp_token_content_WHOLE = 0x01, /** Do not pass the destination buffer to cmpp_b_reuse(). */ cmpp_token_content_APPEND = 0x02, /** If the token is a string or a group token, trim the content of leading and trailing spaces. */ cmpp_token_content_TRIM = 0x04, /** By default it will apply unescaping to string-type tokens unless this flag is set. It does not unescape squiggly groups, despite them being a type of string. */ cmpp_token_content_NO_UNESCAPE = 0x08 }; /** Returns the content part of the given token by appending to b or (depending on the flags) replacing b's content with a copy of tok's contents, possibly a transformed form of them (depending on the tok->ttype and flags). If tok is NULL then tz->token is used. The target buffer is passed to cmpp_b_reuse() unless flags contains the cmpp_token_content_APPEND flag. Returns 0 on success. Errors are propagated via tz->pp. The following tok->ttype values do not produce any output and cause 0 to be returned: cmpp_tt_EOF, cmpp_tt_Error, cmpp_tt_None */ CMPP_EXPORT int cmpp_token_content(cmpp_tizer const * tz, cmpp_token const * tok, cmpp_b * b, cmpp_flag32_t flags); /** Calculates the 1-based line and 0-based column of the given token (or tz->token if tok is NULL) and writes them *l and *c. */ CMPP_EXPORT void cmpp_token_lc(cmpp_tizer const * tz, cmpp_token const * tok, cmpp_size_t *l, cmpp_size_t *c); /** True if (cmpp_token const *)TOKEN's contents match the string literal STR, else false. */ #define cmpp_token_equals_c(TOKEN,STR) \ (sizeof("" STR)-1==(TOKEN)->n && 0==memcmp(STR,(TOKEN)->z,sizeof(STR)-1)) /** True if (cmpp_token const *)TOKEN'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_token_isflag_c(TOKEN,STR) \ (cmpp_token_equals_c(TOKEN,STR) || cmpp_token_equals_c(TOKEN, "-" STR)) /** Returns true if (t->id & cmpp_tt_mask_shape) is cmpp_tt_shape_group, else false. */ CMPP_EXPORT bool cmpp_token_is_group(cmpp_token const * t); /** Returns true if (t->id & cmpp_tt_mask_shape) is cmpp_tt_shape_string, else false. */ CMPP_EXPORT bool cmpp_token_is_string(cmpp_token const * t); /** If z is 0 then the error position is cleared. Else if z is within tz's full input range (including parents), this sets tz's error position to z. Else this has no side effect. To be clear: this does not put the tokenizer into an error state. This records the given position for the sake of any potential following error. APIs which use cmpp_tizer frequently set this for each being-processed token, such that that position will be used for error reporting if something goes wrong in a following step. The overwhelming majority of the time, this will precisely pinpoint the location of an error. */ CMPP_EXPORT void cmpp_tizer_errpos_set(cmpp_tizer *tz, unsigned char const *z); /** Like cmpp_tizer_errpos_set() but requires non-NULL arguments and it uses the position tok->z. */ CMPP_EXPORT void cmpp_tizer_errpos_set_t(cmpp_tizer *tz, cmpp_token const * tok); /** If tz is not NULL and has any token set which could potentially be used for an error position, its address is returned, else 0 is returned. This prefers tok->errToken, then tz->token, then tz->zPos, in that order. This does _not_ imply that there is an error at that position: this is used for trying to guess a position for a pending error report when one is otherwise not provided. (The internals which make use of cmpp_tizer make an honest effort to update the _potential_ error position as they work so that any errors they trigger will have accurate location information.) */ CMPP_EXPORT unsigned char const * cmpp_tizer_errpos(cmpp_tizer const * tz); /** Returns a new sqlite3_str which is prefixed (ideally) with error location information for tz, specifically for position z. Ownership of the object is transferred to the caller, who is generally expected to append to it as if nothing were there and eventually pass it to sqlite3_str_finish() to finalize it and take over its C-string result. Returns NULL on OOM[^1]. If z is NULL then cmpp_tizer_errpos() is used. This function may assert() that z lives in tz's range - any value out of that range represents a serious misuse of the API. In non-debug builds it will return a new, but empty, string in that case. Very minor caveat: the returned object has no access to an sqlite3 DB handle, which means that it is not subject to the size limits configured for the DB owned by any given cmpp instance. Thus it is possible, but only through what may fairly be characterized as blatant malicious misuse, that this string can grow larger than the underlying sqlite3 db is configured to accept, leading to downstream errors if, e.g., the string is used as a the value for a db column. That said: this function is invariably used for error output, not db data, so this caveat is largely moot. [^1]: sqlite3_str_new() _never_ returns NULL. On OOM it returns a dummy object which is in a perpetual error state. This API, however, checks for that and will return NULL on OOM. */ CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix(cmpp_tizer const *tz, unsigned char const *z); /** A proxy for cmpp_tizer_err_prefix() which takes a token as input. If it's null then tz->token is used. */ CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix_t(cmpp_tizer const *tz, cmpp_token const *tok); /** Convenience helper to loop over a cmpp_tizer until it reaches EOF on an error. This can be used within Itch commands to iterate over script-containing strings and token lists. TZP = a cmpp_tizer_init()'d (cmpp_tizer*). RC = the name of an int-type var which will be set to the cmpp_tizer_next() loop on each iteration. It is set to 0 before starting the loop. If it's 0 after the loop, the tokenizer has reached its EOF. It it's not 0 then it hit an error and will have reported it via TZP->err if that's not NULL. The condition (0==RC) is checked before each step and the loop is aborted if it's not 0. i.e. setting it to non-0 will stop the loop on the next iteration without fetching a following token. FLAGS = the second argument to cmpp_tizer_next(). If it's a variable name instead of a literal, the loop body may change the flags during the loop to affect the next interation. POST is the optional Z part of for(X;Y;Z). It may be empty. Usage: ``` int rc; cmpp_tizer tz = cmpp_tizer_empty; cmpp_token const * tok = &tz->token; cmpp_tizer_init(tz, 0, "a b c", -1); cmpp_tizer_foreach4(tz, rc, 0,) { ... use tok->ttype and friends ... } if( rc ){error;} else {reached EOF or break'd out of the loop;} ``` Credits: this trick was gleaned from the ParTcl sources. */ #define cmpp_tizer_foreach4(TZP,RC,FLAGS,POST) \ RC = 0; \ for( ; (TZP)->zPos<(TZP)->zEnd \ && 0==RC \ && (0==(RC=cmpp_tizer_next(TZP,FLAGS))) \ && cmpp_tt_EOF!=(TZP)->token.ttype; \ POST ) /** Slightly-simplified form of cmpp_size_foreach4() which elides that one's fourth argument. */ #define cmpp_tizer_foreach3(TZP,RC,FLAGS) \ cmpp_tizer_foreach4(TZP, RC, FLAGS,) /** A single argument for a cmpp_f() implementation. When a cmpp_d::flags does not have the cmpp_f_F_ARGS_RAW bit set then the part of the input immediately following the directive (and on the same logical line). These objects are created by cmpp instances as they parse directive lines in an input stream, and they pass them, using the cmpp_f() interface, to the native implementations for each directive. Similarly, cmpp_itch uses this for command arguments (which, when it comes down to it, are the same thing except for the small detail of string interpolation performed by the script environment). Lifetime: For the _most_ part: Each of these is valid for the duration of the cmpp_f() call into which the argument was initially passed. Their memory may be freed, recycled, or otherwise vanish, the moment the cmpp_f() impl returns. Caveat: cmpp_f() impls which do _not_ use the cmpp_f_F_ARGS_V4 flag may, depending on what type of work they do, be subject to lifetime invalidation of these objects during the call to cmpp_f(). Specifically, that happens in impls which themselves consume from the input stream (e.g. using cmpp_dx_consume()). Implementations which do not do that have the same lifetime guarantees as V4-using implementations. The workaround for such cases is to use cmpp_dx_args_clone() but (A) the V4 argument handling obviates the need for it and (B) that option will be removed once all built-in directives are ported to V4. Historically they have been used together with cmpp_args, a container for these, but as of 2026-09 the cmpp_args (plural) class is effectively deprecated and not needed for directives which use the cmpp_f_F_ARGS_V4 interface (which all new directives "should" use). */ struct cmpp_arg { /** Token type from the cmpp_tt_e enum. */ int 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. The library guarantees that this is NUL-terminated in the following contexts: - When this object is passed to a cmpp_f() impl. - When this object was initialized from a public cmpp_args API, e.g. cmpp_args_parse() or cmpp_args_clone(). It does not gurantee so in other contexts. This pointer memory is owned by the API which set it up and it may be invalidated by any modifications to, e.g., a cmpp_args object which initialized it. */ 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 [this->z,this->z+n). */ unsigned n; /** DEPRECATED: this is part of the pre-V4 args handling and must not be used in newer versions. The next argument in the list. It is owned by whatever code set it up (typically cmpp_args_parse()). The long-term plan is to get rid of this in favor of argv-style traversal but current cmpp_args infrastructure requires a linked list. This will go away or will eventually only be set for directives which do not opt in to the Newer Ways. DO NOT use this in cmpp_f() impls tagged with cmpp_f_F_ARGS_V4: it is unconditionally set to NULL for those. */ cmpp_arg const * next; /** If this argument came from a cmpp_itch interpreter, this will be set to a buffer which contains the interpolated form of the argument. That is, this->z might say "$x" and this->b might say "3". In non-cmpp_itch invocations of cmpp_f() this is currently always 0 but The Plan is to eventually offer the ability for directives to opt-in to interpolations which most directives already currently use. That would save significant amounts of code for some directives. This buffer is owned by this object but the very simple cmpp_arg lifetime model makes it perfectly legal do one of the following from cmpp_f() implementations: - Pass this to cmpp_b_swap(), giving its memory to a buffer they control. - Pass the buffer to cmpp_itch_result_take(), which is basically a proxy for cmpp_b_swap(). In this way a value can be returned as-is from the command without any allocation. - Take over ownership of b->z and wipe b's contents by copying over it with cmpp_b_empty. - Take over b, assign this->b to 0, and then eventually pass be to cmpp_b_return(theOwningCmppInstance, b) to relinquish it. DO NOT pass b to cmpp_free(). This of course requires a non-const instance, and the ones passed to cmpp_f() implementations are const. */ cmpp_b * interp; /** The raw argument token, referring to memory owned by whoever set this object up. It is intended only for use by low-level directives where the pointer relationships and memory lifetimes are well-understood. Depending on the args-parsing mode, token.z and token.zInner may point into the containing script's memory, so will only be NUL-terminated when they're actually at the right-most edge of their input range (and only if _that_ is the whole input range rather than from a sub-tokenizer). token.n and token.nInner MUST be used to constrain the input range when token.z or token.zInner are emitted or copied. */ cmpp_token token; }; /** Empty-initialized cmpp_arg instance, intended for const-copy initialization. */ #define cmpp_arg_empty_m { \ .ttype=cmpp_tt_None, \ .z=0,.n=0,.next=0, \ .interp=0, \ .token=cmpp_token_empty_m \ } /* Forward decls. */ 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_f_e { /** Sentinel value. */ cmpp_f_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. As of early 2026-08, the memory set up for cmpp_f() implementations for this argument style is stable. That is, it remains valid until the cmpp_f() returns, rather than being invalidated by recursing into the library from the cmpp_f() impl. Some docs may mention such invalidation because it was real for a long time, but that is now thankfully resolved. This flag may be used together with cmpp_f_F_ARGS_V4 but only exotic use cases would want to do so. */ cmpp_f_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_f_F_ARGS_LIST and cmpp_f_F_ARGS_RAW are specified, it is unspecified which one applies. i.e. don't do that. See also (and prefer): cmpp_f_F_ARGS_V4. */ cmpp_f_F_ARGS_LIST = 0x02, /** Specifies that the directive is using "version 4" of the argument-passing mechanism, in which it populates cmpp_f_args::argc and cmpp_f_args::argv with the count of and pointers to the arguments. This mode always includes the directive or command name being invoked as the first argument, so argc is always at least 1 and argv is non-NULL unless this flag is combined with cmpp_f_F_ARGS_RAW (see below). The advantage to this flag over the other ARGS_... variants is that the argument-related memory is not invalidated if the callback recurses into the library. i.e. the memory in cmpp_f_args::argv and cmpp_f_args::raw are stable for the duration of the cmpp_f() call. The earlier modes now gurantee that for cmpp_f_args::raw but not cmpp_dx::args (and the earlier modes to not set cmpp_f_args::argv). This is the argument-handling flag which All New Code Should Use. Lots of old code still doesn't, which is sad, but we're slowly getting them ported to v4. Using this flag together with cmpp_f_F_ARGS_LIST is redundant and harmless but the V4 semantics will be applied. If cmpp_f_F_ARGS_RAW is used together with this flag then it works a bit differently: - cmpp_f_args::argc will be set to the number of arguments tokenized by the preprocessor (it has to do so to be able to get to the point of calling the cmpp_f()). Because V4 includes the name of the being-invoked directive or command as argument 0, argc will always be at least one. - cmpp_f_args::argv will be NULL. Such cmpp_f() impls are expected to parse the arguments using (e.g.) cmpp_tizer with the input range defined by cmpp_f_args::raw.z and cmpp_f_args::raw.n (noting that it is generally NOT nul-terminated). That range contains the directive's raw argument line, up to (but not including) its final newline or EOF. It is guaranteed to be non-NULL and have a non-0 length because the directive or command name being invoked is the first argument in the list. That range is guaranteed to point back into the original source bytes, backslash-escaped newlines and all. */ cmpp_f_F_ARGS_V4 = 0x40, /** Indicates that the cmpp_f() should not be available if the cmpp instance is configured with the cmpp_ctor_F_SAFEMODE flag. All directives which do any of the following are obligated to set this flag: - Filesystem or network access. - Invoking external processes. - 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 (cmpp_d_register()) or cmpp_itch command (cmpp_itch_cmd_add()) 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_itch refuses to invoke commands with this flag when cmpp_is_safemode() is true. The latter two are also protected at registration-time, with the above behaviors representing "defense in depth" rather than resolving a known deficiency. */ cmpp_f_F_NOT_IN_SAFEMODE = 0x008, /** Call-only directives are only usable in [directive ...] "call" contexts. They are not permitted to have a closing directive. This flag has no effect on cmpp_itch commands. */ cmpp_f_F_CALL_ONLY = 0x010, /** Indicates that the directive is incapable of working in a [call] context and an error should be triggered 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. This flag has no effect on cmpp_itch commands. */ cmpp_f_F_NO_CALL = 0x020, /** Tells the framework that the directive's arity should be enforced by the tokenizer (so that the directive implementation does not have to deal with it). A cmpp_d_reg::arity value of 0 or higher causes the framework, when this flag is set, to enforce precisely that many arguments. A negative value means to not enforce any argument count. This capability is generally intended for script bindings (even then, most of them need a negative arity). This only really works for very simple cases, as most directives require variadic flag and arg handling. This also does not (cannot) enforce argument type restrictions - that has to be done in the directive. */ cmpp_f_F_ENFORCE_ARITY = 0x040, /** Indicates that the directive requires an active cmpp_dx in order to work (e.g. because it reads from the input stream). The framework will not invoke such directives or cmpp_itch functions if cmpp_dx_current() returns NULL, generating an error instead. All commands which consume from a being-preprocessed input stream require an active cmpp_dx. Commands which convey all output using cmpp_itch_result_set() and friends do not require a generally require a cmpp_dx. Those which consume preprocessor input or emit output to the cmpp output stream do. */ cmpp_f_F_NEEDS_DX = 0x080, /** See cmpp_args_F_NO_SQUIGGLY_HACK. */ cmpp_f_F_NO_SQUIGGLY_HACK = 0x100, /** 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() or cmpp_itch_cmd_add(). */ cmpp_f_F_MASK = 0x0ffff }; /** State related to invocation of a cmpp_f() function. */ struct cmpp_f_args { /** The preprocessor context. */ cmpp * const pp; /** The current directive context. */ cmpp_dx * const dx; /** Number of arguments passed to the directive. This is ONLY SET to non-0 if this->d->flags contains cmpp_f_F_ARGS_V4. See that flag's docs for details. */ unsigned const argc; /** Array of arguments passed to the directive. This is ONLY SET to non-NULL if this->d->flags contains cmpp_f_F_ARGS_V4. See that flag's docs for details. */ cmpp_arg const * const * const argv; /** The calling directive, if any. This is meant to be used in place of the historical cmpp_dx::d member because this pointer does not change when recursing into the library (whereas that one does). We cannot eliminate that one from the API just yet but we are headed there. This is currently NULL when the cmpp_f() is called from a cmpp_itch interpreter instead of the preprocessor. It's not clear whether we want them to have access to this->dx and this->d, but they can get either via cmpp_dx_current() anyway, noting that this->dx->d may change when calling back into the library to consume input. */ cmpp_d const * const d; /** Brief usage string, intended for use in error messages and --help functionality. */ char const * usage; /** Opaque state defined by whoever created the associated cmpp_f() impl, upon which interpretation depends. */ void * state; /** Flags from the cmpp_f_e enum. */ cmpp_flag32_t const flags; /** The raw arguments memory, for directives which want to use an alternative tokenizer, emit debug info, or whatever it is what one wants to do with it. cmpp_itch, for example, uses this to ensure that its functions can map error location info to the original source location (which we cannot do if we copy it unless we add pieces to record the containing script's position and account for it in error reporting). Achtungs: - For cmpp_itch commands (not cmpp_d callbacks, despite having this same signature), this points directly back into the source script. That _should_ be the case for non-Itch impls with the cmpp_f_F_ARGS_V4 flag as well but cannot be without a considerably compatibility break which is not worth reworking at this point. - For all other cases, this pointer lives in dynamic memory which is edited very slightly from the original script, e.g. to remove backslash-escaped newlines. (Itch scripts don't need that because their tokenizer does it.) That memory stays valid for the life of the cmpp_f() call and is invalidated the moment it returns. (Directives use the same tokenizer but have backwards compatibility constraints on how their implementations receive the arguments until/unless they're ported to the V4 argument handling.) As of early 2026-09 the memory pointed to here remains valid through the duration of the cmpp_f() call even if it recurses into the library's content-consumption APIs. Before that change, it would be invalidated if the impl called certain library APIs. Design note: the ability to get the raw arguments was primarily added so that directives like #pipe have something they can pass as-is to external apps or APIs. That said: the arguments must be able to at least pass through cmpp's argument tokenizer to get to that point, and there are constructs which will not work well with that. (We've yet to hit this limitation in any no small amount of downstream inputs but it can certainly be contrived.) */ struct { /** Start of the args memory. */ unsigned char const * const z; /** Byte length of this->z. */ cmpp_size_t const n; } raw; /** True if this invocation is a [call...] from preprocessor input, as distinct from an cmpp_itch interpreter call. */ bool const isDxCall; }; typedef struct cmpp_f_args cmpp_f_args; /** A helper for cmpp_f() implementations which use the cmpp_f_F_ARGS_V4 flag: it uses cmpp_errf() to write a usage-style error. This currently sets the result code to CMPP_RC_ERROR, for lack of a more specific one (we can't use CMPP_RC_ITCH_F_USAGE because that one is stripped out of %lite builds). Returns its error code. This function relies on the fact that args->argc>0 for v4 arguments and it dereferences args->argv[0] and its contents. */ CMPP_EXPORT int cmpp_f_v4_err_usage(cmpp_f_args const *args); /** Callback type for cmpp_d::impl::f() and cmpp_itch commands. cmpp directives and cmpp_itch commands are all implemented as functions with this signature. Implementations are called by the library as the preprocessor consumes input and encounters directives, or when its cmpp_itch interpreter calls one of its registered commands. The first rule of callback is: to report errors (all of which end processing of the current input) call one of the cmpp_err() family of functions, then clean up any local resources, then return. The library will recognize the error and propagate it. (cmpp_itch callbacks should prefer cmpp_itch_err() and friends, as that one distinguishes between script-only and non-script errors and will not propagate the latter through to the owning cmpp context. We cannot safely make that distinction in cmpp_err() because there might, wildly hypothetically, be any number of cmpp_itch instances and it would have to be told which, which is exactly what cmpp_itch_err() and friends do.) See the cmpp_f_args docs regarding memory lifetime of the argument and its contents. They differ depending on the cmpp_f_e flags used for registration. Sidebar: despite cmpp_d and and cmpp_itch both using this interface, the two things live in separate namespaces. That is, internally there is no confusion about which is which, and trying to invoke #someItchCommand as a directive won't work (even if someItchCommand is a valid Itch command). */ typedef void (*cmpp_f)(cmpp_f_args const *args); /** A typedef for generic deallocation routines. */ typedef void (*cmpp_finalizer_f)(void *); /** State specific to concrete cmpp_d implementations. */ struct cmpp_d_impl { /** Callback func. If any API other than cmpp_dx_process() invokes this, behavior is undefined. */ cmpp_f f; /** 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_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 {.f=0,.dtor=0} /** Each c-pp "directive" is modeled by one of these. */ struct cmpp_d { /** The directive's name. It is stored along with its length to speed up searches. */ 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; /** Brief help text. Its memory must outlive the directive. This member was introduced for the sake of cmpp_itch callbacks, where it has demonstrable utility. It unfortunately does not serve the purpose well for directives, which commonly have multiple call forms, both with and without a closing tag. Historically (pre-Itch), directives did all of their own validation error reporting so did not have a use for this member. As of this writing (2026-09-07), this value is non-NULL but also not terribly helpful for the library's builtin directives because the internal setup makes it difficult to wedge it it. Itch callbacks, on the other hand, tend to set it to something useful. */ char const * usage; /** The number of arguments this directive requires, or -1 for any number. A value of 0 means it does not accept arguments. Enforcement of the arity is opt-in, only enforced if cmpp_f_F_ENFORCE_ARITY is in this->flags and if this->flags has cmpp_f_F_ARGS_V4. Before that addition we did not have any enforcement. Achtung: v4 arguments add the directive/command name as the first argument, so the callback always receives an argc of at least 1 and the arguments start at index 1. That "off by one" is _not_ accounted for in this artity value: it counts only the arguments, not the commeand/directive name. */ int arity; /** Bitmask of flags from cmpp_f_e plus possibly internal flags. */ cmpp_flag32_t flags; #if 0 /** TODO: historically directives have all done their own interpolation, but the internal APIs eventually reached a stage where the directives were all repeating the same interpolation. This flag is to provide interpolation options, initially only for directives, but potentially also for use with (not cmpp_itch callbacks (same interface). */ cmpp_flag32_t interpolationFlags; #endif /** The directive which acts as this directive's closing element element, or 0 if it has none. Directives may have multiple closers but they are managed internally, not via this interface. */ 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_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_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_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_out_raw() or cmpp_out_expand(). */ struct cmpp_dx { /** The cmpp object which owns this context. */ cmpp * const pp; /** The directive on whose behalf this context is active. See this->args for notes about invalidation of this value. ACHTUNG: this may be modified by any APIs which recurse into the library. cmpp_f() impls should generaly use cmpp_f_args::d instead of this, but impls which have multiple closers are known to need to be able to see changes to this in order to behave properly. */ 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. DEPRECATED 2026-09: this member not needed for directives which use cmpp_f_F_ARGS_V4, as that flag uses an approach which (A) is easier to use in practice and (B) does not invalidate argument memory when recursing into the library. Once/if older directive code is updated, this member will go away. This particular struct is LEGACY and DEPRECATED. It will not be removed until all delimiter handlers are adapted to the cmpp_f_F_ARGS_V4 model but it should not be used in any new code. cmpp_f() impls in that mode should use cmpp_f_args::argc/argv for a more conventional experience and none of the inherent memory-invalidation problems which plague the older model. 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_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_dx_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. The contents differ from the raw, unparsed source file in that backslash-escaped newlines are replaced with newlines. The API does not offer access to the raw underlying strings. Maybe it should. This member is set regardless of the argument parsing mode for the directive. */ unsigned const char * z; /** The byte length of z. It is not called "n", like its counterpart in other structs, so as to avoid confusion between dx->args->n and dx->args->argc (see, that was confusing, right?). This member is set regardless of the argument parsing mode for the directive. */ cmpp_size_t nz; /** The parsed arg count for the this->arg0 list. ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode. Use cmpp_f_args::argc instead. */ unsigned argc; /** The first parsed arg or NULL. How this is set up is affected by cmpp_d::flags. ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode. Use cmpp_f_args::argv instead. 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 ){ ... } Directives registered which cmpp_f_F_ARGS_V4 are instead expected to use cmpp_f_args::argv, and this member may be NULL for them. */ cmpp_arg const * arg0; } args; /** Private impl details. */ cmpp__dx_pimpl * const pimpl; }; /** Returns pp's current directive context. When processing directives, this is the context which is active during that time. When not processing directives, this is NULL. Lifetime: the returned object is managed by pp and it will be destroyed as soon as it reaches the end of the dx's input or an error pre-empts that and propgates back up to that point. Callers must not hold onto this pointer for longer than the current function, nor must they expect this function to return the same value before and after calling into APIs which traverse into new input scripts (e.g. via the #include directive). In a case like #include or cmpp_process_string(), this function will return the same thing immediately before and after that operation, but during processing of that source it will have a different value which expires as soon as the source is consumed or errors out. */ CMPP_EXPORT cmpp_dx * cmpp_dx_current(cmpp *pp); /** Thin proxy for cmpp_errf(), replacing only the first argument. */ CMPP_EXPORT int cmpp_dx_errf(cmpp_dx *dx, int rc, char const *zFmt, ...); /** va_list counterpart of cmpp_dx_errf(). */ CMPP_EXPORT int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt, va_list vargs); /** 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_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_ATPOL_OFF = 0x02 }; /** A helper for cmpp_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 nClosers legal closing tags entries. Typically this is the single closing directive/tag of the current directive, available to the opening directive's cmpp_f() impl via dx->d->closer. Some special cases require multiple candidates, however (examples: #query and the #if family). 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_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_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_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, 1, 0) ){ cmpp_b_chomp( &os ); ... maybe modify the buffer or decorate the output in some way... cmpp_out_raw(dx->pp, os.z, os.n); } cmpp_b_clear(&os); ``` */ CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os, cmpp_d const *const * dClosers, unsigned nClosers, cmpp_flag32_t flags); /** Output state for use with cmpp_dx_consume_v2(). Memory lifetimes: - All string pointers in this class point directly back into the source script so have its lifetime. From this library's perspective that means until the cmpp_process_string() call which started the process finishes, at which point those pointers leave the library's site and are owned by their caller. That caller might be cmpp_process_stream() or cmpp_process_file(), in which case such pointers are invalidated before those functions return. - The cmpp_d pointers are owned by the underlying cmpp instance. As of this writing the API provides no means to remove directives, nor are there any plans to introduce any. Thus it is currently "impossible" that those pointers become invalidated before the cmpp instance is passed to cmpp_reset() or cmpp_dtor(). However, users of this API "should" treat them as if they are invalidated when the function set them up for passing to cmpp_dx_consume_v2() returns. */ struct cmpp_dx_consumed { /** Start of the consumed range, starting at the first byte of the directive's line. */ unsigned char const * beginDirective; /** Start of the content part of the consumed range, starting at the byte after the directive's opening line. */ unsigned char const * beginContent; /** One-after-the-end of the content part of the consumed range. */ unsigned char const * endContent; /** One-after-the-end of the full consumed range, including the closing directive. */ unsigned char const * endDirective; /** The opening directive. */ cmpp_d const * dOpen; /** The closing directive. */ cmpp_d const * dClose; }; typedef struct cmpp_dx_consumed cmpp_dx_consumed; #define cmpp_dx_consumed_empty_m {0,0,0,0,0,0} /** Works exactly like cmpp_dx_consume() except for the final argument: When this is called, con is unconditionally cleared and then partially re-populated (see cmpp_dx_consumed docs for the exact semantics of each field): - con->dOpen is set to dx->d - con->beginContent is set - con->beginContent is set It then calls cmpp_dx_consume(). Regardless of success or error, it finishes populating con before returning the result of that call: - con->dClosed = the closing directive (the new dx->d) - con->endContent - con->endDirective The problem this solves: The #itch directive, in order to be able to provide accurate script error location information, needs to be able to locate it within the original source. It uses this function to consume to its #/itch tag and then access the script from the data stored in con. This allows it to provide byte-accurate line/column info. If it instead copied the input, it could provide location info only relative to the directive's contents (which is not horrible but it simplifies the internals if they can access the outer directive's memory directly, as internal buffers, e.g. for string interpolation, otherwise build walls between individual tokens and their original source, making them unable to be mapped to an input source location. Similarly, if #itch were to allow other directives embedded within a script, it would be forced to make a copy (preprocessing produces different content than the source, of course) and it would be literally impossible to map any code location within that into a value which would make sense to any user. Consider the following itch script, which is invalid because Itch does not permit embedded directives (for the reason given above): ``` #define foo "this is foo" #@ policy error #itch #if 1 eval { foo bar baz bar baz barre error "@foo@" } #/if #/itch ``` If sub-directives were permitted, the result would come out looking like: ``` eval { foo bar baz bar baz barre error "this is foo" } ``` Now, when "error" triggers, it would report line 4, column 3. In this trivial example that's only off by 1 line, but the distance grows quickly and eventually spans into evals which run dyamically-generated code via var- and command-call interpolation, at which point the line/column values become meaningless and therefore not only useless but also misleading, causing wasted developer time as they track numbers which point to either nothing at all or something completely different. */ CMPP_EXPORT int cmpp_dx_consume_v2(cmpp_dx * dx, cmpp_outputer * os, cmpp_d const *const * dClosers, unsigned nClosers, cmpp_flag32_t flags, cmpp_dx_consumed * con); /** 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, and due to change. */ CMPP_EXPORT void cmpp_arg_cleanup(cmpp *pp, 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); /** Returns the C-string representation of arg. If arg->interp is set then that string is returned, else arg->z is returned. */ CMPP_EXPORT char const * cmpp_arg_cstr(cmpp_arg const *arg); /** Works like cmpp_arg_cstr() but also writes the byte length of the resulting string into *n (n may not e NULL). */ CMPP_EXPORT char const * cmpp_arg_cstr2(cmpp_arg const *arg, cmpp_size_t *n); #define cmpp_arg_ucstr(ARG) (unsigned char const*)cmpp_arg_cstr(ARG) #define cmpp_arg_ucstr2(ARG,N) (unsigned char const*)cmpp_arg_cstr2((ARG),(N)) /** 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_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_StringDQ and friends. - 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. Their interpretation is context-dependent but the general conventions are that {} are used for strings and context-dependent ad hoc data structures (tcl-style), (...) for subexpressions, and [...] for either "function calls" or lists of values. - 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_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_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_f_pipe() for an example. */ CMPP_EXPORT int cmpp_arg_parse(cmpp * pp, cmpp_arg *pOut, unsigned char const **pzIn, unsigned char const *zInEnd, unsigned char ** pzOut, unsigned char const * zOutEnd); CMPP_EXPORT bool cmpp_arg_equals(cmpp_arg const *arg, char const *z); /** True if (cmpp_arg const *)ARG's contents match the string literal STR, else false. */ #define cmpp_arg_equals_c(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_c(ARG,STR) \ (cmpp_arg_equals_c(ARG,STR) || cmpp_arg_equals_c(ARG, "-" STR)) #define cmpp_if_arg_isflag_c(ARG,STR) \ if( cmpp_arg_isflag_c(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_interpolate(). With my apologies for the long names (but consistency calls for them). TODO (2026-09-07): rename this to cmpp_interpolate() and have it closely mirror cmpp_itch_interpolate(). The plan is to have directives opt in to automatic arg interpolation, which would eliminate a digital ton (==some small fraction of a metric microgram) of code from the built-in directives. */ enum cmpp_arg_interpolate_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_interpolate_FORCE_STRING = 0x01, /** Tells cmpp_arg_interpolate() to not expand arguments with type cmpp_tt_Word, which it normally treats as define keys. It instead treats these as strings. */ cmpp_arg_interpolate_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_interpolate() call. FIXME? Make this opt-out instead of opt-in. We end up _almost_ always wanting this. */ cmpp_arg_interpolate_BRACE_CALL = 0x04, /** Explicitly disable [call] expansion even if cmpp_arg_interpolate_BRACE_CALL is set in the flags. */ cmpp_arg_interpolate_NO_BRACE_CALL = 0x08, #if 0 /** TODO. Open questions: apply to all types, e.g. results of [calls...], or just to string-style? This will eventually backfire if the target buffer has existing content, as we'd end up multi-unescaping the existing parts. */ cmpp_arg_interpolate_UNESCAPE = 0x10, #endif #if 0 /* Going to need this for porting the expression builder from itch to the core. */ cmpp_arg_interpolate_PARENS_EXPR = 0x20, #endif /** TODO? cmpp_arg_interpolate_UNESCAPE to run it through cmpp_b_unescape(). */ }; /** Appends some form of arg to the given buffer. arg->ttype values of cmpp_tt_Word (define keys) cause the value to be expanded appropriately. Others get emitted as specified by the flags argument. The flags argument influences the expansion decisions, as documented in the cmpp_arg_interpolate_e enum. Returns 0 on success and all that. If b is NULL then: (A) if arg->interp is not NULL then it is reused as the target else (B) return CMPP_RC_MISUSE and update's pp's error state with a description of the problem. 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_dx_args_clone() exists solely to work around such potential invalidation). */ CMPP_EXPORT int cmpp_arg_interpolate(cmpp *pp, cmpp_arg const *arg, cmpp_b * b, cmpp_flag32_t flags); /** This is a helper routine for cmpp_f() implementations which process flags like -f filename. This function treats arg as a filename, consumes that file, and returns 0 on success. The argument's value is expanded using the preprocessor's conventional approaches, using cmpp_arg_interpolate(). HUGE WARNING: this routine necessarily requires recursing into dx, which means that it will INVALIDATE dx->args when it does. It is CRITICAL that the caller pass a second argument which was populated by cmpp_dx_args_clone() and eventually, before returning, pass them to cmpp_args_cleanup(). If name is not NULL then the buffer's contents get replaced with the resolved name of the file. content must not be NULL. If filename lookup passes then it is passed to cmpp_b_reuse() before being streamed the content of the file. If preprocessIt is true then the content will be piped through dx->pp and sent to the content buffer, else it will be consumed as-is into the buffer. */ CMPP_EXPORT int cmpp_arg_consume_as_file( cmpp_dx * dx, cmpp_arg const *arg, cmpp_b *name, cmpp_b * content, bool preprocessIt ); /** 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, behaving as if it were prefixed with dx's current directive prefix if it's not already got one. It processes it with cmpp_process_string(), appending a (possibly trimmed) copy of that output to dest. 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. Special cases and considerations: Case 1: dest is not modified if the call produces no output, and dest is not modified until after the end of the call. That makes it legal for [z,z+n) to live inside dest, but for that to work one would need to: 1) Prefix d with a \0 byte. 2) Append what would normally be passed as z to dest. 3) dest->n = 0 (the magic sauce) 4) In place of (z,n) pass (dest->z+1,dest->n-1). After this call, if dest was not modified then dest->n is still pointing to a NUL-terminated buffer (part of the cmpp_b contract), and if it _is_ updated then it will be overwritten from its starting point, rather than appending after the end of the input string. Reminder to self: we cannot avoid copying the call's output here solely because of the trimming. We can easily trim from dest's RHS but not so easily from its start. If it were not for trimming we could pass dest through as the direct output target, rather than using an intermediary buffer. Bummer. Even so: a potential TODO is optimize out the copy when we're not trimming. However... doing so would rule out Case 1 described above, and we currently use that in at least one place. */ int cmpp_call_str(cmpp *pp, unsigned char const * z, cmpp_strlen_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_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(). */ /* Refactoring crutch */ /* Directive name. The library makes a copy of this. */ char const * name; /** Enforcement of the arity is opt-in, only enforced if cmpp_f_F_ENFORCE_ARITY is in this->flags. See the docs for that flag for more info. */ int arity; struct { /** Callback for the directive's opening tag. */ cmpp_f f; /** Flags from cmpp_f_e. Typically one of cmpp_f_F_ARGS_LIST or cmpp_f_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_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. OTOH, #query does that. */ cmpp_f f; /** Flags from cmpp_f_e. For closers this can typically be left at 0, as few do any work beyond ensuring that they match an opener. */ 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; /** Brief help text. */ char const * usage; }; 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 { \ .name=0,.arity=-1, \ .opener={.f=,.flags=0}, \ .closer={.f=0,.flags=0}, \ .dtor=0,.state=0, \ .usage="Missing cmpp_d::usage"\ } /** 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. This function, for backwards compatility with directive implementations, always adds the cmpp_f_F_NEEDS_DX flag to the opening and closing directives. 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_errf()). 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_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_f_dangling_closer(cmpp_f_args const *args); /** Writes the first n bytes of z to pp's current output channel without performing any @token@ parsing. In the following cases this returns current persistent error code without side-effects: - cmpp_err_has() is not 0 when this is called. The preprocessor rejects all output so long as an unhandled error is pending. - cmpp_dx_current() is non-NULL and that context is in skip-mode. (#if does this.) Returns 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_out_expand() */ CMPP_EXPORT int cmpp_out_raw(cmpp* pp, 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_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's current policy. A policy of cmpp_atpol_OFF behaves exactly like cmpp_out_raw(). Returns dx->pp's persistent error code (0 on success) and sets that code to non-0 on error. This function is a no-op if dx->pp has a non-0 error state, returning that code instead of doing any work. 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_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 (a previous #if/#elif having passed 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_out_expand(cmpp * pp, cmpp_outputer * pOut, unsigned char const * zFrom, cmpp_size_t n, cmpp_atpol_e policy); #if 0 #define cmpp_dx_out_expand(DX,OUT,SRC,N,POLICY) \ cmpp_out_expand((DX)->pp, OUT, SRC, N, POLICY) #define cmpp_dx_out_raw(DX,SRC,N) \ cmpp_out_expand((DX)->pp, SRC, N) #endif /** This creates a formatted string using sqlite3_mprintf() and emits it using cmpp_out_raw(). Returns CMPP_RC_OOM if allocation of the string fails, else it returns whatever cmpp_out_raw() returns. This is a no-op if pp is in an error state, returning that code. */ CMPP_EXPORT int cmpp_outf(cmpp *pp, char const *zFmt, ...); /** va_list counterpart of cmpp_outf(). */ CMPP_EXPORT int cmpp_outfv(cmpp *pp, char const *zFmt, va_list va); /** If pp's output channel has a flush method, it is called and its result returned, else this is a no-op returning 0. */ CMPP_EXPORT int cmpp_flush(cmpp *pp); /** Convenience form of cmpp_delimiter_get() which returns the delimiter which was active at the time when the currently-running cmpp_f() was called. This memory may be invalidated by any calls into cmpp_dx_process(), cmpp_delimiter_set(), or when the cmpp_f() impl returns, 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, but that would indicate library misuse because it always starts with the build-time-default delimiter at the top of the stack. */ 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 pp 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. The pp instance has internally-configured limits on the buffer pool, both for the number of buffers and their total capacity. When the count limit is reached, this function starts returning newly-allocated buffers instead of ones from the pool, which can increase total allocations of the library. The limits are not yet configurable. */ CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *pp); /** 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() invokes 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. If recycling b would take pp over its internal limits on buffer count, b is immediately freed (see cmpp_b_return()). If b->nAlloc is larger than its internal single-buffer capacity or keeping b->z would go over the total capacity limit, b's owned memory is freed (cmpp_b_clear()) but b is retained for further recycling. */ CMPP_EXPORT void cmpp_b_return(cmpp *pp, 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": 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_errf()). 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); /** 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, /** Tells cmpp_args_parse() to unescape special character sequences, as per cmpp_b_unescape(). */ cmpp_args_F_UNESCAPE = 0x02, /** Tells the parser to apply the following hack to {...} tokens: Leading spaces up to the first leading newline are stripped from the token, along with that one newline. A sample case where this is relevant: ``` #define x { blah @foo@ } #expand x {foo -> 1} ``` If we don't do this (to retain backwards compatibility) then such constructs may produce a stray newline where they didn't before. When the library parses directive arguments, directives with the cmpp_f_F_NO_SQUIGGLY_HACK do not get this and all others do. The legacy behavior is retained for directives which predate this addition. */ cmpp_args_F_NO_SQUIGGLY_HACK = 0x04 //cmpp_args_F_SQUIGGLY_HACK = 0x04 }; /** An internal detail of cmpp_args. */ typedef struct cmpp_args_pimpl cmpp_args_pimpl; /** This type is deprecated. It is retained to support the pre-2026-09 directive API changes. It is not applicable to directives whose callbacks use the cmpp_f_F_ARGS_V4 flag. 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 before passing them to any APIs. 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_f(), argument lists do not include their directive's name as an argument because they have it via cmpp_f_args::d. */ unsigned argc; /** This is the Olde Waye of doing this. Its state is undefined for cmpp_args objects passed to cmpp_f() imps on behalf of directives or commands which have the the cmpp_f_F_ARGS_V4 flag. 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 ){...} For directives with the cmpp_f_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. That memory IS INVALIDATED by any APIs which consume input, so copy it if needed or use cmpp_f_F_ARGS_V4 instead (which makes lifetime guarantees). */ cmpp_arg const * 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; /** DEPRECATED. Probably. The need for this goes away with cmpp_f_F_ARGS_V4-style args processing, and this can be moved into the internals (where we currently still need it for the older expression engine). 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 * pp, cmpp_args * pOut, unsigned char const * zInBegin, cmpp_strlen_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); /** DEPRECATED. Probably. The need for this goes away with cmpp_f_F_ARGS_V4-style args processing. A wrapper around cmpp_args_parse() which uses dx->args.z as an input source. This is sometimes convenient in cmpp_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); /** DEPRECATED 2026-08-25: this is not needed for directives which use cmpp_f_F_ARGS_V4, as that flag uses an approach which does not invalidate argument memory when recursing into the library. Once/if older directive code is updated, this function will go away. 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_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); /** 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 *pp, 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(). */ enum cmpp_kav_each_e { /** The key argument should be expanded using cmpp_arg_interpolate() 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_interpolate() 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_interpolate_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_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, f() 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. f() 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 *pp, unsigned char const *zBegin, cmpp_strlen_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags ); #if 0 // untested /** 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. */ XCMPP_EXPORT int cmpp_str_each( cmpp * pp, unsigned char const *zBegin, cmpp_strlen_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags ); #endif /** 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_errf() 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_errf() 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. Newlines are not consider to be spaces (see cmpp_skip_snl()). 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_errf()), 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_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); /** 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. See cmpp_dx_pos_save() and cmpp_dx_pos_restore(). */ 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} /** 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); /** Returns, via its output parameters, the current name and input line number of dx's current directive. This is intended for use with synthesizing names such as those of embedded scripts. The *zName bytes are owned by dx and are valid until it is destroyed. Caveat: this specifically refers to the line holding the current directive, not any non-directive lines consumed since that directive was captured. */ CMPP_EXPORT void cmpp_dx_src_pos_info(cmpp_dx const *dx, char const **zName, cmpp_size_t * lineNo); /** Expects to be passed a non-NULL pointer and its ostensible length. If n<0 then this returns strlen(z) else it returns n. This implements the library's common "if n<0 then use strlen()" convention. */ CMPP_EXPORT cmpp_size_t cmpp_strlen(char const *z, cmpp_strlen_t n); /** Unsigned counterpart of cmpp_strlen(). */ CMPP_EXPORT cmpp_size_t cmpp_strlenu(unsigned char const *z, cmpp_strlen_t n); /** Returns true unless: - !*z - '0'==*z and !z[1] z may be NULL only if n is 0. If non-NULL then it must of course be a valid poiner (even if it's the terminating NUL byte of a string). */ CMPP_EXPORT bool cmpp_simple_truth(unsigned char const *z, cmpp_strlen_t n); /** If [z,n) is unambiguously a base-10 int, sets *pOut (if not NULL) to that value and returns true, else returns false. Warning: because this uses strtol() it may scan more bytes than n. n is used to validate that strtol()'s ending position matches n, not to limit the range of z scanned. This is only a warning if z is shorter than 10 bytes (20 for cmpp_is_int64()) and not NUL-terminated, otherwise it's irrelevant. */ CMPP_EXPORT bool cmpp_is_int(unsigned char const *z, unsigned n, int *pOut); /** 64-bit counterpart of cmpp_is_int() which uses strtoll(). See cmpp_is_int() for a caveat about the scan range. */ CMPP_EXPORT bool cmpp_is_int64(unsigned char const *z, unsigned n, int64_t *pOut); /** Returns true if the string [z,z+n) is valid JSON, per SQLite3's json_valid(theString,flags). If n is negative, strlen() is used to calculate it. This function is a no-op, returning false, if pp has pending error state. That is arguable but it's inherited from lower-level APIs which this one builds on. It is wildly hypothetically possible that this call can put pp into an error state due to db-level issues, but that can be effectively ruled out for the sake of usage sanity. It will always return false in that case but may update pp's error state. Similarly: json_valid() triggers a db error if passed a flags value which is not in the range [1,15]. To avoid that a client doing so puts pp into an error state, this function returns false without side-effects if passed a value out of that range. */ CMPP_EXPORT bool cmpp_json_valid(cmpp *pp, unsigned char const *z, cmpp_strlen_t n, int flags); /** Expects [z,z+n) to point to a single JSON value. If cmpp_json_valid() returns true for the range, it is appended to be as-is, else it is appended as a JSON-quoted string. Returns 0 on success, non-0 (OOM or related range overflow) on error, updating pp's error state if it returns non-0. For cmpp_json_valid() purposes, a flags value of 3 (JSON/JSON5) is used. */ CMPP_EXPORT int cmpp_b_append_json_value(cmpp *pp, cmpp_b *b, unsigned char const *z, cmpp_strlen_t n); /** Returns true if [z,z+n) begins with '[', ends with ']', and cmpp_json_valid() for the same range returns true. i.e. if it's a JSON-format list. Because this checks for '[' and ']' before attempting to parse it, this is very fast for most non-list strings. If allowJson5 is true then it uses SQLite3 json_valid() flags of 3, else flags of 1. */ CMPP_EXPORT bool cmpp_json_is_list(cmpp *pp, unsigned char const *z, cmpp_strlen_t n, bool allowJson5); /** The JSON-object counterpart of cmpp_json_is_list(). */ CMPP_EXPORT bool cmpp_json_is_object(cmpp *pp, unsigned char const *z, cmpp_strlen_t n, bool allowJson5); /** Appends some form of [z,z+n) to str, using strlen() to calculate n if n is negative: - 0==n: append "" (and empty JSON string). - cmpp_json_valid(z,n) is true: append the bytes as-is. - Else append n bytes of z as a JSON-format string using sqlite3_str_appendf()'s `%J` formatter. This returns void because that's how sqlite3_str's APIs work. It will only fail if str gets "too big" (the limits of which varies by str's source but all of which are arguably out of scope for cmpp-sized inputs and outputs). */ void cmpp_str_json_append(cmpp *pp, sqlite3_str * str, unsigned char const * z, cmpp_strlen_t n); /** A proxy for sqlite3_prepare_v2() which uses pp's database, supports sqlite3_str formatting, and updates pp's error state on error. zSql is the SQL string. nSql is the byte length of zSql, or its strlen() if nSql is negative. If pp has error state when this is called, it is a no-op returning that error code. For empty inputs sqlite3_prepare() and friends return success but a NULL *pStmt. This wrapper triggers a CMPP_RC_MISUSE error for that case. */ CMPP_EXPORT int cmpp_stmt_prepare(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, cmpp_strlen_t nSql); /** Variadic form of cmpp_stmt_prepare() which supports sqlite3_str formatting. */ CMPP_EXPORT int cmpp_stmt_preparef(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, ...); /** va_list counterpart of cmpp_stmt_preparef(). */ CMPP_EXPORT int cmpp_stmt_preparev(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, va_list); /** Steps statement q one time. On success it returns SQLITE_ROW or SQLITE_DONE, as per sqlite3_step(). On error it returns another non-0 SQLITE_... code and updates pp's error state with a corresponding CMPP_RC_... code and error string. 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. Only pass true when no result columns need to be fetched. Reminder to self: this must return an SQLITE_... code, not a CMPP_RC_... code, because it's more useful that way (but also slightly confusing). */ CMPP_EXPORT int cmpp_stmt_step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt); /** Resets and clears bindings from q if q is not NULL. Returns 0 if q is NULL or if sqlite3_reset() indicates no error (SQLITE_DONE and SQLITE_ROW are considered to be success codes in this context). If sqlite3_reset() indicates an error and pp's error state is not already set then pp's error state is updated and a non-0 value is returned. */ CMPP_EXPORT int cmpp_stmt_reset(cmpp *pp, 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 suffix for the generated error message. nMsgSuffix is the length of zMsgSuffix, or negative to use strlen() to figure it out. */ CMPP_EXPORT int cmpp_db_rc_v2(cmpp *pp, int dbRc, char const *zMsgSuffix, cmpp_strlen_t nMsgSuffix); /** Older signature for cmpp_db_rc_v2(). */ #define cmpp_db_rc(PP,RC,ZMSG) cmpp_db_rc_v2(PP, RC, ZMSG, -1) /** Proxy for sqlite3_bind_int64() which updates pp's error state on error. */ CMPP_EXPORT int cmpp_stmt_bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); /** Proxy for cmpp_stmt_bind_text() which encodes val as a string and updates pp's error state on error. 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. As of this writing (2026-08-23), arg.c depends on this for expression evaluation. */ CMPP_EXPORT int cmpp_stmt_bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); /** SQL NULL counterpart of cmpp_stmt_bind_int(). */ CMPP_EXPORT int cmpp_stmt_bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col); /** Proxy for sqlite3_bind_text() which updates pp's error state on error. */ CMPP_EXPORT int cmpp_stmt_bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col, unsigned const char * zStr); /** Proxy for sqlite3_bind_text() which updates pp's error state on error. */ CMPP_EXPORT int cmpp_stmt_bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col, unsigned const char *zStr, cmpp_strlen_t len); /** Like cmpp_stmt_bind_textn() but takes a finalizer function as its final argument. Don't use this without fully understanding why. Maintenance reminder: if finalizer is declared as void (*dtor)(void *) then tool/pullthunk.sh is emitting it for the #define cmpp_xyz part but not the cmpp_api_thunk_map() part. Weird. */ CMPP_EXPORT int cmpp_stmt_bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr, cmpp_strlen_t n, cmpp_finalizer_f finalizer); /** The current cmpp_api_thunk::apiVersion value. See cmpp_api_thunk_map. */ #define cmpp_api_thunk_version 20260929 /** This stub object is provided for cmpp interface compatibility with loadable-module-capable builds, but that feature is stripped from this build. */ struct cmpp_api_thunk { uint32_t const thunkVersion; }; #ifdef __cplusplus } /* extern "C" */ #endif #endif /*NET_WANDERINGHORSE_CMPP_H_INCLUDED_*/ #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 "expecting 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 #if !defined(CMPP_SWITCH_FALL_THROUGH) # if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7) /* #define CMPP_USING_GCC gcc v7+ treats implicit 'switch' fallthrough as a warning (i.e. error because we always build with -Wall -Werror -Wextra -pedantic). Similarly curious, clang #defines __GNUC__. _Sigh_. */ # define CMPP_SWITCH_FALL_THROUGH __attribute__ ((fallthrough)) # else # define CMPP_SWITCH_FALL_THROUGH # endif #endif /* /CMPP_SWITCH_FALL_THROUGH */ /** 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 na; \ }; \ typedef struct T T; \ int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min) #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->na, sizeof(MT)); \ } #define cmpp__LIST_T_empty_m {.list=0,.n=0,.na=0} /** 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; \ } /** Internal-use-only flags for use with cmpp_d::flags. These supplement the ones from the public API's cmpp_d_e. */ enum cmpp_f_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_f_F_MASK_INTERNAL = ~cmpp_f_F_MASK, /** If true, and if cmpp_f_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_f_F_ARGS_LIST is also used. */ cmpp_f_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_f_F_FLOW_CONTROL = 0x20000 }; /** A single directive line from an input stream. */ struct CmppDLine { /** 1-based line number in the source input. */ cmpp_size_t lineNo; /** Start of the line within its source input, immediately after the directive delimiter. */ unsigned char const * zBegin; /** Start of the line including the directive delimiter. */ unsigned char const * zReallyBegin; /** 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 \ {.lineNo=0U,.zBegin=0,.zReallyBegin=0,.zEnd=0} /** A "stamp" type to turn arbitrary memory blocks into a recycling list. It can be used to recycle blocks of any size equal to or greater than its own. */ struct CmppRecyclerOverlay { /* Size */ int32_t size; void * next; }; typedef struct CmppRecyclerOverlay CmppRecyclerOverlay; struct CmppRecyclerList { /** Head of recycling list. */ CmppRecyclerOverlay *head; /** The number of entries in this->head. We store this only so that we can set some cap on it. */ cmpp_size_t n; }; typedef struct CmppRecyclerList CmppRecyclerList; #define CmppRecyclerList_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 { /** 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} /** 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 }; struct cmpp_b_library { cmpp_b_list list; /** How/whether this->list is sorted. */ enum cmpp_b_list_e sort; /** Max number of buffers. 0 disables borrowing, always allocating. */ uint32_t limitCount; /** Largest capacity of single buffer to keep. If exceeded, we keep the cmpp_b instance but toss its memory. A value of 0 disables this limit. */ uint32_t limitSingleCapacity; /** Max total of cmpp_b::nAlloc in the buffers. 0 means no limit. */ uint64_t limitCapacity; /** Current cmpp_b::nAlloc in the buffers. */ uint64_t currentCapacity; }; typedef struct cmpp_b_library cmpp_b_library; #define cmpp_b_library_empty_m { \ .list=cmpp_b_list_empty_m, \ .sort=cmpp_b_list_UNSORTED, \ .limitCount=50, \ .limitSingleCapacity=512 * 8, \ .limitCapacity=1024 * 1024 * 4, \ .currentCapacity=0 \ } #if 0 // todo CMPP_PRIVATE int cmpp_b_library_borrow(cmpp_b_library *li); CMPP_PRIVATE void cmpp_b_library_return(cmpp_b_library *li); #endif /** Cleans up and clears li but retains its various limit-related members. */ CMPP_PRIVATE void cmpp_b_library_dtor(cmpp_b_library *li); /** A dynamically-allocated list of cmpp_arg objects. Used primarily by cmmp_args. */ struct CmppArgList{ /** The list of arguments. */ cmpp_arg * list; /** From cmpp_b_borrow(). Holds the argv pointer-array memory. */ cmpp_b * avb; /** Current number of args. */ cmpp_size_t n; /** Allocated slots for args. */ cmpp_size_t na; }; typedef struct CmppArgList CmppArgList; #define CmppArgList_empty_m {.list=0,.avb=0,.n=0,.na=0} /** 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); /** Free all memory owned by li but does not free li. */ CMPP_PRIVATE void CmppArgList_dtor(cmpp *pp, CmppArgList *li); CMPP_PRIVATE int CmppArgList_reserve(cmpp *pp, CmppArgList *li, cmpp_size_t min); /** Returns a list of args suitable for settings as cmpp_f_args::argv, or NULL if li->n==0 or on OOM (in which case pp's error state is updated). If ensureNulTerminated is true then this also redirects each cmpp_arg::z pointer which does _not_ point to NUL-terminated memory to to an internal copy of the original which is guaranteed to be NUL-terminated. (A stepping stone towards removing cmpp_args_pimpl's internal buffer.) The memory is owned by this object and is invalidated on any list operations, so should be set up (with this) immediately before the callback needs them. It is invalidated by CmppArgList_append() or CmppArgList_reserve(). */ CMPP_PRIVATE cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp, CmppArgList*li, bool ensureNulTerminated); /** The internal part of the cmpp_args interface. */ struct cmpp_args_pimpl { /** We need(?) a (cmpp*) here for finalization/recycling purposes. */ cmpp *pp; /** True if these are the arguments for directive [call...] syntax. This is not set for cmpp_itch calls. 2026-08-25: this is being replaced. We currently need it to keep some older bits working. */ bool isDxCall; /** Next entry in the free-list. TODO (2026-08-31): replace with CmppRecyclerOverlay. 2026-09-19: we can't do that unless we give up (or reimplement) recycling of the state reused by cmpp__args_pimpl_reuse(). */ cmpp_args_pimpl * nextFree; /** Version 3 and 4 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, \ .isDxCall = false, \ .nextFree = 0, \ .argli = CmppArgList_empty_m, \ .argOut = cmpp_b_empty_m \ } extern const cmpp_args_pimpl cmpp_args_pimpl_empty; CMPP_PRIVATE 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; /** For use in pinpointing line/column during error reporting. */ unsigned const char * zErrPos; /** Next context up in the stack. When cmpp_dx's share an input range in a parent/child manner, We can use this to provide better stack traces and line/column info by delaying line count until an error happens, discovering (through this pointer) the whole input range, and using cmpp_count_nl() to find the line. It's slow, but (A) we only count lines when generating errors and (B) it's more accurate than the current approach, which is definitely off-by-one (or-more). That said: these don't typically share an input range, but can for [calls]. */ cmpp_dx const * up; /** 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. Thus we have cmpp_dx_pos_save() and cmpp_dx_pos_restore() to manipulate this. Achtung: as of 2026-09-23 this is only updated when we find a directive line. Places needing more precise line counts are encouraged to count them. See c.c:cmpp__dx_guess_lineno(). */ cmpp_dx_pos pos; /** Currently input line. */ CmppDLine dline; /** Number of active #savepoints. */ unsigned nSavepoint; /** Current directive's args. Updated by the parsing and dispatching steps. */ 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 { /** Shadow-define rowid for __FILE__. */ int64_t sidFile; /** Shadow-define rowid for #include path entry. */ int64_t ridInclPath; } shadow; struct { /** True if the next directive is the start of a [call]. */ bool nextIsCall; } flags; }; /** Calculates the whole input range of dx and its parents, for purposes of calculating line counts in. Sets *zBegin and *zEnd to the half-open range. */ CMPP_PRIVATE void cmpp__dx_input_range(cmpp_dx const * dx, unsigned char const **zBegin, unsigned char const **zEnd); /** 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, .zErrPos=0,\ .pos=cmpp_dx_pos_empty_m, \ .up=0, \ .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 = { \ .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; cmpp_d d; }; 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. We use a list of pointers instead of a flat list so that the entries are not invalidated by addition of new directives). */ 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 /** 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. */ cmpp_token k; /* Key part of the kvp. Might be empty. */ cmpp_token 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_strlen_t nKey, CmppKvp_op_e opPolicy); 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. */ cmpp_token open; /** Closing @token@ delimiter. This has no meaning for the directive delimiter. */ cmpp_token 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=cmpp_token_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); } /** 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. This was historically const but that was changed on 2026-08-24 for reasons now stored in the SCM history from when this line was added. */ cmpp_dx * dx; /** Output channel. */ cmpp_outputer out; /** Delimiters version 2. */ struct { /** Directive delimiter. */ cmpp__delim_list d; /** @token@ delimiters. */ cmpp__delim_list at; } delim; /** All frequently-reused queries. */ struct { #define CMPP__SEL_V_FROM(N) \ "(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \ " ORDER BY source LIMIT 1)" /** Name of library-generated savepoints. */ #define CMPP__SAVEPOINT_NAME "_cmpp_" /** 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 CmppStmt_map(E) \ E(sdefIns, \ "INSERT INTO " \ CMPP__DB_MAIN_NAME ".sdef" \ "(t,k,v) VALUES(?1,?2,?3) RETURNING id") \ E(defIns, \ "INSERT INTO " \ CMPP__DB_MAIN_NAME ".def" \ "(t,k,v) VALUES(?1,?2,?3) " \ "ON CONFLICT(k) DO UPDATE SET v=EXCLUDED.v" \ ) \ E(defDelLegacy, \ "DELETE FROM " \ CMPP__DB_MAIN_NAME ".def" \ " WHERE k GLOB ?1") \ E(defDelExact, \ "DELETE FROM " \ CMPP__DB_MAIN_NAME ".def" \ " WHERE k = ?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" \ ")") \ /* Compare two values. */ \ E(cmpVV, "SELECT cmpp_compare(?1,?2)") \ /* Compare LHS define to value ?2. */ \ E(cmpDV, \ "SELECT cmpp_compare(" \ CMPP__SEL_V_FROM(1) ", ?2" \ ")") \ /* Compare value ?1 to RHS define. */ \ E(cmpVD, \ "SELECT cmpp_compare(" \ "?1," CMPP__SEL_V_FROM(2) \ ")") \ /* Compare defines ?1 and ?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;") \ E(jsonValid, \ /* beware of validation of json_valid() ?2 */ \ "SELECT json_valid(?1,?2)") \ #define E(N,S) sqlite3_stmt * N; CmppStmt_map(E) #undef E } stmt; cmpp_errinfo err; /** State for SQL tracing. */ struct { bool expandSql; cmpp_size_t counter; cmpp_outputer out; } sqlTrace; struct { /** 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 ctorFlags; /** 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; /** Set to true by cmpp_call_string() if the being-called string does not start with a directive delimiter. This tells cmpp_next_chunk() to assume that it found a delimiter and immediately treat the rest of the line as a directive line. */ bool fakeNextDelim; /** 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; /** Module state. Maintenance reminder: filtering this out in non-module-capable builds is breaking stuff. */ 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. Must be a non-control character. */ char pathSep; } mod; struct { /** Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return(). */ cmpp_b_library buf; /** Head of the free-list. */ cmpp_args_pimpl * argPimpl; } recycler; }; /** Distinct IDs 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); static inline void cmpp__err_reuse(cmpp *pp){ cmpp_errinfo_reuse(&pp->pimpl->err); } static inline bool cmpp__is_safemode(cmpp const * const pp){ return (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags); } /** Initialize pp->db.dbh. If it's already open or ppCode!=0 then ppCode is returned. */ CMPP_PRIVATE 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 have set the error state. These queries MUST NOT be used in contexts where they may be reached concurrently via recursion. The caller is obligated to cmpp_stmt_reset() (or equivalent) the statement when they're done and NOT to sqlite3_finalize() it. 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. */ CMPP_PRIVATE sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which, bool prepEvenIfErr); /** 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. */ CMPP_PRIVATE 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. */ CMPP_PRIVATE int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid); /** 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. If nVal is not NULL then it is set to the byte length of the returned string, or 0 on error or if no result is found. */ CMPP_PRIVATE char * cmpp__include_search(cmpp *pp, unsigned const char * zKey, cmpp_size_t * nVal); /** Searches pp's list of directives. If found, return it else return NULL. See cmpp__d_search3(). */ CMPP_PRIVATE 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, /** As a fallback, search for a DLL if built with that support and not in safe-mode. */ 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_PRIVATE 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. */ CMPP_PRIVATE int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey, cmpp_strlen_t nKey, bool permitEqualSign); /** Helper for #query and friends. Binds aVal's value to column bindNdx of q. It expands 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. TODO (2026-09-24): add [call] support to this, or a flags arg to specify how to interpolate it, and move it to the public API. */ CMPP_PRIVATE int cmpp__bind_arg(cmpp *pp, 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 did not historically allow it here to avoid confusion with the now-defunct 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. */ CMPP_PRIVATE int cmpp__bind_group(cmpp *pp, 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 (TODO 2026-09-21: get rid of that - we no longer rely on it). If it's negative, strlen() is used to calculate it. */ CMPP_PRIVATE int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_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. */ CMPP_PRIVATE int cmpp__get_bool(cmpp *pp, unsigned const char * zName, cmpp_strlen_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. */ CMPP_PRIVATE int cmpp__get_int(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, int *pOut); #if 0 /** Searches for a define where (k == 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. */ CMPP_PRIVATE int cmpp__get(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, unsigned char **zVal, unsigned int *nVal); #endif /** Searches for a define where (k == zName). If one is found, a copy of it is appended to b. Returns true if a record is found, else false. If enforceUndefPolicy is true, no match is found, and the undefined-policy specifies to, pp's error state is set to reflect the unresolved key. Wonky return semantics. */ CMPP_PRIVATE int cmpp__get_b(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, cmpp_b * b, bool enforceUndefPolicy); #if 0 /** Like cmpp__get_b() but with more conventional return semantics. *gotOne is set to true if a record is found, else false. Returns pp's current error code and does not run the query if pp has error state. */ CMPP_PRIVATE int cmpp__get_b2(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, cmpp_b * b, bool enforceUndefPolicy, bool *gotOne); #endif /** 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. */ CMPP_PRIVATE int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, bool defineIfNoRow, cmpp_b * optionalCaptureBuffer); /** Start a new savepoint for dx. */ CMPP_PRIVATE int cmpp__dx_sp_begin(cmpp_dx * const dx); /** Commit and close dx's current savepoint. */ CMPP_PRIVATE int cmpp__dx_sp_commit(cmpp_dx * const dx); /** Roll back and close dx's current savepoint. */ CMPP_PRIVATE 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. */ CMPP_PRIVATE 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. */ CMPP_PRIVATE int cmpp__d_delayed_load(cmpp *pp, char const *zName); CMPP_PRIVATE void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent); /** For each character in ('(' '{' '[' '"' '\'' '`') return a corresponding closing character (which will be the same as the opener for the string types). */ static inline unsigned char cmpp__closer(unsigned char opener){ switch(opener){ case '(': return ')'; case '[': return ']'; case '{': return '}'; //case '<': return '>'; case '"': case '\'': case '`': return opener; default: return 0; } } /** Expects **zPos to be value valid for cmpp__closer() 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. It does not honor backslash-escaping of the closing token within the content, nor does it ensure that other grouping tokens found between its opening and closing bytes are balanced. Similarly, it does not recognize quoted strings in the content _as such_ so inputs like "{ '}'}" will misbehave. 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. Potential TODO (2026-08-20): make this routine recursive so that it can process, at a minimum, quoted strings within non-quoted-string inputs. */ #define CMPP__FIND_CLOSING 3 #if 2 == CMPP__FIND_CLOSING CMPP_PRIVATE 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) #define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \ cmpp__find_closing2(PP, Z0, Z1, LCPTR) #else /** Works just like cmpp__find_closing2() but honors backslash escaping in that it treats all backslash-escaped content as escaped, not as a closer. */ CMPP_PRIVATE int cmpp__find_closing3(cmpp *pp, unsigned char const **zPos, unsigned char const *zEnd, cmpp_size_t *pNl); #define cmpp__find_closing(PP,Z0,Z1) \ cmpp__find_closing3(PP, Z0, Z1, NULL) #define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \ cmpp__find_closing3(PP, Z0, Z1, LCPTR) #endif static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_strlen_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_strlen_t n){ return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n; } /** "define"s zKey to zVal, recording the value type as tType. */ CMPP_PRIVATE int cmpp__define2(cmpp *pp, unsigned char const * zKey, cmpp_strlen_t nKey, unsigned char const *zVal, cmpp_strlen_t nVal, cmpp_tt_e tType); /** Evals pArgs's arguments as an integer expression. On success, sets *pResult to the value. Returns ppCode. */ CMPP_PRIVATE int cmpp__args_evalToInt(cmpp *pp, cmpp_args const *pArgs, int * pResult); /** Passes the contents of arg through to cmpp__args_evalToInt(). */ CMPP_PRIVATE int cmpp__arg_evalSubToInt(cmpp *pp, 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. */ CMPP_PRIVATE int cmpp__arg_toBool(cmpp * pp, cmpp_arg const *arg, int * pResult, cmpp_arg const **pNext); typedef struct cmpp_argOp cmpp_argOp; typedef void (*cmpp_argOp_f)(cmpp *pp, 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_PRIVATE cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt_e tt); CMPP_PRIVATE char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p); //CMPP_PRIVATE 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, &oMine, &oOld); ...do some work then ALWAYS do... cmpp__outputer_swap(pp, &oOld, NULL); ``` 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. */ CMPP_PRIVATE 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. */ CMPP_PRIVATE int cmpp__lazy_init(cmpp *pp); /* Fatally exits the app with the given printf-style message. */ CMPP_NORETURN CMPP_PRIVATE 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 CMPP_PRIVATE 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. */ CMPP_PRIVATE 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. */ CMPP_PRIVATE 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 itchppCode itch->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_errf(pp, CMPP_RC_SYNTAX, __VA_ARGS__) #define dxserr(...) cmpp_errf(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__) #define cmpp__li_reserve1_size(li,nInitial) \ (li->n ? (li->n==li->na ? li->na * 3 / 2 : li->n+1) : nInitial) #define MARKER(pfexp) \ do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ printf pfexp; \ } while(0) //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(zpimpl->db.dbh : 0); return sqlite3_str_errcode(s) ? ( cmpp_check_oom(pp,NULL), NULL /* s is a static singleton in this case, not leaked */ ) : s; } /** Returns true if z starts with "::" not immediately followed by a NUL. */ static inline bool cmpp__has_ns_prefix(unsigned char const *z){ return (unsigned char)':'==z[0] && (unsigned char)':'==z[1] && 0!=z[2]; } CMPP_PRIVATE int cmpp__tt_for_group_char(int ch); /** Like cmpp_tt_cstr(), but if bSymbolName is false then it returns the higher-level token name, which is NULL for most token types. */ CMPP_PRIVATE char const * cmpp__tt_cstr2(int tt, bool bSymbolName); /** Given UTF8 character value c, this calculates its length, in bytes, writes the UTF-8 encoding to output, and returns the number of bytes written. If the calculated size is >length then -1 is returned (which cannot happen if length>=4). If !output then only the UTF8 length of c is calculated and returned (and the length argument is ignored). Returns -1 if c is not a valid UTF8 character. The most bytes it will ever write to *output is four, so an output buffer of four bytes is sufficient for all encoding cases. It does not NUL-terminate the output unless the character is incidentally a NUL byte. That is the only case in which the output will be NUL-terminated. License note: the above docs and the corresponding code were pulled into this tree by the same person who wrote them for the upstream copy. */ CMPP_PRIVATE int cmpp__utf8_char_to_cstr(unsigned int c, unsigned char *output, cmpp_size_t length); /** For unescaping \uNNNN and \UNNNNNNNN sequences. Requires zPos to point to at least howMany bytes of hex digits and must be a multiple of 2. On success, sets *rv to the decoded hex value and returns the number of bytes consumed. On error, returns -1. */ CMPP_PRIVATE int cmpp__read_hex_bytes(unsigned char const *zPos, unsigned int howMany, unsigned int * rv ); /** Examines [z,z+n), expected to be a freshly-parsed token, to try to determine a more precise token type, which it returns. */ CMPP_PRIVATE int cmpp__tt_tok_op(unsigned char const * z, uint8_t n); #define cmpp__dx_current(PP) (PP)->pimpl->dx /** Empty-initialized cmpp_outputer instance, intended for non-const-copy initialization. */ extern const cmpp_arg cmpp_arg_empty; #endif /* include guard */ char const * cmpp_version(void){ return CMPP_VERSION; } const cmpp_errinfo cmpp_errinfo_empty = cmpp_errinfo_empty_m; 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 = { .thunkVersion = cmpp_api_thunk_version, }; CMPP__EXPORT(bool, cmpp_isspace)(int ch){ return ' '==ch || '\t'==ch; } CMPP__EXPORT(void, cmpp_skip_space)( unsigned char const **p, unsigned char const *zEnd ){ assert( *p <= zEnd ); unsigned char const * z = *p; while( 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[-1]) ) --z; *p = z; } /** Sets pp's error state. */ CMPP__EXPORT(int, cmpp_errf)(cmpp *pp, int rc, char const *zFmt, ...); #define cmpp__err cmpp_errf #define cmpp_dx_err cmpp_dx_errf /* 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 \ {cmpp_token_empty_m,cmpp_token_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) // These == other entries: // E(CMPP_RC_ITCH_begin,,) // E(CMPP_RC_ITCH_end,,) #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__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; } CMPP__EXPORT(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; } 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); } } 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(int,cmpp_slurp_file)(cmpp *pp, char const *fn, cmpp_b * b){ if( pp && ppCode ) return ppCode; int rc; cmpp_FILE * const fp = cmpp_fopen(fn, "rb"); if( fp ){ /* TODO: get file size (seek() to end, tell(), rewind()) and cmpp_b_reserve() enough space. */ rc = cmpp_stream(cmpp_input_f_FILE, fp, cmpp_output_f_b, b); cmpp_fclose(fp); if( rc && pp ){ rc = cmpp_errf(pp, rc, "Error reading file %s", fn); } }else{ rc = cmpp_errf(pp, cmpp_errno_rc(errno, CMPP_RC_IO), "Error opening file %s", fn); } return rc; } bool FileWrapper_chomp(FileWrapper * p){ return cmpp_chomp(p->zContent, &p->nContent); } /** 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 {.name={0,0},.arity=-1,.flags=0,.closer=0,.impl=cmpp_d_impl_empty_m} //static const cmpp_d cmpp_f_empty = cmpp_f_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); /** Searches li for a matching NUL-terminated name. */ 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 = cmpp_errinfo_empty_m, .sqlTrace = { .expandSql = false, .counter = 0, .out = cmpp_outputer_empty_m }, .flags = { .nIncludeDir = 0, .nDxDepth = 0, .nSavepoint = 0, .doDebug = 0, .chompF = 0, .ctorFlags = 0, .isInternalDirectiveReg = false, .nextIsCall = false, .fakeNextDelim = 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_library_empty_m, .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_e 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 ){ cmpp__pimpl * const pi = pp->pimpl; pi->flags.needsLazyInit = false; cmpp__delim_list * li = &pi->delim.d; if( !li->n ) cmpp_delimiter_push(pp, NULL); li = &pi->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; } /** If pp is not in an OOM state then ensure that the at-policy and undefined-policy stacks are allocated and then wipe them. Updates pp's error state on OOM. */ static void cmpp__wipe_policies(cmpp *pp){ if( ppCode!=CMPP_RC_OOM /* b/c we're about to allocate */ ){ 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_opt 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_opt 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.ctorFlags = cfg->flags; if( cfg->dbFile ){ pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile); cmpp_check_oom(pp, pi->db.zName); } 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_reuse(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->err = tmp.err; 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.ctorFlags = tmp.flags.ctorFlags; 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_library_dtor(&pi->recycler.buf) /* Reminder to self: that retains the limits */; 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); } cmpp_errinfo_dtor(&pi->err); *pi = cmpp__pimpl_empty; cmpp_mfree(pp); } } CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){ return pp && 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags); } /** 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 CMPP__EXPORT(void *,cmpp_malloc2)(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(pp, rc, "Flush failed.", -1); } } 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); } CMPP__EXPORT(int, cmpp_flush)(cmpp *pp){ cmpp__pi(pp); if( pi->out.flush ){ return pi->out.flush(pi->out.state); } return 0; } 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_out_raw)(cmpp * pp, void const *z, cmpp_size_t n){ cmpp_dx * const dx = cmpp__dx_current(pp); if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode; return cmpp__out2(pp, &pp->pimpl->out, z, n); } CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){ assert( out ); cmpp_dx const * const dx = cmpp__dx_current(pp); if( dx && cmpp_dx_is_eliding(dx) ) return ppCode; 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 ); cmpp_dx const * const dx = cmpp__dx_current(pp); if( dx && cmpp_dx_is_eliding(dx) ) return ppCode; 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; } 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(zLeftopen.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; /* 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. Minor annoyance: the part of @[...]@ may span lines but are processed as directives, so they have the same limitation that newlines must be either backslash-escaped or be contained within a grouping token. */ if( z>", (int)(zEnd-zb), zb); if( cmpp__find_closing_lc(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; } //g_warn("Found: <<%.*s>>", (int)(zb - z -1), z+1); cmpp_call_str(pp, z+1, (zb - z - 1), 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); } } 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_strlen_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_out_expand)(cmpp * const pp, cmpp_outputer * pOut, unsigned char const * zFrom, cmpp_size_t n, cmpp_atpol_e atPolicy){ cmpp_dx const * const dx = cmpp__dx_current(pp); if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode; return cmpp__out_expand(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)); } CMPP__EXPORT(sqlite3_str *, cmpp_sqlite3_str_new)(cmpp*pp){ sqlite3_str * const s = sqlite3_str_new(pp ? pp->pimpl->db.dbh : 0); return sqlite3_str_errcode(s) ? ( cmpp_check_oom(pp,NULL), NULL /* s is a static singleton in this case, not leaked */ ) : s; } CMPP__EXPORT(char *, cmpp_sqlite3_str_finish)(cmpp *pp, sqlite3_str *s, int * n){ char * z = 0; int const rc = sqlite3_str_errcode(s); if( !ppCode ){ 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; } 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_errf(pp, CMPP_RC_SYNTAX, "Delimiters may not contain " "control characters."); return false; } } if( zB==z ){ cmpp_errf(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 = 0; if( zDelim ){ n = cmpp__strlen(zDelim, -1); if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){ return ppCode; }else if( n>32 /* 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_errf(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_errf(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_errf(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; } CMPP__EXPORT(bool, cmpp_is_int)(unsigned char const *z, unsigned n, int *pOut){ if( !n || n > 10 ) return false; char const * zz = (char *)z; char /*const sigh*/* zEnd = 0; int32_t d = strtol(zz, &zEnd, 10); if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){ if( pOut ) *pOut = d; return true; } return false; } CMPP__EXPORT(bool, cmpp_is_int64)(unsigned char const *z, unsigned n, int64_t *pOut){ if( !n || n > 20 ) return false; char const * zz = (char *)z; char /*const sigh*/ * zEnd = 0; int64_t d = strtoll(zz, &zEnd, 10); if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){ if( pOut ) *pOut = d; return true; } return false; } /** Impl for the -Fx=filename flag. TODO?: refactor to take an optional zVal to make it suitable for the public API. This impl requires that zKey contain "key=filename". */ static int cmpp__set_file(cmpp *pp, unsigned const char * zKey, cmpp_strlen_t nKey){ if(ppCode) return ppCode; CmppKvp kvp = CmppKvp_empty; nKey = cmpp__strlenu(zKey, nKey); if( CmppKvp_parse(pp, &kvp, zKey, nKey, CmppKvp_op_eq1) ){ return ppCode; } sqlite3_stmt * q = 0; FileWrapper fw = FileWrapper_empty; if( cmpp__FileWrapper_open(pp, &fw, (char const *)kvp.v.z, "rb") ){ assert(ppCode); return ppCode; } cmpp__FileWrapper_slurp(pp, &fw); q = cmpp__stmt(pp, CmppStmt_defIns, false); if( q && 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, (int)kvp.k.n) ){ //g_warn("zKey=%.*s", (int)kvp.k.n, kvp.k.z); if( pp->pimpl->flags.chompF ){ FileWrapper_chomp(&fw); } if( fw.nContent ){ cmpp_stmt_bind_textx(pp, q, 3, fw.zContent, (cmpp_strlen_t)fw.nContent, SQLITE_STATIC); fw.zContent = 0 /* transferred ownership */; fw.nContent = 0; }else{ cmpp_stmt_bind_null(pp, q, 2); } cmpp_stmt_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; } int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_t nName){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false); if( q ){ nName = cmpp__strlen(zName, nName); cmpp_stmt_bind_textn(pp, q, 1, ustr_c(zName), nName); if(SQLITE_ROW == cmpp_stmt_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_strlen_t nName){ int rc = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp_stmt_bind_textn(pp, q, 1, zName, nName); assert(0==ppCode); if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){ rc = sqlite3_column_int(q, 0); }else{ rc = 0; cmpp__affirm_undef_policy(pp, zName, nName); } cmpp_stmt_reset(pp, q); } return rc; } int cmpp__get_int(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, int *pOut ){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false); if( q ){ nName = cmpp__strlenu(zName, nName); cmpp_stmt_bind_textn(pp, q, 1, zName, nName); assert(0==ppCode); if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){ *pOut = sqlite3_column_int(q,0); }else{ cmpp__affirm_undef_policy(pp, zName, nName); } cmpp_stmt_reset(pp, q); } return ppCode; } int cmpp__get_b(cmpp *pp, unsigned const char * zName, cmpp_strlen_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_stmt_bind_textx(pp, q, 1, zName, nName, SQLITE_STATIC); int n = 0; if(SQLITE_ROW == cmpp_stmt_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(pp, q); g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n", nName, zName, rc, os->n, os->z)); } return rc; } #if 0 int cmpp__get_b2(cmpp *pp, unsigned const char * zName, cmpp_strlen_t nName, cmpp_b * os, bool enforceUndefPolicy, bool *gotOne){ static bool nope; *(gotOne ? gotOne : &nope) = cmpp__get_b(pp, zName, nName, os, enforceUndefPolicy); return ppCode; } #endif CMPP__EXPORT(int, cmpp_undef_legacy)(cmpp *pp, const char * zKey, unsigned int *nRemoved){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDelLegacy, false); if( q ){ unsigned int const n = strlen(zKey); cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), (cmpp_strlen_t)n, SQLITE_STATIC); cmpp_stmt_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; } CMPP__EXPORT(int, cmpp_undefine)(cmpp *pp, const char * zKey, cmpp_strlen_t n){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDelExact, false); if( q ){ n = cmpp__strlen(zKey, n); cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), n, SQLITE_STATIC); cmpp_stmt_step(pp, q, true); 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_stmt_bind_int(pp, q, 1, priority); cmpp_stmt_bind_textx(pp, q, 2, ustr_c(zDir), -1, SQLITE_STATIC); int const rc = cmpp_stmt_step(pp, q, false); if( SQLITE_ROW==rc ){ ++pp->pimpl->flags.nIncludeDir; if( pRowid ){ *pRowid = sqlite3_column_int64(q, 0); } } cmpp_stmt_reset(pp, 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_v2(pp, rc, sqlite3_sql(q), -1); } cmpp_stmt_reset(pp, q); g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid)); } return ppCode; } //%cmpp if not defined strip.module 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 /if 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_strlen_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; } static cmpp_size_t cmpp__dx_guess_lineno(cmpp_dx const * dx, unsigned char const * zWhere){ cmpp__dx_pi(dx); unsigned char const *zB = 0; unsigned char const *zE = 0; if( !zWhere ) zWhere = dpi->zErrPos; cmpp__dx_input_range(dx, &zB, &zE); //g_warn("Whole script:\n%.*s", (int)(zE-zB), zB); assert( zB && zE && zB<=zE ); if( !zWhere ){ if( dpi->pos.zpos.z>=zB ){ /* This is frequently at zEnd because we read line by line and it can be at EOF at the end of that line. Similarly, [call...] contexts have a virtual EOF at the ']'. */ zWhere = dpi->pos.z; }else{ zWhere = dpi->dline.zBegin; } } return zWhere ? 1 + cmpp_count_nl(zB, zE, zWhere, NULL) : dpi->dline.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 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_errf(dx, CMPP_RC_MISUSE, "The directive delimiter stack is empty."); } bool const isCall = dxp->flags.nextIsCall /* If true then this call is in response to cmpp_call_str(). This lets us treat the whole input as arguments to the directive, rather than looking for an EOL. This allows @[call...]@ to span lines without backslash-escaping them. */; 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; #if 0 cmpp_size_t const prevLineNo = dxp->dline.lineNo; unsigned char const * const zPrevDLineBegin = (dxp->dline.zBegin >= dxp->zBegin && dxp->dline.zBegin<=zEnd) ? dxp->dline.zBegin : 0; #endif assert(zD); assert(nD); while( zzEnd ) z=zEnd; \ if( z>zLeft && cmpp_out_expand(dx->pp, &pi->out, zLeft, \ (cmpp_size_t)(z-zLeft), \ cmpp_atpol_CURRENT) ){ \ 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. */; bool const alreadyGotDelim = pi->flags.fakeNextDelim; pi->flags.fakeNextDelim = false; assert( alreadyGotDelim ? isCall : 1 ); /*if( alreadyGotDelim ){ g_warn("alreadyGotDelim: %.*s", (int)(zEnd-z), z); }*/ if( 0 ){ g_warn("scanning... <<%.*s...>>", (zEnd-z)>20?20:(zEnd-z), z); } while( zzReallyBegin = z; goto got_delim; } if( !atBOL ){ /* We're continuing the scan of a line on which the first bytes didn't match a delimiter. */ while( z>", (zEnd-z) > 20 ? 20 : (zEnd-z), z); } /* We're at BOL. Check for a delimiter with optional leading spaces. */ unsigned char const * const zBeforeSpace = z; 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. */ z += skip; 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). */ if( z>zLeft ){ /* Flush up to, but not including, any spaces we skipped. */ unsigned char const * const zx = z; if( zBeforeSpacezReallyBegin = z; z += nD /* skip the delimiter */; got_delim: /* Set up dx->pimpl->dline to encompass the whole directive line sans delimiter and leading spaces. */ #if 0 /* Only count from the previous dline->zBegin. Nope. This is buggy with: #query ... #include ... #/query because #query captures the body and re-processes it. */ if( zPrevDLineBegin ){ dline->lineNo = prevLineNo + cmpp_count_nl(zPrevDLineBegin, z, z, NULL); }else{ dline->lineNo = cmpp__dx_guess_lineno(dx, z); } #else dline->lineNo = cmpp__dx_guess_lineno(dx, z); #endif dxp->pos.lineNo = dline->lineNo; 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. */; /* Now find the end of the line or EOF, accounting for backslash-escaped newlines and _not_ requiring backslashes to escape newlines inside of {...}, (...), or [...]. */ cmpp_tizer tz = cmpp_tizer_empty; cmpp_tizer_init(&tz, &pi->err, z, (zEnd-z)); int tzrc; unsigned int nTok = 0; cmpp_tizer_foreach3(&tz, tzrc, cmpp_tizer_next_SKIP_C_COMMENTS){ #if 0 if( 0 ){ g_warn("tok=%s %.*s", cmpp_tt_cstr(tz.token.ttype), (int)tz.token.n, tz.token.z); } #endif if( cmpp_tt_EOL==tz.token.ttype ){ if( !isCall ){ tz.zPos = tz.token.z /* we assert this below */; break; } }else{ ++nTok; } } if( tzrc ){ assert( dxppCode && "Expecting via tz.err" ); return dxppCode; } if( !nTok ){ 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. cpp does that and i consider it a serious flaw. In the context of cpp's early history that made some sense but it does not here. */ break; } z = tz.zPos; if(0){ g_warn("tokenized: %.*s", (z - dline->zBegin), dline->zBegin); } 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 ); 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_strlen_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->na : 0==li->na ); if( 0==ppCode && 0==CmppLvlList_reserve(pp, li, cmpp__li_reserve1_size(li,5)) ){ p = li->list[li->n]; if( !p ){ p = cmpp_malloc2(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_errf(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->na ){ cmpp_mfree( li->list[--li->na] ); } cmpp_mfree(li->list); *li = CmppLvlList_empty; } static inline void CmppDList_entry_clean(CmppDList_entry * const e){ assert( e && "Else internal API misuse." ); 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->na ){ CmppDList_entry * const dl = li->list[--li->na]; if( dl ){ CmppDList_entry_clean( dl ); cmpp_mfree( dl ); } } 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; uint32_t const minN = eL->d.name.n < eR->d.name.n ? eL->d.name.n : eR->d.name.n; int const rc = memcmp(eL->d.name.z, eR->d.name.z, minN); return rc ? rc : (int)eL->d.name.n - (int)eR->d.name.n; } 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->na : 0==li->na ); if( 0==ppCode && 0==cmpp_array_reserve(pp, (void **)&li->list, cmpp__li_reserve1_size(li, 15), &li->na, sizeof(p)) ){ p = li->list[li->n]; if( !p ){ li->list[li->n] = p = cmpp_malloc2(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 > 32 ){ /* ^^^^^^^^^ See notes itch.c:cmpp_itch_enum_search() */ 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); 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) ){ 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->na : 0==li->na ); if( 0==ppCode && 0==cmpp_array_reserve(pp, (void **)&li->list, cmpp__li_reserve1_size(li,4), &li->na, 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->na ) cmpp__delim_cleanup(li->list + --li->na); cmpp_mfree(li->list); *li = cmpp__delim_list_empty; } void cmpp__dx_input_range(cmpp_dx const * dx, unsigned char const **zBegin, unsigned char const **zEnd){ cmpp_dx const * up; while( (up = dx->pimpl->up) ){ /* If up lives in the same byte range, use it for purposes of figuring out the source range. */ if( dx->pimpl->zBegin>=up->pimpl->zBegin && dx->pimpl->zEnd<=up->pimpl->zEnd ){ dx = up; }else{ break; } } *zBegin = dx->pimpl->zBegin; *zEnd = dx->pimpl->zEnd; } CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){ if( dxppCode ) return dxppCode; cmpp__dx_pimpl * const dpi = dx->pimpl; CmppDLine * const dline = &dpi->dline; if( !dpi->zBegin ){ *pGotOne = false; return 0; } assert(dpi->zEnd); assert(dpi->zEnd > dpi->zBegin); *pGotOne = false; dpi->zErrPos = dline->zBegin; cmpp_dx__reset(dx); bool foundDelim = false; if( cmpp__dx_delim_search(dx, &foundDelim) || !foundDelim ){ return dxppCode; } if( cmpp__args_init(dx->pp, &dpi->args) ){ return dxppCode; } cmpp_skip_space( &dline->zBegin, dline->zEnd ); g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n", dline->lineNo, (int)(dline->zEnd-dline->zBegin), dline->zBegin)); /* Normalize the directive's line and parse arguments */ const unsigned lineLen = (unsigned)(dline->zEnd - dline->zBegin); if(!lineLen){ return cmpp_dx_err(dx, CMPP_RC_SYNTAX, "Line #%u has no directive after %s", dline->lineNo, cmpp_dx_delim(dx)); } unsigned char const * zi = dline->zBegin /* Start of input */; unsigned char const * ziEnd = dline->zEnd /* Input EOF */; cmpp_b * const bufLine = cmpp_b_reuse(&dpi->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", dline->lineNo, lineLen, lineLen, dline->zBegin)); if( 0 ){ g_warn("Directive @ line %u len=%u <<%.*s>>\n", dline->lineNo, lineLen, lineLen, dline->zBegin); } //memset(bufLine->z, 0, bufLine->nAlloc); //#define out(CH) if(zo==zoEnd) break; (*zo++)=CH #define out(CH) assert(zozErrPos = zi; /* Write the line to bufLine for the upcoming args parsing to deal with. Strip backslashes from backslash-escaped newlines. We leave the newlines intact so that downstream error reporting can get more precise location info. Backslashes which do not precede a newline are retained. 2026-09-01: we really don't want this for V4 args, but at this point we don't know which directive we're looking at, so don't know if it has v4-style args. It "would be cool" to move the search for the directive up, "the problem" being that we have to NUL-terminate it for the search so we need bufLine, anyway. */ switch((int)*zi){ case (int)'\\': if( !zi[1] || zi==ziEnd-1 ){ // special case: ending input with a backslash out(*zi); zEsc = 0; }else if( zEsc ){ assert( zEsc==zi-1 ); /* Put them both back. */ out(*zEsc); out(*zi); zEsc = 0; }else{ zEsc = zi; } break; case (int)'\n': out(*zi); zEsc = 0; break; default: if(zEsc){ assert( zEsc==zi-1 ); out(*zEsc); zEsc = 0; } out(*zi); break; } } if( zo>=zoEnd ){ return cmpp_dx_err(dx, CMPP_RC_RANGE, "Ran out of argument-processing space."); } bufLine->n = (cmpp_size_t)(zo - bufLine->z); if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z); *zo = 0; #undef out if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z); /* Line has now been normalized into bufLine->z. */ //unsigned char *zNul = 0; 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", cmpp__dx_guess_lineno(dx, 0), (int)bufLine->n, bufLine->z); } bool const v4 = (cmpp_f_F_ARGS_V4 & dx->d->flags); //if( v4 && zNul ) *zNul = ' '/*undo the NULL terminator*/; dpi->zErrPos = 0; assert( zDirective == bufLine->z ); const bool isDxCall = dpi->args.pimpl->isDxCall = dpi->flags.nextIsCall; dpi->flags.nextIsCall = dx->pp->pimpl->flags.nextIsCall = false; if( isDxCall ){ if( cmpp_f_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_f_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( v4 ){ /* For V4 ignore all of that writing to bufLine (except that we needed it in order to find the directive). a) Include the directive name in the args. b) We also want the cmpp_arg::token entries to point back into dx's memory so that we can us it to calculate error locations precisely. */ dx->args.z = dline->zBegin; dx->args.nz = dline->zEnd - dline->zBegin; }else 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( !dpi->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, &dpi->buf.argsRaw, dx->args.z, dx->args.nz) ){ return dxppCode; } } assert( !dpi->args.arg0 ); assert( !dpi->args.argc ); assert( !dpi->args.pimpl->argOut.n ); assert( !dpi->args.pimpl->argli.n ); assert( dx->args.z ); if( !cmpp_dx_is_eliding(dx) || 0!=(cmpp_f_F_FLOW_CONTROL & dx->d->flags) ){ if( (cmpp_f_F_ARGS_LIST & dx->d->flags) || (v4 && !(cmpp_f_F_ARGS_RAW & dx->d->flags)) ){ cmpp_dx_args_parse(dx, &dpi->args); //g_warn("parsed arg rc=%d count=%d", dxppCode, dpi->args.argc); assert( v4 ? (dpi->args.argc>0 || dxppCode) : 1 ); if( v4 ){ assert( dpi->args.argc>0 || dxppCode ); dpi->args.arg0 = 0; } }else if( cmpp_f_F_ARGS_RAW & dx->d->flags ){ /* Treat rest of line as one token */ if( !dpi->buf.argsRaw.z /*ensure it's alloced to simplify directives which use it. We won't need this if/when we can get all of the directives ported from ARGS_LIST to ARGS_V4, as we can then instead set arg->z="" below. .*/ && cmpp_b_append4(dx->pp, &dpi->buf.argsRaw, "", 0) ){ return dxppCode; } cmpp_arg * const arg = CmppArgList_append(dx->pp, &dpi->args.pimpl->argli); if( !arg ) return dxppCode; arg->ttype = cmpp_tt_RawLine; dpi->args.arg0 = arg; dpi->args.argc = 1; assert( dpi->buf.argsRaw.z ); arg->z = dpi->buf.argsRaw.n ? dpi->buf.argsRaw.z : ustr_c(""); arg->n = dpi->buf.argsRaw.n; //g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z); } } if( 0==dxppCode ){ dx->args.arg0 = dpi->args.arg0; dx->args.argc = dpi->args.argc; *pGotOne = true; } return dxppCode; } 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_f_F_CALL_ONLY & r->opener.flags); if( ppCode ){ goto end; } if( (cmpp_f_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags)) && (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags) ){ cmpp__err(pp, CMPP_RC_ACCESS, "Directive %s%s flag cmpp_f_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 cmpp_size_t const nName = strlen(r->name); if( !pp->pimpl->flags.isInternalDirectiveReg && !cmpp__is_legal_key(ustr_c(r->name), nName, NULL, false) ){ 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.f = 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_f_F_MASK; } e1->d.flags |= cmpp_f_F_NEEDS_DX; e1->zName = sqlite3_mprintf("%.*s", (int)nName, r->name); if( 0==cmpp_check_oom(pp, e1->zName) ){ e1->d.name.z = e1->zName; e1->d.name.n = nName; if( r->closer.f && (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){ e2->d.impl.f = 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_f_F_MASK; } e2->d.flags |= cmpp_f_F_NEEDS_DX; 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{ /* TODO (2026-09-22): delay sort under search. Except then the list is const. */ 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_ATPOL_OFF & 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_v2)( cmpp_dx * const dx, cmpp_outputer * const os, cmpp_d const * const * const dClosers, unsigned nClosers, cmpp_flag32_t flags, cmpp_dx_consumed * const con){ memset(con, 0, sizeof(*con)); con->beginDirective = dx->pimpl->dline.zBegin; con->beginContent = dx->pimpl->pos.z; assert( con->beginContent > con->beginDirective ); con->dOpen = dx->d; int const rc = cmpp_dx_consume(dx, os, dClosers, nClosers, flags); con->endContent = dx->pimpl->dline.zReallyBegin /* of the closer */; con->endDirective = dx->pimpl->pos.z; con->dClose = dx->d; return rc; } 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; } 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_errf(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_errf(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; } #if 0 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; } #endif 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_stmt_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_stmt_step(pp, q, true) ){ q = cmpp__stmt(pp, CmppStmt_spRelease, true); if( q && SQLITE_DONE==cmpp_stmt_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_stmt_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)); } #if 2 == CMPP__FIND_CLOSING 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 const closer = cmpp__closer(opener); int count = 1; int rc = 0; if( !closer ){ return cmpp_errf(pp, CMPP_RC_MISUSE, "Invalid character (0x%02x) for %s().", (int)opener, __func__); } 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"); rc = cmpp__err(pp, CMPP_RC_SYNTAX, "Unbalanced %c%c: %.*s", opener, closer, (int)(z-*zPos), *zPos); assert( CMPP_RC_SYNTAX==rc ); }else{ if( 0 ){ g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos); } *zPos = z; } return rc; } #else int cmpp__find_closing3(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 const closer = cmpp__closer(opener); bool esc = false; int count = 1; int rc = 0; if( !closer ){ return cmpp_errf(pp, CMPP_RC_MISUSE, "Invalid character (0x%02x) for %s().", (int)opener, __func__); } for( ++z ; z < zEnd; ++z ){ switch( *z ){ case '\\': esc = !esc; continue; case '\n': if( pNl && '\n'==*z ) ++*pNl; CMPP_SWITCH_FALL_THROUGH; default: if( esc ){ esc = 0; }else if( closer == *z && 0==--count ){ /* Have to check this first for the case of "" and ''. */ goto out; }else if( opener == *z ){ ++count; } } } out: if( z>=zEnd || count ){ if( 0 ){ g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z, (zEnd - *zPos), *zPos); g_warn("Closer=captured: <<%.*s>>", (z - *zPos), z); } //assert(!"here"); rc = cmpp__err(pp, CMPP_RC_SYNTAX, "Unbalanced %c%c: %.*s", opener, closer, (int)(z-*zPos), *zPos); assert( CMPP_RC_SYNTAX==rc ); }else{ if( 0 ){ g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos); } *zPos = z; } return rc; } #endif cmpp_tt_e cmpp__tt_for_sqlite(int sqType){ cmpp_tt_e rv; switch( sqType ){ case SQLITE_INTEGER: rv = cmpp_tt_IntDec; break; case SQLITE_NULL: rv = cmpp_tt_Null; break; default: rv = cmpp_tt_StringDQ; break; } return rv; } int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, bool defineIfNoRow, cmpp_b * bCol0){ 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; if( bCol0 ) cmpp_b_reuse(bCol0); 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_e const ttype = cmpp__tt_for_sqlite(sqlite3_column_type(q,i)); cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype); if( bCol0 ){ cmpp_b_append4(pp, bCol0, zVal, nVal); } }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_reuse(pp); } return NULL; } e = CmppDList_search(&pp->pimpl->d.list, zName); } #endif if( e ) d = &e->d; } return d; } CMPP__EXPORT(int,cmpp_dx_process)(cmpp_dx * const dx){ if( dxppCode ) return dxppCode; cmpp_d const * const d = cmpp_dx_d(dx); assert( d ); if( cmpp_dx_is_eliding(dx) && !(d->flags & cmpp_f_F_FLOW_CONTROL) ){ return dxppCode; } cmpp_flag32_t const callFlags = 0/*TODO?*/; bool const isDxCall = dx->pimpl->args.pimpl->isDxCall; if( (cmpp_f_F_NOT_IN_SAFEMODE & d->flags) && (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.ctorFlags) ){ return cmpp_dx_err(dx, CMPP_RC_ACCESS, "Directive %s%s is disabled by safe mode.", cmpp_dx_delim(dx), dx->d->name.z); } /* Reminder to self: directives which have modes predating cmpp_f_F_ARGS_V4 _must_ have their args memory pointing into the massaged copy of the argument line, not the original source memory, because that one has backslash-escaped newlines removed and pre-V4 directive impls rely on the library to have removed them. */ #define argsReallyCommon .pp=dx->pp,.dx=dx,.d=d # define argsCommon argsReallyCommon if( cmpp_f_F_ARGS_V4 & d->flags ){ /** ================ V4 ================ */ cmpp_args args = dx->pimpl->args /* transfer ownership. We can't do this for pre-v4 arg modes. */; dx->pimpl->args = cmpp_args_empty; args.arg0 = NULL /*pre-v4 baggage*/; assert( d->impl.f ); assert( args.pimpl ); assert( args.argc && "V4 gets the directive name as arg[0]" ); if( (d->flags & cmpp_f_F_ENFORCE_ARITY) && d->arity>=0 && (unsigned)d->arity!=dx->pimpl->args.argc - 1 /* -1 for the command name*/ ){ cmpp_errf( dx->pp, CMPP_RC_MISUSE, "Invalid argument count (%d) for directive %s. " "Usage: %s", (int)dx->pimpl->args.argc, d->name.z, d->usage ? d->usage : "" ); goto bail; } CmppArgList * const ali = &args.pimpl->argli; assert( ali->n==args.argc && "V4 these should not differ in v4" /* They can differ in pre-v4 because of the old expression engine's token-snipping */ ); unsigned char const * const zRaw = /* token source range */ dx->args.z; assert( zRaw ); cmpp_size_t const nRaw = dx->args.nz; assert( nRaw && "V4 gets the directive name as argv[0]" ); if( cmpp_f_F_ARGS_RAW & d->flags ){ /** =========== V4/RAW ========== */ cmpp_f_args const fargs = { argsCommon, .argc = ali->n, .argv = 0, .flags = callFlags, .isDxCall = isDxCall, .raw = {.z = zRaw, .n = nRaw} }; d->impl.f(&fargs); }else{ /** ========== V4 cooked ========== */ /* 2026-09-04 checkout [d28f326f116f8]. Bug somewhere. This block is working around a reproducible legitimate assert if we do _not_ take ownership of ali->avb. That "should work" (it does in itch.c) but it's inexplicably leading to a legitimate assert via: cmpp_dtor() => cmpp__args_pimpl_cleanup() => CmppArgList_dtor() => cmpp_b_return() for the buffer ali->avb. Reproducer: gdb --args ./c-pp -I. -I./src -Dsrcdir=./src \ -o libcmpp.c ./tool/libcmpp.c-pp.c If, below, we leave ali->avb in place and skip the cmpp_b_return() of it (which is fine, as ali owns that buffer), then the repro triggers. Under the hood that is doing literally the exact same thing except that the lifetimes of the buffers (ba, ali->avb) differ. That suggests that ali->avb is being used uninitialized or being returned without setting it to 0 somewhere, but my eyes are not seeing that anywhere. Maybe we have a lingering misuse somewhere unrelated which is stomping it. Valgrind is happy, which implies that such hypothetically misuse would be well within our rights to do (as opposed to stomping unallocated memory). Hmm. */ cmpp_arg const ** const argv = CmppArgList_makeArgv(dx->pp, ali, true); if( !argv ) goto bail; cmpp_b * const ba = ali->avb; ali->avb = 0; assert( ba && "argv lives in here and argv[X]->z might" ); cmpp_f_args const fargs = { argsCommon, .argc = ali->n, .argv = argv, .flags = callFlags, .isDxCall = isDxCall, .raw = {.z = zRaw, .n = nRaw} }; d->impl.f(&fargs); cmpp_b_return(dx->pp, ba); } bail: cmpp_args_cleanup(&args); }else{ /* V1..3 modes get/handle the args via the legacy dx->args, with the caveat that directives which consume from the input stream invalidate that memory (consumed directives each overwrite it). */ assert(d->impl.f); cmpp_b * const braw = cmpp_b_borrow(dx->pp) /* Ensure that the raw args memory remains valid for the life of the call. */; if( braw ){ cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw); cmpp_f_args const fargs = { argsCommon, .argc = 0, .argv = 0, .flags = callFlags, .isDxCall = isDxCall, .raw = {.z = braw->z ? braw->z : ustr_c(""), .n = braw->n} }; d->impl.f(&fargs); cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw); cmpp_b_return(dx->pp, braw); } } #undef argsCommon #undef argsReallyCommon 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_strlen_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_strlen_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_strlen_t nIn ){ if( !nIn || (nIn<0 && !*zIn) ) return 0; if( 0==cmpp__db_init(pp) ){ if( !zName ) zName = ""; cmpp_dx * 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.up = oldDx; dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall; pp->pimpl->flags.nextIsCall = false; if( dxp.flags.nextIsCall ){ assert( pp->pimpl->dx ); dxp.pos.lineNo = oldDx->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); assert( !dxp.pos.z ); 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 ){ if( os->n ){ 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_strlen_t n, cmpp_b * dest, cmpp_flag32_t flags ){ if( ppCode ) return ppCode; cmpp_b * const bo = cmpp_b_borrow(pp); cmpp_outputer oB = cmpp_outputer_b; if( !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_errf(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); pi->flags.fakeNextDelim = (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, pretend that it does by telling the pending cmpp_dx_next() that we have already parsed the delimiter, causing it to treat the first token as a directive name. */; pi->flags.nextIsCall = true /* Convey (indirectly, via cmpp_process_string()) that the first cmpp_dx_next() call made via the upcoming cmpp_process_string() is considered a call context. */; cmpp_outputer oOld = cmpp_outputer_empty; cmpp__outputer_swap(pp, &oB, &oOld); cmpp_process_string(pp, "call"/*if we knew that z was NUL-terminated then we could use it as the name*/, z, n); cmpp__outputer_swap(pp, &oOld, NULL); assert( !pi->flags.nextIsCall || ppCode ); assert( !pi->flags.fakeNextDelim || ppCode ); pi->flags.nextIsCall = pi->flags.fakeNextDelim = 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, (cmpp_size_t)(zzEnd-zz)); } } end: cmpp_b_return(pp, bo); 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; } CMPP__EXPORT(cmpp_size_t, cmpp_count_nl)(unsigned char const *zBegin, unsigned char const * const zEnd, unsigned char const * const zPos, cmpp_size_t *pCol){ assert( zPos >= zBegin ); assert( zPos <= zEnd ); unsigned char const * z = zBegin; cmpp_size_t nl = 0, col = 0; while( z < zPos ){ switch( *z ){ case '\n': ++nl; col = 0; break; default: ++col; break; } ++z; } if( pCol ) *pCol = col; return nl; } 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 ){ *zMsg = ppCode ? pp->pimpl->err.zStatic : 0; } return ppCode; } #if 0 CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){ int const rc = ppCode; if( rc ){ *zMsg = (char *)pp->pimpl->err.b.z; pp->pimpl->err = cmpp_errinfo_empty_m; }else{ *zMsg = 0; } return rc; } #endif 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){ cmpp__dx_pi(dx); unsigned char const *zB = 0; unsigned char const *zE = 0; unsigned char const *zErr = dpi->zErrPos; cmpp__dx_input_range(dx, &zB, &zE); //g_warn("Whole script:\n%.*s", (int)(zE-zB), zB); assert( zB ); assert( zE ); assert( zB<=zE ); if( !zErr ){ if( dpi->pos.z>=zB && dpi->pos.zpos.z; }else{ zErr = dpi->dline.zBegin; } } //g_warn("dpi->zErrPos: %p\n%.3s ...", dpi->zErrPos, dpi->zErrPos); //g_warn("dpi->pos.z: %p\n %.3s ...", dpi->pos.z, dpi->pos.z); //g_warn("zErr: %p\n%.3s ...", zErr, zErr); assert( (!zErr || (zErr>=zB && zErr<=zE)) && "Else we're pointing to incompatible sources"); cmpp_size_t const nl = zErr ? 1 + cmpp_count_nl(zB, zE, zErr, NULL) : dpi->dline.lineNo; if( dx->d ){ sqlite3_str_appendf(sstr, "%s @ %s:%d", dx->d->name.z, (dx->sourceName && 0==strcmp("-", (char const *)dx->sourceName)) ? "" : (char const *)dx->sourceName, (int)dpi->dline.lineNo); } if( nl != dpi->dline.lineNo ){ /* Input position past the directive line. */ sqlite3_str_appendf(sstr, " from line %" CMPP_SIZE_T_PFMT, nl); } } CMPP__EXPORT(void, cmpp_errinfo_dtor)(cmpp_errinfo *err){ cmpp_b_clear(&err->b); *err = cmpp_errinfo_empty; } CMPP__EXPORT(cmpp_errinfo *, cmpp_errinfo_reuse)(cmpp_errinfo *err){ cmpp_b_reuse(&err->b); err->zStatic = 0; err->code = 0; return err; } CMPP__EXPORT(int,cmpp_errinfo_set)(cmpp_errinfo *err, int rc, char const *zMsg, cmpp_strlen_t nMsg){ if( err ){ cmpp_errinfo_reuse(err); if( rc && zMsg && *zMsg ){ if( cmpp_b_append_str(&err->b, zMsg, nMsg) ){ rc = err->b.errCode; } } err->code = rc; } return rc; } int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt, va_list va){ if( err ){ cmpp_errinfo_reuse(err); err->code = 0 /* some APIs become no-ops if pp->pimpl->err.code is set, so delay setting this. */; if( 0==rc ) return rc; if( CMPP_RC_OOM==rc ){ oom: err->zStatic ="An allocation failed."; return err->code = CMPP_RC_OOM; } assert( !err->zStatic ); if( zFmt && *zFmt ){ sqlite3_str * sstr = sqlite3_str_new(0); sqlite3_str_vappendf(sstr, zFmt, va); int const nz = sqlite3_str_length(sstr); char * const z = sqlite3_str_finish(sstr); sstr = 0; if( !z ){ goto oom; } if( err->b.z ){ cmpp_b_append_str(&err->b, z, nz); sqlite3_free(z); if( err->b.errCode ) goto oom; }else{ err->b.z = (unsigned char*)z; err->b.n = (cmpp_size_t)nz; err->b.nAlloc = err->b.n + 1/*NUL byte*/; } err->zStatic = (char const *)err->b.z; }else{ err->zStatic = "No error info provided."; } err->code = rc; } return rc; } int cmpp_errinfo_setf(cmpp_errinfo *err, int rc, char const *zFmt, ...){ if( err ){ va_list va; va_start(va, zFmt); rc = cmpp_errinfo_setv(err, rc, zFmt, va); va_end(va); } return rc; } CMPP__EXPORT(int, cmpp_err)(cmpp *pp, int rc, char const *zMsg, cmpp_strlen_t nMsg){ if( pp ){ if( rc ){ nMsg = cmpp__strlen(zMsg, nMsg); rc = cmpp_errf(pp, rc, "%.*s", (int)nMsg, zMsg) /* Reminder to self: we have to go through cmpp_errf(), instead of cmpp_errinfo_set(), so that error location information gets tacked on. */; }else{ cmpp_errinfo_reuse(&pp->pimpl->err); } } return rc; } int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list va){ if( pp ){ cmpp_errinfo * const err = &pp->pimpl->err; cmpp_errinfo_reuse(err); assert( 0==err->code ) /* some APIs become no-ops if pp->pimpl->err.code is set, so delay setting this. */; if( 0==rc ) return rc; if( CMPP_RC_OOM==rc ){ oom: err->zStatic ="An allocation failed."; return err->code = CMPP_RC_OOM; } assert( !err->zStatic ); if( pp->pimpl->dx || (zFmt && *zFmt) ){ sqlite3_str * const sstr = cmpp__sqlite3_str_new(pp); if( !sstr ) goto oom; 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."); } int nz = 0; char * const z = cmpp_sqlite3_str_finish(pp, sstr, &nz); if( !z ) goto oom; #if 0 cmpp_b_append(&err->b, z, nz); sqlite3_free(z); #else if( err->b.z ){ if( cmpp_b_append4_str(pp, &err->b, z, nz) ){ sqlite3_free(z); goto oom; } sqlite3_free(z); }else{ /* Transfer ownership. */ err->b.z = (unsigned char*)z; err->b.n = (cmpp_size_t)nz; err->b.nAlloc = err->b.n + 1 /*NUL terminator*/; } #endif err->zStatic = (char *)err->b.z; }else{ err->zStatic = "No error info provided."; } err->code = rc; } return rc; } //CMPP_WASM_EXPORT no - variadic int cmpp_errf(cmpp *pp, int rc, char const *zFmt, ...){ if( pp ){ va_list va; va_start(va, zFmt); //rc = cmpp_errinfo_setv(&pp->pimpl->err, rc, zFmt, va); rc = cmpp_errfv(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; } #undef cmpp_dx_err //CMPP_WASM_EXPORT no - variadic int cmpp_dx_errf(cmpp_dx *dx, int rc, char const *zFmt, ...){ if( rc ){ if( !dx->pimpl->zErrPos ){ dx->pimpl->zErrPos = dx->pimpl->dline.zBegin; //g_warn("dx->pimpl->dline.zBegin: %.3s...", dx->pimpl->dline.zBegin); if( !dx->pimpl->zErrPos ) dx->pimpl->zErrPos = dx->pimpl->zBegin; //g_warn("dx->pimpl->zBegin: %.3s...", dx->pimpl->zBegin); } }else{ dx->pimpl->zErrPos = 0; } va_list va; va_start(va, zFmt); rc = cmpp_errfv(dx->pp, rc, zFmt, va); va_end(va); return rc; } int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt, va_list vargs){ return cmpp_errfv(dx->pp, rc, zFmt, vargs); } //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_stmt_bind_text(pp, q, 1, ustr_c(zBaseName)); cmpp_stmt_bind_text(pp, q, 2, sep); cmpp_stmt_bind_text(pp, q, 3, ustr_c((zExt ? zExt : ""))); cmpp_stmt_bind_text(pp, q, 4, ustr_c((zPath ? zPath: ""))); int const dbrc = cmpp_stmt_step(pp, q, false); if( SQLITE_ROW==dbrc ){ unsigned char const * s = sqlite3_column_text(q, 1); int const n = sqlite3_column_bytes(q,1); if( n ){ zrc = sqlite3_mprintf("%.*s", n, s); cmpp_check_oom(pp, zrc); }else{ g_warn("WARNING: empty path result from: %s", sqlite3_sql(q)); } } cmpp_stmt_reset(pp, q); } } return zrc; } CMPP__EXPORT(cmpp_dx *,cmpp_dx_current)(cmpp *pp){ return cmpp__dx_current(pp); } //cmpp__ListType_impl(cmpp__delim_list,cmpp__delim) //cmpp__ListType_impl(CmppDList,CmppDList_entry*) //cmpp__ListType_impl(CmppSohList,void*) 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; } CMPP__EXPORT(cmpp_size_t, cmpp_strlen)(char const *z, cmpp_strlen_t n){ return cmpp__strlen(z, n); } CMPP__EXPORT(cmpp_size_t, cmpp_strlenu)(unsigned char const *z, cmpp_strlen_t n){ return cmpp__strlenu(z, n); } CMPP__EXPORT(void,cmpp_dx_src_pos_info)( cmpp_dx const *dx, char const **zName, cmpp_size_t * lineNo ){ if( zName ) *zName = (char const *)dx->sourceName; if( lineNo ) *lineNo = dx->pimpl->pos.lineNo; } CMPP__EXPORT(int, cmpp_f_v4_err_usage)(cmpp_f_args const *args){ return cmpp_errf(args->pp, CMPP_RC_ERROR, "Usage: %s %s", args->argv[0]->z, args->usage); } CMPP__EXPORT(bool, cmpp_simple_truth)(unsigned char const *z, cmpp_strlen_t n){ switch(n){ case 0: return false; case 1: return *z && '0'!=*z; //case 4: return 0==memcmp(z,"true",4+1); //case 5: return 0!=memcmp(z,"false",5+1); default: return true; } } CMPP__EXPORT(bool, cmpp_json_valid)(cmpp *pp, unsigned char const *z, cmpp_strlen_t n, int flags){ bool rv = false; n = cmpp__strlenu(z, n); if( n && flags>0 && flags<16 ){ /* ^^^^ json_valid() is picky about its flags argument. We skip processing for that case because it can put pp into an error state which the caller is not likely to check for because who would after such an innocuous function? */ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_jsonValid, false); if( q ){ cmpp_stmt_bind_textx(pp, q, 1, z, n, SQLITE_STATIC); cmpp_stmt_bind_int(pp, q, 2, flags); if( SQLITE_ROW==cmpp_stmt_step(pp, q, false) ){ rv = 0!=sqlite3_column_int(q, 0); } cmpp_stmt_reset(pp, q); } } return rv; } static inline bool cmpp__json_is_container(cmpp * const pp, char opener, char closer, unsigned char const * const z, cmpp_strlen_t n, bool allowJson5){ n = cmpp__strlenu(z, n); return ( n>1 && z[0]==(unsigned char)opener && z[n-1]==(unsigned char)closer ) ? ( n==2 ? true : cmpp_json_valid(pp, z, n, allowJson5 ? 3 : 1) ) : false; } CMPP__EXPORT(bool,cmpp_json_is_list)(cmpp *pp, unsigned char const * const z, cmpp_strlen_t n, bool allowJson5){ return cmpp__json_is_container(pp, '[', ']', z, n, allowJson5); } CMPP__EXPORT(bool,cmpp_json_is_object)(cmpp *pp, unsigned char const * const z, cmpp_strlen_t n, bool allowJson5){ return cmpp__json_is_container(pp, '{', '}', z, n, allowJson5); } CMPP__EXPORT(void,cmpp_str_json_append)(cmpp *pp, sqlite3_str * str, unsigned char const * z, cmpp_strlen_t n){ n = cmpp__strlenu(z, n); if( !n ){ sqlite3_str_append(str, "\"\"", 2); return; } int64_t j; if( cmpp_is_int64(z, n, &j) ){ sqlite3_str_appendf(str, "%lld", j); }else{ /* If it's JSON then append it as-is, don't escape it. We "could" handle ints here but it's much faster to do them above. */ //g_warn("arg %d: %.*s", (int)i, (int)n, z); if( cmpp_json_valid(pp, z, n, 1) ){ /* Using any json_valid() flags which allow json5 will end up generating JSON5 output, which we don't want here because it would probably give us headaches everywhere else. e.g. [list new a b c] would result in [a, b, c] instead of ["a", "b", "c"]. */ //g_warn("is-valid 1: %.*s", (int)n, z); sqlite3_str_append(str, (char *)z, (int)n); }else{ //g_warn("is-valid 0: %.*s", (int)n, z); sqlite3_str_appendf(str, "%.*J", (int)n, z); } } } 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_cstr2(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(pp, 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_f_F_MASK_INTERNAL & cmpp_f_F_FLOW_CONTROL); cmpp__staticAssert(Mask2, cmpp_f_F_MASK_INTERNAL & cmpp_f_F_NOT_SIMPLIFY); cmpp__staticAssert(Mask3, 0==(cmpp_f_F_MASK_INTERNAL & cmpp_f_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_legacy(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_errf(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*/ } 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_append_str)(cmpp_b * b, char const * str, cmpp_strlen_t n){ return cmpp_b_append(b, str, cmpp__strlen(str, n)); } CMPP__EXPORT(int, cmpp_b_append4_str)(cmpp * pp, cmpp_b * b, char const * str, cmpp_strlen_t n){ return cmpp_b_append4(pp, b, str, cmpp__strlen(str, n)); } 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; } if( n ){ 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->na ){ cmpp_b * const b = li->list[--li->na]; 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->na : 0==li->na ); 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); #define re pi->recycler.buf cmpp_b_list * const li = &re.list; cmpp_b * b = 0; if( cmpp_b_list_UNSORTED==re.sort ){ re.sort = cmpp_b_list_DESC; cmpp_b_list__sort(li, re.sort); assert( cmpp_b_list_UNSORTED!=re.sort || re.list.n<2 ); } for( cmpp_size_t i = 0; i < li->n; ++i ){ b = li->list[i]; if( b ){ assert( b->nAlloc <= re.currentCapacity ); re.currentCapacity -= b->nAlloc; li->list[i] = 0; assert( !b->n && "Someone wrote to a buffer after giving it back" ); if( i < li->n-1 ){ re.sort = 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; } #undef re return b; } CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){ if( !b ) return; cmpp__pi(pp); #define re pi->recycler.buf if( !re.limitCount || (re.list.n+1>re.limitCount) ){ goto free_it; } if( (re.limitCapacity && (b->nAlloc + re.currentCapacity > re.limitCapacity)) || (re.limitSingleCapacity && b->nAlloc>re.limitSingleCapacity) ){ /* keep b but not its memory. */ cmpp_b_clear(b); } cmpp_b_list * const li = &re.list; /* TODO (2026-09-05): we should go from the back of the list, as these are always returned in reverse order of borrow, sometimes out of order by 2-3 for a batch of buffers. */ cmpp_size_t i = li->n; while( i ){ --i; if( !li->list[i] ){ re.currentCapacity += b->nAlloc; li->list[i] = cmpp_b_reuse(b); if( i+1 < li->n ) re.sort = cmpp_b_list_UNSORTED; return; } } #undef re assert( !"This shouldn't be possible - no available " "slot in buffer recycler. " "This suggests that cmpp_b_return() was used without a " "corresponding cmpp_b_borrow() or that a given buffer " "was returned multiple times." ); free_it: cmpp_b_clear(b); cmpp_mfree(b); } void cmpp_b_library_dtor(cmpp_b_library *li){ static const cmpp_b_library empty = cmpp_b_library_empty_m; cmpp_b_list_cleanup(&li->list); cmpp_b_library const old = *li; *li = empty; li->limitCount = old.limitCount; li->limitSingleCapacity = old.limitSingleCapacity; li->limitCapacity = old.limitCapacity; } 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 /* base64 pieces copied from sqlite3.org/src/file/ext/misc/base64.c on 2026-07-19. */ #define PC 0x80 /* pad character */ #define WS 0x81 /* whitespace */ #define ND 0x82 /* Not above or digit-value */ #define PAD_CHAR '=' static const char b64Numerals[64+1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; #define BX_NUMERAL(dv) (b64Numerals[(uint8_t)(dv)]) /* Width of base64 lines. Should be an integer multiple of 4. */ #define B64_DARK_MAX 72 /* Encode a byte buffer into base64 text with linefeeds appended to limit ** encoded group lengths to B64_DARK_MAX or to terminate the last group. */ static char* toBase64( uint8_t const *pIn, cmpp_size_t nbIn, char *pOut ){ int nCol = 0; while( nbIn >= 3 ){ /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ pOut[0] = BX_NUMERAL(pIn[0]>>2); pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); pOut[3] = BX_NUMERAL(pIn[2]&0x3f); pOut += 4; nbIn -= 3; pIn += 3; if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ *pOut++ = '\n'; nCol = 0; } } if( nbIn > 0 ){ signed char nco = nbIn+1; cmpp_strlen_t nbe; unsigned long qv = *pIn++; for( nbe=1; nbe<3; ++nbe ){ qv <<= 8; if( (cmpp_size_t)nbe=0; --nbe ){ char ce = (nbe>= 6; pOut[nbe] = ce; } pOut += 4; *pOut++ = '\n'; } *pOut = 0; return pOut; } #if 0 /* from-base-64. We don't yet have a #base64 -d[ecode] */ /* Decoding table, ASCII (7-bit) value to base 64 digit value or other */ static const uint8_t b64DigitValues[128] = { /* HT LF VT FF CR */ ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, /* US */ ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, /*sp + / */ WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, /* 0 1 5 9 = */ 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, /* A O */ ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, /* P Z */ 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, /* a o */ ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, /* p z */ 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND }; #define BX_DV_PROTO(c) \ ((((uint8_t)(c))<0x80)? (uint8_t)(b64DigitValues[(uint8_t)(c)]) : 0x80) #define IS_BX_DIGIT(bdp) (((uint8_t)(bdp))<0x80) #define IS_BX_WS(bdp) ((bdp)==WS) #define IS_BX_PAD(bdp) ((bdp)==PC) /* Skip over text which is not base64 numeral(s). */ static char * skipNonB64( char *s, int nc ){ char c; while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; return s; } /* Decode base64 text into a byte buffer. */ static uint8_t* fromBase64( char *pIn, int ncIn, uint8_t *pOut ){ if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; while( ncIn>0 && *pIn!=PAD_CHAR ){ static signed char nboi[] = { 0, 0, 1, 2, 3 }; char *pUse = skipNonB64(pIn, ncIn); unsigned long qv = 0L; int nti, nbo, nac; ncIn -= (pUse - pIn); pIn = pUse; nti = (ncIn>4)? 4 : ncIn; ncIn -= nti; nbo = nboi[nti]; if( nbo==0 ) break; for( nac=0; nac<4; ++nac ){ char c = (nac>8) & 0xff; deliberate_fall_through; /* FALLTHRU */ case 1: pOut[0] = (qv>>16) & 0xff; break; } pOut += nbo; } return pOut; } #endif /* base64 decode */ /** Base64-encodes the contents of bIn and replaces bIn's contents (if any) with these. Potential TODO is have it append to bOut, but it's simpler to replace it and that's all our use cases currently need. */ int cmpp__b_base64_encode(cmpp *pp, cmpp_b const * bIn, cmpp_b * bOut){ cmpp_size_t nc; cmpp_size_t const nv = bIn->n; char *cBuf; cmpp_size_t const nvMax = 1000 * 1000 * 1000 /* 1B == default SQLITE_MAX_LENGTH, but that symbol is not exposed in its public API. We "could" extract that limit from pp's db here, but... no. We're only using it as a guideline: bOut is not not part of the db state so we're not beholden to the db's limits. In this context the limit is arbitrary, anyway - we inherit it from this code's origins and retain it for simplicity. */; if( ppCode ) return ppCode; nc = 4*((nv+2)/3); /* quads needed */ nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ if( nvMax < nc ){ return cmpp_errf(pp, CMPP_RC_RANGE, "Blob expanded to base64 is too big."); } cmpp_b_reuse(bOut); if( !bIn->z || !bIn->n ){ /* empty is okay */ return 0; } if( 0==cmpp_b_reserve3(pp, bOut, nc) ){ cBuf = (char*)bOut->z; bOut->n = (cmpp_size_t)(toBase64(bIn->z, bIn->n, cBuf) - cBuf); assert( bOut->n < bOut->nAlloc ); cBuf[bOut->n] = 0; } return ppCode; } static int cmpp__hexbyte( int ch ){ if(ch>='0' && ch<='9') return ch - '0'; else if(ch>='a' && ch<='f') return ch - 'a' + 10; else if(ch>='A' && ch<='F') return ch - 'A' + 10; else return -1; } int cmpp__utf8_char_to_cstr(unsigned int c, unsigned char *output, cmpp_size_t length){ /* Stolen from the internet many years ago, adapted from several variations which all _seem_ to have derived from librdf. */ cmpp_size_t size=0; /* check for illegal code positions: * U+D800 to U+DFFF (UTF-16 surrogates) * U+FFFE and U+FFFF */ if((c > 0xD7FF && c < 0xE000) || c == 0xFFFE || c == 0xFFFF) return -1; /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */ if(c > 0x10ffff) return -1; if(c < 0x00000080) size = 1; else if(c < 0x00000800) size = 2; else if(c < 0x00010000) size = 3; else size = 4; if(!output) return (int)size; else if(size > length) return -1; else switch(size) { case 0: assert(!"can't happen anymore"); output[0] = 0; return 0; case 4: output[3] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0x10000; CMPP_SWITCH_FALL_THROUGH; case 3: output[2] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0x800; CMPP_SWITCH_FALL_THROUGH; case 2: output[1] = 0x80 | (c & 0x3F); c = c >> 6; c |= 0xc0; CMPP_SWITCH_FALL_THROUGH; case 1: output[0] = (unsigned char)c; CMPP_SWITCH_FALL_THROUGH; default: return (int)size; } } int cmpp__read_hex_bytes(unsigned char const *zPos, unsigned int howMany, unsigned int * rv ){ unsigned int rc = 0; int ch1, ch2; unsigned int n = 0; assert(!(howMany%2) && "howMany must be an even number!"); assert(zPos && rv); while(n=0) ? cmpp__hexbyte(*zPos++) : -1; if(ch2<0) return -1; rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2); n += 2; } *rv = rc; return (int)n; } CMPP__EXPORT(void,cmpp_b_unescape)(cmpp_b * b){ unsigned char const * z = b->z; unsigned char * oz = b->z; unsigned char const * const zEnd = z ? z + b->n : 0; unsigned char const *zEsc = 0; while( z < zEnd ){ switch( *z ){ case '\\': if( zEsc ){ *oz++ = '\\'; zEsc = 0; }else{ zEsc = z; } ++z; continue; case 'b': case 'n': case 'r': case 't': case '"': case '`': case '\'': if( zEsc ){ switch( *z ){ case 'b': *oz++ = '\b'; break; case 'n': *oz++ = '\n'; break; case 'r': *oz++ = '\r'; break; case 't': *oz++ = '\t'; break; /* No \f or \v. Nobody uses those. And no \0 because it's inappropriate in this context. */ case '"': case '`': case '\'': *oz++ = *z; break; case 'u': case 'U':{ /* \uXXXX and \UXXXXXXXX */ unsigned int uChar = 0; unsigned const ulen = 'u'==*z ? (unsigned)4 : (unsigned)8; unsigned char const uCount = z+ulenerrCode BUT the tokenizer we feed this from is supposed to have validated these already. We'll just emit invalid ones as-is. */ *oz++ = *zEsc; *oz++ = *z++; continue; } assert(0 == (uCount % 2)); int const check = cmpp__utf8_char_to_cstr(uChar, oz, uCount); if(check<0){ goto no_such_u; } oz += check; z += uCount; continue; } } ++z; break; } CMPP_SWITCH_FALL_THROUGH; default: if( zEsc ) *oz++ = '\\' /* put it back */; *oz++ = *z++; } zEsc = 0; } if( oz ){ assert( oz<=zEnd ); if( zEsc ){ assert( ozn = (cmpp_size_t)(oz - b->z); } } CMPP__EXPORT(int,cmpp_b_cmp)(cmpp_b const * b, void const *p, cmpp_ssize32_t n){ unsigned char const * z = p; cmpp_size_t const nn = cmpp__strlenu(z, n); int const rc = memcmp(z, b->z, nnn ? nn : b->n); return (rc || b->n==nn) ? rc : (b->npimpl->db.dbh, zSql, nSql, pStmt, 0); if( 0==rc && !*pStmt ){ cmpp_err_c(pp, CMPP_RC_MISUSE, "Empty queries are not permitted here."); }else if( cmpp_db_rc_v2(pp, rc, zSql, nSql) ){ g_warn("SQLite rc=%d Offending SQL: %.*s", rc, nSql, zSql); } return ppCode; } static int cmpp__stmt_preparev(cmpp *pp, bool honorError, sqlite3_stmt **pStmt, const char * zSql, va_list va){ if( honorError && ppCode ) return ppCode; sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh); char * z = 0; int n = 0; assert( pp->pimpl->db.dbh ); sqlite3_str_vappendf(str, zSql, va); z = cmpp_sqlite3_str_finish(pp, str, &n); if( z ){ (void)cmpp__stmt_prepare(pp, honorError, pStmt, z, n); sqlite3_free(z); } return ppCode; } CMPP__EXPORT(int,cmpp_stmt_prepare)(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, cmpp_strlen_t nSql){ return cmpp__stmt_prepare(pp, true, pStmt, zSql, nSql); } CMPP__EXPORT(int,cmpp_stmt_preparev)(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, va_list va){ return cmpp__stmt_preparev(pp, true, pStmt, zSql, va); } CMPP__EXPORT(int,cmpp_stmt_preparef)(cmpp *pp, sqlite3_stmt **pStmt, const char * zSql, ...){ if( ppCode ) return ppCode; int rc; va_list va; va_start(va, zSql); rc = cmpp__stmt_preparev(pp, true, pStmt, zSql, va); va_end(va); return rc; } 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 /* not reached */; #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__stmt_prepare(pp, false, q, zSql, -1); } return *q; } CMPP__EXPORT(int,cmpp_stmt_reset)(cmpp *pp, sqlite3_stmt * const q){ int rc = 0; if( q ){ sqlite3_clear_bindings(q); rc = sqlite3_reset(q); switch( rc ){ case 0: case SQLITE_DONE: case SQLITE_ROW: rc = 0; break; default: cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1); } } return rc; } 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; } CMPP__EXPORT(int,cmpp_stmt_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_v2(pp, rc, sqlite3_sql(q), -1); } if( resetIt /* even if ppCode!=0 */ ) cmpp_stmt_reset(pp, q); assert( 0!=rc ); return rc; } CMPP__EXPORT(int, cmpp_stmt_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_stmt_bind_int()"); } CMPP__EXPORT(int, cmpp_stmt_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_stmt_bind_textn(pp, pStmt, col, buf, -1); } CMPP__EXPORT(int, cmpp_stmt_bind_null)(cmpp *pp, sqlite3_stmt *pStmt, int col){ return ppCode ? ppCode : cmpp_db_rc(pp, sqlite3_bind_null(pStmt, col), "from cmpp_stmt_bind_null()"); } CMPP__EXPORT(int, cmpp_stmt_bind_textx)(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr, cmpp_ssize32_t n, void (*dtor)(void *)){ if( 0==ppCode ){ cmpp_db_rc_v2( pp, (zStr && n) ? sqlite3_bind_text(pStmt, col, (char const *)zStr, (int)n, dtor) : sqlite3_bind_null(pStmt, col), sqlite3_sql(pStmt), -1 ); } return ppCode; } CMPP__EXPORT(int, cmpp_stmt_bind_textn)(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr, cmpp_ssize32_t n){ return cmpp_stmt_bind_textx(pp, pStmt, col, zStr, (int)n, SQLITE_TRANSIENT); } CMPP__EXPORT(int, cmpp_stmt_bind_text)(cmpp *pp, sqlite3_stmt *pStmt, int col, unsigned const char * zStr){ return cmpp_stmt_bind_textn(pp, pStmt, col, zStr, -1); } #if 0 int cmpp_stmt_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; 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 /** 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){ (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; } CMPP__EXPORT(int,cmpp_db_rc_v2)(cmpp *pp, int dbRc, char const *zMsg, cmpp_strlen_t nMsg){ switch(dbRc){ case 0: case SQLITE_DONE: case SQLITE_ROW: return 0; case SQLITE_NOMEM: return CMPP_RC_OOM; default: if( zMsg && (nMsg = cmpp__strlen(zMsg, nMsg)) ){ return cmpp_errf( pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc), "SQLite error #%d: %s: %.*s", dbRc, pp->pimpl->db.dbh ? sqlite3_errmsg(pp->pimpl->db.dbh) : "", nMsg, zMsg ); }else{ return cmpp_errf( pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc), "SQLite error #%d: %s", dbRc, pp->pimpl->db.dbh ? sqlite3_errmsg(pp->pimpl->db.dbh) : "" ); } } } /** 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. nVal must be the number of bytes of zVal to bind. If zVal is NULL then (zVal="1", nVal=1) is assumed, else if nVal is 0 then zVal is bound as SQL NULL. That's wonky, but it's what we currently have. */ static int cmpp__define_impl(cmpp * const pp, sqlite3_stmt * const q, unsigned char const * zKey, cmpp_strlen_t nKey, unsigned char const *zVal, cmpp_strlen_t nVal, int tType, bool resetStmt){ if( 0==ppCode){ assert( q ); nKey = cmpp__strlenu(zKey, nKey); nVal = cmpp__strlenu(zVal, nVal); if( 0==cmpp_stmt_bind_textn(pp, q, 2, zKey, (int)nKey) && 0==cmpp_stmt_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_stmt_bind_textn(pp, q, 3, zVal, (int)nVal); }else{ /* Arguable */ cmpp_stmt_bind_null(pp, q, 3); } }else{ cmpp_stmt_bind_int(pp, q, 3, 1); } cmpp_stmt_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_strlen_t nKey, unsigned char const *zVal, cmpp_strlen_t nVal, cmpp_tt_e 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_e 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_errf(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_None: if(kvp.v.n){ if( cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ ttype = cmpp_tt_IntDec; if( '+'==*kvp.v.z ){ ++kvp.v.z; --kvp.v.n; } }else{ ttype = cmpp_tt_DbString; } }else if( kvp.v.z ){ ttype = cmpp_tt_DbString; }else{ ttype = cmpp_tt_IntDec; intCheck = 1 /* No value ==> value of 1. */; } break; case cmpp_tt_IntDec: if( !cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ ttype = cmpp_tt_DbString; } break; default: break; } if( 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, kvp.k.n) && 0==cmpp_stmt_bind_int(pp, q, 1, ttype) ){ //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq); switch( ttype ){ case cmpp_tt_IntDec: cmpp_stmt_bind_int(pp, q, 3, intCheck); break; case cmpp_tt_Null: cmpp_stmt_bind_null(pp, q, 3); break; default: cmpp_stmt_bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n); break; } cmpp_stmt_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_None); } 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_DbString); } static int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey, cmpp_strlen_t nKey, unsigned char const *zVal, cmpp_strlen_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(pp, 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_DbString, pId); } static int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey, cmpp_strlen_t nKey, int64_t id){ sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false); if( q ){ cmpp_stmt_bind_textn(pp, q, 1, zKey, (int)nKey); cmpp_stmt_bind_int(pp, q, 2, id); cmpp_stmt_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)); } } /* ** sqlite3 UDF which returns the size of a file, or NULL if ** stat(2) fails. */ static void cmpp__udf_file_size( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zName; (void)(argc); zName = (const char*)sqlite3_value_text(argv[0]); if( 0!=zName && 0!=zName[0] ){ struct stat sb; if( 0==stat(zName, &sb) ){ sqlite3_result_int64(context, (int64_t)sb.st_size); } } } static void cmpp__udf_truthy( sqlite3_context *context, int argc, sqlite3_value **argv ){ (void)(argc); 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_reuse(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_v2(pp, rc, pi->db.zName ? pi->db.zName : ":memory:", -1); 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_file_size", .xUdf = cmpp__udf_file_size, .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_errf(pp, cmpp__db_errcode(pi->db.dbh, rc), "SQLite error #%d initializing DB: %s", rc, zErr); sqlite3_free(zErr); }else{ cmpp_errf(pp, cmpp__db_errcode(pi->db.dbh, rc), "SQLite error #%d initializing DB", rc); } goto end; } #if 0 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_stmt_bind_int(pp, q, 1, cmpp_tt_ ## N); \ cmpp_stmt_bind_textn(pp, q, 2, \ ustr_c("cmpp_tt_ " # N), sizeof("cmpp_tt_" # N)-1); \ if( STR ) cmpp_stmt_bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \ else cmpp_stmt_bind_null(pp, q, 3); \ if( SQLITE_DONE!=cmpp_stmt_step(pp, q, true) ) return ppCode; cmpp_tt_map(E) #undef E sqlite3_finalize(q); pi->stmt.insTtype = 0; break; } #endif 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). We need to be able to run these ** even in OOM cases (while admitting that they may fail to run in ** OOM cases). */ cmpp__stmt(pp, CmppStmt_spBegin, false); cmpp__stmt(pp, CmppStmt_spRelease, false); cmpp__stmt(pp, CmppStmt_spRollback, false); cmpp__lazy_init(pp); } return ppCode; } static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){ return cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' may only be used once.", arg->z); } /* No-op cmpp_f() impl. */ static void cmpp_f_noop(cmpp_f_args const * args){ (void)args; } /** cmpp_kav_each_f() impl for use by #define {k->v}. */ static int cmpp_kav_each_f_define__group( cmpp *pp, unsigned char const *zKey, cmpp_size_t nKey, unsigned char const *zVal, cmpp_size_t nVal, void* callbackState ){ if( (callbackState==pp) && cmpp__has(pp, (char const*)zKey, nKey) ){ return ppCode; } return cmpp__define2(pp, zKey, nKey, zVal, nVal, cmpp_tt_DbString); } /* #error impl. */ static void cmpp_f_error(cmpp_f_args const * args){ cmpp_dx * const dx = args->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_errf(dx, CMPP_RC_ERROR, "%.*s", n, zBegin); }else{ cmpp_dx_errf(dx, CMPP_RC_ERROR, "(no additional info)"); } } /* Impl. for #define. */ static void cmpp_f_define(cmpp_f_args const * args){ assert(args->argc>0 && "V4 arg semantics."); assert(args->argv && "V4|!RAW arg semantics."); assert( cmpp_arg_equals(args->argv[0], (char const*)args->dx->d->name.z) ); cmpp_dx * const dx = args->dx; if( args->argc<2 ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp_arg const * aKey = 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; bool raw = false; #define checkIsDefined(ARG) \ if(ifNotDefined && (cmpp__has(dx->pp, (char const*)ARG->z, ARG->n) \ || dxppCode)) break for( unsigned i = 1; 0==dxppCode && i < args->argc; ++i){ cmpp_arg const * arg = args->argv[i]; //g_warn("arg=%s", arg->z); if( 1==i && cmpp_arg_equals_c(arg, "?") ){ /* Only set the key if it's not already defined. */ ifNotDefined = true; continue; } switch( arg->ttype ){ case cmpp_tt_OpShiftL3: ++nChomp; /* fall through */ case cmpp_tt_OpShiftL: if( i<1 || i+1argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Ill-placed '%s'.", arg->z); }else if( arg->n >= sizeof(acHeredoc)-1 ){ cmpp_dx_errf(dx, CMPP_RC_RANGE, "Heredoc name is too large."); }else if( !aKey ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Missing key before %s.", cmpp__tt_cstr2(cmpp_tt_OpShiftL, false)); }else{ assert( aKey ); nHeredoc = aKey->n; memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/); } break; case cmpp_tt_OpCmpEq: cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Ill-placed '%s'.", arg->z); break; case cmpp_tt_IntDec: case cmpp_tt_StringBT: case cmpp_tt_StringDQ: case cmpp_tt_StringSQ: case cmpp_tt_Word: if( cmpp_arg_isflag_c(arg,"-chomp") ){ ++nChomp; break; } if( cmpp_arg_isflag_c(arg,"-raw") ){ raw = true; break; } if( cmpp_arg_isflag_c(arg,"-append") || cmpp_arg_isflag_c(arg,"-a") ){ aAppend = args->argv[++i]; if( !aAppend ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting argument for %s", arg->z); } arg = aAppend; break; } handle_value: if( aKey ){ /* This is the second arg - the value */ #if 1 if( i+1argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unexpected token after %s", arg->z); break; } #endif 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_interpolate(args->pp, arg, os, cmpp_arg_interpolate_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(args->pp, ba, os->z, os->n); } int n = nChomp; while( n-- && cmpp_b_chomp(which) ){} 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_errf(dx, CMPP_RC_TYPE, "Expecting a define-name token here."); }else if( i+1argc ){ 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_IntDec); } break; case cmpp_tt_GroupSquiggly: assert( !acHeredoc[0] ); if( aKey ){ /* Treat this as a value */ goto handle_value; }else if( (ifNotDefined ? i>2 : i>1) || i+1argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "{...} must be the only argument.") /* This is for simplicity's sake. */; }else{ cmpp_kav_each(args->pp, arg->z, arg->n, cmpp_kav_each_f_define__group, ifNotDefined ? args->pp : 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_errf(dx, CMPP_RC_MISUSE, "(...) is not permitted as a key."); break; } checkIsDefined(aKey); int d = 0; if( 0==cmpp__arg_evalSubToInt(args->pp, 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_IntDec); } break; } case cmpp_tt_GroupBrace:{ if( !aKey ){ cmpp_dx_errf(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) ){ int n = nChomp; while( n-- && cmpp_b_chomp(b) ){} 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_errf(dx, CMPP_RC_MISUSE, "Unhandled arg type %s: %s", cmpp__tt_cstr2(arg->ttype, true), arg->z); break; } } if( 0==dxppCode && nHeredoc ){ // Process (#define KEY <<) cmpp_b * const os = cmpp_b_borrow(dx->pp); assert( args->d->closer ); if( os && 0==cmpp_dx_consume_b(dx, os, &args->d->closer, 1, raw ? cmpp_dx_consume_F_ATPOL_OFF : 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_DbString ); } } cmpp_b_return(dx->pp, os); } #undef checkIsDefined return; } /* Impl. for #undef */ static void cmpp_f_undef(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; if( args->argc<2 ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp_size_t n = 0; char const * z; for( unsigned i = 1; i < args->argc && 0==dxppCode; ++i ){ cmpp_arg const * arg = args->argv[i]; z = cmpp_arg_cstr2(arg, &n); if( 0 ){ g_stderr(" %s: %s %p n=%d %.*s\n", args->d->name.z, cmpp__tt_cstr2(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, z, n, false) ) { cmpp_undefine(dx->pp, (char const *)z); } #else cmpp_undefine(dx->pp, z, n); #endif }else{ cmpp_errf(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s", args->d->name.z, arg->z); } } } /* Impl. for #eval. */ static void cmpp_f_eval(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; if( 2!=args->argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "#eval requires one argument"); return; } cmpp_arg const * const arg = args->argv[1]; cmpp_b * const b = arg->n ? cmpp_b_borrow(dx->pp) : 0; if( b ){ /* We need to collect the line info before processing the arg so that it points to the start of the script. */ sqlite3_str * const sstr = cmpp__sqlite3_str_new(args->pp); if( sstr ){ cmpp__dx_append_script_info(dx, sstr); char * const z = sqlite3_str_finish(sstr); if(0==cmpp_check_oom(args->pp, z) && 0==cmpp_arg_interpolate(args->pp, arg, b, cmpp_arg_interpolate_BRACE_CALL) && b->n){ cmpp_process_string(args->pp, z ? z : args->d->name.z, b->z, b->n); } sqlite3_free(z); } cmpp_b_return(args->pp, b); } return; } /* Impl. for #expand. */ static void cmpp_f_expand(cmpp_f_args const * args){ if( args->argc<2 || args->argc>3 ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "%sexpand requires one or two arguments", cmpp__dx_zdelim(args->dx)); return; } cmpp_arg const * argKav = 0 /* {key -> value ...} */; cmpp_arg const * argToB = 0 /* one of X or Y from: #expand X Y */; cmpp_arg const * aLhs = 0; cmpp_arg const * aRhs = 0; cmpp_arg const * aErr = 0; /* Current arg for error reportin */ cmpp_b * const osIn = cmpp_b_borrow(args->pp); int nChomp = 0 /* Strip nChomp newlines from result */; int unChomp = 0 /* Add unChomp newlines to result */; bool raw = false /* interpret block content as raw or preprocessor-ready? */; bool inSp = false /* started a local savepoint? */; if( !osIn ) goto end; for( unsigned i = 1; i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; aErr = arg; switch( arg->ttype ){ case cmpp_tt_Word: if( cmpp_arg_isflag_c(arg,"-chomp") ){ ++nChomp; continue; } if( cmpp_arg_isflag_c(arg,"-unchomp") ){ ++unChomp; continue; } if( cmpp_arg_isflag_c(arg,"-raw") ){ raw = true; continue; } if( !aLhs ){ aLhs = arg; continue; } goto unexpected_arg; default: assert( !aRhs ); if( aLhs ){ if( i+1argc ){ assert( arg!=aLhs ); assert( !aRhs ); goto unexpected_arg; } aRhs = arg; }else{ aLhs = arg; } continue; } } if( !aLhs ){ cmpp_err(args->pp, CMPP_RC_MISUSE, "Expecting one or two arguments.", -1); goto end; } if( aRhs ){ if( cmpp_tt_GroupSquiggly!=aRhs->ttype ){ aErr = aRhs; goto unexpected_arg; } argKav = aRhs; argToB = aLhs; }else{ if( cmpp_tt_GroupSquiggly==aLhs->ttype ){ argKav = aLhs; }else{ argToB = aLhs; } } assert( argKav || argToB ); if( argToB ){ if( cmpp_arg_interpolate(args->pp, argToB, osIn, cmpp_arg_interpolate_BRACE_CALL) ){ goto end; } } if( argKav ){ assert(!argKav->next); if( (aErr = argKav->next) ){ goto unexpected_arg; } if( cmpp_sp_begin(args->pp) ) goto end; inSp = true; if( cmpp_kav_each(args->pp, argKav->z, argKav->n, cmpp_kav_each_f_define__group, 0, cmpp_kav_each_F_EXPAND_VAL | cmpp_kav_each_F_CALL_VAL | cmpp_kav_each_F_PARENS_EXPR) ){ goto end; } } if( argToB && cmpp_tt_GroupSquiggly==argToB->ttype ){ cmpp_outputer xout = cmpp_outputer_b; cmpp_outputer old = {0}; cmpp_b * const b = cmpp_b_borrow(args->pp); if( !(xout.state = b) ) goto end; cmpp__outputer_swap(args->pp, &xout, &old); cmpp_process_string(args->pp, "#expand buffer", osIn->z, osIn->n); cmpp__outputer_swap(args->pp, &old, &xout); cmpp_b_swap(b, osIn); cmpp_b_return(args->pp, b); if( cmpp_err_has(args->pp) ) goto end; } if( !argToB ){ assert(!osIn->n); if( args->isDxCall ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "#expand.../#expand is not legal for [call]."); goto end; } if( cmpp_dx_consume_b(args->dx, osIn, &args->d->closer, 1, raw ? cmpp_dx_consume_F_ATPOL_OFF : cmpp_dx_consume_F_PROCESS_OTHER_D) ){ goto end; } } while( nChomp-- && cmpp_b_chomp(osIn) ){} while( unChomp-- && cmpp_b_append_ch(osIn, '\n') ){} //MARKER(("Emitting %d bytes: %.*s\n", (int)osIn->n, (int)osIn->n, osIn->z)); cmpp_out_expand(args->pp, NULL, osIn->z, osIn->n, cmpp_atpol_CURRENT); end: if( inSp ){ cmpp_sp_rollback(args->pp); } cmpp_b_return(args->pp, osIn); return; unexpected_arg: assert( aErr ); cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unexpected argument: %.*s", (int)aErr->n, aErr->z); goto end; } /* Impl. for #once. */ static void cmpp_f_once(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; cmpp_d const * const d = args->d; assert(d); assert(d->closer); if( dx->args.arg0 ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting no arguments"); return; } cmpp_b * const b = cmpp_b_borrow(args->pp); if( !b ) return; else{ /* Synthesize an ID from the source code position. Typically we'll hit any position only once in a given run, but #include and #query loops can cause re-visits. */ cmpp__delim const * const dlim = cmpp__dx_delim(args->dx); cmpp_b_append4(args->pp, b, dlim->open.z, dlim->open.n); cmpp_b_append4(args->pp, b, d->name.z, d->name.n); cmpp_b_append_ch(b, ':'); char const * zFile = 0; cmpp_size_t lnNo = 0; cmpp_dx_src_pos_info(dx, &zFile, &lnNo); cmpp_b_append4_str(args->pp, b, zFile, -1); cmpp_b_append_ch(b, ':'); cmpp_b_append_i32(b, (int)lnNo); if( b->errCode ) goto end; } if( 0 ){ g_warn("#once dline=%d pos.lineNo=%d, key: %s", (int)dx->pimpl->dline.lineNo, (int)dx->pimpl->pos.lineNo, b->z); } int const had = cmpp__has(args->pp, (char const *)b->z, b->n); if( dxppCode ) goto end; else if( had ){ /* Consume until #/once in elide mode. */ 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( 0==cmpp_define_v2(args->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(args->pp, b); return; } /* Impl. for #/define, /#query, /#pipe. */ CMPP__EXPORT(void, cmpp_f_dangling_closer)( cmpp_f_args const * args ){ cmpp_dx * const dx = args->dx; char const * const zD = cmpp_dx_delim(dx); dxserr("%s%s used without its opening directive.", zD, args->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_stmt_bind_text(pp, q, 1, zName) ){ if(SQLITE_ROW == cmpp_stmt_step(pp, q, true)){ rc = 1; }else{ rc = 0; } g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc)); } return rc; } char * cmpp__include_search(cmpp *pp, unsigned const char * zKey, cmpp_size_t * nVal){ char * zName = 0; sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false); if( nVal ) *nVal = 0; if( q && 0==cmpp_stmt_bind_text(pp, q, 1, zKey) ){ int const rc = cmpp_stmt_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(pp, 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_stmt_bind_text(pp, q, 1, ustr_c(zKey)); cmpp_stmt_step(pp, q, true); g_debug(pp,2,("incl rm [%s]\n", zKey)); } return ppCode; } /** 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_stmt_bind_text(pp, q, 1, zKey); cmpp_stmt_bind_text(pp, q, 2, zSrc); cmpp_stmt_bind_int(pp, q, 3, srcLine); cmpp_stmt_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_f_include(cmpp_f_args const * args){ char * zResolved = 0; cmpp_size_t nResolved; cmpp_b * const ob = cmpp_b_borrow(args->pp); bool raw = false; bool resolve = false; cmpp_arg const * argDelim = 0; if( !ob ){ goto end; } unsigned i = 1; for( ; i < args->argc; ++i){ cmpp_arg const * arg = args->argv[i]; if( cmpp_arg_isflag_c(arg, "-raw") ){ if( resolve ) goto no_raw_resolve; raw = true; continue; } if( cmpp_arg_isflag_c(arg, "-delimiter") ){ if( ++i==args->argc ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "-delimiter requires an argument"); goto end; } argDelim = args->argv[i]; continue; } if( cmpp_arg_isflag_c(arg, "-resolve") ){ if( raw ){ no_raw_resolve: cmpp_errf(args->pp, CMPP_RC_MISUSE, "-raw and -resolve may not be used together."); goto end; }else{ resolve = true; continue; } } break; #undef FLAG } if( i>=args->argc ){ cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Expecting at least one filename argument."); } if( argDelim && (raw || resolve) ) argDelim = 0; if( argDelim ){ if( cmpp_arg_interpolate(args->pp, argDelim, cmpp_b_reuse(ob), cmpp_arg_interpolate_BRACE_CALL) || cmpp_delimiter_push(args->pp, (char*)ob->z) ){ argDelim = 0/*our signal to not pop the delimiter*/; goto end; } } for( ; i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(ob), cmpp_arg_interpolate_BRACE_CALL) ){ break; } //g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved); if(!raw && !resolve && cmpp__including_has(args->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_errf(args->pp, CMPP_RC_RANGE, "Recursive include of file: %s", ob->z); break; } cmpp_mfree(zResolved); nResolved = 0; zResolved = cmpp__include_search(args->pp, ob->z, &nResolved); if( !zResolved ){ if( !cmpp_err_has(args->pp) ){ cmpp_errf(args->pp, CMPP_RC_NOT_FOUND, "file not found: %s", ob->z); } break; } if( resolve ){ cmpp_out_raw(args->pp, zResolved, nResolved); }else if( raw ){ if( !args->pp->pimpl->out.out ) break; FILE * const fp = cmpp_fopen(zResolved, "r"); if( fp ){ int const rc = cmpp_stream(cmpp_input_f_FILE, fp, args->pp->pimpl->out.out, args->pp->pimpl->out.state); if( rc ){ cmpp_errf(args->pp, rc, "Unknown error streaming file %s.", arg->z); } cmpp_fclose(fp); }else{ cmpp_errf(args->pp, cmpp_errno_rc(errno, CMPP_RC_IO), "Unknown error opening file %s.", arg->z); } }else{ cmpp__including_add(args->pp, ob->z, ustr_c(args->dx->sourceName), args->dx->pimpl->dline.lineNo); cmpp_process_file(args->pp, zResolved); cmpp__include_rm(args->pp, ob->z); } } end: if( argDelim ){ cmpp_delimiter_pop(args->pp); } cmpp_mfree(zResolved); cmpp_b_return(args->pp, ob); } #endif /* #ifndef CMPP_OMIT_D_INCLUDE */ /** cmpp_f() callback state for cmpp_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_f_if(cmpp_f_args const * xargs){ cmpp_dx * const dx = xargs->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 = xargs->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( xargs->d==cis->dIf ); if( !dx->args.arg0 ){ cmpp_dx_errf(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(xargs->pp, &dx->pimpl->args, &gotTruth) ){ goto end; }else if( !gotTruth ){ skipOn; } cmpp_d const * dPrev = xargs->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(xargs->pp, &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_errf(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_f_if_dangler(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; CmppIfState const * const cis = args->d->impl.state; char const *zDelim = cmpp_dx_delim(dx); cmpp_dx_errf(dx, CMPP_RC_SYNTAX, "%s%s with no matching %s%s", zDelim, args->d->name.z, zDelim, cis->dIf->name.z); } static void cmpp__dump_sizeofs(void){ #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__dx_pimpl); SO(cmpp_outputer); SO(cmpp__pimpl); SO(((cmpp__pimpl*)0)->stmt); SO(((cmpp__pimpl*)0)->policy); SO(cmpp_tizer); SO(cmpp_token); SO(CmppArgList); SO(CmppDLine); SO(CmppDList); SO(CmppDList_entry); SO(CmppLvl); SO(PodList__atpol); printf("cmpp_tt__last = %d\n", cmpp_tt__last); #undef SO } /* Impl. for #pragma. */ static void cmpp_f_pragma(cmpp_f_args const * args){ if(args->argc<2){ cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Expecting an argument"); return; }else if(args->argc>2){ cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Too many arguments"); return; } cmpp_arg const * arg = args->argv[1]; const char * const zArg = (char const *)arg->z; #define M(X) 0==strcmp(zArg,X) if(M("defines")){ cmpp__dump_defines(args->pp, stderr, 1); return; } if(M("sizeof")){ cmpp__dump_sizeofs(); return; } if(M("chomp-F")){ args->pp->pimpl->flags.chompF = 1; return; } if(M("no-chomp-F")){ args->pp->pimpl->flags.chompF = 0; return; } if(M("api-thunk")){ return; }/*API thunk*/ #undef M cmpp_errf(args->pp, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg); } /* Impl. for #savepoint. */ static void cmpp_f_savepoint(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; if( 2 != args->argc ){ cmpp_dx_errf(dx, CMPP_RC_SYNTAX, "Expecting one argument"); return; } cmpp_arg const * const arg = args->argv[1]; if( cmpp_arg_isflag_c(arg, "begin") ){ cmpp__dx_sp_begin(dx); }else if( cmpp_arg_isflag_c(arg, "rollback") ){ cmpp__dx_sp_rollback(dx); }else if( cmpp_arg_isflag_c(arg, "commit") ){ cmpp__dx_sp_commit(dx); }else{ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unknown savepoint option: %s", arg->z); } } /* #stderr impl. */ static void cmpp_f_stderr(cmpp_f_args const * args){ cmpp_dx * const dx = args->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 = args->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 ?<dx; cmpp_arg const * arg = dx->args.arg0; if( !arg ){ cmpp_dx_errf(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 = !args->isDxCall; cmpp_arg const * argDelimO = 0 /* @token@ opener */; cmpp_arg const * argDelimC = 0 /* @token@ closer */; cmpp__pi(args->pp); cmpp_atpol_e polNew = cmpp_atpol_get(args->pp); for( ; arg; arg = arg ? arg->next : NULL ){ //g_warn("arg=%s", arg->z); if( !checkedCallForm ){ assert( args->isDxCall ); checkedCallForm = true; if( cmpp_arg_equals_c(arg, "policy") ){ char const * z = cmpp__atpol_name(args->pp, cmpp__policy(args->pp,at)); if( z ){ cmpp_out_raw(args->pp, z, strlen(z)); } }else if( cmpp_arg_equals_c(arg, "delimiter") ){ char const * zO = 0; char const * zC = 0; cmpp_atdelim_get(args->pp, &zO, &zC); if( zC ){ cmpp_out_raw(args->pp, zO, strlen(zO)); cmpp_out_raw(args->pp, " ", 1); cmpp_out_raw(args->pp, zC, strlen(zC)); } goto end; }else{ cmpp_dx_errf(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_c(arg, "pop") ){ arg = arg->next; if( !arg ){ cmpp_dx_errf(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_c(arg, "policy") ){ popWhich |= pop_policy; }else if( 0==(pop_delim & popWhich) && cmpp_arg_equals_c(arg, "delimiter") ){ popWhich |= pop_delim; }else if( 0==(pop_both & popWhich) && cmpp_arg_equals_c(arg, "both") ){ popWhich |= pop_both; }else{ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Invalid argument to 'pop': ", arg->z); goto end; } } assert( !arg ); op = op_pop; break; }/* pop */ if( cmpp_arg_equals_c(arg, "push") ){ op = op_push; continue; } if( cmpp_arg_equals_c(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_c(arg, "policy") ){ arg = arg->next; if( !arg ){ cmpp_dx_errf(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(args->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_c(arg, "delimiter") ){ assert( !argDelimO && !argDelimC ); argDelimO = arg->next; argDelimC = argDelimO ? argDelimO->next : NULL; if( !argDelimC ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'delimiter' requires two arguments."); goto end; } arg = argDelimC->next; continue; } if( op_pop!=op ){ if( cmpp_arg_equals_c(arg,"<<") ){ if( arg->next ) { cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' must be the final argument.", arg->z); goto end; } op = op_heredoc; break; } } cmpp_dx_errf(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(args->pp); } if( pop_delim & popWhich ){ popcheck(pi->delim.at); cmpp_atdelim_pop(args->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(args->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(args->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(args->pp, polNew) ){ if( argDelimC ){ popcheck(pi->delim.at); cmpp_atdelim_pop(args->pp); } }else{ bool const pushedDelim = NULL!=argDelimC; assert( args->d->closer ); cmpp_dx_consume(dx, NULL, &args->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D) /* !Invalidates argDelimO and argDelimC! */; popcheck(pi->policy.at); cmpp_atpol_pop(args->pp); if( pushedDelim ) cmpp_atdelim_pop(args->pp); } }else if( op_push==op ){ if( cmpp_atpol_push(args->pp, polNew) && argDelimC ){ /* Roll back delimiter push */ cmpp_atdelim_pop(args->pp); } }else{ assert( op_set==op ); if( cmpp__policy(args->pp,at)!=polNew ){ cmpp_atpol_set(args->pp, polNew); } } end: return; bad_pop: cmpp_dx_errf(dx, CMPP_RC_RANGE, "Cannot pop an empty stack."); #undef popcheck } static void cmpp_f_expr(cmpp_f_args const * args){ cmpp_dx * const dx = args->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_cstr2(a->ttype, true), (unsigned)a->n, (int)a->n, a->z); } #endif if( 0==cmpp__args_evalToInt(args->pp, &dx->pimpl->args, &rv) ){ if( 'a'==args->d->name.z[0] ){ if( !rv ){ cmpp_errf(args->pp, CMPP_RC_ASSERT, "Assertion failed: %s", args->raw.z); } }else{ char buf[60]; snprintf(buf, sizeof(buf), "%d\n", rv); cmpp_out_raw(args->pp, buf, strlen(buf)); } } } static void cmpp_f_undef_policy(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; cmpp_unpol_e up = cmpp_unpol_invalid; int nSeen = 0; assert( args->argc>0 && "v4 args" ); enum ops { op_invalid, op_set, op_push, op_pop }; enum ops op = op_invalid; if( 1==args->argc ){ cmpp_f_v4_err_usage(args); return; } unsigned i = 1; cmpp_arg const * arg; cmpp_arg const * argPol = 0; again: arg = args->argv[i]; ++nSeen; if( 1==nSeen ){ if( cmpp_arg_equals_c(arg, "push") ){ if( ++i==args->argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting argument to 'push'."); return; } op = op_push; goto again; }else if( cmpp_arg_equals_c(arg, "pop") ){ if( ++i < args->argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Extra argument after 'pop': %s", cmpp_arg_cstr(args->argv[i])); return; } op = op_pop; } } if( op_pop!=op ){ if( cmpp_arg_equals_c(arg,"error") ){ if( op_invalid==op ) op = op_set; argPol = arg; up = cmpp_unpol_ERROR; }else if( cmpp_arg_equals_c(arg,"null") ){ if( op_invalid==op ) op = op_set; argPol = arg; up = cmpp_unpol_NULL; }else{ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unhandled argument: %s", cmpp_arg_cstr(arg)); return; } } assert( op_invalid!=op ); if( op_pop!=op && cmpp_unpol_invalid==up ){ if( argPol ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unhandled undefined-policy '%s'." " Try one of: error, null", cmpp_arg_cstr(argPol)); }else{ cmpp_f_v4_err_usage(args); } return; } switch( op ){ case op_set: cmpp_unpol_set(args->pp, up); break; case op_push: cmpp_unpol_push(args->pp, up); break; case op_pop: if( !cmpp__epol(args->pp,un).n ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "No %s%s push is active.", cmpp_dx_delim(dx), args->d->name.z); }else{ cmpp_unpol_pop(args->pp); } break; default: cmpp__fatal("Not possible - unhandled switch case"); } } #ifndef CMPP_OMIT_D_DB /* Impl. for #attach. */ static void cmpp_f_attach(cmpp_f_args const * args){ if( 4!=args->argc ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s expects: STRING as NAME", args->d->name.z); return; } cmpp_b * bDbFile = 0; cmpp_b * bSchema = 0; for( unsigned i = 1; i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; if( !bDbFile ){ bDbFile = cmpp_b_borrow(args->pp); if( !bDbFile ) goto end; if( 0==cmpp_arg_interpolate(args->pp, arg, bDbFile, cmpp_arg_interpolate_BRACE_CALL) && !bDbFile->n ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Empty db file name is not permitted. " "If '%s' is intended as a value, " "it should be quoted.", arg->z); goto end; } if( ++i == args->argc || !cmpp_arg_equals_c(args->argv[i], "as") ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Expecting 'as' after db file name."); goto end; } continue; } if( !bSchema ){ bSchema = cmpp_b_borrow(args->pp); if( !bSchema ) goto end; if( 0==cmpp_arg_interpolate(args->pp, arg, bSchema, cmpp_arg_interpolate_BRACE_CALL) && !bSchema->n ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Empty db schema name is not permitted." "If '%s' is intended as a value, " "it should be quoted.", arg->z); goto end; } continue; } cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); goto end; } if( !bSchema ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Missing schema name."); goto end; } sqlite3_stmt * const q = cmpp__stmt(args->pp, CmppStmt_dbAttach, false); if( q ){ cmpp_stmt_bind_textn(args->pp, q, 1, bDbFile->z, bDbFile->n); cmpp_stmt_bind_textn(args->pp, q, 2, bSchema->z, bSchema->n); cmpp_stmt_step(args->pp, q, true); } end: cmpp_b_return(args->pp, bDbFile); cmpp_b_return(args->pp, bSchema); } /* Impl. for #detach. */ static void cmpp_f_detach(cmpp_f_args const * args){ if( 2!=args->argc ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s expects: NAME", args->d->name.z); return; } cmpp_b * const b = cmpp_b_borrow(args->pp); if( !b ) return; if( cmpp_arg_interpolate(args->pp, args->argv[1], b, cmpp_arg_interpolate_BRACE_CALL) ){ goto end; } if( b->n ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Empty db schema name is not permitted."); goto end; } sqlite3_stmt * const q = cmpp__stmt(args->pp, CmppStmt_dbDetach, false); if( q ){ cmpp_stmt_bind_textn(args->pp, q, 1, b->z, b->n); cmpp_stmt_step(args->pp, q, true); } end: cmpp_b_return(args->pp, b); } #endif /* #ifndef CMPP_OMIT_D_DB */ static void cmpp_f_delimiter(cmpp_f_args const * args){ cmpp_dx * const dx = args->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; for( ; arg; arg = arg->next ){ if( op_none==op ){ /* Look for push|pop. */ if( cmpp_arg_equals_c(arg, "push") ){ op = op_push; continue; }else if( cmpp_arg_equals_c(arg, "pop") ){ if( arg->next ){ cmpp_dx_errf(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_c(arg,"<<") ){ if( args->isDxCall ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' is not legal in [call] form.", arg->z); return; }else if( arg->next ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' must be the final argument.", arg->z); return; } op = op_push; doHeredoc = true; continue; } cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); return; } if( op_pop==op ){ cmpp_delimiter_pop(args->pp); }else if( !argD ){ if( args->isDxCall ){ cmpp__delim const * const del = cmpp__dx_delim(dx); if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n); }else{ cmpp_errf(args->pp, 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(args->pp, (char const*)argD->z); }else{ assert( op_set==op ); if( doHeredoc ) cmpp_delimiter_push(args->pp, z); else cmpp_delimiter_set(args->pp, z); } } if( !cmpp_err_has(dx->pp) ){ if( args->isDxCall ){ cmpp__delim const * const del = cmpp__dx_delim(dx); if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n); }else if( doHeredoc ){ assert( op_push==op ); cmpp_dx_consume(dx, NULL, &args->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D); cmpp_delimiter_pop(args->pp); } } } #ifndef NDEBUG /* Experimenting grounds. */ static void cmpp_f_experiment(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; void * st = args->d->impl.state; (void)st; g_warn("raw args: %s", args->raw.z); g_warn("argc=%u", args->argc); g_warn("isCall=%d\n", args->isDxCall); if( 1 ){ g_warn("argc=%u", args->argc); for(unsigned i = 0; i < args->argc; ++i ){ cmpp_arg const * const a = args->argv[i]; g_stderr("got type=%s, n=%u z=%.*s\n", cmpp__tt_cstr2(a->ttype, true), (unsigned)a->n, (int)a->n, a->z); } } if( 0 ){ cmpp__dump_sizeofs(); } if( 0 ){ cmpp_tizer tz = cmpp_tizer_empty; cmpp_token const * const tok = &tz.token; int rc = 0; cmpp_b * const b = cmpp_b_borrow(args->pp); assert( b ); cmpp_b_append4(args->pp, b, args->raw.z, args->raw.n); cmpp_tizer_init(&tz, &args->pp->pimpl->err, b->z, b->n); g_warn0("Begin tizer test"); cmpp_b * const bc = cmpp_b_borrow(args->pp); const cmpp_flag32_t cflags = 0;//cmpp_token_content_WHOLE; while( 0==(rc = cmpp_tizer_next(&tz, 0)) && cmpp_tt_EOF!=tok->ttype ){ cmpp_token_content(&tz, 0, bc, cflags); g_warn("token type=%d: %.*s ==> %.*s", tok->ttype, (int)tok->n, tok->z, (int)bc->n, bc->z); } cmpp_b_return(args->pp, bc); g_warn("End tizer test. rc=%d", dxppCode); cmpp_dx_errf(dx, 0, 0); cmpp_tizer_dtor(&tz); cmpp_b_return(args->pp, b); if(rc) return; } } #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_f_args const *args, unsigned int * i, cmpp_arg const **pBind){ if( 0==ppCode ){ #define nextArg (++*iargc ? args->argv[*i] : 0) *pBind = 0; cmpp_arg const *pN = nextArg; if( pN && cmpp_arg_equals_c(pN, "bind") ){ pN = nextArg; if( !pN || ( cmpp_tt_GroupSquiggly!=pN->ttype && cmpp_tt_GroupBrace!=pN->ttype ) ){ return serr("Expecting {...} or [...] after 'bind'."); } *pBind = pN; }else{ *pBind = 0; } #undef nextArg } return ppCode; } /** cmpp_kav_each_f() impl for used by #query's `bind {...}` argument. */ static int cmpp_kav_each_f_query__bind( cmpp * const pp, 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_errf(pp, CMPP_RC_MISUSE, "Bind keys must start with ':' or '$' or '@'."); }else{ sqlite3_stmt * const q = callbackState; assert( q ); int const bindNdx = sqlite3_bind_parameter_index(q, (char const*)zKey); if( bindNdx ){ cmpp_stmt_bind_textn(pp, q, bindNdx, zVal, nVal); }else{ cmpp_errf(pp, CMPP_RC_RANGE, "Invalid bind name: %.*s", (int)nKey, zKey); } } return ppCode; } int cmpp__bind_group(cmpp * const pp, sqlite3_stmt * const q, cmpp_arg const * const aGroup){ if( ppCode ) return ppCode; if( cmpp_tt_GroupSquiggly==aGroup->ttype ){ return cmpp_kav_each( pp, 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_errf(pp, CMPP_RC_MISUSE, "Expecting {...} or [...] " "for SQL binding list."); } int bindNdx = 0; cmpp_args args = cmpp_args_empty; cmpp_args_parse(pp, &args, aGroup->z, aGroup->n, 0); if( !args.argc && !ppCode ){ cmpp_errf(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; !ppCode && aVal; aVal = aVal->next ){ ++bindNdx; if( 0 ){ g_warn("bind #%d %s <<%s>>", bindNdx, cmpp__tt_cstr2(aVal->ttype, true), aVal->z); } cmpp__bind_arg(pp, q, bindNdx, aVal); } cmpp_args_cleanup(&args); return ppCode; } /** #query impl */ static void cmpp_f_query(cmpp_f_args const * args){ if( !args->argc ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Expecting one or more arguments"); return; } cmpp * const pp = args->pp; cmpp_dx * const dx = args->dx; sqlite3_stmt * q = 0; cmpp_b * const obBody = cmpp_b_borrow(args->pp); cmpp_b * const sql = cmpp_b_borrow(args->pp); cmpp_outputer obNull = cmpp_outputer_empty; int nChomp = 0; int nUnchomp = 0; bool spStarted = false; bool seenDefine = false; bool seenEmit = false; bool batchMode = false; cmpp_arg const * aBind = 0; cmpp_d const * const dNoRows = args->d->impl.state; cmpp_d const * const dClosers[2] = {args->d->closer, dNoRows}; if( !obBody || !sql ) goto cleanup; assert( dNoRows ); //g_warn("args.argc=%d", args.argc); #define nextArg (++iargc ? args->argv[i] : 0) for( unsigned i = 1; i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; //g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z); if( cmpp_arg_equals_c(arg, "define") ){ if( seenDefine ){ cmpp__dx_err_just_once(dx, arg); goto cleanup; } seenDefine = true; continue; } if( cmpp_arg_equals_c(arg, "emit") ){ if( seenEmit ){ cmpp__dx_err_just_once(dx, arg); goto cleanup; } seenEmit = true; continue; } if( cmpp_arg_equals_c(arg, "-chomp") ){ ++nChomp; continue; } if( cmpp_arg_equals_c(arg, "-unchomp") ){ ++nUnchomp; continue; } if( cmpp_arg_equals_c(arg, "-batch") ){ if( batchMode ){ cmpp__dx_err_just_once(dx, arg); goto cleanup; } batchMode = true; continue; } if( !sql->n ){ if( !arg->n ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Expecting a non-empty SQL argument."); goto cleanup; } if( cmpp__consume_sql_args(pp, args, &i, &aBind) ){ goto cleanup; } if( cmpp_arg_interpolate(pp, arg, sql, cmpp_arg_interpolate_BRACE_CALL) ){ goto cleanup; } //g_warn("SQL: <<%s>>", sql->z); continue; } cmpp_errf(pp, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); goto cleanup; } #undef nextArg if( ppCode ) goto cleanup; seenEmit |= args->isDxCall; if( seenDefine || seenEmit ){ if( seenDefine && nChomp ){ /* fixme (2026-07-30): support chomp for define */ serr("-chomp and define may not be used together."); goto cleanup; }else if( batchMode ){ serr("-batch and define/emit may not be used together."); goto cleanup; } } if( !sql->n ){ serr("Expecting an SQL-string argument."); goto cleanup; } if( batchMode ){ if( aBind ){ serr("Bindable emits may not be used with -batch."); goto cleanup; } char *zErr = 0; cmpp__pi(args->pp); int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr); rc = cmpp_db_rc(args->pp, rc, zErr); sqlite3_free(zErr); goto cleanup; } if( cmpp_db_rc_v2(pp, sqlite3_prepare_v2( pp->pimpl->db.dbh, (char const *)sql->z, (int)sql->n, &q, 0), 0, 0) ){ goto cleanup; }else if( !q ){ cmpp_errf(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_errf(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(pp, 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_stmt_step(pp, q, false); if( SQLITE_ROW==dbrc ){ gotARow = true; nChomp = nChompOrig; cmpp_b_reuse(obBody); if( cmpp__define_from_row(pp, q, false, seenEmit ? obBody : 0) ){ break; } if( seenEmit ){ while( nChomp-- && cmpp_b_chomp(obBody) ){} while( nUnchomp-- ) cmpp_b_append_ch(obBody, '\n'); if( seenEmit && !obBody->errCode && obBody->n ){ cmpp_out_raw(args->pp, obBody->z, obBody->n); } break; }else if( seenDefine ){ break; } cmpp_dx_pos_restore(dx, &dxPosStart); /* 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_out_raw(args->pp, 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 || seenEmit) ){ /* If we got here, there was no result row. */ cmpp__define_from_row(pp, q, true, 0); } break; }/*result row loop*/ cmpp_stmt_reset(pp, q); if( ppCode ) goto cleanup; while( !seenDefine && !seenEmit && !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(args->pp); if( cmpp_atpol_set(args->pp, cmpp_atpol_OFF) ) break; cmpp_dx_consume(dx, &obNull, dClosers, sizeof(dClosers)/sizeof(dClosers[0]), consumeFlags); cmpp_atpol_set(args->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_b_return(args->pp, obBody); cmpp_b_return(args->pp, sql); sqlite3_finalize(q); if( spStarted ) cmpp_sp_rollback(pp); } #endif /* #ifndef CMPP_OMIT_D_DB */ /** #sum ...args Emits the sum of its arguments, treating each as an integer. Non-integer arguments are silently skipped, i.e. treated as zeroes. */ static void cmpp_f_sum(cmpp_f_args const * args){ int64_t nTotal = 0, nn = 0; cmpp_b * const b = cmpp_b_borrow(args->pp); if( !b ) return; int rc = b ? 0 : CMPP_RC_OOM; for( unsigned i = 1; 0==rc && i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; rc = cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), cmpp_arg_interpolate_BRACE_CALL); if( 0==rc && cmpp_is_int64(b->z, b->n, &nn) ){ nTotal += nn; } } if( 0==rc ){ cmpp_b_append_i64(cmpp_b_reuse(b), nTotal); cmpp_out_raw(args->pp, b->z, b->n); } cmpp_b_return(args->pp, b); } /** #arg ?flags? the-arg -trim-left -trim-right -trim: trim both sides It sends its arg to cmpp_arg_interpolate() 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_f_arg(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL; bool trimL = false, trimR = false; cmpp_arg const * arg = dx->args.arg0; if( !dx->args.argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting an argument."); return; } //g_warn("argc=%d", dx->args.argc); cmpp_b * const b = cmpp_b_borrow(args->pp); for( ; arg && !cmpp_err_has(args->pp); arg = arg->next ){ #define FLAG(X)if( cmpp_arg_isflag_c(arg, X) ) FLAG("-raw") { a2bFlags = cmpp_arg_interpolate_FORCE_STRING; continue; } FLAG("-trim-left") { trimL=true; continue; } FLAG("-trim-right") { trimR=true; continue; } FLAG("-trim") { trimL=trimR=true; continue; } #undef FLAG if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(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( 0 ){ g_warn("arg n=%d bn=%d %s %.*s", (int)arg->n, (int)b->n, cmpp_tt_cstr(arg->ttype), (int)(zzEnd-zz), zz); } //assert(!cmpp_dx_is_eliding(args->dx)); if( zzEnd-zz>0 ){ cmpp_out_raw(args->pp, zz, zzEnd-zz); //g_warn0("here"); } } } cmpp_b_return(args->pp, b); } /** #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 !(flags & cmpp_f_F_IS_CALL). -nonl: do not append a newline. Default when dx->isDxCall. */ static void cmpp_f_join(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; cmpp_b * const b = cmpp_b_borrow(args->pp); cmpp_b * const bSep = cmpp_b_borrow(args->pp); cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL; bool addNl = !args->isDxCall; int n = 0; if( !b || !bSep ) goto end; if( !dx->args.argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "%s%s expects ?flags? ...args", cmpp_dx_delim(dx), args->d->name.z); goto end; } cmpp_b_append_ch(bSep, ' '); cmpp_check_oom(args->pp, bSep->z); for( cmpp_arg const * arg = dx->args.arg0; arg && !b->errCode && !bSep->errCode && !cmpp_err_has(args->pp); arg = arg->next ){ #define FLAG(X)if( cmpp_arg_isflag_c(arg, X) ) FLAG("-s"){ if( !arg->next ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Missing SEPARATOR argument to -s."); break; } cmpp_arg_interpolate(args->pp, arg->next, cmpp_b_reuse(bSep), cmpp_arg_interpolate_BRACE_CALL); arg = arg->next; /* FIXME: unescape bSep */ continue; } //FLAG("-nl"){ addNl=true; continue; } FLAG("-nonl"){ addNl=false; continue; } #undef FLAG if( n++ && cmpp_out_raw(args->pp, bSep->z, bSep->n) ){ break; } if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), a2bFlags) ){ break; } cmpp_out_raw(args->pp, b->z, b->n); } if( !cmpp_err_has(args->pp) ){ if( !n ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting at least one argument."); }else if( addNl ){ cmpp_out_raw(args->pp, "\n", 1); } } end: cmpp_b_return(args->pp, b); cmpp_b_return(args->pp, bSep); } /* Impl. for #file */ static void cmpp_f_file(cmpp_f_args const * args){ cmpp_dx * const dx = args->dx; enum e_op { op_none, op_exists, op_join }; cmpp_b * const b0 = cmpp_b_borrow(args->pp); if( !b0 ) goto end; enum e_op op = op_none; cmpp_arg const * arg = 0; #define nextArg if( ++i==args->argc ) goto missing_arg; arg = args->argv[i] unsigned i = 1; for(; i < args->argc; ++i ){ arg = args->argv[i]; if( op_none==op ){ if( cmpp_arg_equals_c(arg, "exists") ){ nextArg; op = op_exists; if( i+1argc ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "%s%s exists: too many arguments", cmpp_dx_delim(dx), args->d->name.z); goto end; } break; }else if( cmpp_arg_equals_c(arg, "join") ){ nextArg; op = op_join; break; }else{ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unknown %s%s command: %s", cmpp_dx_delim(dx), args->d->name.z, arg->z); goto end; } cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s unhandled argument: %s", cmpp_dx_delim(dx), args->d->name.z, arg->z); goto end; } } cmpp_flag32_t const bFlags = cmpp_arg_interpolate_BRACE_CALL; switch( op ){ case op_none: goto missing_arg; case op_join: { assert( arg ); for( int j = 0; arg; ++j ){ if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags) || (j && cmpp_out_raw(args->pp, "/", 1)) || (b0->n && cmpp_out_raw(args->pp, b0->z, b0->n)) ){ break; } arg = ++i==args->argc ? 0 : args->argv[i] /* The internals actually NULL-pad argv, so we don't _need_ to do that ternary, but that detail is not part of the public API and i'm not yet sure whether it should be. */; } cmpp_out_raw(args->pp, "\n", 1); break; } case op_exists: { assert( arg ); if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags) ){ bool const b = cmpp__file_is_readable((char const *)b0->z); cmpp_out_raw(args->pp, b ? "1\n" : "0\n", 2); } break; } } #undef nextArg end: cmpp_b_return(args->pp, b0); return; missing_arg: if( arg ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s %s: missing argument", cmpp_dx_delim(dx), args->d->name.z, arg->z ); }else{ cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s: missing subcommand", cmpp_dx_delim(dx), args->d->name.z); } goto end; } /** #cmp LHS op RHS */ static void cmpp_f_cmp(cmpp_f_args const * args){ cmpp_b * const bL = cmpp_b_borrow(args->pp); cmpp_b * const bR = cmpp_b_borrow(args->pp); cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL; if( !bL || !!bR ) goto end; for( cmpp_arg const * arg = args->dx->args.arg0; arg && !cmpp_err_has(args->pp); arg = arg->next ){ if( !bL->z ){ cmpp_arg_interpolate(args->pp, arg, bL, a2bFlags); continue; } if( !bR->z ){ cmpp_arg_interpolate(args->pp, arg, bR, a2bFlags); continue; } break; } if( cmpp_err_has(args->pp) ) goto end; if( !bL->z || !bR->z ){ cmpp_errf(args->pp, 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_out_raw(args->pp, cbuf, (cmpp_size_t)n); end: cmpp_b_return(args->pp, bL); cmpp_b_return(args->pp, bR); } /** Impl for #base64. */ static void cmpp_f_base64(cmpp_f_args const * args){ bool comma = false; bool quote = false; cmpp_arg const * argFile = 0; bool preprocessFileArg = false; cmpp_b * const ob = cmpp_b_borrow(args->pp); if( !ob ){ goto end; } for( unsigned i = 1; i < args->argc; ++i ){ cmpp_arg const * arg = args->argv[i]; #define M(STR) cmpp_arg_equals_c(arg, STR) if( M("-comma") ) comma = true; else if( M("-quote") ) quote = true; else if( M("-f") || M("-file") || (preprocessFileArg=M("-F")) || (preprocessFileArg=M("-FILE")) ){ if( ++i==args->argc ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Expecting an argument after %s.", arg->z); goto end; } argFile = arg = args->argv[i]; continue; }else{ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); goto end; } #undef M } if( argFile ){ if(cmpp_arg_consume_as_file(args->dx, argFile, NULL, ob, preprocessFileArg) ){ goto end; } }else{ if( args->isDxCall ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "#%s...#/%s is not legal for a [call]. " "Pass the -f FILENAME flag instead.", args->d->name.z, args->d->name.z); goto end; }else{ assert( !ob->n ); if( cmpp_dx_consume_b(args->dx, ob, &args->d->closer, 1, cmpp_dx_consume_F_PROCESS_OTHER_D) ){ goto end; } } } if( ob->n ){ cmpp_b * const b64 = cmpp_b_borrow(args->pp); if( !b64 ) goto end; int cmpp__b_base64_encode(cmpp *, cmpp_b const *, cmpp_b *)/* in b.c */; cmpp__b_base64_encode(args->pp, ob, b64); cmpp_b_swap(ob, b64); cmpp_b_return(args->pp, b64); if( cmpp_err_has(args->pp) ) goto end; } unsigned char const * zEnd = ob->z + ob->n; #define out(P,N) if( cmpp_out_raw(args->pp, P, N) ) goto end if( !quote ){ cmpp_out_raw(args->pp, ob->z, ob->n); goto end; } for( unsigned char const * z = ob->z; z < zEnd; ++z ){ if( quote ){ out("\"", 1); } bool closed = false; for( ;zpp, ob); return; } /** 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_f_F_ ## X #define F_A_RAW CF(ARGS_RAW) #define F_A_V4 CF(ARGS_V4) #define F_A_LIST CF(ARGS_LIST) #define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY) #define F_UNSAFE cmpp_f_F_NOT_IN_SAFEMODE #define F_NC cmpp_f_F_NO_CALL #define F_CALL cmpp_f_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, \ .arity = -1, \ .usage = "#directive .usage is TODO. See README.md for docs." \ } #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_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL), cmpp_f_if_dangler, 0); DREG0(rElif, "elif", cmpp_f_if_dangler, F_NC, 0, 0); DREG0(rElse, "else", cmpp_f_if_dangler, F_NC, 0, 0); CmppIfState * const cis = cmpp_malloc2(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_f_at, F_A_LIST, cmpp_f_dangling_closer, 0); M_IF_CORE("arg", cmpp_f_arg, F_A_LIST, 0, 0); M_IF_CORE("assert", cmpp_f_expr, F_EXPR, 0, 0); M_IF_CORE("base64", cmpp_f_base64, F_A_V4, cmpp_f_dangling_closer, 0); M_IF_CORE("cmp", cmpp_f_cmp, F_A_LIST, 0, 0); M_IF_CORE("define", cmpp_f_define, F_A_V4, cmpp_f_dangling_closer, 0); M_IF_CORE("delimiter", cmpp_f_delimiter, F_A_LIST, cmpp_f_dangling_closer, 0); M_IF_CORE("error", cmpp_f_error, F_A_RAW, 0, 0); M_IF_CORE("eval", cmpp_f_eval, F_A_V4, cmpp_f_dangling_closer, 0); M_IF_CORE("expand", cmpp_f_expand, F_A_V4, cmpp_f_dangling_closer, 0); M_IF_CORE("expr", cmpp_f_expr, F_EXPR, 0, 0); M_IF_CORE("join", cmpp_f_join, F_A_LIST, 0, 0); M_IF_CORE("once", cmpp_f_once, F_A_LIST | F_NC, cmpp_f_dangling_closer, 0); M_IF_CORE("pragma", cmpp_f_pragma, F_A_V4, 0, 0); M_IF_CORE("savepoint", cmpp_f_savepoint, F_A_V4, 0, 0); M_IF_CORE("stderr", cmpp_f_stderr, F_A_RAW, 0, 0); M_IF_CORE("sum", cmpp_f_sum, F_A_V4, 0, 0); M_IF_CORE("undef", cmpp_f_undef, F_A_V4, 0, 0); M_IF_CORE("undefined-policy", cmpp_f_undef_policy, F_A_V4, 0, 0); M_IF_CORE("//", cmpp_f_noop, F_A_RAW, 0, 0); M_IF_CORE("file", cmpp_f_file, F_A_V4 | 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.ctorFlags) ) { \ DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \ } \ goto end; \ } #ifndef CMPP_OMIT_D_INCLUDE M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE, cmpp_f_include, F_A_V4 | F_UNSAFE, 0, 0); #endif #ifndef CMPP_OMIT_D_DB M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB, cmpp_f_attach, F_A_V4 | F_UNSAFE, 0, 0); M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB, cmpp_f_detach, F_A_V4 | F_UNSAFE, 0, 0); if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.ctorFlags) && (M_OC("query") || M("query:no-rows")) ){ DREG0(rQ, "query", cmpp_f_query, F_A_V4 | F_UNSAFE, cmpp_f_dangling_closer, 0); cmpp_d * dQ = 0; rc = cmpp_d_register(pp, &rQ, &dQ); if( rc ) goto end; /* 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", .arity = -1, .opener = { .f = cmpp_f_dangling_closer, .flags = F_NC } }; rc = cmpp_d_register(pp, &rNR, &dNoRows); if( rc ) goto end; dNoRows->closer = dQ->closer; assert( !dQ->impl.state ); dQ->impl.state = dNoRows; goto end; } #endif /*CMPP_OMIT_D_DB*/ #undef M_IF_FLAGGED #ifndef NDEBUG M_IF("experiment"){ DREG("experiment", cmpp_f_experiment, F_A_V4 | F_UNSAFE, 0, 0); } #endif end: #undef DREG #undef DREG0 #undef F_EXPR #undef F_A_RAW #undef F_A_LIST #undef F_A_V4 #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; } #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 #include #include 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; CMPP__EXPORT(bool, cmpp_arg_equals)(cmpp_arg const *arg, char const *z){ return 0==memcmp(arg->z, z, arg->n) ? arg->n==strlen(z) : 0; } CMPP__EXPORT(char const *,cmpp_arg_cstr)(cmpp_arg const *arg){ return (char const *)arg->z; } CMPP__EXPORT(char const *,cmpp_arg_cstr2)(cmpp_arg const *arg, cmpp_size_t *n){ *n = arg->n; return (char const *)arg->z; } //just in case these ever get dynamic state void cmpp_arg_cleanup(cmpp *pp, cmpp_arg *arg){ if( arg ){ (void)pp; *arg = cmpp_arg_empty; } } void cmpp_arg_reuse(cmpp_arg *arg){ if( arg ){ *arg = cmpp_arg_empty; } } void CmppArgList_reuse(cmpp*pp, CmppArgList *li){ for(cmpp_size_t n = li->na; n; ){ cmpp_arg * const arg = &li->list[--n]; if( arg ){ (void)pp; cmpp_arg_reuse( arg ); } assert( !li->list[n].next ); } if( li->avb ) cmpp_b_reuse(li->avb); li->n = 0; } void CmppArgList_dtor(cmpp *pp, CmppArgList *li){ static const CmppArgList CmppArgList_empty = CmppArgList_empty_m; assert(pp); while( li->na ){ cmpp_arg * const arg = &li->list[--li->na]; if( arg ){ cmpp_arg_cleanup(pp, arg); } } cmpp_mfree(li->list); cmpp_b_return(pp, li->avb); *li = CmppArgList_empty; } cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp, CmppArgList*li, bool ensureNulTerminated){ if( li->n ){ if( !li->avb ){ li->avb = cmpp_b_borrow(pp); if( !li->avb ) return 0; } cmpp_size_t sz = ( /* argv */ sizeof(cmpp_arg*) * (li->n+1/*NULL-pad it*/) ); cmpp_size_t const strOffset = /* Offset into li->abv->z of the string table */ sz; if( ensureNulTerminated ){ for(cmpp_size_t i = 0; i < li->n; ++i ){ cmpp_arg const * const a = &li->list[i]; if( a->n && a->z[a->n]/* not NUL-terminated*/ ){ sz += a->n + 1/*NUL*/; } } } if( cmpp_b_reserve(li->avb, sz) ){ cmpp_b_return(pp, li->avb); li->avb = 0; return 0; } li->avb->n = sz; /* Direct argv[X] to a suitable pointer and argv[X]->z to a NUL-terminated copy of itself. */ cmpp_arg const ** const av = (cmpp_arg const **)li->avb->z; unsigned char * oz = li->avb->z + strOffset; for(cmpp_size_t i = 0; i < li->n; ++i){ cmpp_arg * const a = &li->list[i]; av[i] = a; if( ensureNulTerminated && a->n && a->z[a->n]/* not NUL-terminated*/ ){ //g_warn("arg: %.*s", (int)a->n, a->z); assert( oz < (li->avb->z + li->avb->n - a->n) ); memcpy(oz, a->z, a->n); a->z = oz; oz += a->n; *oz++ = 0; } #if 0 /* Legacy cruft we don't want at this level */ if( i && av[i] && av[i-1] ){ av[i-1]->next = av[i]; } #endif } av[li->n] = 0; return av; } return 0; } cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){ cmpp_arg * p = 0; assert( li->list ? li->na : 0==li->na ); if( 0==ppCode && 0==CmppArgList_reserve(pp, li, cmpp__li_reserve1_size(li,10)) ){ p = &li->list[li->n++]; cmpp_arg_reuse( p ); if( li->avb ){ cmpp_b_return(pp, li->avb); li->avb = 0; } } return p; } #if 0 /** 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] ); } } #endif void cmpp__args_pimpl_cleanup(cmpp_args_pimpl *p){ assert( p->pp ); assert( !p->nextFree ); cmpp_b_clear(&p->argOut); CmppArgList_dtor(p->pp, &p->argli); *p = cmpp_args_pimpl_empty; } static void cmpp__args_pimpl_reuse(cmpp_args_pimpl *p){ assert( p->pp ); assert( !p->nextFree ); cmpp_b_reuse(&p->argOut); CmppArgList_reuse(p->pp, &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_malloc2(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) 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 *pp, \ 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_OpCmp ## 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_e 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_OpCmp ## 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) /** 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 * const pp, sqlite3_stmt * const q, int bindNdx, cmpp_arg const ** paArg){ cmpp_arg const * const arg = *paArg; assert(arg); switch( ppCode ? 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_stmt_bind_textn(pp, q, bindNdx, arg->z, arg->n); *paArg = arg->next; break; case cmpp_tt_StringBT: case cmpp_tt_StringDQ: case cmpp_tt_StringSQ: case cmpp_tt_IntDec:{ cmpp__bind_arg(pp, 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(pp, arg, &rv, paArg) ){ cmpp_stmt_bind_int(pp, q, bindNdx, rv); } *paArg = arg->next; break; } /* TODO? cmpp_tt_GroupParen */ default: cmpp_errf(pp, 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_stmt_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_OpCmpEq: *pResult = 0==rc; break; case cmpp_tt_OpCmpNeq: *pResult = 0!=rc; break; case cmpp_tt_OpCmpLt: *pResult = rc<0; break; case cmpp_tt_OpCmpLe: *pResult = rc<=0; break; case cmpp_tt_OpCmpGt: *pResult = rc>0; break; case cmpp_tt_OpCmpGe: *pResult = rc>=0; break; default: cmpp__fatal("Cannot happen: invalid arg mapping"); } } cmpp_stmt_reset(pp, 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 *pp, 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(pp, CmppStmt_cmpVD, false) : cmpp__stmt(pp, CmppStmt_cmpVV, false); if( q ){ char numbuf[32]; int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs); cmpp_stmt_bind_textn(pp, q, 1, ustr_c(numbuf), nNum); cmpp_argOp__cmp_bind(pp, q, 2, paRhs); cmpp_argOp__cmp_apply(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(pp, CmppStmt_cmpDD, false) : cmpp__stmt(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(pp, CmppStmt_cmpVD, false); }else{ q = cmpp__stmt(pp, CmppStmt_cmpVV, false); } if( q ){ //cmpp_stmt_bind_textn(pp, q, 1, vLhs->z, vLhs->n); cmpp_argOp__cmp_bind(pp, q, 1, &vLhs); cmpp_argOp__cmp_bind(pp, q, 2, pvRhs); cmpp_argOp__cmp_apply(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 *pp, cmpp_arg const *arg, int * pResult){ cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(pp, &sub, pResult); } cmpp_args_cleanup(&sub); return ppCode; } int cmpp__args_evalToInt(cmpp * const pp, cmpp_args const *pArgs, int * pResult){ if( ppCode ) return ppCode; 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==ppCode; 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_cstr2(arg->ttype, true), arg, arg->z); if(1){ if( pPrev ) lout(" prev arg: %s %s\n", cmpp__tt_cstr2(pPrev->ttype, true), pPrev->z); if( pNext ) lout(" next arg: %s %s\n", cmpp__tt_cstr2(pNext->ttype, true), pNext->z); } } if( thisOp ){ /* Basic validation */ if( !pNext ){ serr("Missing '%s' RHS.", cmpp__tt_cstr2(thisOp->ttype, false)); break; }else if( !pPrev && 2==thisOp->arity ){ serr("Missing %s LHS.", cmpp__tt_cstr2(thisOp->ttype, false)); break; } if( nextOp && nextOp->arity>1 ){ serr("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_errf(pp, CMPP_RC_CANNOT_HAPPEN, "We expected to have consumed '%s' by " "this point.", pPrev->z); }else{ cmpp__arg_toBool(pp, 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(pp, 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_interpolate(pp, pNext, &osL, 0) ){ break; } unsigned char const * const zGlob = osL.z; if( 0==cmpp_arg_interpolate(pp, 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_OpCmp ## NAME: cmpp_argOps_cmp_map(E) { cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0; if( prevOp ){ /* Chained operators */ cmpp_argOp_applyTo(pp, thisOp, result, &pNext, &result); }else{ assert( pNext ); assert( pPrev ); assert( thisOp ); assert( thisOp->xCall ); thisOp->xCall(pp, thisOp, pPrev, &pNext, &result); } break; } #undef E #define checkConsecutiveNonOps \ if( pPrev && !argOp(pPrev) ){ \ serr("Illegal consecutive non-operators: %s %s", \ pPrev->z, arg->z); \ break; \ }(void)0 case cmpp_tt_IntDec: case cmpp_tt_StringBT: case cmpp_tt_StringDQ: case cmpp_tt_StringSQ: 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(pp, arg->z, arg->n, &result); break; case cmpp_tt_GroupParen:{ checkConsecutiveNonOps; cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(pp, &sub, &result); } cmpp_args_cleanup(&sub); break; } case cmpp_tt_GroupBrace:{ checkConsecutiveNonOps; cmpp_b b = cmpp_b_empty; if( 0==cmpp_call_str(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 ); serr("Illegal expression token %s: %s", cmpp__tt_cstr2(arg->ttype, true), arg->z); }/*switch(arg->ttype)*/ }/* foreach arg */ if( 0 ){ lout("END %s() result=%d\n", __func__, result); } --level; if( !ppCode ){ *pResult = result; } cmpp_b_clear(&osL); cmpp_b_clear(&osR); return ppCode; #undef lout } #undef argOp #undef cmpp_argOp_decl /** Workaround for legacy (pre-2026-08-30) behaviour: trim leading spaces and one leading newline on groups. We do not really want cmpp_tizer_next() to do that so we'll account for it here to avoid a regression causing extra output lines for directive arguments. This was only ever relevant for squiggly groups, as neither (...) nor [...] were ever used in ways which affected output. A sample case: #define x { blah @foo@ } #expand x {foo -> 1} If we don't retain the legacy behavior then such constructs start producing a stray newline where they didn't before. */ void cmpp__squiggly_strip_lead(cmpp_token *tok){ if( cmpp_tt_GroupSquiggly!=tok->ttype || !tok->nInner ) return; if( tok->nInner ){ unsigned char const * inz = tok->zInner; unsigned char const * enz = tok->zInner + tok->nInner; cmpp_skip_space(&inz, tok->z+tok->nInner); if( inz < tok->zInner+tok->nInner ){ if( '\n'==*inz ) ++inz; else if(inz+1zInner+tok->nInner - inz), inz); } tok->n = tok->nInner = (tok->zInner+tok->nInner - inz); tok->z = tok->zInner = inz; } } static void cmpp__t2a_ttype_set(cmpp_token const *tok, cmpp_arg * arg){ arg->ttype = tok->ttype; arg->token = *tok; if( cmpp_tt_Word==arg->ttype ){ int const ttop = cmpp__tt_tok_op(tok->z, tok->n); if( ttop ){ //g_warn("ttop %s", cmpp_tt_cstr(ttop)); arg->ttype = ttop; } /*else if( cmpp_is_int64(arg->z, arg->n, 0) ){ arg->ttype = cmpp_tt_IntDec; }*/ } } CMPP__EXPORT(int, cmpp_args_parse)(cmpp * const pp, cmpp_args * const pArgs, unsigned char const * const zInBegin, cmpp_strlen_t nIn, cmpp_flag32_t flags){ assert( zInBegin ); unsigned char const * const zInEnd = zInBegin + cmpp__strlenu(zInBegin, nIn); nIn = cmpp__strlenu(zInBegin, nIn); cmpp_tizer tz = cmpp_tizer_empty; cmpp_token * const tok = &tz.token; int rc = 0; cmpp_b * bUnesc = 0; unsigned char * zOut = 0; unsigned char const * zOutEnd = 0; #if !defined(NDEBUG) unsigned char const * zReallocCheck = 0; #endif cmpp_tizer_init(&tz, &pp->pimpl->err, zInBegin, (cmpp_size_t)nIn); if( cmpp__args_init(pp, pArgs) ) return ppCode; if( 0 ){ g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin), 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))/2 + 5/*fudge room*/; cmpp_b * const buffer = &pArgs->pimpl->argOut; assert( !buffer->n ); if( cmpp_b_reserve3(pp, buffer, nBuffer) ){ goto end; } if( cmpp_args_F_UNESCAPE & flags ){ bUnesc = cmpp_b_borrow(pp); if( !bUnesc ) goto end; } zOut = buffer->z; zOutEnd = zOut + buffer->nAlloc - 1; #if !defined(NDEBUG) zReallocCheck = buffer->z; #endif if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin), zInBegin); pArgs->arg0 = NULL; pArgs->argc = 0; cmpp_flag32_t const tFlags = cmpp_tizer_next_SKIP_NL | cmpp_tizer_next_SKIP_C_COMMENTS; for( int i = 0; (0==(rc=cmpp_tizer_next(&tz,tFlags))) && cmpp_tt_EOF!=tok->ttype ; ++i){ (void)i; #if 0 if( cmpp_tt_EOC==tok->ttype && ';'!=*tok->z ){ //EOL continue; } #endif if( !(cmpp_args_F_NO_SQUIGGLY_HACK & flags) ){ cmpp__squiggly_strip_lead(tok); } cmpp_arg * const arg = CmppArgList_append(pp, &pArgs->pimpl->argli); if( !arg ) break; assert( pArgs->pimpl->argli.n ); assert(zReallocCheck==buffer->z && "Else buffer was reallocated, invalidating other arg->z"); if( 0 ){ g_warn("tok %s=%.*s", cmpp_tt_cstr(tok->ttype), (int)(tok->n), tok->z); if( tok->z!=tok->zInner ){ g_warn("tok => %.*s", (int)(tok->nInner), tok->zInner); } } unsigned char const * zX = 0; unsigned char const * zXEnd = 0; if( bUnesc ){ if( cmpp_b_append4(pp, cmpp_b_reuse(bUnesc), tok->zInner, tok->nInner) ){ break; } cmpp_b_unescape(bUnesc); zX = bUnesc->z; zXEnd = zX + bUnesc->n; }else{ zX = tok->zInner; zXEnd = zX + tok->nInner; } assert( zOut + (zXEnd-zX) < zOutEnd ); if( 0 ){ g_warn("arg %s: %.*s", cmpp_tt_cstr(tok->ttype), (int)(zXEnd-zX), zX); } memcpy(zOut, zX, (zXEnd-zX)); arg->z = zOut; arg->n = (zXEnd-zX); zOut += arg->n; *zOut++ = 0; cmpp__t2a_ttype_set(tok, arg); assert( arg->token.z==tok->z ); if( 0 ){ g_warn("tok.ttype=%s arg ttype %s: %.*s", cmpp_tt_cstr(tok->ttype), cmpp_tt_cstr(arg->ttype), (int)arg->n, arg->z); } assert( arg->ttype ); if( 0==(flags & cmpp_args_F_NO_PARENS) && cmpp_tt_GroupParen==tok->ttype ){ /* 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(pp, &sub, arg->z, arg->n, flags); //g_stderr("Parsed sub-expr: %s\n", sub.buffer.z); cmpp_args_cleanup(&sub); if( ppCode ) break; } }/*foreach token*/ //g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(ppCode), pArgs->args.n); end: cmpp_b_return(pp, bUnesc); cmpp_tizer_dtor(&tz); if( 0==ppCode ){ pArgs->argc = pArgs->pimpl->argli.n; assert( !pArgs->arg0 ); for( unsigned int i = 1; i < pArgs->argc; ++i ){ /* We have to link arg->next after the loop because CmppArgList_append() can realloc, invalidating the links. 2026-09-04: this is legacy stuff but many directives rely on it. */ cmpp_arg * const a = &pArgs->pimpl->argli.list[i]; assert( ! a->next ); pArgs->pimpl->argli.list[i-1].next = a; assert( a->token.z ); } if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list; if( zOutarg0; a; a = a->next ){ g_stderr(" got: %s %.*s\n", cmpp__tt_cstr2(a->ttype, true), a->n, a->z); } } } assert(zReallocCheck==buffer->z && "Else buffer was reallocated, invalidating argN->z"); return ppCode; } 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->na>=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; aNew->token = a->token; 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); } CMPP__EXPORT(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; } CMPP__EXPORT(int,cmpp_arg_parse)( cmpp * const pp, cmpp_arg *pOut, unsigned char const **pzIn, unsigned char const *zInEnd, unsigned char ** pzOut, unsigned char const * zOutEnd ){ cmpp_tizer tz = cmpp_tizer_empty; cmpp_token * const tok = &tz.token; cmpp_tizer_init(&tz, &pp->pimpl->err, *pzIn, (zInEnd-*pzIn)); unsigned char * zo = *pzOut; int rc = 0; cmpp_flag32_t const tnFlags = cmpp_tizer_next_SKIP_NL | cmpp_tizer_next_SKIP_C_COMMENTS; cmpp_arg_reuse( pOut ); pOut->z = zo; rc = cmpp_tizer_next(&tz, tnFlags); if( rc ) return rc/*pp's err already set*/; if( cmpp_tt_EOF==tok->ttype ){ pOut->z = zInEnd; pOut->n = 0; pOut->ttype = cmpp_tt_EOF; return 0; } if( zo+tok->nInner>=zOutEnd-1 ){ cmpp_errf(pp, CMPP_RC_RANGE, "Ran out of output space (%u bytes) while " "parsing an argument", (unsigned)(zOutEnd-*pzOut)); }else{ cmpp__squiggly_strip_lead(tok); pOut->n = tok->n; memcpy(zo, tok->zInner, tok->nInner); zo += tok->nInner; *zo++ = 0; *pzOut = zo; *pzIn = tz.zPos; cmpp__t2a_ttype_set(tok, pOut); if(0){ g_stderr("parse1: %s n=%u <<%.*s>>", cmpp__tt_cstr2(pOut->ttype, true), pOut->n, pOut->n, pOut->z); } } #undef out #undef eot_break return ppCode; } int cmpp__arg_toBool(cmpp * const pp, cmpp_arg const *arg, int * pResult, cmpp_arg const **pNext){ switch( ppCode ? 0 : arg->ttype ){ case 0: break; case cmpp_tt_Word: *pNext = arg->next; *pResult = cmpp__get_bool(pp, arg->z, arg->n); break; case cmpp_tt_IntDec: *pNext = arg->next; cmpp_is_int(arg->z, arg->n, pResult)/*was already validated*/; break; case cmpp_tt_StringBT: case cmpp_tt_StringDQ: case cmpp_tt_StringSQ:{ int rc = 0; *pNext = arg->next; if( cmpp_is_int(arg->z, arg->n, &rc) ){ *pResult = rc; }else{ *pResult = arg->n>0 && 0!=memcmp("0\0", arg->z, 2); } break; } case cmpp_tt_GroupParen:{ *pNext = arg->next; cmpp_args sub = cmpp_args_empty; if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){ cmpp__args_evalToInt(pp, &sub, pResult); } cmpp_args_cleanup(&sub); break; } case cmpp_tt_OpDefined: if( !arg->next ){ serr("Missing '%s' RHS.", arg->z); }else if( cmpp_tt_Word!=arg->next->ttype ){ serr( "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(pp, (char const*)aOperand->z+1, cmpp__d_search3_F_NO_DLL) ){ *pResult = 1; }else{ *pResult = cmpp__has(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(pp, arg->next, pResult, pNext) ){ *pResult = !*pResult; } break; } default: serr("Invalid token type %s for %s(): %s", cmpp__tt_cstr2(arg->ttype, true), __func__, arg->z); break; } return ppCode; } CMPP__EXPORT(int, cmpp_arg_interpolate)(cmpp * const pp, 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. 2026-09-21: The above still currently applies but we're going to want expressions if/when the v1 expression cruft is replaced with v2. */ if( !ob ){ if( arg->interp ){ ob = cmpp_b_reuse(arg->interp); }else{ return cmpp_errf(pp, CMPP_RC_MISUSE, "%s() only accepts a NULL cmpp_b if arg->interp " "is not NULL.", __func__); } } switch( ppCode ? 0 : ((cmpp_arg_interpolate_FORCE_STRING & flags) ? cmpp_tt_shape_string : arg->ttype) ){ case 0: break; case cmpp_tt_Word: if( 0==(flags & cmpp_arg_interpolate_NO_DEFINES) ){ cmpp__get_b(pp, arg->z, arg->n, ob, true); break; } goto theDefault; case cmpp_tt_GroupBrace: if( !(cmpp_arg_interpolate_NO_BRACE_CALL & flags) && (cmpp_arg_interpolate_BRACE_CALL & flags) ){ cmpp_call_str(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(pp, &oss, arg->z, arg->n); break; } } return ppCode; } int cmpp__bind_arg(cmpp * const pp, sqlite3_stmt * const q, int bindNdx, cmpp_arg const * const arg){ if( 0 ){ g_warn("bind #%d %s <<%.*s>>", bindNdx, cmpp__tt_cstr2(arg->ttype, true), (int)arg->n, arg->z); } switch( arg->ttype ){ default: case cmpp_tt_IntDec: case cmpp_tt_StringBT: case cmpp_tt_StringDQ: case cmpp_tt_StringSQ: cmpp_stmt_bind_textn(pp, q, bindNdx, arg->z, (int)arg->n); break; case cmpp_tt_Word:{ cmpp_b os = cmpp_b_empty; if( 0==cmpp_arg_interpolate(pp, arg, &os, 0) ){ if( 0 ){ g_warn("bind #%d <<%s>> => <<%.*s>>", bindNdx, arg->z, (int)os.n, os.z); } cmpp_stmt_bind_textn(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(pp, &sub, arg->z, arg->n, 0) && 0==cmpp__args_evalToInt(pp, &sub, &i) ){ /* See comment above about cmpp_tt_Int. */ cmpp_stmt_bind_int_text(pp, q, bindNdx, i); } cmpp_args_cleanup(&sub); break; } case cmpp_tt_GroupBrace:{ cmpp_b b = cmpp_b_empty; cmpp_call_str(pp, arg->z, arg->n, &b, 0); cmpp_stmt_bind_textn(pp, q, bindNdx, b.z, b.n); cmpp_b_clear(&b); break; } } return ppCode; } /** 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->na && a>=li->list && a<(li->list + li->na) ){ 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 ){ cmpp_flag32_t const flags = cmpp_args_F_NO_PARENS | ((cmpp_f_F_NO_SQUIGGLY_HACK & dx->d->flags) ? cmpp_args_F_NO_SQUIGGLY_HACK : 0/* legacy compatibility */); if( 0==cmpp_args_parse(dx->pp, args, dx->args.z, dx->args.nz, flags) && (cmpp_f_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 *pp, cmpp_args *args, unsigned char const *zBegin, cmpp_strlen_t nz, cmpp_flag32_t flags){ if( 0==cmpp_args_parse(pp, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){ if( !args->argc && (cmpp_kav_each_F_NOT_EMPTY & flags) ){ cmpp_errf(pp, CMPP_RC_RANGE, "Empty list is not permitted here."); } } return ppCode; } /* Helper for cmpp_kav_each() and friends. */ static int cmpp__each_paren_expr(cmpp *pp, cmpp_arg const * arg, unsigned char * pOut, size_t nOut){ cmpp_args sub = cmpp_args_empty; int rc = cmpp_args_parse(pp, &sub, arg->z, arg->n, 0); if( 0==rc ){ int d = 0; rc = cmpp__args_evalToInt(pp, &sub, &d); if( 0==rc ){ snprintf((char *)pOut, nOut, "%d", d); } } cmpp_args_cleanup(&sub); return rc; } CMPP__EXPORT(int, cmpp_kav_each)(cmpp *pp, unsigned char const *zBegin, cmpp_strlen_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags){ if( ppCode ) return ppCode; /* 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_args args = cmpp_args_empty; cmpp_b * const bKey = cmpp_b_borrow(pp); cmpp_b * const bVal = bKey ? cmpp_b_borrow(pp) : 0; if( !bVal ) goto end; bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags); unsigned char exprBuf[32] = {0}; cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn); cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_interpolate() flags*/; assert( zBegin ); if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){ goto end; }else if( reqArrow && 0!=args.argc%3 ){ cmpp_errf(pp, CMPP_RC_RANGE, "Expecting a list of 3 tokens per entry: " "KEY -> VALUE"); goto end; }else if( !reqArrow && 0!=args.argc%2 ){ cmpp_errf(pp, CMPP_RC_RANGE, "Expecting a list of 2 tokens per entry: " "KEY VALUE"); goto end; } if( cmpp_kav_each_F_CALL_KEY & flags ){ a2bK |= cmpp_arg_interpolate_BRACE_CALL; flags |= cmpp_kav_each_F_EXPAND_KEY; } if( cmpp_kav_each_F_CALL_VAL & flags ){ a2bV |= cmpp_arg_interpolate_BRACE_CALL; flags |= cmpp_kav_each_F_EXPAND_VAL; } cmpp_arg const * aNext = 0; for( cmpp_arg const * aKey = args.arg0; !ppCode && aKey; aKey = aNext ){ aNext = aKey->next; cmpp_arg const * aVal = aKey->next; if( !aVal ){ serr("Expecting %s after key '%s'.", (reqArrow ? "->" : "a value"), aKey->z); break; } if( reqArrow ){ if( cmpp_tt_ThinArrowR!=aVal->ttype ){ serr("Expecting -> after key '%s'.", aKey->z); break; } aVal = aVal->next; if( !aVal ){ serr("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_interpolate(pp, 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(pp, 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_interpolate(pp, 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(pp, zKey, nKey, zVal, nVal, callbackState) ){ break; } } end: cmpp_b_return(pp, bKey); cmpp_b_return(pp, bVal); cmpp_args_cleanup(&args); return ppCode; } #if 0 CMPP__EXPORT(int, cmpp_str_each)(cmpp *pp, unsigned char const *zBegin, cmpp_strlen_t nIn, cmpp_kav_each_f callback, void *callbackState, cmpp_flag32_t flags){ g_warn0("UNTESTED!"); if( ppCode ) return ppCode; /* 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); assert( zBegin ); if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){ goto cleanup; } cmpp_arg const * aNext = 0; for( cmpp_arg const * arg = args.arg0; !ppCode && 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(pp, 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_interpolate(pp, arg, cmpp_b_reuse(&ob), 0) ){ break; } zVal = ob.z; nVal = ob.n; }else{ zVal = arg->z; nVal = arg->n; } if( 0!=callback(pp, arg->z, arg->n, zVal, nVal, callbackState) ){ break; } } cleanup: cmpp_b_clear(&ob); cmpp_args_cleanup(&args); return ppCode; } #endif int cmpp_arg_consume_as_file( cmpp_dx * dx, cmpp_arg const *arg, cmpp_b * bNameOut, cmpp_b * content, bool preprocessIt ){ if( !arg ){ return cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting filename argument."); } cmpp_b * const bFile = cmpp_b_borrow(dx->pp); if( !bFile ) return dxppCode; else if( cmpp_arg_interpolate(dx->pp, arg, bFile, cmpp_arg_interpolate_BRACE_CALL) ){ goto end; } if( !bFile->n || !bFile->z[0] ){ cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Empty filename is not permitted."); goto end; } cmpp_FILE * const fp = cmpp_fopen((char const*)bFile->z, "r") /* Resolving as -Idir does would arguably be a bug. */; if( fp ){ cmpp_outputer out = cmpp_outputer_b; int rc; out.state = cmpp_b_reuse(content); if( preprocessIt ){ cmpp_outputer oOld = {0}; cmpp__outputer_swap(dx->pp, &out, &oOld); rc = cmpp_process_stream(dx->pp, (char const *)bFile->z, cmpp_input_f_FILE, fp); cmpp__outputer_swap(dx->pp, &oOld, NULL); }else{ rc = cmpp_stream(cmpp_input_f_FILE, fp, out.out, out.state); } if( rc && !cmpp_err_has(dx->pp) ){ /* Propagate error. */ cmpp_dx_errf(dx, rc, "Unknown error streaming file [%s].", bFile->z); } cmpp_fclose(fp); }else{ cmpp_dx_errf(dx, cmpp_errno_rc(errno, CMPP_RC_IO), "Unknown error opening file %s.", bFile->z); } end: if( bNameOut && bFile && !dxppCode ){ cmpp_b_swap(bFile, bNameOut); } cmpp_b_return(dx->pp, bFile); return dxppCode; } cmpp__ListType_impl(CmppArgList,cmpp_arg) #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. */ CMPP_PRIVATE void cmpp_f_module(cmpp_f_args const * args){ cmpp_arg const * aName = 0; cmpp_arg const * aDll = 0; cmpp_b * const bDll = cmpp_b_borrow(args->pp); cmpp_b * bName = 0; if( !bDll ) goto end; for( unsigned i = 1; i < args->argc; ++i ){ cmpp_arg const * const arg = args->argv[i]; //MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z)); if( !aDll ){ aDll = arg; continue; } if( !aName ){ aName = arg; bName = cmpp_b_borrow(args->pp); if( !bName ) goto end; continue; } cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); goto end; } if( !aDll ){ cmpp_errf(args->pp, CMPP_RC_MISUSE, "Expecting (dllName ?directiveName?) arguments."); goto end; } if( cmpp_arg_interpolate(args->pp, aDll, bDll, cmpp_arg_interpolate_BRACE_CALL) ){ goto end; } if( bName && cmpp_arg_interpolate(args->pp, aName, bName, cmpp_arg_interpolate_BRACE_CALL) ){ goto end; } if( !bDll->n ){ cmpp_errf(args->pp, CMPP_RC_RANGE, "Empty DLL name is not permitted."); goto end; } cmpp_module_load(args->pp, (char const *)bDll->z, (bName && bName->n) ? (char const *)bName->z : NULL); end: cmpp_b_return(args->pp, bDll); cmpp_b_return(args->pp, bName); return; } #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) #if CmppSohList_works int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){ int const rc = cmpp_array_reserve(pp, (void**)&soli->list, soli->n ? (soli->n==soli->na ? soli->na*3/2 : soli->n+1) : 8, &soli->na, sizeof(void*)); if( 0==rc ){ soli->list[soli->n++] = soh; } return rc; } #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 ){ } } /** 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 static int cmpp__err_no_dlls(cmpp * const pp){ return cmpp_errf(pp, CMPP_RC_UNSUPPORTED, "No dlopen() equivalent is installed " "for this build configuration."); } #endif //no: CMPP_WASM_EXPORT int cmpp_module_load(cmpp * pp, char const * fname, char const * symName){ (void)fname; (void)symName; return cmpp__err_no_dlls(pp); } /* ** 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