| /** |
| This C file contains both the header and source file for c-pp, |
| a.k.a. libcmpp. |
| */ |
| #if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED) |
| #define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED |
| #if !defined(_POSIX_C_SOURCE) |
| # define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */ |
| #endif |
| #define CMPP_AMALGAMATION |
| #if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED) |
| /** |
| This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated |
| using: |
| |
| ./c-pp -I. -I./src -Dsrcdir=./src -Dsed=/usr/bin/sed -o libcmpp.h ./tool/libcmpp.c-pp.h -o libcmpp.c ./tool/libcmpp.c-pp.c |
| |
| with libcmpp 2.0.x c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37 @ 2026-03-08 14:50:35.123 UTC |
| */ |
| #define CMPP_PACKAGE_NAME "libcmpp" |
| #define CMPP_LIB_VERSION "2.0.x" |
| #define CMPP_LIB_VERSION_HASH "c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37" |
| #define CMPP_LIB_VERSION_TIMESTAMP "2026-03-08 14:50:35.123 UTC" |
| #define CMPP_LIB_CONFIG_TIMESTAMP "2026-03-08 15:32 GMT" |
| #define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP |
| #define CMPP_PLATFORM_EXT_DLL ".so" |
| #define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp" |
| |
| #if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_) |
| #define NET_WANDERINGHORSE_CMPP_H_INCLUDED_ |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** |
| ** The C-minus Preprocessor: C-like preprocessor. Why? Because C |
| ** preprocessors _can_ process non-C code but generally make quite a |
| ** mess of it. The purpose of this library is a customizable |
| ** preprocessor suitable for use with arbitrary UTF-8-encoded text. |
| ** |
| ** The supported preprocessor directives are documented in the |
| ** README.md hosted with this file (or see the link below). |
| ** |
| ** Any mention of "#" in the docs, e.g. "#if", is symbolic. The |
| ** directive delimiter is configurable and defaults to "##". Define |
| ** CMPP_DEFAULT_DELIM to a string when compiling to define the default |
| ** at build-time. |
| ** |
| ** This API is presented as a library but was evolved from a |
| ** monolithic app. Thus is library interface is likely still missing |
| ** some pieces needed to make it more readily usable as a library. |
| ** |
| ** Author(s): |
| ** |
| ** - Stephan Beal <https://wanderinghorse.net/home/stephan/> |
| ** |
| ** Canonical homes: |
| ** |
| ** - https://fossil.wanderinghorse.net/r/c-pp |
| ** - https://sqlite.org/src/file/ext/wasm/c-pp-lite.c |
| ** |
| ** With the former hosting this app's SCM and the latter being the |
| ** original deployment of c-pp.c, from which this library |
| ** evolved. SQLite uses a "lite" version of c-pp, whereas _this_ copy |
| ** is its much-heavier-weight fork. |
| */ |
| |
| #if defined(CMPP_HAVE_AUTOCONFIG_H) |
| #include "libcmpp-autoconfig.h" |
| #endif |
| #if defined(HAVE_AUTOCONFIG_H) |
| #include "autoconfig.h" |
| #endif |
| #if defined(HAVE_CONFIG_H) |
| #include "config.h" |
| #endif |
| |
| #ifdef _WIN32 |
| # if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD) |
| # define CMPP_EXPORT extern |
| # elif defined(BUILD_libcmpp) |
| # define CMPP_EXPORT extern __declspec(dllexport) |
| # else |
| # define CMPP_EXPORT extern __declspec(dllimport) |
| # endif |
| #else |
| # define CMPP_EXPORT extern |
| #endif |
| |
| /** |
| cmpp_FILE is a portability hack for WASM builds, where we want to |
| elide the (FILE*)-using pieces to avoid having a dependency on |
| Emscripten's POSIX I/O proxies. In all non-WASM builds it is |
| guaranteed to be an alias for FILE. On WASM builds it is guaranteed |
| to be an alias for void and the cmpp APIs which use it become |
| inoperative in WASM builds. |
| |
| That said: the code does not yet support completely compiling out |
| (FILE*) dependencies, and may not be able to because canonical |
| sqlite3 (upon which it is based) depends heavily on file |
| descriptors and slightly on FILE handles. |
| */ |
| #if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__) |
| typedef void cmpp_FILE; |
| # define CMPP_PLATFORM_IS_WASM 1 |
| #else |
| #include <stdio.h> |
| typedef FILE cmpp_FILE; |
| # define CMPP_PLATFORM_IS_WASM 0 |
| #endif |
| |
| #include <stdint.h> |
| #include <inttypes.h> /* PRIu32 and friends */ |
| #include <stdbool.h> |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| /** |
| 32-bit flag bitmask type. This typedef exists primarily to improve |
| legibility of function signatures and member structs by conveying |
| their intent for use as flags instead of result codes or lengths. |
| */ |
| typedef uint32_t cmpp_flag32_t; |
| //typedef uint16_t cmpp_flag16_t; |
| |
| /** |
| An X-macro which invokes its argument (a macro name) to expand to |
| all possible values of cmpp_rc_e entries. The macro name passed to |
| it is invoked once for each entry and passed 3 arguments: the enum |
| entry's full name (CMPP_RC_...), its integer value, and a help-text |
| string. |
| */ |
| #define cmpp_rc_e_map(E) \ |
| E(CMPP_RC_OK, 0, \ |
| "The quintessential not-an-error value.") \ |
| E(CMPP_RC_ERROR, 100, \ |
| "Generic/unknown error.") \ |
| E(CMPP_RC_NYI, 101, \ |
| "A placeholder return value for not yet implemented functions.") \ |
| E(CMPP_RC_OOM, 102, \ |
| "Out of memory. Indicates that a resource allocation " \ |
| "request failed.") \ |
| E(CMPP_RC_MISUSE, 103, \ |
| "API misuse (invalid args)") \ |
| E(CMPP_RC_RANGE, 104, \ |
| "A range was violated.") \ |
| E(CMPP_RC_ACCESS, 105, \ |
| "Access to or locking of a resource was denied " \ |
| "by some security mechanism or other.") \ |
| E(CMPP_RC_IO, 106, \ |
| "Indicates an I/O error. Whether it was reading or " \ |
| "writing is context-dependent.") \ |
| E(CMPP_RC_NOT_FOUND, 107, \ |
| "Requested resource not found.") \ |
| E(CMPP_RC_ALREADY_EXISTS, 108, \ |
| "Indicates that a to-be-created resource already exists.") \ |
| E(CMPP_RC_CORRUPT, 109, \ |
| "Data consistency problem.") \ |
| E(CMPP_RC_SYNTAX, 110, \ |
| "Some sort of syntax error.") \ |
| E(CMPP_RC_NOOP, 111, \ |
| "Special sentinel value for some APIs.") \ |
| E(CMPP_RC_UNSUPPORTED, 112, \ |
| "An unsupported operation was request.") \ |
| E(CMPP_RC_DB, 113, \ |
| "Indicates db-level error (e.g. statement prep failed). In such " \ |
| "cases, the error state of the related db handle (cmpp_db) " \ |
| "will be updated to contain more information directly from the " \ |
| "db driver.") \ |
| E(CMPP_RC_NOT_DEFINED, 114, \ |
| "Failed to expand an undefined value.") \ |
| E(CMPP_RC_ASSERT, 116, "An #assert failed.") \ |
| E(CMPP_RC_TYPE, 118, \ |
| "Indicates that some data type or logical type is incorrect.") \ |
| E(CMPP_RC_CANNOT_HAPPEN, 140, \ |
| "This is intended only for internal use, to " \ |
| "report conditions which \"cannot possibly happen\".") \ |
| E(CMPP_RC_HELP, 141, \ |
| "--help was used in the arguments to cmpp_process_argv()") \ |
| E(CMPP_RC_NO_DIRECTIVE, 142, \ |
| "A special case of CMPP_RC_NOT_FOUND needed to disambiguate.") \ |
| E(CMPP_RC_end,200, \ |
| "Must be the final entry in the enum. Used for creating client-side " \ |
| "result codes which are guaranteed to live outside of this one's " \ |
| "range.") |
| |
| /** |
| Most functions in this library which return an int return result |
| codes from the cmpp_rc_e enum. None of these entries are |
| guaranteed to have a specific value across library versions except |
| for CMPP_RC_OK, which is guaranteed to always be 0 (and the API |
| guarantees that no other code shall have a value of zero). |
| |
| The only reasons numbers are hard-coded to the values is to |
| simplify debugging during development. Clients may use |
| cmpp_rc_cstr() to get some human-readable (or programmer-readable) |
| form for any given value in this enum. |
| */ |
| enum cmpp_rc_e { |
| #define E(N,V,H) N = V, |
| cmpp_rc_e_map(E) |
| #undef E |
| }; |
| typedef enum cmpp_rc_e cmpp_rc_e; |
| |
| /** |
| Returns the string form of the given cmpp_rc_e value or NULL if |
| it's not a member of that enum. |
| */ |
| char const * cmpp_rc_cstr(int rc); |
| |
| /** |
| CMPP_BITNESS specifies whether the library should use 32- or 64-bit |
| integer types for its size/length measurements. It's difficult to |
| envision use cases for a preprocessor which would require counters |
| or rangers larger than 32 bits provide for, so the default is 32 |
| bits. Builds created with different CMPP_BITNESS values are |
| not binary-compatible. |
| */ |
| #define CMPP_BITNESS 32 |
| #if 32==CMPP_BITNESS |
| /** |
| Unsigned integer type for string/stream lengths. 32 bits is |
| sufficient for all but the weirdest of inputs and outputs. |
| */ |
| typedef uint32_t cmpp_size_t; |
| |
| /** |
| A signed integer type indicating the maximum length of strings or |
| byte ranges in a stream. It is most frequently used in API |
| signatures where a negative value means "if it's negative then use |
| strlen() to count it". |
| */ |
| typedef int32_t cmpp_ssize_t; |
| |
| /** |
| The printf-format-compatible format letter (or group of letters) |
| appropriate for use with cmpp_size_t. Contrary to popular usage, |
| size_t cannot be portably used with printf(), without careful |
| casting, because it has neither a fixed size nor a standardized |
| printf/scanf format specifier (like the stdint.h types do). |
| */ |
| #define CMPP_SIZE_T_PFMT PRIu32 |
| #elif 64==CMPP_BITNESS |
| typedef uint64_t cmpp_size_t; |
| typedef int64_t cmpp_ssize_t; |
| #define CMPP_SIZE_T_PFMT PRIu64 |
| #else |
| #error "Invalid CMPP_BITNESS value. Expecting 32 or 64." |
| #endif |
| |
| /** |
| Generic interface for streaming in data. Implementations must read |
| (at most) *n bytes from their input, copy it to dest, assign *n to |
| the number of bytes actually read, return 0 on success, and return |
| non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO). |
| |
| When called, *n is the max length to read. On return, *n must be |
| set to the amount actually read. Implementations may need to |
| internally distinguish a short read due to EOF from a short read |
| due to an I/O error, e.g. using feof() and/or ferror(). A short |
| read for EOF is not an error but a short read for input failure is. |
| This library invariably treats a short read as EOF. |
| |
| The state parameter is the implementation-specified input |
| file/buffer/whatever channel. |
| */ |
| typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n); |
| |
| /** |
| Generic interface for streaming out data. Implementations must |
| write n bytes from src to their destination channel and return 0 on |
| success, or a value from the cmpp_rc_e enum on error |
| (e.g. CMPP_RC_IO). The state parameter is the |
| implementation-specified output channel. |
| |
| It is implementation-defined whether an n of 0 is legal. This |
| library refrains from passing 0 to these functions. |
| |
| In the context of cmpp, the library makes no guarantees that output |
| will always end at a character boundary. It may send any given |
| multibyte character as the end resp. start of two calls to this |
| function. If that is of a concern for implementors of these |
| functions (e.g. because they're appending the output to a UI |
| widget), they may need to buffer all of the output before applying |
| it (see cmpp_b), or otherwise account for partial characters. |
| |
| That said: the core library, by an accident of design, will always |
| emit data at character boundaries, assuming that its input is |
| well-formed UTF-8 text (which cmpp does not validate to be the |
| case). Custom cmpp_dx_f() implementations are not strictly |
| required to do so but, because of how cmpp is used, almost |
| certainly will. But relying on that is ill-advised. |
| */ |
| typedef int (*cmpp_output_f)(void * state, void const * src, |
| cmpp_size_t n); |
| |
| /** |
| Generic interface for flushing arbitrary output streams. Must |
| return 0 on success, a non-0 cmpp_rc_e value on error. When in |
| doubt, return CMPP_RC_IO on error. The interpretation of the state |
| parameter is implementation-specific. |
| */ |
| typedef int (*cmpp_flush_f)(void * state); |
| |
| typedef struct cmpp_pimpl cmpp_pimpl; |
| typedef struct cmpp_api_thunk cmpp_api_thunk; |
| typedef struct cmpp_outputer cmpp_outputer; |
| |
| /** |
| The library's primary class. Each one of these represents a |
| separate preprocessor instance. |
| |
| See also: cmpp_dx (the class which client-side extensions interact |
| with the most). |
| */ |
| struct cmpp { |
| |
| /** |
| API thunk object to support use via loadable modules. Client code |
| does not normally need to access this member, but it's exposed |
| here to give loadable modules more flexibility in how they use |
| the thunk. |
| |
| This pointer is _always_ the same singleton object. The library |
| never exposes a cmpp object with a NULL api member. |
| */ |
| cmpp_api_thunk const * const api; |
| |
| /** |
| Private internal state. |
| */ |
| cmpp_pimpl * const pimpl; |
| }; |
| typedef struct cmpp cmpp; |
| |
| /** |
| Flags for use with cmpp_ctor_cfg::flags. |
| */ |
| enum cmpp_ctor_e { |
| /* Sentinel value. */ |
| cmpp_ctor_F_none = 0, |
| /* Disables #include. */ |
| cmpp_ctor_F_NO_INCLUDE = 0x01, |
| /* Disables #pipe. */ |
| cmpp_ctor_F_NO_PIPE = 0x02, |
| /* Disables #attach, #detach, and #query. */ |
| cmpp_ctor_F_NO_DB = 0x04, |
| /* Disables #module. */ |
| cmpp_ctor_F_NO_MODULE = 0x08, |
| /** |
| Disable all built-in directives which may work with the filesystem |
| or invoke external processes. Client-defined directives with the |
| cmpp_d_F_NOT_IN_SAFEMODE flag are also disabled. Directives |
| disabled via the cmpp_ctor_F_NO_... flags (or equivalent library |
| built-time options) do not get registered, so will trigger |
| "unknown directive" errors rather than safe-mode violation errors. |
| */ |
| cmpp_ctor_F_SAFEMODE = 0x10, |
| }; |
| |
| /** |
| A configuration object for cmpp_ctor(). This type may be extended |
| as new construction-time customization opportunities are |
| discovered. |
| */ |
| struct cmpp_ctor_cfg { |
| /** |
| Bitmask from the cmpp_ctor_e enum. |
| */ |
| cmpp_flag32_t flags; |
| /** |
| If not NULL then this must name either an existing SQLite3 db |
| file or the name of one which can be created on demand. If NULL |
| then an in-memory or temporary database is used (which one is |
| unspecified). The library copies these bytes, so they need not be |
| valid after a call to cmpp_ctor(). |
| */ |
| char const * dbFile; |
| }; |
| typedef struct cmpp_ctor_cfg cmpp_ctor_cfg; |
| |
| /** |
| Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return |
| value must eventually be passed to cmpp_dtor() to free it. |
| |
| The cfg argument, if not NULL, holds config info for the new |
| instance. If NULL, an instance with unspecified defaults is |
| used. These configuration pieces may not be modified after the |
| instance is created. |
| |
| It returns 0 if *pp is ready to use and non-0 if either allocation |
| fails (in which case *pp will be set to 0) or initialization of *pp |
| failed (in which case cmpp_err_get() can be used to determine why |
| it failed). In either case, the caller must eventually pass *pp to |
| cmpp_dtor() to free it. |
| |
| If the library is built with the symbol CMPP_CTOR_INSTANCE_INIT |
| defined, it must refer to a function with this signature: |
| |
| int CMPP_CTOR_INSTANCE_INIT(cmpp *); |
| |
| The library calls this before returning and arranges to call it |
| lazily if pp gets reset. The intent is that the init function |
| installs custom directives using cmpp_d_register(). That |
| initialization, on error, is expected to set its argument's error |
| state with cmpp_err_set(). |
| */ |
| CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_cfg const * cfg); |
| |
| /** |
| If pp is not NULL, it is passed to cmpp_reset() and then freed. |
| */ |
| CMPP_EXPORT void cmpp_dtor(cmpp *pp); |
| |
| |
| /** |
| realloc(3)-compatible allocator used by the library. |
| |
| This API very specifically uses sqlite3_realloc() as its basis. |
| */ |
| CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n); |
| |
| /** |
| malloc(3)-compatible allocator used by the library. |
| |
| This API very specifically uses sqlite3_malloc() as its basis. |
| */ |
| CMPP_EXPORT void * cmpp_malloc(size_t n); |
| |
| /** |
| free(3)-compatible deallocator. It can also be used as a destructor |
| for cmpp_d_register() _if_ the memory in question is allocated by |
| cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of |
| APIs. |
| |
| This is not called cmpp_free() to try to avoid any confusion with |
| cmpp_dtor(). |
| */ |
| CMPP_EXPORT void cmpp_mfree(void *); |
| |
| /** |
| If m is NULL then pp's persistent error code is set to CMPP_RC_OOM, |
| else this is a no-op. Returns pp's error code. |
| |
| To simplify certain uses, pp may be NULL, in which case this |
| function returns CMPP_RC_OOM if m is NULL and 0 if it's not. |
| */ |
| CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * const m ); |
| |
| /** |
| Re-initializes all state of pp. This saves some memory for reuse |
| but resets it all to default states. This closes the database and |
| will also reset any autoloader, policies, or delimiter |
| configurations to their compile-time defaults. It retains only a |
| small amount of state, like any configuration which was passed to |
| cmpp_ctor(). |
| |
| After calling this, pp is in a cleanly-initialized state and may be |
| re-used with the cmpp API. Its database will not be initialized |
| until an API which needs it is called, so pp can be used with |
| functions which may otherwise be prohibited after the db is |
| opened. (Do we still have any?) |
| |
| As of this writing, this is the only way to reliably recover a cmpp |
| instance from any significant errors. Errors may do things like |
| leave savepoints out of balance, and this cleanup step resets all |
| of that state. However, it also loses state like the autoloader. |
| |
| TODO?: we need(?) a partial-clear operation which keeps some of the |
| instance's state, most notably custom directives, the db handle, |
| and any cached prepared statements. See cmpp_err_set() for the |
| distinction between recoverable and non-recoverable errors. |
| */ |
| CMPP_EXPORT void cmpp_reset(cmpp *pp); |
| |
| #if 0 |
| Not yet; |
| /** |
| If called before pp has initialized its database, this sets the |
| file name used for that database. If called afterwards, pp's error |
| state is updated and CMPP_RC_MISUSE is returned. If called while |
| pp has error state set, that code is returned without side-effects. |
| |
| This does not open the database. It is opened on demand when |
| processing starts. |
| |
| On success it returns 0 and this function makes a copy of zName. |
| |
| As a special case, zName may be NULL to use the default name, but |
| there is little reason to do so unless one changes their mind after |
| setting it to non-NULL. |
| */ |
| CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName); |
| #endif |
| |
| /** |
| Returns true if the bytes in the range [zName, zName+n) comprise a |
| legal name for a directive or a define. |
| |
| It disallows any control characters, spaces, and most punctuation, |
| but allows alphanumeric (but must not start with a number) as well |
| as any of: -./:_ (but it may not start with '-'). Any characters |
| with a high bit set are assumed to be UTF-8 and are permitted as |
| well. |
| |
| The name's length is limited, rather arbitrarily, to 64 bytes. |
| |
| If the key is not legal then false is returned and if zErrPos is |
| not NULL then *zErrPos is set to the position in zName of the first |
| offending character. If validation fails because n is too long then |
| *zErrPos (if zErrPos is not NULL) will be set to 0. |
| |
| Design note: this takes unsigned characters because it most |
| commonly takes input from cmpp_args::z strings. |
| */ |
| CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName, |
| cmpp_size_t n, |
| unsigned char const **zErrPos); |
| |
| /** |
| Adds the given `#define` macro name to the list of macros, overwriting |
| any previous value. |
| |
| zKey must be NUL-terminated and legal as a key. The rules are the |
| same as for cmpp_is_legal_key() except that a '=' is also permitted |
| if it's not at the start of the string because... |
| |
| If zVal is NULL then zKey may contain an '=', from which the value |
| will be extracted. If zVal is not NULL then zKey may _not_ contain |
| an '='. |
| |
| The ability for zKey to contain a key=val was initially to |
| facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was |
| initially a CLI app (as opposed to a library). It's considered a |
| "legacy" feature, not recommended for most purposes, but it _is_ |
| convenient for that particular purpose. |
| |
| Returns 0 on success and updates pp's error state on error. |
| |
| See: cmpp_define_v2() |
| See: cmpp_undef() |
| */ |
| CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey, |
| char const *zVal); |
| |
| /** |
| Works like cmpp_define_legacy() except that it does not examine zKey to |
| see if it contains an '='. |
| */ |
| CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal); |
| |
| /** |
| Removes the given `#define` macro name from the list of |
| macros. zKey is, in this case, treated as a GLOB pattern, and all |
| matching defines are deleted. |
| |
| If nRemoved is not NULL then, on success, it is set to the number |
| of entries removed by this call. |
| |
| Returns 0 on success and updates pp's error state on error. It is not |
| an error if no value was undefined. |
| |
| This does _not_ affect defines made using cmpp_define_shadow(). |
| */ |
| CMPP_EXPORT int cmpp_undef(cmpp *pp, const char * zKey, |
| unsigned int *nRemoved); |
| |
| /** |
| This works similarly to cmpp_define_v2() except that: |
| |
| - It does not permit its zKey argument to contain the value |
| part like that function does. |
| |
| - The new define "shadows", rather than overwrites, an existing |
| define with the same name. |
| |
| All APIs which look up define keys will get the value of the shadow |
| define. The shadow can be uninstalled with cmpp_define_unshadow(), |
| effectively restoring its previous value (if any). That function |
| should be called one time for each call to this one, passing the |
| same key to each call. A given key may be shadowed any number of |
| times by this routine. Each one saves the internal ID of the shadow |
| into *pId (and pId must not be NULL). That value must be passed to |
| cmpp_define_unshadow() to ensure that the "shadow stack" stays |
| balanced in the face of certain error-handling paths. |
| |
| cmpp_undef() will _not_ undefine an entry added through this |
| interface. |
| |
| Returns pp's persistent error code (0 on success). |
| |
| Design note: this function was added to support adding a define |
| named __FILE__ to input scripts which works like it does in a C |
| preprocessor. Alas, supporting __LINE__ would be much more costly, |
| as it would have to be updated in the db from several places, so |
| its cost would outweigh its meager benefits. |
| */ |
| CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey, |
| char const *zVal, |
| int64_t * pId); |
| |
| /** |
| Removes the most shadow define matching the zKey and id values |
| which where previously passed to cmpp_define_shadow(). It is not |
| an error if no match is found, in which case this function has no |
| visible side-effects. |
| |
| Unlike cmpp_undef(), zKey is matched precisely, not against a glob. |
| |
| In order to keep the "shadow stack" properly balanced, this will |
| delete any shadow entries for the given key which have the same id |
| or a newer one (i.e. they were left over from a missed call to |
| cmpp_define_unshadow()). |
| |
| Returns pp's persistent error code (0 on success). |
| */ |
| CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey, |
| int64_t id); |
| |
| /** |
| Adds the given dir to the list of includes. They are checked in the |
| order they are added. |
| */ |
| CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey); |
| |
| /** |
| Sets pp's default output channel. If pp already has a channel, it |
| is closed[^1]. |
| |
| The second argument, if not NULL, is _bitwise copied_, which has |
| implications for the ownership of out->state (see below). If it is |
| is NULL, cmpp_outputer_empty is copied in its place, which makes |
| further output a no-op. |
| |
| The third argument is a symbolic name for the channel (perhaps its |
| file name). It is used in debugging and error messages. cmpp does |
| _not_ copy it, so its bytes must outlive the cmpp instance. (In |
| practice, the byte names come from main()'s argv or scope-local |
| strings in the same scope as the cmpp instance.) This argument |
| should only be NULL if the second argument is. |
| |
| cmpp_reset(), or opening another channel, will end up calling |
| out->cleanup() (if it's not NULL) and passing it a pointer to a |
| _different_ cmpp_outputer object, but with the _same_ |
| cmpp_outputer::state pointer, which may invalidate out->state. |
| |
| To keep cmpp from doing that, make a copy of the output object, set |
| the cleanup member of that copy to NULL, then pass that copy to this |
| function. It is then up to the client to call out->cleanup(out) when |
| the time is right. |
| |
| For example: |
| |
| ``` |
| cmpp_outputer my = cmpp_outputer_FILE; |
| my.state = cmpp_fopen("/some/file", "wb"); |
| cmpp_outputer tmp = my; |
| tmp.cleanup = NULL; |
| cmpp_outputer_set( pp, &tmp, "my file"); |
| ... |
| my.cleanup(&my); // will cmpp_fclose(my.state) |
| ``` |
| |
| Potential TODO: internally store the output channel as a pointer. |
| It's not clear whether that would resolve the above grief or |
| compound it. |
| |
| [^1]: depending on the output channel, it might not _actually_ be |
| closed, but pp is disassociated from it, in any case. |
| */ |
| CMPP_EXPORT |
| void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName); |
| |
| /** |
| Treats the range (zIn,zIn+nIn] as a complete cmpp input and process |
| it appropriately. zName is the name of the input for purposes of |
| error messages. If nIn is negative, strlen() is used to calculate |
| it. |
| |
| This is a no-op if pp has any error state set. It returns pp's |
| persistent error code. |
| */ |
| CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName, |
| unsigned char const * zIn, |
| cmpp_ssize_t nIn); |
| |
| /** |
| A thin proxy for cmpp_process_string() which reads its input from |
| the given file. Returns 0 on success, else returns pp's persistent |
| error code. |
| */ |
| CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName); |
| |
| /** |
| A thin proxy for cmpp_process_string() which reads its input from |
| the given input source, consuming it all before passing it |
| on. Returns 0 on success, else returns pp's persistent error code. |
| */ |
| CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName, |
| cmpp_input_f src, void * srcState); |
| |
| /** |
| Process the given main()-style arguments list. When calling from |
| main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0] |
| (the binary's name). |
| |
| Each argument is expected to be one of the following: |
| |
| 1) One of --help or -?: causes this function to return CMPP_RC_HELP |
| without emitting any output. |
| |
| 2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to |
| Y. |
| |
| 3) -UX: unsets all defines matching glob X. |
| |
| 4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to |
| the contents of that file. |
| |
| 5) -IX: adds X to the "include path". If _no_ include path is |
| provided then cmpp assumes a path of ".", but if _any_ paths are |
| provided then it does not assume that "." is in the path. |
| |
| 6) --chomp-F: specifies whether subsequent -F flags should "chomp" |
| one trailing newline from their input files. |
| |
| 7) --delimiter|-d=X sets the directive delimiter to X. Its default |
| is a compile-time constant. |
| |
| 8) --output|-o=filename: sets the output channel to the given file. |
| A value of "-" means stdout. If no output channel is opened when |
| this is called, and files are to be processed, stdout is |
| assumed. (That's a historical artifact from earlier evolutions.) |
| To override that behavior use cmpp_outputer_set(). |
| |
| 9) --file|-f=filename: sets the input channel to the given file. |
| A value of "-" means stdin. |
| |
| 10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it. |
| Because it's difficult to pack multiple lines of text into this, |
| it's really of use for testing #expr and #assert. |
| |
| 11) --@policy=X sets the @token@ parsing policy. X must be |
| one of (retain, elide, error, off) and defaults to off. |
| |
| 12) -@: shorthand for --@policy=error. |
| |
| 13) --sql-trace: enables tracing of all SQL statements to stderr. |
| This is useful for seeing how a script interacts with the |
| database. Use --no-sql-trace to disable it. |
| |
| 14) --sql-trace-x: like --sql-trace but replaces bound parameter |
| placeholders with their SQL values. Use --no-sql-trace to disable |
| it. |
| |
| 15) --dump-defines: emit all defines to stdout. They should |
| arguably go to stderr but that interferes with automated testing. |
| |
| Any argument which does not match one of the above flags, and does |
| not start with a "-", is treated as if it were passed to the --file |
| flag. |
| |
| Flags may start with either 1 or 2 dashes - they are equivalent. |
| |
| Flags which take a value may either be in the form X=Y or X Y, i.e. |
| may be a single argv entry or a pair of them. |
| |
| It performs two passes on the arguments: the first is for validation |
| checking for --help/-?. No processing of the input(s) and output(s) |
| happens unless the first pass completes. Similarly, no validation of |
| whether any provided filename are actually readable is performed |
| until the second pass. |
| |
| Arguments are processed in the order they are given. Thus the following |
| have completely different meanings: |
| |
| 1) -f foo.in -Dfoo |
| 2) -Dfoo -f foo.in |
| |
| The former will process foo.in before defining foo. |
| |
| This behavior makes it possible to process multiple input files in |
| a single go: |
| |
| --output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in |
| |
| It returns 0 on success. cmpp_err_get() can be used to fetch any |
| error message. |
| */ |
| CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv); |
| |
| /** |
| Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP. |
| It emits --help-style text to the given output stream. |
| As the first argument pass it either argv[0] or NULL. The second |
| should normally be stdout or stderr. |
| |
| Reminder to self: this could take a (cmpp_output_f,void*) pair |
| instead, and should do so for the sake of WASI builds, but its impl |
| currently relies heavily on fprintf() formatting. |
| */ |
| CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName, |
| cmpp_FILE *os); |
| |
| /** |
| Returns pp's current error number (from the cmpp_rc_e enum) and |
| sets *zMsg (if zMsg is not NULL) to the error string. The bytes are |
| owned by pp and may be invalidated by any functions which take pp |
| as an argument. |
| |
| See cmpp_err_get() for more information. |
| |
| */ |
| CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg); |
| |
| /** |
| Sets or clears (if 0==rc) pp's persistent error state. zFmt may be |
| NULL or a format string compatible with sqlite3_mprintf(). |
| |
| To simplify certain uses, this is a no-op if pp is NULL, returning |
| rc without other side effects. |
| |
| Returns rc with one exception: if allocation of a copy of the error |
| string fails then CMPP_RC_OOM will be returned (and pp will be |
| updated appropriately). |
| |
| If pp is currently processing a script, the resulting error string |
| will be prefixed with the name of the current input script and the |
| line number of the directive which triggered the error. |
| |
| It is legal for zFmt to be NULL or an empty string, in which case a |
| default, vague error message is used (without requiring allocation |
| of a new string). |
| |
| Recoverable vs. unrecoverable errors: |
| |
| Most cmpp APIs become no-ops if their cmpp object has error state |
| set, treating any error as unrecoverable. That approach simplifies |
| writing code for it by allowing multiple calls to be chained |
| without concern for whether the previous one succeeded. |
| |
| ACHTUNG: simply clearing the error state by passing 0 as the 2nd |
| argument to this function is _not_ enough to recover from certain |
| errors. e.g. an error in the middle of a script may leave db |
| savepoints imbalanced. The only way to _fully_ recover from any |
| significant failures is to use cmpp_reset(), which resets all of |
| pp's state. |
| |
| APIs which may set the error state but are recoverable by simply |
| clearing that state will document that. Errors from APIs which do |
| not claim to be recoverable in error cases must be treated as |
| unrecoverable. |
| |
| See cmpp_err_get() for more information. |
| |
| FIXME: we need a different variant for WASM builds, where variadics |
| aren't a usable thing. |
| |
| Potential TODO: change the error-reporting interface to support |
| distinguishing from recoverable and non-recoverable errors. "The |
| problem" is that no current uses need that - they simply quit and |
| free up the cmpp instance on error. Maybe that's the way it |
| _should_ be. |
| */ |
| CMPP_EXPORT int cmpp_err_set(cmpp *pp, int rc, char const *zFmt, ...); |
| |
| /** |
| A variant of cmpp_err_set() which is not variadic, as a consolation |
| for WASM builds. zMsg may be NULL. The given string, if not NULL, |
| is copied. |
| */ |
| CMPP_EXPORT int cmpp_err_set1(cmpp *pp, int rc, char const *zMsg); |
| |
| #if 0 |
| /** |
| Clears any error state in pp. Most cmpp APIs become no-ops if their |
| cmpp instance has its error flag set. |
| |
| See cmpp_err_get() for important details about doing this. |
| */ |
| //CMPP_EXPORT void cmpp_err_clear(cmpp *pp); |
| |
| /** |
| This works like a combination of cmpp_err_get() and |
| cmpp_err_clear(), in that it clears pp's error state by transferring |
| ownership of it to the caller. If pp has any error state, *zMsg is |
| set to the error string and the error code is returned, else 0 is |
| returned and *zMsg is set to 0. |
| |
| The string returned via *zMsg must eventually be passed to |
| cmpp_mfree() to free it. |
| |
| This function is provided simply as an optimization to avoid |
| having to copy the error string in some cases. |
| |
| ACHTUNG: see the ACHTUNG in cmpp_err_clear(). |
| */ |
| CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg); |
| #endif |
| |
| /** |
| Returns pp's current error code, which will be 0 if it currently |
| has no error state. |
| |
| To simplify certain uses, this is a no-op if pp is NULL, returning |
| 0. |
| */ |
| CMPP_EXPORT int cmpp_err_has(cmpp const * pp); |
| |
| /** |
| Returns true if pp was initialized in "safe mode". That is: if the |
| cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor(). |
| |
| To simplify certain uses, this is a no-op if pp is NULL, returning |
| false. |
| */ |
| CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp); |
| |
| /** |
| Starts a new SAVEPOINT in the database. Returns non-0, and updates |
| pp's persistent error state, on failure. |
| |
| If this returns 0, the caller is obligated to later call either |
| cmpp_sp_commit() or cmpp_sp_rollback() later. |
| */ |
| CMPP_EXPORT int cmpp_sp_begin(cmpp *pp); |
| |
| /** |
| Commits the most recently-opened savepoint UNLESS pp's error state |
| is set, in which case this behaves like cmpp_sp_rollback(). |
| Returns 0 on success. |
| |
| A call to cmpp_sp_begin() which returns 0 obligates the caller to |
| call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal |
| for either to be called in any other context. |
| */ |
| CMPP_EXPORT int cmpp_sp_commit(cmpp *pp); |
| |
| /** |
| Rolls back the most recently-opened savepoint. Returns 0 on |
| success. |
| |
| A call to cmpp_sp_begin() which returns 0 obligates the caller to |
| call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal |
| for either to be called in any other context. |
| */ |
| CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp); |
| |
| /** |
| A cmpp_output_f() impl which requires state to be a (FILE*), which |
| this function passes the call on to fwrite(). Returns 0 on |
| success, CMPP_RC_IO on error. |
| |
| If state is NULL then stdout is used. |
| */ |
| CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n); |
| |
| /** |
| A cmpp_output_f() impl which requires state to be a ([const] int*) |
| referring to a writable file descriptor, which this function |
| dereferences and passes to write(2). |
| */ |
| CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n); |
| |
| /** |
| A cmpp_input_f() implementation which requires that state be |
| a readable (FILE*) handle, which it passes to fread(3). |
| */ |
| CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n); |
| |
| /** |
| A cmpp_input_f() implementation which requires that state be a |
| readable file descriptor, in the form of an ([const] int*), which |
| this function passes to write(2). |
| */ |
| CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n); |
| |
| /** |
| A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened |
| for writing, which this function passes the call on to |
| fflush(). If fflush() returns 0, so does this function, else it |
| returns non-0. |
| */ |
| CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile); |
| |
| /** |
| A generic streaming routine which copies data from an |
| cmpp_input_f() to an cmpp_outpuf_f(). |
| |
| Reads all data from inF(inState,...) in chunks of an unspecified |
| size and passes them on to outF(outState,...). It reads until inF() |
| returns fewer bytes than requested or returns non-0. Returns the |
| result of the last call to outF() or (if reading fails) inF(). |
| Results are undefined if either of inState or outState arguments |
| are NULL and their callbacks require non-NULL. (This function |
| cannot know whether a NULL state argument is legal for the given |
| callbacks.) |
| |
| Here is an example which basically does the same thing as the |
| cat(1) command on Unix systems: |
| |
| ``` |
| cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout); |
| ``` |
| |
| Or copy a FILE to a string buffer: |
| |
| ``` |
| cmpp_b os = cmpp_b_empty; |
| FILE * f = cmpp_fopen(...); |
| rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os); |
| // On error os might be partially populated. |
| // Eventually clean up the buffer: |
| cmpp_b_clear(&os); |
| ``` |
| */ |
| CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState, |
| cmpp_output_f outF, void * outState); |
| |
| /** |
| Reads the entire contents of the given input stream, allocating it |
| in a buffer. On success, returns 0, assigns *pOut to the buffer, |
| and *nOut to the number of bytes read (which will be fewer than are |
| allocated). It guarantees that on success it NUL-terminates the |
| buffer at one byte after the returned size, with one exception: if |
| the string has no input, both *pOut and *nOut will be set to 0. |
| |
| On error it returns whatever code xIn() returns. |
| */ |
| CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn, |
| unsigned char **pOut, cmpp_size_t * nOut); |
| |
| /** |
| _Almost_ equivalent to fopen(3) but: |
| |
| - If name=="-", it returns one of stdin or stdout, depending on the |
| mode string: stdout is returned if 'w' or '+' appear, otherwise |
| stdin. |
| |
| If it returns NULL, the global errno "should" contain a description |
| of the problem unless the problem was argument validation. |
| |
| If at all possible, use cmpp_fclose() (as opposed to fclose()) to |
| close these handles, as it has logic to skip closing the three |
| standard streams. |
| */ |
| CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode); |
| |
| /** |
| Passes f to fclose(3) unless f is NULL or one of the C-standard |
| handles (stdin, stdout, stderr), in which cases it does nothing at |
| all. |
| */ |
| CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f); |
| |
| /** |
| A cleanup callback interface for use with cmpp_outputer::cleanup(). |
| Implementations must handle self->state appropriately for its type, |
| and clear self->state if appropriate, but must not free the self |
| object. It is implementation-specified whether self->state and/or |
| self->name are set to NULL by this function. Whether they should be |
| often depends on how they're used. |
| */ |
| typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self); |
| |
| /** |
| An interface which encapsulates data for managing a streaming |
| output destination, primarily intended for use with cmpp_stream() |
| but also used internally by cmpp for directing output to a buffer. |
| */ |
| struct cmpp_outputer { |
| /** |
| An optional descriptive name for the channel. The bytes |
| are owned elsewhere and are typically static or similarly |
| long-lived. |
| */ |
| char const * name; |
| |
| /** |
| Output channel. |
| */ |
| cmpp_output_f out; |
| |
| /** |
| flush() implementation. This may be NULL for most uses of this |
| class. Cases which specifically require it must document that |
| requirement so. |
| */ |
| cmpp_flush_f flush; |
| |
| /** |
| Optional: if not NULL, it must behave appropriately for its state |
| type, cleaning up any memory it owns. |
| */ |
| cmpp_outputer_cleanup_f cleanup; |
| |
| /** |
| State to be used when calling this->out() and this->flush(), |
| namely: this->out(this->state, ... ) and |
| this->flush(this->state). |
| |
| Whether or not any given instance of this class owns the memory |
| pointed to by this member must be documented for their cleanup() |
| method. |
| |
| Because cmpp_outputer instances frequently need to be stashed and |
| unstashed via bitwise copying, it is illegal to replace this |
| pointer after its initial assignment. The object it points to may |
| be mutated freely, but this pointer must stay stable for the life |
| of this object. |
| */ |
| void * state; |
| }; |
| |
| /** |
| Empty-initialized cmpp_outputer instance, intended for |
| const-copy initialization. |
| */ |
| #define cmpp_outputer_empty_m \ |
| {.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL} |
| |
| /** |
| Empty-initialized cmpp_outputer instance, intended for |
| non-const-copy initialization. These copies can, for purposes of |
| cmpp's output API, be used as-is to have cmpp process its inputs |
| but generate no output. |
| */ |
| CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty; |
| |
| /** |
| If o->out is not NULL, the result of o->out(o->state,p,n) is |
| returned, else 0 is returned. |
| */ |
| CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n); |
| |
| /** |
| If o->flush is not NULL, the result of o->flush(o->state) is |
| returned, else 0 is returned. |
| */ |
| CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o); |
| |
| /** |
| If o->cleanup is not NULL, it is called, otherwise this is a no-op. |
| */ |
| CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o); |
| |
| /** |
| A cmpp_outputer initializer which uses cmpp_flush_f_FILE(), |
| cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its |
| implementation. After copying this, the state member must be |
| pointed to an opened-for-writing (FILE*). |
| */ |
| CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE; |
| |
| /** |
| The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE. If |
| self->state is not NULL then it is passed to fclose() (_unless_ it |
| is stdin, stdout, or stderr) and set to NULL. self->name is |
| also set to NULL. |
| */ |
| CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self); |
| |
| /** |
| Sets pp's current directive delimiter to a copy of the |
| NUL-terminated zDelim. The delimiter is the sequence which starts |
| line and distinguishes cmpp directives from other input, in the |
| same way that C preprocessors use '#' as a delimiter. |
| |
| If zDelim is NULL then the default delimiter is used. The default |
| delimiter can be set when compiling the library by defining |
| CMPP_DEFAULT_DELIM to a quoted string value. |
| |
| zDelim is assumed to be in UTF-8 encoding. If any bytes in the |
| range (0,32) are found, CMPP_RC_MISUSE is returned and pp's |
| persistent error state is set. |
| |
| The delimiter must be short and syntactically unambiguous for the |
| intended inputs. It has a rather arbitrary maximum length of 12, |
| but it's difficult to envision it being remotely human-friendly |
| with a delimiter longer than 3 bytes. It's conceivable, but |
| seemingly far-fetched, that longer delimiters might be interesting |
| in some machine-generated cases, e.g. using a random sequence as |
| the delimiter. |
| |
| Returns 0 on success. Returns non-0 if called when the delimiter |
| stack is empty, if it cannot copy the string or zDelim is deemed |
| unsuitable for use as a delimiter. Calling this when the stack is |
| empty represents a serious API misuse (indicating that |
| cmpp_delimiter_pop() was used out of scope) and will trigger an |
| assert() in debug builds. Except for that last case, errors from |
| this function are recoverable (see cmpp_err_set()). |
| */ |
| CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim); |
| |
| /** |
| Fetches pp's current delimiter string, assigning it to *zDelim. |
| The string is owned by pp and will be invalidated by any call to |
| cmpp_delimiter_set() or the #delimiter script directive. |
| |
| If, by some odd usage constellation, this is called after an |
| allocation of the delimiter stack has failed, this will set *zDelim |
| to the compile-time-default delimiter. That "cannot happen" in normal use because such a failure |
| would have been reacted to and this would not be called. |
| */ |
| CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim); |
| |
| /** |
| Pushes zDelim as the current directive delimiter. Returns 0 on |
| success and non-zero on error (invalid zDelim value or allocation |
| error). If this returns 0 then the caller is obligated to |
| eventually call cmpp_delimiter_pop() one time. If it returns non-0 |
| then they must _not_ call that function. |
| */ |
| CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim); |
| |
| /** |
| Must be called one time for each successful call to |
| cmpp_delimiter_push(). It restores the directive delimimter to the |
| value it has when cmpp_delimiter_push() was last called. |
| |
| Returns pp's current error code, and will set it to non-0 if called |
| when no cmpp_delimiter_push() is active. Popping an empty stack |
| represents a serious API misuse and may fail an assert() in debug |
| builds. |
| */ |
| CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp); |
| |
| /** |
| If z[*n] ends on a \n or \r\n pair, it/they are stripped, |
| *z is NUL-terminated there, and *n is adjusted downwards |
| by 1 or 2. Returns true if it chomped, else false. |
| */ |
| CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n); |
| |
| /** |
| A basic memory buffer class. This is primarily used with |
| cmpp_outputer_b to capture arbitrary output for later use. |
| It's also used for incrementally creating dynamic strings. |
| |
| TODO: add the heuristic that an nAlloc of 0 with a non-NULL z |
| refers to externally-owned memory. This would change the |
| buffer-write APIs to automatically copy it before making any |
| changes. We have code for this in the trees this class derives |
| from, it just needs to be ported over. It would allow us to avoid |
| allocating in some cases where we need a buffer but it will always |
| (or commonly) be a copy of a static string, like a single space. |
| */ |
| struct cmpp_b { |
| /** |
| This buffer's memory, owned by this object. This library exclusively |
| uses sqlite3_realloc() and friends for memory management. |
| |
| If this pointer is taken away from this object then it must |
| eventually be passed to cmpp_mfree(). |
| */ |
| unsigned char * z; |
| /** |
| Number of bytes of this->z which are in use, not counting any |
| automatic NUL terminator which this class's APIs may add. |
| */ |
| cmpp_size_t n; |
| /** |
| Number of bytes allocated in this->z. |
| |
| Potential TODO: use a value of zero here, with a non-zero |
| this->n, to mean that this->z is owned elsewhere. This would |
| cause cmpp_b_append() to copy its original source before |
| appending. Similarly, cmpp_b_clear() would necessarily _not_ |
| free this->z. We've used that heuristic in a predecessor of this |
| class in another tree to good effect for years, but it's not |
| certain that we'd get the same level of utility out of that |
| capability as we do in that other project. |
| */ |
| cmpp_size_t nAlloc; |
| |
| /** |
| cmpp_b APIs which may fail will set this. Similarly, most |
| of the cmpp_b APIs become no-ops if this is non-0. |
| */ |
| int errCode; |
| }; |
| |
| typedef struct cmpp_b cmpp_b; |
| |
| /** |
| An empty-initialized cmpp_b struct for use in const-copy |
| initialization. |
| */ |
| #define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0} |
| |
| /** |
| An empty-initialized cmpp_b struct for use in non-copy copy |
| initialization. |
| */ |
| extern const cmpp_b cmpp_b_empty; |
| |
| /** |
| Frees s->z and zeroes out s but does not free s. |
| */ |
| CMPP_EXPORT void cmpp_b_clear(cmpp_b *s); |
| |
| /** |
| If s has content, s->nUsed is set to 0 and s->z is NUL-terminated |
| at its first byte, else this is a no-op. s->errCode is |
| set to 0. Returns s. |
| */ |
| CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *s); |
| |
| /** |
| Swaps all contents of the given buffers, including their persistent |
| error code. |
| */ |
| CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r); |
| |
| /** |
| If s->errCode is 0 and s->nAlloc is less than n, s->z is |
| reallocated to have at least n bytes, else this is a no-op. Returns |
| 0 on success, CMPP_RC_OOM on error. |
| */ |
| CMPP_EXPORT int cmpp_b_reserve(cmpp_b *s, cmpp_size_t n); |
| |
| /** |
| Works just like cmpp_b_reserve() but on allocation error it |
| updates pp's error state. |
| */ |
| CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n); |
| |
| /** |
| Appends n bytes from src to os, reallocating os as necessary. |
| Returns 0 on succes, CMPP_RC_OOM on allocation error. |
| |
| Errors from this function, and the other cmpp_b_append...() |
| variants, are recoverable (see cmpp_err_set()). |
| */ |
| CMPP_EXPORT int cmpp_b_append(cmpp_b * os, void const *src, |
| cmpp_size_t n); |
| |
| /** |
| Works just like cmpp_b_append() but on allocation error it |
| updates pp's error state. |
| */ |
| CMPP_EXPORT int cmpp_b_append4(cmpp * pp, |
| cmpp_b * os, |
| void const * src, |
| cmpp_size_t n); |
| |
| /** |
| Appends ch to the end of os->z, expanding as necessary, and |
| NUL-terminates os. Returns os->errCode and is a no-op if that is |
| non-0 when this is called. This is slightly more efficient than |
| passing length-1 strings to cmpp_b_append() _if_ os's memory |
| is pre-allocated with cmpp_b_reserve(), otherwise it may be |
| less efficient because it may need to allocate frequently if used |
| repeatedly. |
| */ |
| CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch); |
| |
| /** |
| Appends a decimal string representation of d to os. Returns |
| os->errCode and is a no-op if that is non-0 when this is called. |
| */ |
| CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d); |
| |
| /** int64_t counterpart of cmpp_b_append_i32(). */ |
| CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d); |
| |
| /** |
| A thin wrapper around cmpp_chomp() which chomps b->z. |
| */ |
| CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b); |
| |
| /** |
| A cmpp_output_f() impl which requires that its first argument be a |
| (cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes |
| of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in |
| reallocation error. On success it always NUL-terminates buffer->z. |
| A NULL buffer is treated as success but has no side effects. |
| |
| Example usage: |
| |
| ``` |
| cmpp_b os = cmpp_b_empty; |
| int rc = cmpp_stream(cmpp_input_f_FILE, stdin, |
| cmpp_output_f_b, &os); |
| ... |
| cmpp_b_clear(&os); |
| ``` |
| */ |
| CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src, |
| cmpp_size_t n); |
| |
| /** |
| A cmpp_outputer_cleanup_f() implementation which requires that |
| self->state be either NULL or a cmpp_b pointer. This function |
| passes it to cmpp_b_clear(). It does _not_ set self->state or |
| self->name to NULL. |
| */ |
| CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self); |
| |
| /** |
| A cmpp_outputer prototype which can be copied to use a dynamic |
| string buffer as an output source. Its state member must be set (by |
| the client) to a cmpp_b instance. Its out() method is |
| cmpp_output_f_b(). Its cleanup() method is |
| cmpp_outputer_cleanup_f_b(). It has no flush() method. |
| */ |
| extern const cmpp_outputer cmpp_outputer_b; |
| |
| /** |
| Returns a string containing version information in an unspecified |
| format. |
| */ |
| CMPP_EXPORT char const * cmpp_version(void); |
| |
| /** |
| Type IDs for directive lines and argument-parsing tokens. |
| |
| This is largely a historical artifact and work is underway |
| to factor this back out of the public API. |
| */ |
| enum cmpp_tt { |
| |
| /** |
| X-macro which defines token types. It invokes E(X,Y) for each |
| entry, where X is the base name part of the token type and Y is the |
| token name as it appears in input scripts (if any, else it's 0). |
| |
| Maintenance reminder: their ordering in this map is insignificant |
| except that None must be first and must have the value 0. |
| |
| Some of the more significant ones are: |
| |
| - Word: an unquoted word-like token. |
| |
| - String: a quoted string. |
| |
| - StringAt: an @"..." string. |
| |
| - GroupParen, GroupBrace, GroupSquiggly: (), [], and {} |
| |
| - All which start with D_ are directives. D_Line is a transitional |
| state between "unparsed" and another D_... value. |
| */ |
| #define cmpp_tt_map(E) \ |
| E(None, 0) \ |
| E(RawLine, 0) \ |
| E(Unknown, 0) \ |
| E(Word, 0) \ |
| E(Noop, 0) \ |
| E(Int, 0) \ |
| E(Null, 0) \ |
| E(String, 0) \ |
| E(StringAt, 0) \ |
| E(GroupParen, 0) \ |
| E(GroupBrace, 0) \ |
| E(GroupSquiggly,0) \ |
| E(OpEq, "=") \ |
| E(OpNeq, "!=") \ |
| E(OpLt, "<") \ |
| E(OpLe, "<=") \ |
| E(OpGt, ">") \ |
| E(OpGe, ">=") \ |
| E(ArrowR, "->") \ |
| E(ArrowL, "<-") \ |
| E(Plus, "+") \ |
| E(Minus, "-") \ |
| E(ShiftR, ">>") \ |
| E(ShiftL, "<<") \ |
| E(ShiftL3, "<<<") \ |
| E(OpNot, "not") \ |
| E(OpAnd, "and") \ |
| E(OpOr, "or") \ |
| E(OpDefined, "defined") \ |
| E(OpGlob, "glob") \ |
| E(OpNotGlob, "not glob") \ |
| E(AnyType, 0) \ |
| E(Eof, 0) |
| |
| #define E(N,TOK) cmpp_TT_ ## N, |
| cmpp_tt_map(E) |
| #undef E |
| /** Used by cmpp_d_register() to assign new IDs. */ |
| cmpp_TT__last |
| }; |
| typedef enum cmpp_tt cmpp_tt; |
| |
| /** |
| For all of the cmpp_tt enum entries, returns a string form of the |
| enum entry name, e.g. "cmpp_TT_D_If". Returns NULL for any other |
| values |
| */ |
| CMPP_EXPORT char const * cmpp_tt_cstr(int tt); |
| |
| /** |
| Policies for how to handle undefined @tokens@ when performing |
| content filtering. |
| */ |
| enum cmpp_atpol_e { |
| /** Sentinel value. */ |
| cmpp_atpol_invalid = -1, |
| /** Turn off @token@ parsing. */ |
| cmpp_atpol_OFF = 0, |
| /** Retain undefined @token@ - emit it as-is. */ |
| cmpp_atpol_RETAIN, |
| /** Elide undefined @token@. */ |
| cmpp_atpol_ELIDE, |
| /** Error for undefined @token@. */ |
| cmpp_atpol_ERROR, |
| /** A sentinel value for use with cmpp_dx_out_expand(). */ |
| cmpp_atpol_CURRENT, |
| /** |
| This isn't _really_ the default. It's the default for the |
| --@policy CLI flag and #@pragma when it's given no value. |
| */ |
| cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR, |
| /** |
| The compile-time default for all cmpp instances. |
| */ |
| cmpp_atpol_DEFAULT = cmpp_atpol_OFF |
| |
| }; |
| typedef enum cmpp_atpol_e cmpp_atpol_e; |
| |
| /** |
| Policies describing how cmpp should react to attempts to use |
| undefined keys. |
| */ |
| enum cmpp_unpol_e { |
| /* Sentinel. */ |
| cmpp_unpol_invalid, |
| /** Treat undefined keys as NULL/falsy. This is the default. */ |
| cmpp_unpol_NULL, |
| /** Trigger an error for undefined keys. This should probably be the |
| default. */ |
| cmpp_unpol_ERROR, |
| /** |
| The compile-time default for all cmpp instances. |
| */ |
| cmpp_unpol_DEFAULT = cmpp_unpol_NULL |
| }; |
| typedef enum cmpp_unpol_e cmpp_unpol_e; |
| |
| typedef struct cmpp_arg cmpp_arg; |
| /** |
| A single argument for a directive. When a cmpp_d::flags have |
| cmpp_d_F_ARGS_V2 set then the part of the input immediately |
| following the directive (and on the same line) is parsed into a |
| cmpp_args, a container for these. |
| */ |
| struct cmpp_arg { |
| /** Token type. */ |
| cmpp_tt ttype; |
| /** |
| The arg's string value, shorn of any opening/closing quotes or () |
| or {} or []. The args-parsing process guarantees to NUL-terminate |
| this. The bytes are typically owned by a cmpp_args object, but |
| clients may direct them wherever the need to, so long as the |
| bytes are valid longer than this object is. |
| */ |
| unsigned char const * z; |
| /** |
| The arg's effective length, in bytes, after opening/closing chars |
| are stripped. That is, its string form is the range [z,z+n). |
| */ |
| unsigned n; |
| /** |
| The next argument in the list. It is owned by whatever code set |
| it up (typically cmpp_args_parse()). |
| */ |
| cmpp_arg const * next; |
| }; |
| |
| /** |
| Empty-initialized cmpp_arg instance, intended for |
| const-copy initialization. |
| */ |
| #define cmpp_arg_empty_m {cmpp_TT_None,0,0,0} |
| |
| /** |
| Empty-initialized cmpp_outputer instance, intended for |
| non-const-copy initialization. |
| */ |
| extern const cmpp_arg cmpp_arg_empty; |
| |
| typedef struct cmpp_dx cmpp_dx; |
| typedef struct cmpp_dx_pimpl cmpp_dx_pimpl; |
| typedef struct cmpp_d cmpp_d; |
| |
| /** |
| Flags for use with cmpp_d::flags. |
| */ |
| enum cmpp_d_e { |
| /** Sentinel value. */ |
| cmpp_d_F_none = 0, |
| /** |
| cmpp_dx_next() will not parse the directive's arguments. Instead, |
| it makes cmpp_dx::arg0 encapsulate the whole line of the |
| directive (sans the directive's name) as a single argument. The |
| only transformation which is performed is the removal of |
| backslashes from backslash-escaped newlines. It is up to the |
| directive's callback to handle (or not) the arguments. |
| */ |
| cmpp_d_F_ARGS_RAW = 0x01, |
| |
| /** |
| cmpp_dx_next() will parse the directive's arguments. |
| cmpp_dx::arg0 will point to the first argument in the list, or |
| NULL if there are no arguments. |
| |
| If both cmpp_d_F_ARGS_LIST and cmpp_d_F_ARGS_RAW are specified, |
| cmpp_d_F_ARGS_LIST will win. |
| */ |
| cmpp_d_F_ARGS_LIST = 0x02, |
| |
| /** |
| Indicates that the direction should not be available if the cmpp |
| instance is configured with any of the cmpp_ctor_F_SAFEMODE flags. |
| All directives when do any of the following are obligated to |
| set this flag: |
| |
| - Filesystem or network access. |
| - Invoking external processes. |
| |
| Or anything else which might be deamed "security-relevant". |
| |
| When registering a directive which has both opener and closer |
| implementations, it is sufficient to set this only on the opener. |
| |
| The library imposes this flag in the following places: |
| |
| - Registration of a directive with this flag will fail if |
| cmpp_is_safemode() is true for that cmpp instance. |
| |
| - cmpp_dx_process() will refuse to invoke a directive with this |
| flag when cmpp_is_safemode() is true. |
| */ |
| cmpp_d_F_NOT_IN_SAFEMODE = 0x04, |
| |
| /** |
| Call-only directives are only usable in [directive ...] "call" |
| contexts. They are not permitted to have a closing directive. |
| */ |
| cmpp_d_F_CALL_ONLY = 0x08, |
| /** |
| Indicates that the directive is incapable of working in a [call] |
| context and an error should be trigger if it is. _Most_ |
| directives which have a closing directive should have this |
| flag. The exceptions are directives which only conditionally use |
| a closing directive, like #query. |
| */ |
| cmpp_d_F_NO_CALL = 0x10, |
| |
| /** |
| Mask of the client-usable range for this enum. Values outside of |
| this mask are reserved for internal use and will be stripped from |
| registrations made with cmpp_d_register(). |
| */ |
| cmpp_d_F_MASK = 0x0000ffff |
| |
| }; |
| |
| /** |
| Callback type for cmpp_d::impl::callback(). cmpp directives are all |
| implemented as functions with this signature. Implementations are |
| called only by cmpp_dx_process() (and only after cmpp_dx_next() has |
| found a preprocessor line), passed the current context object. |
| These callbacks are only ever passed directives which were |
| specifically registered with them (see cmpp_d_register()). |
| |
| The first rule of callback is: to report errors (all of which end |
| processing of the current input) call cmpp_dx_err_set(), passing it |
| the callback's only argument, then clean up any local resources, |
| then return. The library will recognize the error and propagate it. |
| |
| dx's memory is only valid for the duration of this call. It must |
| not be held on to longer than that. dx->args.arg0 has slightly different |
| lifetime: if this callback does _not_ call back in to |
| cmpp_dx_next() then dx->args.arg0 and its neighbors will survive until |
| this call is completed. Calling cmpp_dx_next(), or any API which |
| invokes it, invalidates dx->args.arg0's memory. Thus directives which |
| call into that must _copy_ any data they need from their own |
| arguments before doing so, as their arguments list will be |
| invalidated. |
| */ |
| typedef void (*cmpp_dx_f)(cmpp_dx * dx); |
| |
| /** |
| A typedef for generic deallocation routines. |
| */ |
| typedef void (*cmpp_finalizer_f)(void *); |
| |
| /** |
| State specific to concrete cmpp_d implementations. |
| |
| TODO: move this, except for the state pointer, out of cmpp_d |
| so that directives cannot invoke these callbacks directly. Getting |
| that to work requires moving the builtin directives into the |
| dynamic directives list. |
| */ |
| struct cmpp_d_impl { |
| /** |
| Callback func. If any API other othan cmpp_dx_process() invokes |
| this, behavior is undefined. |
| */ |
| cmpp_dx_f callback; |
| |
| /** |
| For custom directives with a non-NULL this->state, this will be |
| called, and passed that object, when the directive is cleaned |
| up. For directives with both an opening and a closing tag, this |
| destructor is only attached to the opening tag. |
| |
| If any API other othan cmpp's internal cleanup routines invoke |
| this, behavior is undefined. |
| */ |
| cmpp_finalizer_f dtor; |
| |
| /** |
| State for the directive's callback. It is accessible in |
| cmpp_dx_f() impls via theDx->d->impl.state. For custom |
| directives with both an opening and closing directive, this |
| same state object gets assigned to both. |
| */ |
| void * state; |
| }; |
| typedef struct cmpp_d_impl cmpp_d_impl; |
| #define cmpp_d_impl_empty_m {0,0,0} |
| |
| /** |
| Each c-pp "directive" is modeled by one of these. |
| */ |
| struct cmpp_d { |
| |
| struct { |
| /** |
| The directive's name, as it must appear after the directive |
| delimiter. Its bytes are assumed to be static or otherwise |
| outlive this object. |
| */ |
| const char *z; |
| /** Byte length of this->z. We record this to speed up searches. */ |
| unsigned n; |
| } name; |
| |
| /** |
| Bitmask of flags from cmpp_d_e plus possibly internal flags. |
| */ |
| cmpp_flag32_t flags; |
| |
| /** |
| The directive which acts as this directive's closing element |
| element, or 0 if it has none. |
| */ |
| cmpp_d const * closer; |
| |
| /** |
| State specific to concrete implementations. |
| */ |
| cmpp_d_impl impl; |
| }; |
| |
| /** |
| Each instance of the cmpp_dx class (a.k.a. "directive context") |
| manages a single input source. It's responsible for the |
| tokenization of all input, locating directives, and processing |
| ("running") directives. The directive-specific work happens in |
| cmpp_dx_f() implementations, and this class internally manages the |
| setup, input traversal, and teardown. |
| |
| These objects only exist while cmpp is actively processing |
| input. Client code interacts with them only through cmpp_dx_f() |
| implementations which the library invokes. |
| |
| The process of filtering input to look for directives is to call |
| cmpp_dx_next() until it indicates either an error or that a |
| directive was found. In the latter case, the cmpp_dx object is |
| populated with info about the current directive. cmpp_dx_process() |
| will run that directive, but cmpp_dx_f() implementations sometimes |
| need to make decisions based on the located directive before doing |
| so (and sometimes they need to skip running it). |
| |
| If cmpp_dx_next() finds no directive, the end of the input has been |
| reached and there is no further output to generate. |
| |
| Content encountered before a directive is found is passed on to the |
| output stream via cmpp_dx_out_raw() or cmpp_dx_out_expand(). |
| */ |
| struct cmpp_dx { |
| /** |
| The cmpp object which owns this context. |
| */ |
| cmpp * const pp; |
| |
| /** |
| The directive on whose behalf this context is active. |
| */ |
| cmpp_d const *d; |
| |
| /** |
| Name of the input for error reporting. Typically an input script |
| file name, but it need not refer to a file. |
| */ |
| unsigned const char * const sourceName; |
| |
| /** |
| State related to arguments passed to the current directive. |
| |
| It is important to keep in mind that the memory for the members |
| of this sub-struct may be modified or reallocated |
| (i.e. invalidated) by any APIs which call in to cmpp_dx_next(). |
| cmpp_dx_f() implementations must take care not to use any of this |
| memory after calling into that function, cmpp_dx_consume(), or |
| similar. If needed, it must be copied (e.g. using |
| cmpp_args_clone() to create a local copy of the parsed |
| arguments). |
| */ |
| struct { |
| /** |
| Starting byte of unparsed arguments. This is for cmpp_d_f() |
| implementations which need custom argument parsing. |
| */ |
| unsigned const char * z; |
| |
| /** |
| The byte length of z. |
| */ |
| cmpp_size_t nz; |
| |
| /** |
| The parsed arg count for the this->arg0 list. |
| */ |
| unsigned argc; |
| |
| /** |
| The first parsed arg or NULL. How this is set up is affected by |
| cmpp_d::flags. |
| |
| This is specifically _NOT_ defined as a sequential array and |
| using pointer math to traverse it invokes undefined behavior. |
| |
| To traverse the list: |
| |
| for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){ |
| ... |
| } |
| */ |
| cmpp_arg const * arg0; |
| } args; |
| |
| /** |
| Private impl details. |
| */ |
| cmpp_dx_pimpl * const pimpl; |
| }; |
| |
| /** |
| Thin proxy for cmpp_err_set(), replacing only the first argument. |
| */ |
| CMPP_EXPORT int cmpp_dx_err_set(cmpp_dx *dx, int rc, |
| char const *zFmt, ...); |
| |
| |
| /** |
| Returns true if dx's current call into the API is the result |
| of a function call, else false. Any APIs which recurse into |
| input processing will reset this to false, so it needs to be |
| evaluated before doing any such work. |
| |
| Design note: this flag is actually tied to dx's arguments, which |
| get reset by APIs which consume from the input stream. |
| */ |
| CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx); |
| |
| /** |
| Returns true if dx->pp has error state, else false. If this |
| function returns true, cmpp_dx_f() implementations are required to |
| stop working, clean up any local resources, and return. Continuing |
| to use dx when it's in an error state may exacerbate the problem. |
| */ |
| #define cmpp_dx_err_check(DX) (DX)->pp->api->err_has((DX)->pp) |
| |
| /** |
| Scans dx to the next directive line, emitting all input before that |
| which is _not_ a directive line to dx->pp's output channel unless |
| it's elided due to being inside a block which elides its content |
| (e.g. #if). |
| |
| Returns 0 if no errors were triggered, else a cmpp_rc_e code. This |
| is a no-op if dx->pp has persistent error state set, and that error |
| code is returned. |
| |
| If it returns 0 then it sets *pGotOne to true if a directive was |
| found and false if not (in which case the end of the input has |
| been reached and further calls to this function for the same input |
| source will be no-ops). If it sets *pGotOne to true then it also |
| sets up dx's state for use with cmpp_dx_process(), which should |
| (normally) then be called. |
| |
| ACHTUNG: calling this resets any argument-handling-related state of |
| dx. That is important for cmpp_dx_f() implemenations, which _must |
| not_ hold copies of any pointers from dx->args.arg0 or dx->args.z |
| beyond a call to this function. Any state they need must be |
| evaluated, potentially copied, before calling this function(). |
| */ |
| CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne); |
| |
| /** |
| This is only legal to call immediately after a successful call to |
| cmpp_dx_next(). It requires that cmpp_dx_next() has just located |
| the next directive. This function runs that directive. Returns 0 |
| on success and all that. |
| |
| Design note: directive-Search and directive-process are two |
| distinctly separate steps because directives which have both |
| open/closing tags frequently discard the closing directive without |
| running it (it exists to tell the directive how far to read). Those |
| closing directives exist independently, though, and will trigger |
| errors when encountered outside of the context of their opening |
| directive tag (e.g. an "#/if" without an "#if"). |
| */ |
| CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx); |
| |
| /** |
| A bitmask of flags for use with cmpp_dx_consume() |
| */ |
| enum cmpp_dx_consume_e { |
| /** |
| Tells cmpp_dx_consume() to process any directives it encounters |
| which are not in the specified set of closing directives. Its |
| default is to fail if another directive is seen. |
| */ |
| cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01, |
| /** |
| Tells cmpp_dx_consume() that non-directive content encountered |
| before the designated closing directive(s) must use an at-policy |
| of cmpp_atpol_OFF. That is: the output target of that function will |
| get the raw, unfiltered content. This is for cases where the |
| consumer will later re-emit that content, delaying @token@ |
| parsing until a later step (e.g. #query does this). |
| |
| This may misinteract in unpredictable ways when used with |
| cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs. |
| */ |
| cmpp_dx_consume_F_RAW = 0x02 |
| }; |
| |
| /** |
| A helper for cmpp_dx_f() implementations which read in their |
| blocked-off content instead of passing it through the normal output |
| channel. e.g. `#define x <<` stores that content in a define named |
| "x". |
| |
| This function runs a cmpp_dx_next() loop which does the following: |
| |
| If the given output channel is not NULL then it first replaces the |
| output channel with the given one, such that all output which would |
| normally be produced will be sent there until this function |
| returns, at which point the output channel is restored. If the |
| given channel is NULL then output is not captured - it instead goes |
| dx's current output channel. |
| |
| dClosers must be a list of legal closing tags nClosers entries |
| long. Typically this is the single closing directive/tag of the |
| current directive, available to the opening directive's cmpp_dx_f() |
| impl via dx->d->closer. Some special cases require multiple |
| candidates, however. |
| |
| The flags argument may be 0 or a bitmask of values from the |
| cmpp_dx_consume_e enum. |
| |
| If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set |
| then this function requires that the next directive in the input be |
| one specified by dClosers. If the next directive is not one of |
| those, it will fail with code CMPP_RC_SYNTAX. |
| |
| If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it |
| will continue to search for and process directives until the |
| dCloser directive is found. Calling into other directives will |
| invalidate certain state that a cmpp_dx_f() has access to - see |
| below for details. If dCloser is not found before EOF, a |
| CMPP_RC_SYNTAX error is triggered. |
| |
| Once one of dCloser is found, this function returns with dx->d |
| referring to the that directive. In practice, the caller should |
| _not_ call cmpp_dx_process() at that point - the closing directive |
| is typically a no-op placeholder which exists only to mark the end |
| of the block. If the closer has work to do, however, the caller of |
| this function should call cmpp_dx_process() at that point. |
| |
| On success it returns 0, the input stream will have been consumed |
| between the directive dx and its closing tag, and dx->d will point |
| to the new directive. If os is not NULL then os will have been |
| sent any content. |
| |
| On error, processing of the directive must end immediately, |
| returning from the cmpp_dx_f() impl after cleaning up any local |
| resources. |
| |
| ACHTUNG: since this invokes cmpp_dx_next(), it invalidates |
| dx->args.arg0. Its dx->d is also replaced but the previous value |
| remains valid until the cmpp instance is cleaned up. |
| |
| Example from the context of a cmpp_dx_f() implementation |
| |
| ``` |
| // "dx" is the cmpp_dx arg to this function |
| cmpp_outputer oss = cmpp_outputer_b; |
| cmpp_b os = cmpp_b_empty; |
| oss.state = &os; |
| if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 0) ){ |
| cmpp_b_chomp( &os ); |
| ... maybe modify the buffer or decorate the output in some way... |
| cmpp_dx_out_raw(dx, os.z, os.n); |
| } |
| cmpp_b_clear(&os); |
| ``` |
| |
| Design issue: this API does not currently have a way to handle |
| directives which have multiple potential waypoints/endpoints, in |
| the way that an #if may optionally have an #elif or #else before |
| the #/if. Such processing has to be done in the directive's |
| impl. |
| */ |
| CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os, |
| cmpp_d const *const * dClosers, |
| unsigned nClosers, |
| cmpp_flag32_t flags); |
| |
| /** |
| Equivalent to cmpp_dx_consume(), capturing to the given buffer |
| instead of a cmpp_outputer object. |
| */ |
| CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b, |
| cmpp_d const * const * dClosers, |
| unsigned nClosers, |
| cmpp_flag32_t flags); |
| |
| /** |
| If arg is not NULL, cleans up any resources owned by |
| arg but does not free arg. |
| |
| As of this writing, they own none and some code still requires |
| that. That is Olde Thynking, though. |
| */ |
| CMPP_EXPORT void cmpp_arg_cleanup(cmpp_arg *arg); |
| |
| /** |
| If arg is not NULL resets arg to be re-used. arg must have |
| initially been cleanly initialized by copying cmpp_arg_empty (or |
| equivalent, i.e. zeroing it out). |
| */ |
| CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg); |
| |
| /** |
| This is the core argument-parsing function used by the library's |
| provided directives. Its is available in the public API as a |
| convenience for custom cmpp_dx_f() implementations, but custom |
| implementations are not required to make use of it. |
| |
| Populates a cmpp_arg object by parsing the next token from its |
| input source. |
| |
| Expects *pzIn to point to the start of input for parsing arguments |
| and zInEnd to be the logical EOF of that range. This function |
| populates pOut with the info of the parse. Returns 0 on success, |
| non-0 (and updates pp's error state) on error. |
| |
| Output (the parsed token) is written to *pzOut. zOutEnd must be the |
| logical EOF of *pzOut. *pzOut needs to be, at most, |
| (zInEnd-*pzIn)+1 bytes long. This function range checks the output |
| and will not write to or past zOutEnd, but that will trigger a |
| CMPP_RC_RANGE error. |
| |
| On success, *pzIn will be set to 1 byte after the last one parsed |
| for pOut and *pzOut will be set to one byte after the final output |
| (NUL-terminated). pOut->z will point to the start of *pzOut and |
| pOut->n will be set to the byte-length of pOut->z. |
| |
| When the end of the input is reached, this function returns 0 |
| and sets pOut->ttype to cmpp_TT_EOF. |
| |
| General tokenization rules: |
| |
| Tokens come in the following flavors: |
| |
| - Quoted strings: single- or double-quoted. cmpp_arg::ttype: |
| cmpp_TT_String. |
| |
| - "At-strings": @"..." and @'...'. cmpp_arg::ttype value: |
| cmpp_TT_StringAt. |
| |
| - Decimal integers with an optional sign. cmpp_arg::ttype value: |
| cmpp_TT_Int. |
| |
| - Groups: (...), {...}, and [...]. cmpp_arg::ttype values: |
| cmpp_TT_GroupParen, cmpp_TT_GroupSquiggly, and |
| cmpp_TT_GroupBrace. These types do not automatically get parsed |
| recursively. To recurse into one of these, pass cmpp_arg_parse() |
| the grouping argument's bytes as the input range. |
| |
| - Word: anything which doesn't look like one of these above. Token |
| type IDs: cmpp_TT_Word. These are most often interpreted as |
| #define keys but cmpp_dx_f() implementations sometimes treat |
| them as literal values. |
| |
| - A small subset of words and operator-like tokens, e.g. '=' and |
| '!=', get a very specific ttype, e.g. cmpp_TT_OpNeq, but these |
| can generally be treated as strings. |
| |
| - Outside of strings and groups, spaces, tabs, carriage-returns, |
| and newlines are skipped. |
| |
| These are explained in more detail in the user's manual |
| (a.k.a. README.md). |
| |
| There are many other token types, mostly used internally. |
| |
| This function supports _no_ backslash escape sequences in |
| tokens. All backslashes, with the obligatory exception of those |
| which make up backslash-escaped newlines in the input stream, are |
| retained as-is in all token types. That means, for example, that |
| strings may not contain their own quote character. |
| |
| As an example of where this function is useful: cmpp_dx_f() |
| implementations which need to selectively parse a subset of the |
| directive's arguments can use this. As input, their dx argument's |
| args.z and args.nz members delineate the current directive line's |
| arguments. See c-pp.c:cmpp_dx_f_pipe() for an example. |
| */ |
| CMPP_EXPORT int cmpp_arg_parse(cmpp_dx * dx, |
| cmpp_arg *pOut, |
| unsigned char const **pzIn, |
| unsigned char const *zInEnd, |
| unsigned char ** pzOut, |
| unsigned char const * zOutEnd); |
| |
| /** |
| True if (cmpp_arg const *)ARG's contents match the string literal |
| STR, else false. |
| */ |
| #define cmpp_arg_equals(ARG,STR) \ |
| (sizeof(STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1)) |
| |
| /** |
| True if (cmpp_arg const *)ARG's contents match the string literal |
| STR or ("-" STR), else false. The intent is that "-flag" be passed |
| here to tolerantly accept either "-flag" or "--flag". |
| */ |
| #define cmpp_arg_isflag(ARG,STR) \ |
| cmpp_arg_equals(ARG,STR) || cmpp_arg_equals(ARG, "-" STR) |
| |
| /** |
| Creates a copy of arg->z. If allocation fails then pp's persistent |
| error code is set to CMPP_RC_OOM. If pp's error code is not 0 when |
| this is called then this is a no-op and returns NULL. In other |
| words, if this function returns NULL, pp's error state was either |
| already set when this was called or it was set because allocation |
| failed. |
| |
| Ownership of the returned memory is transferred to the caller, who |
| must eventually free it using cmpp_mfree(). |
| */ |
| CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg); |
| |
| /** |
| Flag bitmasks for use with cmpp_arg_to_b(). With my apologies |
| for the long names (but consistency calls for them). |
| */ |
| enum cmpp_arg_to_b_e { |
| /** |
| Specifies that the argument's string value should be used as-is, |
| rather than expanding it (if the arg's ttype would normally cause |
| it to be expanded). |
| */ |
| cmpp_arg_to_b_F_FORCE_STRING = 0x01, |
| |
| /** |
| Tells cmpp_arg_to_b() to not expand arguments with type |
| cmpp_TT_Word, which it normally treats as define keys. It instead |
| treats these as strings. |
| */ |
| cmpp_arg_to_b_F_NO_DEFINES = 0x02, |
| |
| /** |
| If set, arguments with a ttype of cmpp_TT_GroupBrace will be |
| "called" by passing them to cmpp_call_arg(). The space-trimmed |
| result of the call becomes the output of the cmpp_arg_to_b() |
| call. |
| |
| FIXME: make this opt-out instead of opt-in. We end up _almost_ |
| always wanting this. |
| */ |
| cmpp_arg_to_b_F_BRACE_CALL = 0x04, |
| |
| /** |
| Explicitly disable [call] expansion even if |
| cmpp_arg_to_b_F_BRACE_CALL is set in the flags. |
| */ |
| cmpp_arg_to_b_F_NO_BRACE_CALL = 0x08 |
| |
| /** |
| TODO? cmpp_arg_to_b_F_UNESCAPE |
| */ |
| }; |
| |
| /** |
| Appends some form of arg to the given buffer. |
| |
| arg->ttype values of cmpp_TT_Word (define keys) and |
| cmpp_TT_StringAt cause the value to be expanded appropriately (the |
| latter according to dx->pp's current at-policy). Others get emitted |
| as-is. |
| |
| The flags argument influences the expansion decisions, as documented |
| in the cmpp_arg_to_b_e enum. |
| |
| Returns 0 on success and all that. |
| |
| See: cmpp_atpol_get(), cmpp_atpol_set() |
| |
| Reminder to self: though this function may, via script-side |
| function call resolution, recurse into the library, any such |
| recursion gets its own cmpp_dx instance. In this context that's |
| significant because it means this call won't invalidate arg's |
| memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on |
| where args came from - typically it's owned by dx but |
| cmpp_args_clone() exists solely to work around such potential |
| invalidation). |
| */ |
| CMPP_EXPORT int cmpp_arg_to_b(cmpp_dx * dx, cmpp_arg const *arg, |
| cmpp_b * os, cmpp_flag32_t flags); |
| |
| /** |
| Flags for use with cmpp_call_str() and friends. |
| */ |
| enum cmpp_call_e { |
| /** Do not trim a newline from the result. */ |
| cmpp_call_F_NO_TRIM = 0x01, |
| /** Trim all leading and trailing space and newlines |
| from the result. */ |
| cmpp_call_F_TRIM_ALL = 0x02 |
| }; |
| |
| /** |
| This assumes that arg->z holds a "callable" directive |
| string in the form: |
| |
| directiveName ...args |
| |
| This function composes a new cmpp input source from that line |
| (prefixed with dx's current directive prefix if it's not already |
| got one), processes it with cmpp_process_string(), redirecting the |
| output to dest (which gets appended to, so be sure to |
| cmpp_b_reuse() it if needed before calling this). |
| |
| To simplify common expected usage, by default the output is trimmed |
| of a single newline. The flags argument, 0 or a bitmask of values |
| from the cmpp_call_e enum, can be used to modify that behavior. |
| |
| This is the basis of "function calls" in cmpp. |
| |
| Returns 0 on success. |
| */ |
| int cmpp_call_str(cmpp *dx, |
| unsigned char const * z, |
| cmpp_ssize_t n, |
| cmpp_b * dest, |
| cmpp_flag32_t flags); |
| |
| /** |
| Convert an errno value to a cmpp_rc_e approximation, defaulting to |
| dflt if no known match is found. This is intended for use by |
| cmpp_dx_f implementations which use errno-using APIs. |
| */ |
| CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt); |
| |
| /** |
| Configuration object for use with cmpp_d_register(). |
| */ |
| struct cmpp_d_reg { |
| /** |
| The name of the directive as it will be used in |
| input scripts, e.g. "mydirective". It will be copied by |
| cmpp_d_register(). |
| */ |
| char const *name; |
| /** |
| A combination of bits from the cmpp_d_e enum. |
| |
| These flags are currently applied only to this->opener. |
| this->closer, because of how it's typically used, assumes |
| */ |
| struct { |
| /** |
| Callback for the directive's opening tag. |
| */ |
| cmpp_dx_f f; |
| /** |
| Flags from cmpp_d_e. Typically one of cmpp_d_F_ARGS_LIST or |
| cmpp_d_F_ARGS_RAW. |
| */ |
| cmpp_flag32_t flags; |
| } opener; |
| struct { |
| /** |
| Callback for the directive's closing tag, if any. |
| |
| This is only relevant for directives which have both an open and |
| a closing tag (even if that closing tag is only needed in some |
| contexts, e.g. "#define X <<" (with a closer) vs "#define X Y" |
| (without)). See cmpp_dx_f_dangling_closer() for a default |
| implementation which triggers an error if it's seen in the input |
| and not consumed by its counterpart opening directive. That |
| implementation has proven useful for #define, #pipe, and friends. |
| |
| Design notes: it's as yet unclear how to model, in the public |
| interface, directives which have a whole family of cooperating |
| directives, namely #if/#elif/#else. |
| */ |
| cmpp_dx_f f; |
| /** |
| Flags from cmpp_d_e. For closers this can typically be |
| left at 0. |
| */ |
| cmpp_flag32_t flags; |
| } closer; |
| /** |
| If not NULL then it is assigned to the directive's opener part |
| and will be called by the library in either of the following |
| cases: |
| |
| - When the custom directive is cleaned up. |
| |
| - If cmpp_d_register() fails (returns non-0), regardless of how |
| it fails. |
| |
| It is passed this->state. |
| */ |
| cmpp_finalizer_f dtor; |
| /** |
| Implementation state for the callbacks. |
| */ |
| void * state; |
| }; |
| typedef struct cmpp_d_reg cmpp_d_reg; |
| /** |
| Empty-initialized cmpp_d_reg instance, intended for const-copy |
| initialization. |
| */ |
| #define cmpp_d_reg_empty_m {0,{0,0},{0,0},0,0} |
| /** |
| Empty-initialized instance, intended for non-const-copy |
| initialization. |
| */ |
| //extern const cmpp_d_reg cmpp_d_reg_empty; |
| |
| /** |
| Registers a new directive, or a pair of opening/closing directives, |
| with pp. |
| |
| The semantics of r's members are documented in the cmpp_d_reg |
| class. r->name and r->opener.f are required. The remainder may be |
| 0/NULL. Its members are copied - r need not live longer than this |
| call. |
| |
| When the new directive is seen in a script, r->opener.f() will be |
| called. If the closing directive (if any) is seen in a script, |
| r->closer.f() is called. In both cases, the callback |
| implementation can get access to the r->state object via |
| cmdd_dx::d::impl::state (a.k.a dx->d->impl.state). |
| |
| If r->closer.f is not NULL then the closing directive will be named |
| "/${zName}". (Design note: it is thought that forcing a common |
| end-directive syntax will lead to fewer issues than allowing |
| free-form closing tag names, e.g. fewer chances of a name collision |
| or not quite remembering the spelling of a given closing tag |
| (#endef vs #enddefine vs #/define).) |
| |
| Returns 0 on success and updates pp's error state on error. Similarly, |
| this is a no-op if pp has an error code when this is called, in which |
| case it returns that result code without other side-effects. |
| |
| On success, if pOut is not NULL then it is set to the directive |
| pointer, memory owned by pp until it cleans up its directives. This |
| is the only place in the API a non-const pointer to a directive can |
| be found, and it is provided only for very specific use-cases where |
| a directive needs to be manipulated (carefully) after |
| registration[^post-reg-manipulation]. If this function also |
| registered a closing directive, it is available as (*pOut)->closer. |
| pOut should normally be NULL. |
| |
| Failure modes include: |
| |
| - Returns CMPP_RC_RANGE if zName is not legal for use as a |
| directive name. See cmpp_is_legal_key(). |
| |
| - Returns CMPP_RC_OOM on an allocation error. |
| |
| Errors from this function are recoverable (see cmpp_err_set()). A |
| failed registration, even one which doesn't fail until the |
| registration of the closing element, will leave pp in a |
| well-defined state (with neither of r's directives being |
| registered). |
| |
| [^post-reg-manipulation]: The one known use case if the #if family |
| of directives, all of which use the same #/if closing |
| directive. The public registration API does not account for sharing |
| of closers that way, and whether it _should_ is still TBD. The |
| workaround, for this case, is to get the directives as they're |
| registered and point the cmpp_d::closer of each of #if, #elif, and |
| #else to #/if. |
| */ |
| CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r, |
| cmpp_d **pOut); |
| |
| /** |
| A cmpp_dx_f() impl which is intended to be used as a callback for |
| directive closing tags for directives in which the opening tag's |
| implementation consumes the input up to the closing tag. This impl |
| triggers an error if called, indicating that the directive closing |
| was seen in the input without its accompanying directive opening. |
| */ |
| CMPP_EXPORT void cmpp_dx_f_dangling_closer(cmpp_dx *dx); |
| |
| /** |
| Writes the first n bytes of z to dx->pp's current output channel |
| without performing any @token@ parsing. |
| |
| Returns dx->pp's persistent error code (0 on success) and sets that |
| code to non-0 on error. This is a no-op if dx->pp has a non-0 error |
| state, returning that code. |
| |
| See: cmpp_dx_out_expand() |
| */ |
| CMPP_EXPORT int cmpp_dx_out_raw(cmpp_dx * dx, void const *z, |
| cmpp_size_t n); |
| |
| /** |
| Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the |
| given policy says to (else it passes the content through as-is, as |
| per cmpp_dx_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's |
| current policy. A policy of cmpp_atpol_OFF behaves exactly like |
| cmpp_dx_out_raw(). |
| |
| Returns dx->pp's persistent error code (0 on success) and sets that |
| code to non-0 on error. This is a no-op if dx->pp has a non-0 error |
| state, returning that code. |
| |
| If pOut is NULL then dx->pp's default channel is used, with the |
| caveat that atPolicy's only legal value in that case is |
| cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be |
| overridden for that particular output channel, to avoid accidental |
| filtering when it's not enabled. They do not impose that |
| restriction for other output channels, which are frequently used |
| for filtering intermediary results.) |
| |
| See: cmpp_dx_out_raw() |
| |
| Notes regarding how this is used internally: |
| |
| - This function currently specifically does nothing when invoked in |
| skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode |
| except when evaluating #elif expressions (previous #if/#elifs |
| having failed and put us in skip-mode), where it's expanding |
| expression operands. That part currently (as of 2025-10-21) uses |
| dx->pp's current policy, and it's not clear whether that is |
| sufficient or whether we need to force it to expand (and which |
| policy to use when doing so). We could possibly get away with |
| always using cmpp_atpol_ERROR for purposes of evaluating at-string |
| expression operands. |
| |
| [^1]: Skip-mode is the internal mechanism which keeps directives |
| from running, and content from being emitted, within a falsy branch |
| of an #if/#elif block. Only flow-control directives are ever run |
| when skip-mode is active, and client-provided directives cannot |
| easily provide flow-control support. Ergo, much of this paragraph |
| is not relevant for client-level code, but it is for this library's |
| own use of this function. |
| */ |
| CMPP_EXPORT int cmpp_dx_out_expand(cmpp_dx const * dx, |
| cmpp_outputer * pOut, |
| unsigned char const * zFrom, |
| cmpp_size_t n, |
| cmpp_atpol_e policy); |
| |
| /** |
| This creates a formatted string using sqlite3_mprintf() and emits it |
| using cmpp_dx_out_raw(). Returns CMPP_RC_OOM if allocation of the |
| string fails, else it returns whatever cmpp_dx_out_raw() returns. |
| |
| This is a no-op if dx->pp is in an error state, returning |
| that code. |
| */ |
| CMPP_EXPORT int cmpp_dx_outf(cmpp_dx *dx, char const *zFmt, ...); |
| |
| /** |
| Convenience form of cmpp_delimiter_get() which returns the |
| delimiter which was active at the time when the currently-running |
| cmpp_dx_f() was called. This memory may be invalidated by any calls |
| into cmpp_dx_process() or cmpp_delimiter_set(), so a copy of this |
| pointer must not be retained past such a point. |
| |
| This function is primarily intended for use in generating debug and |
| error messages. |
| |
| If the delimiter stack is empty, this function returns NULL. |
| */ |
| CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx); |
| |
| /** |
| Borrows a buffer from pp's buffer recycling pool, allocating one if |
| needed. It returns NULL only on allocation error, in which case it |
| updates pp's error state. |
| |
| This transfers ownership of the buffer to the caller, who is |
| obligated to eventually do ONE of the following: |
| |
| - Pass it to cmpp_b_return() with the same dx argument. |
| |
| - Pass it to cmpp_b_clear() then cmpp_mfree(). |
| |
| The purpose of this function is a memory reuse optimization. Most |
| directives, and many internals, need to use buffers for something |
| or other and this gives them a way to reuse buffers. |
| |
| Potential TODO: How this pool optimizes (or not) buffer allotment |
| is an internal detail. Maybe add an argument which provides a hint |
| about the buffer usage. e.g. argument-conversion buffers are |
| normally small but block content buffers can be arbitrarily large. |
| */ |
| CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *dx); |
| |
| /** |
| Returns a buffer borrowed from cmpp_b_borrow(), transferring |
| ownership back to pp. Passing a non-NULL b which was not returned |
| by cmpp_b_borrow() invoked undefined behavior (possibly delayed |
| until the list is cleaned up). To simplify usage, b may be NULL. |
| |
| After calling this, b must be considered "freed" - it must not be |
| used again. This function is free (as it were) to immediately free |
| the object's memory instead of recycling it. |
| */ |
| CMPP_EXPORT void cmpp_b_return(cmpp *dx, cmpp_b *b); |
| |
| /** |
| If NUL-terminated z matches one of the strings listed below, its |
| corresponding cmpp_atpol_e entry is returned, else |
| cmpp_atpol_invalid is returned. |
| |
| If pp is not NULL then (A) this also sets its current at-policy and |
| (B) it recognizes an additional string (see below). In this case, |
| if z is not a valid string then pp's persistent error state is set. |
| |
| Its accepted values each correspond to a like-named policy value: |
| |
| - "off" (the default): no processing of `@` is performed. |
| |
| - "error": fail if an undefined `X` is referenced in @token@ |
| parsing. |
| |
| - "retain": emit any unresolved `@X@` tokens as-is to the output |
| stream. i.e. `@X@` renders as `@X@`. |
| |
| - "elide": omit unresolved `@X@` from the output, as if their values |
| were empty. i.e. `@X@` renders as an empty string, i.e. is not |
| emitted at all. |
| |
| - "current": if pp!=NULL then it returns the current policy, else |
| this string resolves to cmpp_atpol_invalid. |
| */ |
| CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z); |
| |
| /** |
| Returns pp's current at-token policy. |
| */ |
| CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp); |
| |
| /** |
| Sets pp's current at-token policy. Returns 0 if pol is valid, else |
| it updates pp's error state and returns CMPP_RC_RANGE. This is a |
| no-op if pp has error state, returning that code instead. |
| |
| The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify |
| certain client-side usage. |
| */ |
| CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol); |
| |
| /** |
| Pushes pol as the current at-policy. Returns 0 on success and |
| non-zero on error (bad pol value or allocation error). If this |
| returns 0 then the caller is obligated to eventually call |
| cmpp_atpol_pop() one time. If it returns non-0 then they _must not_ |
| call that function. |
| */ |
| CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol); |
| |
| /** |
| Must be called one time for each successful call to |
| cmpp_atpol_push(). It restores the at-policy to the value it |
| has when cmpp_atpol_push() was last called. |
| |
| If called when no cmpp_delimiter_push() is active then debug builds |
| will fail an assert(), else pp's error state is updated if it has |
| none already. |
| */ |
| CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp); |
| |
| /** |
| The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It |
| behaves identically, just for a different policy group with |
| different names. |
| |
| Its accepted values are: "null" and "error". The value "current" is |
| only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid. |
| */ |
| CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z); |
| |
| /** |
| Returns pp's current policy regarding use of undefined define keys. |
| */ |
| CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp); |
| |
| /** |
| Sets pp's current policy regarding use of undefined define keys. |
| Returns 0 if pol is valid, else it updates pp's error state and |
| returns CMPP_RC_RANGE. |
| */ |
| CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol); |
| |
| /** |
| The undefined-policy counterpart of cmpp_atpol_push(). |
| */ |
| CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol); |
| |
| /** |
| The undefined-policy counterpart of cmpp_atpol_pop(). |
| */ |
| CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp); |
| |
| /** |
| The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen |
| (if zOpen is not NULL) to the opening delimiter and *zClose (if |
| zClose is not NULL) to the closing delimiter. The memory is owned |
| by pp and may be invalidated by any calls to cmpp_atdelim_set(), |
| cmpp_atdelim_push(), or any APIs which consume input. Each string |
| is NUL-terminated and must be copied by the caller if they need |
| these strings past a point where they might be invalidated. |
| |
| If called when the the delimiter stack is empty, debug builds with |
| fail an assert() and non-debug builds will behave as if the stack |
| contains the compile-time default delimiters. |
| */ |
| CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp, |
| char const **zOpen, |
| char const **zClose); |
| /** |
| The `@token@`-delimiter counterpart of cmpp_delimeter_set(). |
| |
| This sets the delimiter for `@token@` content to the given opening |
| and closing strings (which the library makes a copy of). If zOpen |
| is NULL then the compile-time default is assumed. If zClose is NULL |
| then zOpen is assumed. |
| |
| Returns 0 on success. Returns non-0 if called when the delimiter |
| stack is empty, if it cannot copy the string or zDelim is deemed |
| unsuitable for use as a delimiter. |
| |
| In debug builds this will trigger an assert if no `@token@` |
| delimiter has been set, but pp starts with one level in place, so |
| it is safe to call without having made an explicit |
| cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused. |
| */ |
| CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp, |
| char const *zOpen, |
| char const *zClose); |
| |
| /** |
| The `@token@`-delimiter counterpart of cmpp_delimeter_push(). |
| |
| See cmpp_atdelim_set() for the semantics of the arguments. |
| */ |
| CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp, |
| char const *zOpen, |
| char const *zClose); |
| |
| /** |
| The @token@-delimiter counterpart of cmpp_delimiter_pop(). |
| */ |
| CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp); |
| |
| /** |
| Searches the given path (zPath), split on the given path separator |
| (pathSep), for the given file (zBaseName), optionally with the |
| given file extension (zExt). |
| |
| If zBaseName or zBaseName+zExt are found as-is, without any |
| search path prefix, that will be the result, else the result |
| is either zBaseName or zBaseName+zExt prefixed by one of the |
| search directories. |
| |
| On success, returns a new string, transfering ownership to the |
| caller (who must eventually pass it to cmpp_mfree() to deallocate). |
| |
| If no match is found, or on error, returns NULL. On a genuine |
| error, pp's error state is updated and the error is unlikely to be |
| recoverable (see cmpp_err_set()). |
| |
| This function is a no-op if called when pp's error state is set, |
| returning NULL. |
| |
| Results are undefined (in the sense of whether it will work or not, |
| as opposed to whether it will crash or not) if pathSep is a control |
| character. |
| |
| Design note: this is implemented as a Common Table Expression |
| query. |
| */ |
| CMPP_EXPORT char * cmpp_path_search(cmpp *pp, |
| char const *zPath, |
| char pathSep, |
| char const *zBaseName, |
| char const *zExt); |
| |
| /** |
| Scans [*zPos,zEnd) for the next chSep character. Sets *zPos to one |
| after the last consumed byte, so its result includes the separator |
| character unless EOF is hit before then. If pCounter is not NULL |
| then it does ++*pCounter when finding chSep. |
| |
| Returns true if any input is consumed, else false (EOF). When it |
| returns false, *zPos will have the same value it had when this was |
| called. If it returns true, *zPos will be greater than it was |
| before this call and <= zEnd. |
| |
| Usage: |
| |
| ``` |
| unsigned char const * zBegin = ...; |
| unsigned char const * const zEnd = zBegin + strlen(zBegin); |
| unsigned char const * zEol = zBegin; |
| cmpp_size_t nLn = 0; |
| while( cmpp_next_chunk(&zEol, zEnd, '\n', &nLn) ){ |
| ... |
| } |
| ``` |
| */ |
| CMPP_EXPORT |
| bool cmpp_next_chunk(unsigned char const **zPos, |
| unsigned char const *zEnd, |
| unsigned char chSep, |
| cmpp_size_t *pCounter); |
| |
| /** |
| Flags and constants related to the cmpp_args type. |
| */ |
| enum cmpp_args_e { |
| /** |
| cmpp_args_parse() flag which tells cmpp_args_parse() not to |
| dive into (...) group tokens. It insteads leaves them to be parsed |
| (or not) by downstream code. The only reason to parse them in |
| advance is to catch syntax errors sooner rather than later. |
| */ |
| cmpp_args_F_NO_PARENS = 0x01 |
| }; |
| |
| /** |
| An internal detail of cmpp_args. |
| */ |
| typedef struct cmpp_args_pimpl cmpp_args_pimpl; |
| |
| /** |
| A container for parsing a line's worth of cmpp_arg |
| objects. |
| |
| Instances MUST be cleanly initialized by bitwise-copying either |
| cmpp_args_empty or (depending on the context) cmpp_args_empty_m. |
| |
| Instances MUST eventually be passed to cmpp_args_cleanup(). |
| |
| Design notes: this class is provided to the public API as a |
| convenience, not as a core/required component. It offers one of |
| many possible solutions for dealing with argument lists and is not |
| the End All/Be All of solutions. I didn't _really_ want to expose |
| this class in the public API at all but I also want client-side |
| directives to have the _option_ to to do some of the things |
| currently builtin directives can do which are (as of this writing) |
| unavailable in the public API, e.g. evaluate expressions (in that |
| limited form which this library supports). A stepping stone to |
| doing so is making this class public. |
| */ |
| struct cmpp_args { |
| /** |
| Number of parsed args. In the context of a cmpp_dx_f(), argument |
| lists do not include their directive's name as an argument. |
| */ |
| unsigned argc; |
| |
| /** |
| The list of args. This is very specifically NOT an array (or at |
| least not one which client code can rely on to behave |
| sensibly). Some internal APIs adjust a cmpp_args's arg list, |
| re-linking the entries via cmpp_arg::next and making array-style |
| traversal a foot-gun. |
| |
| To loop over them: |
| |
| for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...} |
| |
| This really ought to be const but it currenty cannot be for |
| internal reasons. Client code really should not modify these |
| objects, though. Doing so invokes undefined behavior. |
| |
| For directives with the cmpp_d_F_ARGS_RAW flag, this member will, |
| after a successful call to cmpp_dx_next(), point to a single |
| argument which holds the directive's entire argument string, |
| stripped of leading spaces. |
| */ |
| cmpp_arg * arg0; |
| |
| /** |
| Internal implementation details. This is initialized via |
| cmpp_args_parse() and freed via cmpp_args_cleanup(). |
| */ |
| cmpp_args_pimpl * pimpl; |
| }; |
| typedef struct cmpp_args cmpp_args; |
| |
| /** |
| Empty-initialized cmpp_args instance, intended for const-copy |
| initialization. |
| */ |
| #define cmpp_args_empty_m { \ |
| .argc = 0, \ |
| .arg0 = 0, \ |
| .pimpl = 0 \ |
| } |
| |
| /** |
| Empty-initialized instance, intended for non-const-copy |
| initialization. |
| */ |
| extern const cmpp_args cmpp_args_empty; |
| |
| /** |
| Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg |
| objects by iteratively processing that range with cmpp_arg_parse(). |
| If nIn is negative, strlen() is used to calculate it. |
| |
| Requires that arg be a cleanly-initialized instance (via |
| bitwise-copying cmpp_args_empty) or that it have been successfully |
| used with this function before. Behavior is undefined if pArgs was |
| not properly initialized. |
| |
| The 3rd argument is an optional bitmask of flags from the |
| cmpp_args_e enum. |
| |
| On success it populates arg, noting that an empty list is valid. |
| The memory pointed to by the arguments made available via |
| arg->arg0 is all owned by arg and will be invalidated by either a |
| subsequent call to this function (the memory will be overwritten or |
| reallocated) or cmpp_args_cleanup() (the memory will be freed). |
| |
| On error, returns non-0 and updates pp's error state with info |
| about the problem. |
| */ |
| CMPP_EXPORT int cmpp_args_parse(cmpp_dx * dx, |
| cmpp_args * pOut, |
| unsigned char const * zInBegin, |
| cmpp_ssize_t nIn, |
| cmpp_flag32_t flags); |
| |
| /** |
| Frees any resources owned by its argument but does not free the |
| argument (which is typically stack-allocated). After calling this, |
| the object may again be used with cmpp_args_parse() (in which case |
| it eventually needs to be passed to this again). |
| |
| This is a harmless no-op if `a` is already cleaned up but `a` must |
| not be NULL. |
| */ |
| CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a); |
| |
| /** |
| A wrapper around cmpp_args_parse() which uses dx->args.z as an |
| input source. This is sometimes convenient in cmpp_dx_f() |
| implementations which use cmpp_dx_next(), or similar, to read and |
| process custom directives, as doing so invalidates dx->arg's |
| memory. |
| |
| On success, returns 0 and populates args. On error, returns non-0 |
| and sets dx->pp's error state. |
| |
| cmpp_dx_args_clone() does essentially the same thing, but is more |
| efficient when dx->args.arg0 is is already parsed. |
| */ |
| CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args); |
| |
| /** |
| Populates pOut, replacing any current content, with a copy of each |
| arg in dx->args.arg0 (traversing arg0->next). |
| |
| *pOut MUST be cleanly initialized via copying cmpp_args_empty or it |
| must have previously been used with either cmpp_args_parse() (which |
| has the same initialization requirement) or this function has |
| undefined results. |
| |
| On success, pOut->argc and pOut->arg0 will refer to pOut's copy |
| of the arguments. |
| |
| Copying of arguments is necessary in cmpp_dx_f() implementations |
| which need to hold on to arguments for use _after_ calling |
| cmpp_dx_next() or any API which calls that (which most directives |
| don't do). See that function for why. |
| */ |
| CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut); |
| |
| /** Flags for cmpp_popen(). */ |
| enum cmpp_popen_e { |
| /** |
| Use execl[p](CMD, CMD,0) instead of |
| execl[p]("/bin/sh","-c",CMD,0). |
| */ |
| cmpp_popen_F_DIRECT = 0x01, |
| /** Use execlp() or execvp() instead of execl() or execv(). */ |
| cmpp_popen_F_PATH = 0x02 |
| }; |
| |
| /** |
| Result state for cmpp_popen() and friends. |
| */ |
| struct cmpp_popen_t { |
| /** |
| The child process ID. |
| */ |
| int childPid; |
| /** |
| The child process's stdout. |
| */ |
| int fdFromChild; |
| /** |
| If not NULL, cmpp_popen() will set *fpToChild to a FILE handle |
| mapped to the child process's stdin. If it is NULL, the child |
| process's stdin will be closed instead. |
| */ |
| cmpp_FILE **fpToChild; |
| }; |
| typedef struct cmpp_popen_t cmpp_popen_t; |
| /** |
| Empty-initialized cmpp_popen_t instance, intended for const-copy |
| initialization. |
| */ |
| #define cmpp_popen_t_empty_m {-1,-1,0} |
| /** |
| Empty-initialized instance, intended for non-const-copy |
| initialization. |
| */ |
| extern const cmpp_popen_t cmpp_popen_t_empty; |
| |
| /** |
| Uses fork()/exec() to run a command in a separate process and open |
| a two-way stream to it. It is provided in this API to facilitate |
| the creation of custom directives which shell out to external |
| processes. |
| |
| zCmd must contain the NUL-terminated command to run and any flags |
| for that command, e.g. "myapp --flag --other-flag". It is passed as |
| the 4th argument to: |
| |
| execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL) |
| |
| The po object MUST be cleanly initialized before calling this by |
| bitwise copying cmpp_popen_t_empty or (depending on the context) |
| cmpp_popen_t_empty_m. |
| |
| Flags: |
| |
| - cmpp_popen_F_DIRECT: zCmd is passed to execl(zCmd, zCmd, NULL). |
| instead of exec(). That can only work if zCmd is a single command |
| without arguments. |
| |
| - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which |
| performs path lookup of its initial argument. Again, that can |
| only work if zCmd is a single command without arguments. |
| |
| On success: |
| |
| - po->childPid will be set to the PID of the child process. |
| |
| - po->fdFromChild is set to the child's stdout file |
| descriptor. read(2) from it to read from the child. |
| |
| - If po->fpToChild is not NULL then *po->fpToChild is set to a |
| buffered output handle to the child's stdin. fwrite(3) to it to |
| send the child stuff. Be sure to fflush(3) and/or fclose(3) it to |
| keep it from hanging forever. If po->fpToChild is NULL then the |
| stdin of the child is closed. (Why buffered instead of unbuffered? |
| My attempts at getting unbuffered child stdin to work have all |
| failed when write() is called on it.) |
| |
| On success, the caller is obligated to pass po to cmpp_pclose(). |
| The caller may pass pi to cmpp_pclose() on error, if that's easier |
| for them, provided that the po argument was cleanly initialized |
| before passing it to this function. |
| |
| If the caller fclose(3)s *po->fpToChild then they must set it to |
| NULL so that passing it to cmpp_pclose() knows not to close it. |
| |
| On error: you know the drill. This function is a no-op if pp has |
| error state when it's called, and the current error code is |
| returned instead. |
| |
| This function is only available on non-WASM Unix-like environments. |
| On others it will always trigger a CMPP_RC_UNSUPPORTED error. |
| |
| Bugs: because the command is run via /bin/sh -c ... we cannot tell |
| if it's actually found. All we can tell is that /bin/sh ran. |
| |
| Also: this doesn't capture stderr, so commands should redirect |
| stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is |
| a potential TODO without a current use case. |
| |
| See: cmpp_pclose() |
| See: cmpp_popenv() |
| */ |
| CMPP_EXPORT int cmpp_popen(cmpp *pp, unsigned char const *zCmd, |
| cmpp_flag32_t flags, cmpp_popen_t *po); |
| |
| /** |
| Works like cmpp_popen() except that: |
| |
| - It takes it arguments in the form of a main()-style array of |
| strings because it uses execv() instead of exec(). The |
| cmpp_popen_F_PATH flag causes it to use execvp(). |
| |
| - It does not honor the cmpp_popen_F_DIRECT flag because all |
| arguments have to be passed in via the arguments array. |
| |
| As per execv()'s requirements: azCmd _MUST_ end with a NULL entry. |
| */ |
| CMPP_EXPORT int cmpp_popenv(cmpp *pp, char * const * azCmd, |
| cmpp_flag32_t flags, cmpp_popen_t *po); |
| |
| /** |
| Closes handles returned by cmpp_popen() and zeroes out po. If the |
| caller fclose()d *po->fpToChild then they need to set it to NULL so |
| that this function does not double-close it. |
| |
| Returns the result code of the child process. |
| |
| After calling this, po may again be used as an argument to |
| cmpp_popen(). |
| */ |
| CMPP_EXPORT int cmpp_pclose(cmpp_popen_t *po); |
| |
| /** |
| A cmpp_popenv() proxy which builds up an execv()-style array of |
| arguments from the given args. It has a hard, and mostly arbitrary, |
| upper limit on the number of args it can take in order to avoid |
| extra allocation. |
| */ |
| CMPP_EXPORT int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args, |
| cmpp_popen_t *p); |
| |
| |
| /** |
| Callback type for use with cmpp_kav_each(). |
| |
| cmpp_kav_each() calls this one time per key/value in such a list, |
| passing it the relevant key/value strings and lengths, plus the |
| opaque state pointer which is passed to cmpp_kav_each(). |
| |
| Must return 0 on success or update (or propagate) dx->pp's error |
| state on error. |
| */ |
| typedef int cmpp_kav_each_f( |
| cmpp_dx *dx, |
| unsigned char const *zKey, cmpp_size_t nKey, |
| unsigned char const *zVal, cmpp_size_t nVal, |
| void* callbackState |
| ); |
| |
| /** |
| Flag bitmask for use with cmpp_kav_each() and cmpp_str_each(). |
| */ |
| enum cmpp_kav_each_e { |
| /** |
| The key argument should be expanded using cmpp_arg_to_b() |
| with a 0 flags value. This flag should normally not be used. |
| */ |
| cmpp_kav_each_F_EXPAND_KEY = 0x01, |
| /** |
| The key argument should be expanded using cmpp_arg_to_b() |
| with a 0 flags value. This flag should normally be used. |
| */ |
| cmpp_kav_each_F_EXPAND_VAL = 0x02, |
| /** |
| Treat (...) value tokens (ttype=cmpp_TT_GroupParen) as integer |
| expressions. Keys are never treated this way. Without this flag, |
| the token expands to the ... part of (...). |
| */ |
| cmpp_kav_each_F_PARENS_EXPR = 0x04, |
| /** |
| Indicates that an empty input list is an error. If this flag is |
| not set and the list is empty, the callback will not be called |
| and no error will be triggered. |
| */ |
| cmpp_kav_each_F_NOT_EMPTY = 0x08, |
| /** |
| Indicates that the list does not have the '->' part(s). That is, |
| the list needs to be in pairs of KEY VAL rather than triples of |
| KEY -> VALUE. |
| */ |
| cmpp_kav_each_F_NO_ARROW = 0x10, |
| |
| /** |
| If set, keys get the cmpp_arg_to_b_F_BRACE_CALL flag added |
| to them. This implies cmpp_kav_each_F_EXPAND_KEY. |
| */ |
| cmpp_kav_each_F_CALL_KEY = 0x20, |
| /** Value counterpart of cmpp_kav_each_F_CALL_KEY. */ |
| cmpp_kav_each_F_CALL_VAL = 0x40, |
| /** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */ |
| cmpp_kav_each_F_CALL = 0x60, |
| |
| //TODO: append to defines which already exist |
| cmpp_kav_each_F_APPEND = 0, |
| cmpp_kav_each_F_APPEND_SPACE = 0, |
| cmpp_kav_each_F_APPEND_NL = 0 |
| }; |
| |
| /** |
| A helper for cmpp_dx_f() implementations in processing directive |
| arguments which are lists in this form: |
| |
| { a -> b c -> d ... } |
| |
| ("kav" is short for "key arrow value".) |
| |
| The range [zBegin,zBegin+nIn) contains the raw list (not including |
| any containing braces, parentheses, quotes, or the like). If nIn is |
| negative, strlen() is used to calculate it. |
| |
| The range is parsed using cmpp_args_parse(). |
| |
| For each key/arrow/value triplet in that list, callback() is passed |
| the stringified form of the key and the value, plus the |
| callbackState pointer. |
| |
| The flags argument controls whether the keys and values get |
| expanded or not. (Typically the keys should not be expanded but the |
| values should.) |
| |
| Returns 0 on success. If the callback returns non-0, it is expected |
| to have updated dx's error state. callback() will never be called |
| when dx's error state is non-0. |
| |
| Error results include: |
| |
| - CMPP_RC_RANGE: the list is empty does not contain the correct |
| number of entries (groups of 3, or 2 if flags has |
| cmpp_kav_each_F_NO_ARROW). |
| |
| - CMPP_RC_OOM: allocation error. |
| |
| - Any value returned by cmpp_args_parse(). |
| |
| - Any number of errors can be triggered during expansion of |
| keys and values. |
| */ |
| CMPP_EXPORT int cmpp_kav_each( |
| cmpp_dx *dx, |
| unsigned char const *zBegin, |
| cmpp_ssize_t nIn, |
| cmpp_kav_each_f callback, void *callbackState, |
| cmpp_flag32_t flags |
| ); |
| |
| /** |
| This works like cmpp_kav_each() except that it treats each token in |
| the list as a single entry. |
| |
| When the callback is triggered, the "key" part will be the raw |
| token and the "value" part will be the expanded form of that |
| value. Its flags may contain most of the cmpp_kav_each_F_... flags, |
| with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect |
| here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the |
| callback receives the same string for both the key and value. |
| */ |
| CMPP_EXPORT int cmpp_str_each( |
| cmpp_dx *dx, |
| unsigned char const *zBegin, |
| cmpp_ssize_t nIn, |
| cmpp_kav_each_f callback, void *callbackState, |
| cmpp_flag32_t flags |
| ); |
| |
| /** |
| An interface for clients to provide directives to the library |
| on-demand. |
| |
| This is called when pp has encountered a directive name is does not |
| know. It is passed the cmpp object, the name of the directive, and |
| the opaque state pointer which was passed to cmpp_d_autoloader_et(). |
| |
| Implementations should compare dname to any directives they know |
| about. If they find no match they must return CMPP_RC_NO_DIRECTIVE |
| _without_ using cmpp_err_set() to make the error persistent. |
| |
| If they find a match, they must use cmpp_d_register() to register |
| it and (on success) return 0. The library will then look again in |
| the registered directive list for the directive before giving up. |
| |
| If they find a match but registration fails then the result of that |
| failure must be returned. |
| |
| For implementation-specific errors, e.g. trying to load a directive |
| from a DLL but the loading of the DLL fails, implementations are |
| expected to use cmpp_err_set() to report the error and to return |
| that result code after performing any necessary cleanup. |
| |
| It is legal for an implementation to register multiple directives |
| in a single invocation (in particular a pair of opening/closing |
| directives), as well as to register directives other than the one |
| requested (if necessary). Regardless of which one(s) it registers, |
| it must return 0 only if it registers one named dname. |
| */ |
| typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state); |
| |
| /** |
| A c-pp directive "autoloader". See cmpp_d_autoloader_set() |
| and cmpp_d_autoloader_take(). |
| */ |
| struct cmpp_d_autoloader { |
| /** The autoloader callback. */ |
| cmpp_d_autoloader_f f; |
| /** |
| Finalizer for this->state. After calling this, if there's any |
| chance that this object might be later used, then it is important |
| that this->state be set to 0 (which this finalizer cannot |
| do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty |
| over any instances immediately after calling dtor(). |
| */ |
| cmpp_finalizer_f dtor; |
| /** |
| Implementation-specific state, to be passed as the final argument |
| to this->f and this->dtor. |
| */ |
| void * state; |
| }; |
| typedef struct cmpp_d_autoloader cmpp_d_autoloader; |
| /** |
| Empty-initialized cmpp_d_autoloader instance, intended for |
| const-copy initialization. |
| */ |
| #define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0} |
| /** |
| Empty-initialized cmpp_d_autoloader instance, intended for |
| non-const-copy initialization. |
| */ |
| extern const cmpp_d_autoloader cmpp_d_autoloader_empty; |
| |
| /** |
| Sets pp's "directive autoloader". Each cmpp instance has but a |
| single autoloader but this API is provided so that several |
| instances may be chained from client-side code. |
| |
| This function will call the existing autoloader's destructor (if |
| any), invalidating any pointers to its state object. |
| |
| If pNew is not NULL then pp's autoloader is set to a bitwise copy |
| of *pNew, otherwise it is zeroed out. This transfers ownership of |
| pNew->state to pp. |
| |
| See cmpp_d_autoloader_f()'s docs for how pNew must behave. |
| |
| This function has no error conditions but downstream results are |
| undefined if if pNew and an existing autoloader refer to the same |
| dtor/state values (a gateway to double-frees). |
| */ |
| CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew); |
| |
| /** |
| Moves pp's current autoloader state into pOld, transerring |
| ownership of it to the caller. |
| |
| This obligates the caller to eventually either pass that same |
| pointer to cmpp_d_autoloader_set() (to transfer ownership back to |
| pp) or to call pOld->dtor() (if it's not NULL), passing it it |
| pOld->state (even if pOld->state is NULL). In either case, all |
| contents of pOld are semantically invalidated and perhaps freed. |
| |
| This would normally be a prelude to cmpp_d_autoloader_set() to |
| install a custom, perhaps chained, autoloader. |
| */ |
| CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld); |
| |
| /** |
| True only for ' ' and '\t'. |
| */ |
| CMPP_EXPORT bool cmpp_isspace(int ch); |
| |
| /** |
| Reassigns *p to the address of the first non-space character at or |
| after the initial *p value. It stops looking if it reaches zEnd. |
| |
| If `*p` does not point to memory before zEnd, or is not a part of |
| the same logical string, results are undefined. |
| |
| |
| Achtung: do not pass this the address of a cmpp_b::z, |
| or similar, as that will effectively corrupt the buffer's |
| memory. To trim a whole buffer, use something like: |
| |
| ``` |
| cmpp_b ob = cmpp_b_empty; |
| ... populate ob...; |
| // get the trimmed range: |
| unsigned char const *zB = ob.z; |
| unsigned char const *zE = zB + n; |
| cmpp_skip_snl(&zB, zE); |
| assert( zB<=zE ); |
| cmpp_skip_snl_trailing(zB, &zE); |
| assert( zE>=zB ); |
| printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB); |
| ``` |
| |
| Those assert()s are not error handling - they're demonstrating |
| invariants of the calls made before them. |
| */ |
| CMPP_EXPORT void cmpp_skip_space( unsigned char const **p, |
| unsigned char const *zEnd ); |
| |
| /** |
| Works just like cmpp_skip_space() but it also |
| skips newlines. |
| |
| FIXME (2026-02-21): it does not recognize CRNL pairs as |
| atomic newlines. |
| */ |
| CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p, |
| unsigned char const *zEnd ); |
| |
| /** |
| "Trims" trailing cmpp_isspace() characters from the range [zBegin, |
| *p). *p must initially point to one byte after the end of zBegin |
| (i.e. its NUL byte or virtual EOF). Upon return *p will be modified |
| leftwards (if at all) until a non-space is found or *p==zBegin. |
| */ |
| CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin, |
| unsigned char const **p ); |
| |
| /** |
| Works just like cmpp_skip_space_trailing() but |
| skips cmpp_skip_snl() characters. |
| |
| FIXME (2026-02-21): it does not recognize CRNL pairs as |
| atomic newlines. |
| */ |
| CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin, |
| unsigned char const **p ); |
| |
| |
| /** |
| Generic array-of-T list memory-reservation routine. |
| |
| *list is the input array-of-T. nDesired is the number of entries to |
| reserve (list entry count, not byte length). *nAlloc is the number |
| of entries allocated in the list. sizeOfEntry is the sizeof(T) for |
| each entry in *list. T may be either a value type or a pointer |
| type and sizeofEntry must match, i.e. it must be sizeof(T*) for a |
| list-of-pointers and sizeof(T) for a list-of-objects. |
| |
| If pp is not NULL then this function updates pp's error state on |
| error, else it simply returns CMPP_RC_OOM on error. If pp is not |
| NULL then this function is a no-op if called when pp's error state |
| is set, returning that code without other side-effects. |
| |
| If nDesired > *nAlloc then *list is reallocated to contain at least |
| nDesired entries, else this function returns without side effects. |
| |
| On success *list is re-assigned to the reallocated list memory, all |
| newly-(re)allocated memory is zeroed out, and *nAlloc is updated to |
| the new allocation size of *list (the number of list entries, not |
| the number of bytes). |
| |
| On failure neither *list nor *nAlloc are modified. |
| |
| Returns 0 on success or CMPP_RC_OOM on error. Errors generated by |
| this routine are, at least in principle, recoverable (see |
| cmpp_err_set()), though that simply means that the pp object is |
| left in a well-defined state, not that the app can necessarily |
| otherwise recover from an OOM. |
| |
| This seemingly-out-of-API-scope routine is in the public API as a |
| convenience for client-level cmpp_dx_f() implementations[^1]. This API |
| internally has an acute need for basic list management and non-core |
| extensions inherit that as well. |
| |
| [^1]: this project's own directives are written as if they were |
| client-side whenever feasible. Some require cmpp-internal state to |
| do their jobs, though. |
| */ |
| CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired, |
| cmpp_size_t * nAlloc, unsigned sizeOfEntry); |
| |
| |
| /** |
| The current cmpp_api_thunk::apiVersion value. |
| See cmpp_api_thunk_map. |
| */ |
| #define cmpp_api_thunk_version 20260206 |
| |
| /** |
| A helper for use with cmpp_api_thunk. |
| |
| V() defines the API version number. It invokes |
| V(NAME,TYPE,VERSION) once. NAME is the member name for the |
| cmpp_api_thunk struct. TYPE is an integer type. VERSION is the |
| cmpp_api_thunk object version. This is initially 0 and will |
| eventually be given a number which increments which new members |
| appended. This is to enable DLLs to check whether their |
| cmpp_api_thunk object has the methods they're looking for. |
| |
| Then it invokes F(NAME,RETTYPE,PARAMS) and O(NAME,TYPE) |
| once for each cmpp_api_thunk member in an unspecified order, and |
| and A(VERSION) an arbitrary number of times. |
| |
| F() is for functions. O() is for objects, which are exposed here as |
| pointers to those objects so that we don't copy them. A() is |
| injected at each point where a new API version was introduced, and |
| that number (an integer) is its only argument. A()'s definition |
| can normally be empty. |
| |
| In all cases, NAME is the public API symbol name minus the "cmpp_" |
| prefix. RETTYPE is the function return type or object type. PARAMS |
| is the function parameters, wrapped in (...). For O(), TYPE is the |
| const-qualified type of the object referred to by |
| NAME. cmpp_api_thunk necessarily exposes those as pointers, but |
| that pointer is not part of the TYPE argument. |
| |
| See cmpp_api_thunk for details. |
| |
| In order to help DLLs to not inadvertently use invalid areas of the |
| API object by referencing members which they loading c-pp version |
| does not have, this list must only ever be modified by appending to |
| it. That enables DLLs to check their compile-time |
| cmpp_api_thunk_version against the dx->pp->api->apiVersion. If |
| the runtime version is older (less than) than their compile-time |
| version, the DLL must not access any methods added after |
| dx->pp->api->apiVersion. |
| */ |
| #define cmpp_api_thunk_map(A,V,F,O) \ |
| A(0) \ |
| V(apiVersion,unsigned,cmpp_api_thunk_version) \ |
| F(mrealloc,void *,(void * p, size_t n)) \ |
| F(malloc,void *,(size_t n)) \ |
| F(mfree,void,(void *)) \ |
| F(ctor,int,(cmpp **pp, cmpp_ctor_cfg const *)) \ |
| F(dtor,void,(cmpp *pp)) \ |
| F(reset,void,(cmpp *pp)) \ |
| F(check_oom,int,(cmpp * const pp, void const * m)) \ |
| F(is_legal_key,bool,(unsigned char const *, cmpp_size_t n, \ |
| unsigned char const **)) \ |
| F(define_legacy,int,(cmpp *, const char *,char const *)) \ |
| F(define_v2,int,(cmpp *, const char *, char const *)) \ |
| F(undef,int,(cmpp *, const char *, unsigned int *)) \ |
| F(define_shadow,int,(cmpp *, char const *, char const *, \ |
| int64_t *)) \ |
| F(define_unshadow,int,(cmpp *, char const *, int64_t)) \ |
| F(process_string,int,(cmpp *, const char *, \ |
| unsigned char const *, cmpp_ssize_t)) \ |
| F(process_file,int,(cmpp *, const char *)) \ |
| F(process_stream,int,(cmpp *, const char *, \ |
| cmpp_input_f, void *)) \ |
| F(process_argv,int,(cmpp *, int, char const * const *)) \ |
| F(err_get,int,(cmpp *, char const **)) \ |
| F(err_set,int,(cmpp *, int, char const *, ...)) \ |
| F(err_set1,int,(cmpp *, int, char const *)) \ |
| F(err_has,int,(cmpp const *)) \ |
| F(is_safemode,bool,(cmpp const *)) \ |
| F(sp_begin,int,(cmpp *)) \ |
| F(sp_commit,int,(cmpp *)) \ |
| F(sp_rollback,int,(cmpp *)) \ |
| F(output_f_FILE,int,(void *, void const *, cmpp_size_t)) \ |
| F(output_f_fd,int,(void *, void const *, cmpp_size_t)) \ |
| F(input_f_FILE,int,(void *, void *, cmpp_size_t *)) \ |
| F(input_f_fd,int,(void *, void *, cmpp_size_t *)) \ |
| F(flush_f_FILE,int,(void *)) \ |
| F(stream,int,(cmpp_input_f, void *, \ |
| cmpp_output_f, void *)) \ |
| F(slurp,int,(cmpp_input_f, void *, \ |
| unsigned char **, cmpp_size_t *)) \ |
| F(fopen,cmpp_FILE *,(char const *, char const *)) \ |
| F(fclose,void,(cmpp_FILE * )) \ |
| F(outputer_out,int,(cmpp_outputer *, void const *, cmpp_size_t)) \ |
| F(outputer_flush,int,(cmpp_outputer *)) \ |
| F(outputer_cleanup,void,(cmpp_outputer *)) \ |
| F(outputer_cleanup_f_FILE,void,(cmpp_outputer *)) \ |
| F(delimiter_set,int,(cmpp *, char const *)) \ |
| F(delimiter_get,void,(cmpp const *, char const **)) \ |
| F(chomp,bool,(unsigned char *, cmpp_size_t *)) \ |
| F(b_clear,void,(cmpp_b *)) \ |
| F(b_reuse,cmpp_b *,(cmpp_b *)) \ |
| F(b_swap,void,(cmpp_b *, cmpp_b *)) \ |
| F(b_reserve,int,(cmpp_b *, cmpp_size_t)) \ |
| F(b_reserve3,int,(cmpp *, cmpp_b *,cmpp_size_t)) \ |
| F(b_append,int,(cmpp_b *, void const *,cmpp_size_t)) \ |
| F(b_append4,int,(cmpp *,cmpp_b *,void const *, \ |
| cmpp_size_t)) \ |
| F(b_append_ch, int,(cmpp_b *, char)) \ |
| F(b_append_i32,int,(cmpp_b *, int32_t)) \ |
| F(b_append_i64,int,(cmpp_b *, int64_t)) \ |
| F(b_chomp,bool,(cmpp_b *)) \ |
| F(output_f_b,int,(void *, void const *,cmpp_size_t)) \ |
| F(outputer_cleanup_f_b,void,(cmpp_outputer *self)) \ |
| F(version,char const *,(void)) \ |
| F(tt_cstr,char const *,(int tt)) \ |
| F(dx_err_set,int,(cmpp_dx *dx, int rc, char const *zFmt, ...)) \ |
| F(dx_next,int,(cmpp_dx * dx, bool * pGotOne)) \ |
| F(dx_process,int,(cmpp_dx * dx)) \ |
| F(dx_consume,int,(cmpp_dx *, cmpp_outputer *, \ |
| cmpp_d const *const *, unsigned, cmpp_flag32_t)) \ |
| F(dx_consume_b,int,(cmpp_dx *, cmpp_b *, cmpp_d const * const *, \ |
| unsigned, cmpp_flag32_t)) \ |
| F(arg_parse,int,(cmpp_dx * dx, cmpp_arg *, \ |
| unsigned char const **, unsigned char const *, \ |
| unsigned char ** , unsigned char const * )) \ |
| F(arg_strdup,char *,(cmpp *pp, cmpp_arg const *arg)) \ |
| F(arg_to_b,int,(cmpp_dx * dx, cmpp_arg const *arg, \ |
| cmpp_b * os, cmpp_flag32_t flags)) \ |
| F(errno_rc,int,(int errNo, int dflt)) \ |
| F(d_register,int,(cmpp * pp, cmpp_d_reg const * r, cmpp_d **pOut)) \ |
| F(dx_f_dangling_closer,void,(cmpp_dx *dx)) \ |
| F(dx_out_raw,int,(cmpp_dx * dx, void const *z, cmpp_size_t n)) \ |
| F(dx_out_expand,int,(cmpp_dx const * dx, cmpp_outputer * pOut, \ |
| unsigned char const * zFrom, cmpp_size_t n, \ |
| cmpp_atpol_e policy)) \ |
| F(dx_outf,int,(cmpp_dx *dx, char const *zFmt, ...)) \ |
| F(dx_delim,char const *,(cmpp_dx const *dx)) \ |
| F(atpol_from_str,cmpp_atpol_e,(cmpp * pp, char const *z)) \ |
| F(atpol_get,cmpp_atpol_e,(cmpp const * const pp)) \ |
| F(atpol_set,int,(cmpp * const pp, cmpp_atpol_e pol)) \ |
| F(atpol_push,int,(cmpp * pp, cmpp_atpol_e pol)) \ |
| F(atpol_pop,void,(cmpp * pp)) \ |
| F(unpol_from_str,cmpp_unpol_e,(cmpp * pp,char const *z)) \ |
| F(unpol_get,cmpp_unpol_e,(cmpp const * const pp)) \ |
| F(unpol_set,int,(cmpp * const pp, cmpp_unpol_e pol)) \ |
| F(unpol_push,int,(cmpp * pp, cmpp_unpol_e pol)) \ |
| F(unpol_pop,void,(cmpp * pp)) \ |
| F(path_search,char *,(cmpp *pp, char const *zPath, char pathSep, \ |
| char const *zBaseName, char const *zExt)) \ |
| F(args_parse,int,(cmpp_dx * dx, cmpp_args * pOut, \ |
| unsigned char const * zInBegin, \ |
| cmpp_ssize_t nIn, cmpp_flag32_t flags)) \ |
| F(args_cleanup,void,(cmpp_args *a)) \ |
| F(dx_args_clone,int,(cmpp_dx * dx, cmpp_args *pOut)) \ |
| F(popen,int,(cmpp *, unsigned char const *, cmpp_flag32_t, \ |
| cmpp_popen_t *)) \ |
| F(popenv,int,(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags, \ |
| cmpp_popen_t *po)) \ |
| F(pclose,int,(cmpp_popen_t *po)) \ |
| F(popen_args,int,(cmpp_dx *, cmpp_args const *, cmpp_popen_t *)) \ |
| F(kav_each,int, (cmpp_dx *,unsigned char const *, cmpp_ssize_t, \ |
| cmpp_kav_each_f, void *, cmpp_flag32_t)) \ |
| F(d_autoloader_set,void,(cmpp *pp, cmpp_d_autoloader const * pNew)) \ |
| F(d_autoloader_take,void,(cmpp *pp, cmpp_d_autoloader * pOld)) \ |
| F(isspace,bool,(int ch)) \ |
| F(skip_space,void,(unsigned char const **, unsigned char const *)) \ |
| F(skip_snl,void,(unsigned char const **, unsigned char const *)) \ |
| F(skip_space_trailing,void,(unsigned char const *zBegin, \ |
| unsigned char const **p)) \ |
| F(skip_snl_trailing,void,(unsigned char const *zBegin, \ |
| unsigned char const **p)) \ |
| F(array_reserve,int,(cmpp *pp, void **list, cmpp_size_t nDesired, \ |
| cmpp_size_t * nAlloc, unsigned sizeOfEntry)) \ |
| F(module_load,int,(cmpp *, char const *,char const *)) \ |
| F(module_dir_add,int,(cmpp *, const char *)) \ |
| O(outputer_FILE,cmpp_outputer const) \ |
| O(outputer_b,cmpp_outputer const) \ |
| O(outputer_empty,cmpp_outputer const) \ |
| O(b_empty,cmpp_b const) \ |
| A(20251116) \ |
| F(next_chunk,bool,(unsigned char const **,unsigned char const *, \ |
| unsigned char,cmpp_size_t*)) \ |
| A(20251118) \ |
| F(atdelim_get,void,(cmpp const *,char const **,char const **)) \ |
| F(atdelim_set,int,(cmpp *,char const *,char const *)) \ |
| F(atdelim_push,int,(cmpp *,char const *,char const *)) \ |
| F(atdelim_pop,int,(cmpp *)) \ |
| A(20251224) \ |
| F(dx_pos_save,void,(cmpp_dx const *, cmpp_dx_pos *)) \ |
| F(dx_pos_restore,void,(cmpp_dx *, cmpp_dx_pos const *)) \ |
| A(20260130) \ |
| F(dx_is_call,bool,(cmpp_dx * const)) \ |
| A(20260206) \ |
| F(b_borrow,cmpp_b *,(cmpp *dx)) \ |
| F(b_return,void,(cmpp *dx, cmpp_b*)) \ |
| A(1+cmpp_api_thunk_version) |
| |
| |
| /** |
| Callback signature for cmpp module import routines. |
| |
| This is called by the library after having first encountering this |
| module (typically after looking for it in a DLL, but static |
| instances are supported). |
| |
| The primary intended purpose of this interface is for |
| implementations to call cmpp_d_register() (any number of times). It |
| is also legal to use APIs which set or query defines. This |
| interface is not intended to interact with pp's I/O in any way |
| (that's the job of the directives which these functions |
| register). Violating that will invoke undefined results, perhaps |
| stepping on the toes of any being-processed directive which |
| triggered the dynamic load of this directive. |
| |
| Errors in module initialization must be reported via cmpp_err_set() |
| and that code must be returned. |
| |
| Implementations must typically call cmpp_api_init(pp) as their |
| first operation. |
| |
| See the files named d-*.c in libcmpp's source tree for examples. |
| */ |
| typedef int (*cmpp_module_init_f)(cmpp * pp); |
| |
| /** |
| Holds information for mapping a cmpp_module_init_f to a name. |
| Its purpose is to get installed by the CMPP_MODULE_xxx family of |
| macros and referenced later via a module-loading mechanism. |
| */ |
| struct cmpp_module{ |
| /** |
| Symbolic name of the module. |
| */ |
| char const * name; |
| |
| /** |
| The initialization routine for the module. |
| */ |
| cmpp_module_init_f init; |
| }; |
| |
| /** Convenience typedef. */ |
| typedef struct cmpp_module cmpp_module; |
| |
| /** @def CMPP_MODULE_DECL |
| |
| Declares an extern (cmpp_module*) symbol called |
| cmpp_module__#\#CNAME. |
| |
| Use CMPP_MODULE_IMPL2() or CMPP_MODULE_IMPL3() to create the |
| matching implementation code. |
| |
| This macro should be used in the C or H file for a loadable module. |
| It may be compined in a file with a single CMPP_MODULE_IMPL_SOLO() |
| declaration with the same name, such that the module can be loaded |
| both with and without the explicit symbol name. |
| */ |
| #define CMPP_MODULE_DECL(CNAME) \ |
| extern const cmpp_module * cmpp_module__##CNAME |
| |
| /** @def CMPP_MODULE_IMPL |
| |
| Intended to be used to implement module declarations. If a module |
| has both C and H files, CMPP_MODULE_DECL(CNAME) should be used in the |
| H file and CMPP_MODULE_IMPL2() should be used in the C file. If the |
| DLL has only a C file (or no public H file), CMPP_MODULE_DECL is |
| unnecessary. |
| |
| If the module's human-use name is a legal C identifier, |
| CMPP_MODULE_IMPL2() is slightly easier to use than this macro. |
| |
| Implements a static cmpp_module object named |
| cmpp_module__#\#CNAME#\#_impl and a non-static |
| (cmpp_module*) named cmpp_module__#\#CNAME which points to |
| cmpp_module__#\#CNAME#\#_impl. (The latter symbol may optionally be |
| declared in a header file via CMPP_MODULE_DECL.) NAME is used as |
| the cmpp_module::name value. |
| |
| INIT_F must be a cmpp_module_init_f() function pointer. That function |
| is called when cmpp_module_load() loads the module. |
| |
| This macro may be combined in a file with a single |
| CMPP_MODULE_IMPL_SOLO() declaration using the same CNAME value, |
| such that the module can be loaded both with and without the |
| explicit symbol name. |
| |
| Example usage, in a module's header file, if any: |
| |
| ``` |
| CMPP_MODULE_DECL(mymodule); |
| ``` |
| |
| (The declaration is not strictly necessary - it is more of a matter |
| of documentation.) |
| |
| And in the C file: |
| |
| ``` |
| CMPP_MODULE_IMPL3(mymodule,"mymodule",mymodule_install); |
| // OR: |
| CMPP_MODULE_IMPL2(mymodule,mymodule_install); |
| ``` |
| |
| If it will be the only module in the target DLL, one can also add |
| this: |
| |
| ``` |
| CMPP_MODULE_IMPL2(mymodule,mymodule_install); |
| // _OR_ (every so slightly different): |
| CMPP_MODULE_STANDALONE_IMPL2(mymodule,mymodule_install); |
| ``` |
| |
| Which simplifies client-side module loading by allowing them to |
| leave out the module name when loading, but that approach only |
| works if modules are compiled one per DLL (as opposed to being |
| packaged together in one DLL). |
| |
| @see CMPP_MODULE_DECL |
| @see CMPP_MODULE_IMPL_SOLO |
| */ |
| #define CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F) \ |
| static const cmpp_module \ |
| cmpp_module__##CNAME##_impl = { NAME, INIT_F }; \ |
| const cmpp_module * \ |
| cmpp_module__##CNAME = &cmpp_module__##CNAME##_impl |
| |
| /** @def CMPP_MODULE_IMPL3 |
| |
| A simplier form of CMPP_MODULE_IMPL3() for cases where a module name |
| is a legal C symbol name. |
| */ |
| #define CMPP_MODULE_IMPL2(CNAME,INIT_F) \ |
| CMPP_MODULE_IMPL3(CNAME,#CNAME,INIT_F) |
| |
| /** @def CMPP_MODULE_IMPL_SOLO |
| |
| Implements a static cmpp_module symbol called |
| cmpp_module1_impl and a non-static (cmpp_module*) named |
| cmpp_module1 which points to cmpp_module1_impl |
| |
| INIT_F must be a cmpp_module_init_f. |
| |
| This macro must only be used in the C file for a loadable module |
| when that module is to be the only one in the resuling DLL. Do not |
| use it when packaging multiple modules into one DLL: use |
| CMPP_MODULE_IMPL for those cases (CMPP_MODULE_IMPL can also be used |
| together with this macro). |
| |
| @see CMPP_MODULE_IMPL |
| @see CMPP_MODULE_DECL |
| @see CMPP_MODULE_STANDALONE_IMPL |
| */ |
| #define CMPP_MODULE_IMPL_SOLO(NAME,INIT_F) \ |
| static const cmpp_module \ |
| cmpp_module1_impl = { NAME, INIT_F }; \ |
| const cmpp_module * cmpp_module1 = &cmpp_module1_impl |
| /** @def CMPP_MODULE_STANDALONE_IMPL |
| |
| CMPP_MODULE_STANDALONE_IMPL2() works like CMPP_MODULE_IMPL_SOLO() |
| but is only fully expanded if the preprocessor variable |
| CMPP_MODULE_STANDALONE is defined (to any value). If |
| CMPP_MODULE_STANDALONE is not defined, this macro expands to a |
| dummy placeholder which does nothing (but has to expand to |
| something to avoid leaving a trailing semicolon in the C code, |
| which upsets the compiler (the other alternative would be to not |
| require a semicolon after the macro call, but that upsets emacs' |
| sense of indentation (and keeping emacs happy is more important |
| than keeping compilers happy (all of these parens are _not_ a |
| reference to emacs lisp, by the way)))). |
| |
| This macro may be used in the same source file as |
| CMPP_MODULE_IMPL. |
| |
| The intention is that DLLs prefer this option over |
| CMPP_MODULE_IMPL_SOLO, to allow that the DLLs can be built as |
| standalone DLLs, multi-plugin DLLs, and compiled directly into a |
| project (in which case the code linking it in needs to resolve and |
| call the cmpp_module entry for each built-in module). |
| |
| @see CMPP_MODULE_IMPL_SOLO |
| @see CMPP_MODULE_REGISTER |
| */ |
| #if defined(CMPP_MODULE_STANDALONE) |
| # define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \ |
| CMPP_MODULE_IMPL_SOLO(NAME,INIT_F) |
| //arguably too much magic in one place: |
| //# if !defined(CMPP_API_THUNK) |
| //# define CMPP_API_THUNK |
| //# endif |
| #else |
| # define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \ |
| extern void cmpp_module__dummy_does_not_exist__(void) |
| #endif |
| |
| /** @def CMPP_MODULE_REGISTER3 |
| |
| Performs all the necessary setup for registering a loadable module, |
| including declaration and definition. NAME is the stringified name |
| of the module. This is normally called immediately after defining |
| the plugin's init func (which is passed as the 3rd argument to this |
| macro). |
| |
| See CMPP_MODULE_IMPL3() and CMPP_MODULE_STANDALONE_IMPL2() for |
| the fine details. |
| */ |
| #define CMPP_MODULE_REGISTER3(CNAME,NAME,INIT_F) \ |
| CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F); \ |
| CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) |
| |
| /** |
| Slight convenience form of CMPP_MODULE_REGISTER3() which assumes a |
| registration function name of cpp_ext_${CNAME}_register(). |
| */ |
| #define CMPP_MODULE_REGISTER2(CNAME,NAME) \ |
| CMPP_MODULE_REGISTER3(CNAME,NAME,cmpp_module__ ## CNAME ## _register) |
| |
| /** |
| Slight convenience form of CMPP_MODULE_REGISTER2() for cases when |
| CNAME and NAME are the same. |
| */ |
| #define CMPP_MODULE_REGISTER1(CNAME) \ |
| CMPP_MODULE_REGISTER3(CNAME,#CNAME,cmpp_module__ ## CNAME ## _register) |
| |
| /** |
| This looks for a DLL file named fname. If found, it is dlopen()ed |
| (or equivalent) and searched for a symbol named symName. If found, |
| it is assumed to be a cmpp_module instance and its init() method is |
| invoked. |
| |
| If fname is NULL then the module is looked up in the |
| currently-running program. |
| |
| If symName is NULL then the name "cmpp_module1" is assumed, which |
| is the name used by CMPP_MODULE_IMPL_SOLO() and friends (for use |
| when a module is the only one in its DLL). |
| |
| If no match is found, or there's a problem loading the DLL or |
| resolving the name, non-0 is returned. Similarly, if the init() |
| method fails, non-0 is returned. |
| |
| The file name is searched using the cmpp_module_dir_add() path, and |
| if fname is an exact match, or an exact when the system's |
| conventional DLL file extension is appended to it, that is used |
| rather than any potential match from the search path. |
| |
| On error, pp's error state will contain more information. It's |
| indeterminate which errors from this API are recoverable. |
| |
| This function is a no-op if called when pp's error state is set, |
| returning that code. |
| |
| If built without module-loading support then this will always |
| fail with CMPP_RC_UNSUPPORTED. |
| */ |
| CMPP_EXPORT int cmpp_module_load(cmpp * pp, char const * fname, |
| char const * symName); |
| |
| /** |
| Adds the directory or directories listed in zDirs to the search |
| path used by cmpp_module_load(). The entries are expected to be |
| either colon- or semicolon-delimited, depending on the platform the |
| library was built for. |
| |
| If zDirs is NULL and pp's library path is empty then it looks for |
| the environment variable CMPP_MODULE_PATH. If that is set, it is |
| used in place of zDirs, otherwise the library's compile-time |
| default is used (as set by the CMPP_MODULE_PATH compile-time value, |
| which defaults to ".:$prefix/lib/cmpp" in the canonical builds). |
| This should only be done once per cmpp instance, as the path will |
| otherwise be extended each time. (The current list structure does |
| not make it easy to recognize duplicates.) |
| |
| Returns 0 on success or if zDirs is empty. Returns CMPP_RC_OOM on |
| allocation error (ostensibly recoverable - see cmpp_err_set()). |
| |
| This is a no-op if called when pp has error state, returning that |
| code without other side-effects. |
| |
| If modules are not enabled then this function is a no-op and always |
| returns CMPP_RC_UNSUPPORTED _without_ setting pp's error state (as |
| it's not an error, per se). That can typically be ignored as a |
| non-error. |
| */ |
| CMPP_EXPORT int cmpp_module_dir_add(cmpp *pp, const char * zDirs); |
| |
| |
| /** |
| State for a cmpp_dx_pimpl which we need in order to snapshot the |
| parse position for purposes of restoring it later. This is |
| basically to support that #query can contain other #query |
| directives, but this same capability is required by any directives |
| which want to both process directives in their content block and |
| loop over the content block. |
| */ |
| struct cmpp_dx_pos { |
| /** Current parse pos. */ |
| unsigned char const *z; |
| /** Current line number. */ |
| cmpp_size_t lineNo; |
| }; |
| typedef struct cmpp_dx_pos cmpp_dx_pos; |
| #define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}//,.dline=CmppDLine_empty_m} |
| |
| /** |
| Stores dx's current input position into pos. pos gets completely |
| initialized by this routine - it need not (in contrast to many |
| other functions in this library) be cleanly initialized by the |
| caller first. |
| */ |
| CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos); |
| |
| /** |
| Restores dx's input position from pos. Results are undefined if pos |
| is not populated with the result of having passed the same dx/pos |
| pointer combination to cmpp_dx_pos_save(). |
| */ |
| CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos); |
| |
| /** |
| A "thunk" for use with loadable modules, encapsulating all of the |
| functions from the public cmpp API into an object. This allows |
| loadable modules to call into the cmpp API if the binary which |
| loads them not built in such a way that it exports libcmpp's |
| symbols to the DLL. (On Linux systems, that means if it's not |
| linked with -rdynamic.) |
| |
| For every public cmpp function, this struct has a member with the |
| same signature and name, minus the "cmpp_" name prefix. Thus |
| cmpp_foo(...) is accessible as api->foo(...). |
| |
| Object-type exports, e.g. cmpp_b_empty, are exposed here as |
| pointers instead of objects. The CMPP_API_THUNK-installed API |
| wrapper macros account for that. |
| |
| There is only one instance of this class and it gets passed into |
| cmpp_module_init_f() methods. It is also assigned to the |
| cmpp_dx::api member of cmpp_dx instances which get passed to |
| cmpp_dx_f() implementations. |
| |
| Loadable modules "should" use this interface to access the API, |
| rather than the global symbols. If they don't then the module may, |
| depending on how the loading application was linked, throw |
| unresolved symbols errors when loading. |
| */ |
| struct cmpp_api_thunk { |
| #define A(VER) |
| #define V(N,T,VER) T N; |
| #define F(N,T,P) T (*N)P; |
| #define O(N,T) T * const N; |
| cmpp_api_thunk_map(A,V,F,O) |
| #undef F |
| #undef O |
| #undef V |
| #undef A |
| }; |
| |
| /** |
| For loadable modules to be able portably access the cmpp API, |
| without requiring that their loading binary be linked with |
| -rdynamic, we need a "thunk". The API exposes cmpp_api_thunk |
| for that purpose. The following macros set up the thunk for |
| a given compilation unit. They are intended to only be used |
| by loadable modules, not generic client code. |
| |
| Before including this header, define CMPP_API_THUNK with no value |
| and/or define CMPP_API_THUNK_NAME to a C symbol name. The latter |
| macro implies the former and defines the name of the static symbol |
| to be the local cmpp_api_thunk instance, defaulting to cmppApi. |
| |
| The first line of a module's registration function should then be: |
| |
| cmpp_api_init(pp); |
| |
| where pp is the name of the sole argument to the registration |
| callback. After that is done, the cmpp_...() APIs may be used via |
| the macros defined below, all of which route through the thunk |
| object. |
| */ |
| #if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME) |
| # if !defined(CMPP_API_THUNK) |
| # define CMPP_API_THUNK |
| # endif |
| # if !defined(CMPP_API_THUNK_NAME) |
| # define CMPP_API_THUNK_NAME cmppApi |
| # endif |
| # if !defined(CMPP_API_THUNK__defined) |
| # define CMPP_API_THUNK__defined |
| static cmpp_api_thunk const * CMPP_API_THUNK_NAME = 0; |
| # endif |
| /** |
| cmpp_api_init() must be invoked from the module's registration |
| function, passed the only argument to that function. It sets the |
| global symbol CMPP_API_THUNK_NAME to its argument. From that point |
| on, the thunk's API is accessible via cmpp_foo macros which proxy |
| theThunk->foo. |
| |
| It is safe to call this from, e.g. a cmpp_dx_f() implementation, as |
| it will always have the same pointer, so long as it is not passed |
| NULL, which would make the next cmpp_...() call segfault. |
| */ |
| # if !defined(CMPP_API_THUNK__assigned) |
| # define CMPP_API_THUNK__assigned |
| # define cmpp_api_init(PP) CMPP_API_THUNK_NAME = (PP)->api |
| # else |
| # define cmpp_api_init(PP) (void)(PP)/*CMPP_API_THUNK_NAME*/ |
| # endif |
| /* What follows is generated code from c-pp's (#pragma api-thunk). */ |
| /* Thunk APIs which follow are available as of version 0... */ |
| #define cmpp_mrealloc CMPP_API_THUNK_NAME->mrealloc |
| #define cmpp_malloc CMPP_API_THUNK_NAME->malloc |
| #define cmpp_mfree CMPP_API_THUNK_NAME->mfree |
| #define cmpp_ctor CMPP_API_THUNK_NAME->ctor |
| #define cmpp_dtor CMPP_API_THUNK_NAME->dtor |
| #define cmpp_reset CMPP_API_THUNK_NAME->reset |
| #define cmpp_check_oom CMPP_API_THUNK_NAME->check_oom |
| #define cmpp_is_legal_key CMPP_API_THUNK_NAME->is_legal_key |
| #define cmpp_define_legacy CMPP_API_THUNK_NAME->define_legacy |
| #define cmpp_define_v2 CMPP_API_THUNK_NAME->define_v2 |
| #define cmpp_undef CMPP_API_THUNK_NAME->undef |
| #define cmpp_define_shadow CMPP_API_THUNK_NAME->define_shadow |
| #define cmpp_define_unshadow CMPP_API_THUNK_NAME->define_unshadow |
| #define cmpp_process_string CMPP_API_THUNK_NAME->process_string |
| #define cmpp_process_file CMPP_API_THUNK_NAME->process_file |
| #define cmpp_process_stream CMPP_API_THUNK_NAME->process_stream |
| #define cmpp_process_argv CMPP_API_THUNK_NAME->process_argv |
| #define cmpp_err_get CMPP_API_THUNK_NAME->err_get |
| #define cmpp_err_set CMPP_API_THUNK_NAME->err_set |
| #define cmpp_err_set1 CMPP_API_THUNK_NAME->err_set1 |
| #define cmpp_err_has CMPP_API_THUNK_NAME->err_has |
| #define cmpp_is_safemode CMPP_API_THUNK_NAME->is_safemode |
| #define cmpp_sp_begin CMPP_API_THUNK_NAME->sp_begin |
| #define cmpp_sp_commit CMPP_API_THUNK_NAME->sp_commit |
| #define cmpp_sp_rollback CMPP_API_THUNK_NAME->sp_rollback |
| #define cmpp_output_f_FILE CMPP_API_THUNK_NAME->output_f_FILE |
| #define cmpp_output_f_fd CMPP_API_THUNK_NAME->output_f_fd |
| #define cmpp_input_f_FILE CMPP_API_THUNK_NAME->input_f_FILE |
| #define cmpp_input_f_fd CMPP_API_THUNK_NAME->input_f_fd |
| #define cmpp_flush_f_FILE CMPP_API_THUNK_NAME->flush_f_FILE |
| #define cmpp_stream CMPP_API_THUNK_NAME->stream |
| #define cmpp_slurp CMPP_API_THUNK_NAME->slurp |
| #define cmpp_fopen CMPP_API_THUNK_NAME->fopen |
| #define cmpp_fclose CMPP_API_THUNK_NAME->fclose |
| #define cmpp_outputer_out CMPP_API_THUNK_NAME->outputer_out |
| #define cmpp_outputer_flush CMPP_API_THUNK_NAME->outputer_flush |
| #define cmpp_outputer_cleanup CMPP_API_THUNK_NAME->outputer_cleanup |
| #define cmpp_outputer_cleanup_f_FILE CMPP_API_THUNK_NAME->outputer_cleanup_f_FILE |
| #define cmpp_delimiter_set CMPP_API_THUNK_NAME->delimiter_set |
| #define cmpp_delimiter_get CMPP_API_THUNK_NAME->delimiter_get |
| #define cmpp_chomp CMPP_API_THUNK_NAME->chomp |
| #define cmpp_b_clear CMPP_API_THUNK_NAME->b_clear |
| #define cmpp_b_reuse CMPP_API_THUNK_NAME->b_reuse |
| #define cmpp_b_swap CMPP_API_THUNK_NAME->b_swap |
| #define cmpp_b_reserve CMPP_API_THUNK_NAME->b_reserve |
| #define cmpp_b_reserve3 CMPP_API_THUNK_NAME->b_reserve3 |
| #define cmpp_b_append CMPP_API_THUNK_NAME->b_append |
| #define cmpp_b_append4 CMPP_API_THUNK_NAME->b_append4 |
| #define cmpp_b_append_ch CMPP_API_THUNK_NAME->b_append_ch |
| #define cmpp_b_append_i32 CMPP_API_THUNK_NAME->b_append_i32 |
| #define cmpp_b_append_i64 CMPP_API_THUNK_NAME->b_append_i64 |
| #define cmpp_b_chomp CMPP_API_THUNK_NAME->b_chomp |
| #define cmpp_output_f_b CMPP_API_THUNK_NAME->output_f_b |
| #define cmpp_outputer_cleanup_f_b CMPP_API_THUNK_NAME->outputer_cleanup_f_b |
| #define cmpp_version CMPP_API_THUNK_NAME->version |
| #define cmpp_tt_cstr CMPP_API_THUNK_NAME->tt_cstr |
| #define cmpp_dx_err_set CMPP_API_THUNK_NAME->dx_err_set |
| #define cmpp_dx_next CMPP_API_THUNK_NAME->dx_next |
| #define cmpp_dx_process CMPP_API_THUNK_NAME->dx_process |
| #define cmpp_dx_consume CMPP_API_THUNK_NAME->dx_consume |
| #define cmpp_dx_consume_b CMPP_API_THUNK_NAME->dx_consume_b |
| #define cmpp_arg_parse CMPP_API_THUNK_NAME->arg_parse |
| #define cmpp_arg_strdup CMPP_API_THUNK_NAME->arg_strdup |
| #define cmpp_arg_to_b CMPP_API_THUNK_NAME->arg_to_b |
| #define cmpp_errno_rc CMPP_API_THUNK_NAME->errno_rc |
| #define cmpp_d_register CMPP_API_THUNK_NAME->d_register |
| #define cmpp_dx_f_dangling_closer CMPP_API_THUNK_NAME->dx_f_dangling_closer |
| #define cmpp_dx_out_raw CMPP_API_THUNK_NAME->dx_out_raw |
| #define cmpp_dx_out_expand CMPP_API_THUNK_NAME->dx_out_expand |
| #define cmpp_dx_outf CMPP_API_THUNK_NAME->dx_outf |
| #define cmpp_dx_delim CMPP_API_THUNK_NAME->dx_delim |
| #define cmpp_atpol_from_str CMPP_API_THUNK_NAME->atpol_from_str |
| #define cmpp_atpol_get CMPP_API_THUNK_NAME->atpol_get |
| #define cmpp_atpol_set CMPP_API_THUNK_NAME->atpol_set |
| #define cmpp_atpol_push CMPP_API_THUNK_NAME->atpol_push |
| #define cmpp_atpol_pop CMPP_API_THUNK_NAME->atpol_pop |
| #define cmpp_unpol_from_str CMPP_API_THUNK_NAME->unpol_from_str |
| #define cmpp_unpol_get CMPP_API_THUNK_NAME->unpol_get |
| #define cmpp_unpol_set CMPP_API_THUNK_NAME->unpol_set |
| #define cmpp_unpol_push CMPP_API_THUNK_NAME->unpol_push |
| #define cmpp_unpol_pop CMPP_API_THUNK_NAME->unpol_pop |
| #define cmpp_path_search CMPP_API_THUNK_NAME->path_search |
| #define cmpp_args_parse CMPP_API_THUNK_NAME->args_parse |
| #define cmpp_args_cleanup CMPP_API_THUNK_NAME->args_cleanup |
| #define cmpp_dx_args_clone CMPP_API_THUNK_NAME->dx_args_clone |
| #define cmpp_popen CMPP_API_THUNK_NAME->popen |
| #define cmpp_popenv CMPP_API_THUNK_NAME->popenv |
| #define cmpp_pclose CMPP_API_THUNK_NAME->pclose |
| #define cmpp_popen_args CMPP_API_THUNK_NAME->popen_args |
| #define cmpp_kav_each CMPP_API_THUNK_NAME->kav_each |
| #define cmpp_d_autoloader_set CMPP_API_THUNK_NAME->d_autoloader_set |
| #define cmpp_d_autoloader_take CMPP_API_THUNK_NAME->d_autoloader_take |
| #define cmpp_isspace CMPP_API_THUNK_NAME->isspace |
| #define cmpp_isnl CMPP_API_THUNK_NAME->isnl |
| #define cmpp_issnl CMPP_API_THUNK_NAME->issnl |
| #define cmpp_skip_space CMPP_API_THUNK_NAME->skip_space |
| #define cmpp_skip_snl CMPP_API_THUNK_NAME->skip_snl |
| #define cmpp_skip_space_trailing CMPP_API_THUNK_NAME->skip_space_trailing |
| #define cmpp_skip_snl_trailing CMPP_API_THUNK_NAME->skip_snl_trailing |
| #define cmpp_array_reserve CMPP_API_THUNK_NAME->array_reserve |
| #define cmpp_module_load CMPP_API_THUNK_NAME->module_load |
| #define cmpp_module_dir_add CMPP_API_THUNK_NAME->module_dir_add |
| #define cmpp_outputer_FILE (*CMPP_API_THUNK_NAME->outputer_FILE) |
| #define cmpp_outputer_b (*CMPP_API_THUNK_NAME->outputer_b) |
| #define cmpp_outputer_empty (*CMPP_API_THUNK_NAME->outputer_empty) |
| #define cmpp_b_empty (*CMPP_API_THUNK_NAME->b_empty) |
| /* Thunk APIs which follow are available as of version 20251116... */ |
| #define cmpp_next_chunk CMPP_API_THUNK_NAME->next_chunk |
| /* Thunk APIs which follow are available as of version 20251118... */ |
| #define cmpp_atdelim_get CMPP_API_THUNK_NAME->atdelim_get |
| #define cmpp_atdelim_set CMPP_API_THUNK_NAME->atdelim_set |
| #define cmpp_atdelim_push CMPP_API_THUNK_NAME->atdelim_push |
| #define cmpp_atdelim_pop CMPP_API_THUNK_NAME->atdelim_pop |
| /* Thunk APIs which follow are available as of version 20251224... */ |
| #define cmpp_dx_pos_save CMPP_API_THUNK_NAME->dx_pos_save |
| #define cmpp_dx_pos_restore CMPP_API_THUNK_NAME->dx_pos_restore |
| /* Thunk APIs which follow are available as of version 20260130... */ |
| #define cmpp_dx_is_call CMPP_API_THUNK_NAME->dx_is_call |
| /* Thunk APIs which follow are available as of version 20260206... */ |
| #define cmpp_b_borrow CMPP_API_THUNK_NAME->b_borrow |
| #define cmpp_b_return CMPP_API_THUNK_NAME->b_return |
| |
| |
| #else /* not CMPP_API_THUNK */ |
| /** |
| cmpp_api_init() is a no-op when not including a file-local API |
| thunk. |
| */ |
| # define cmpp_api_init(PP) (void)0 |
| #endif /* CMPP_API_THUNK */ |
| |
| #ifdef __cplusplus |
| } /* extern "C" */ |
| #endif |
| #endif /* include guard */ |
| #endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */ |
| #if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED) |
| #define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED |
| /** |
| This file houses declarations and macros for the private/internal |
| libcmpp APIs. |
| */ |
| #include "sqlite3.h" |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <errno.h> |
| |
| /* write() and friends */ |
| #if defined(_WIN32) || defined(WIN32) |
| # include <io.h> |
| # include <fcntl.h> |
| # ifndef access |
| # define access(f,m) _access((f),(m)) |
| # endif |
| #else |
| # include <unistd.h> |
| # include <sys/wait.h> |
| #endif |
| |
| #ifndef CMPP_DEFAULT_DELIM |
| #define CMPP_DEFAULT_DELIM "##" |
| #endif |
| |
| #ifndef CMPP_ATSIGN |
| #define CMPP_ATSIGN (unsigned char)'@' |
| #endif |
| |
| #ifndef CMPP_MODULE_PATH |
| #define CMPP_MODULE_PATH "." |
| #endif |
| |
| #if defined(NDEBUG) |
| #define cmpp__staticAssert(NAME,COND) (void)1 |
| #else |
| #define cmpp__staticAssert(NAME,COND) \ |
| static const char staticAssert_ ## NAME[ (COND) ? 1 : -1 ] = {0}; \ |
| (void)staticAssert_ ## NAME |
| #endif |
| |
| #if defined(CMPP_OMIT_ALL_UNSAFE) |
| #undef CMPP_OMIT_D_PIPE |
| #define CMPP_OMIT_D_PIPE |
| #undef CMPP_OMIT_D_DB |
| #define CMPP_OMIT_D_DB |
| #undef CMPP_OMIT_D_INCLUDE |
| #define CMPP_OMIT_D_INCLUDE |
| #undef CMPP_OMIT_D_MODULE |
| #define CMPP_OMIT_D_MODULE |
| #endif |
| |
| #if !defined(CMPP_VERSION) |
| #error "exporting CMPP_VERSION to have been set up" |
| #endif |
| |
| #define CMPP__DB_MAIN_NAME "cmpp" |
| |
| #if defined(CMPP_AMALGAMATION) |
| #define CMPP_PRIVATE static |
| #else |
| #define CMPP_PRIVATE |
| #endif |
| |
| #if CMPP_PLATFORM_IS_WASM |
| # define CMPP_PLATFORM_IS_WINDOWS 0 |
| # define CMPP_PLATFORM_IS_UNIX 0 |
| # define CMPP_PLATFORM_PLATFORM "wasm" |
| # define CMPP_PATH_SEPARATOR ':' |
| # define CMPP__EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default"))) |
| // See also: |
| //__attribute__((export_name("theExportedName"), used, visibility("default"))) |
| # define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */ |
| # if !defined(CMPP_PLATFORM_EXT_DLL) |
| # define CMPP_PLATFORM_EXT_DLL "" |
| # endif |
| #else |
| //# define CMPP_WASM_EXPORT |
| # define CMPP__EXPORT_NAMED(X) |
| # if defined(_WIN32) || defined(WIN32) |
| # define CMPP_PLATFORM_IS_WINDOWS 1 |
| # define CMPP_PLATFORM_IS_UNIX 0 |
| # define CMPP_PLATFORM_PLATFORM "windows" |
| # define CMPP_PATH_SEPARATOR ';' |
| //# include <io.h> |
| # elif defined(__MINGW32__) || defined(__MINGW64__) |
| # define CMPP_PLATFORM_IS_WINDOWS 1 |
| # define CMPP_PLATFORM_IS_UNIX 0 |
| # define CMPP_PLATFORM_PLATFORM "windows" |
| # define CMPP_PATH_SEPARATOR ':' /*?*/ |
| # elif defined(__CYGWIN__) |
| # define CMPP_PLATFORM_IS_WINDOWS 0 |
| # define CMPP_PLATFORM_IS_UNIX 1 |
| # define CMPP_PLATFORM_PLATFORM "unix" |
| # define CMPP_PATH_SEPARATOR ':' |
| # else |
| # define CMPP_PLATFORM_IS_WINDOWS 0 |
| # define CMPP_PLATFORM_IS_UNIX 1 |
| # define CMPP_PLATFORM_PLATFORM "unix" |
| # define CMPP_PATH_SEPARATOR ':' |
| # endif |
| #endif |
| |
| #define CMPP__EXPORT(RETTYPE,NAME) CMPP__EXPORT_NAMED(NAME) RETTYPE NAME |
| |
| #if !defined(CMPP_PLATFORM_EXT_DLL) |
| # error "Expecting CMPP_PLATFORM_EXT_DLL to have been set by the auto-configured bits" |
| # define CMPP_PLATFORM_EXT_DLL "???" |
| #endif |
| |
| #if 1 |
| # define CMPP_NORETURN __attribute__((noreturn)) |
| #else |
| # define CMPP_NORETURN |
| #endif |
| |
| /** @def CMPP_HAVE_DLOPEN |
| |
| If set to true, use dlopen() and friends. Requires |
| linking to -ldl on some platforms. |
| |
| Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be |
| true. |
| */ |
| /** @def CMPP_HAVE_LTDLOPEN |
| |
| If set to true, use lt_dlopen() and friends. Requires |
| linking to -lltdl on most platforms. |
| |
| Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be |
| true. |
| */ |
| #if !defined(CMPP_HAVE_DLOPEN) |
| # if defined(HAVE_DLOPEN) |
| # define CMPP_HAVE_DLOPEN HAVE_DLOPEN |
| # else |
| # define CMPP_HAVE_DLOPEN 0 |
| # endif |
| #endif |
| |
| #if !defined(CMPP_HAVE_LTDLOPEN) |
| # if defined(HAVE_LTDLOPEN) |
| # define CMPP_HAVE_LTDLOPEN HAVE_LTDLOPEN |
| # else |
| # define CMPP_HAVE_LTDLOPEN 0 |
| # endif |
| #endif |
| |
| #if !defined(CMPP_ENABLE_DLLS) |
| # define CMPP_ENABLE_DLLS (CMPP_HAVE_LTDLOPEN || CMPP_HAVE_DLOPEN) |
| #endif |
| #if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE) |
| # define CMPP_D_MODULE 1 |
| #else |
| # define CMPP_D_MODULE 0 |
| #endif |
| |
| /** |
| Many years of practice have taught that it is literally impossible |
| to safely close DLLs because simply opening one may trigger |
| arbitrary code (at least for C++ DLLs) which "might" be used by the |
| application. e.g. some classloaders use DLL initialization to inject |
| new classes into the application without the app having to do |
| anything more than open the DLL. (That's precisely what the cmpp |
| port of this code is doing except that we don't call it classloading |
| here.) |
| |
| So cmpp does not close DLLs. Except (...sigh...) to please valgrind. |
| |
| When CMPP_CLOSE_DLLS is true then this API will keep track of DLL |
| handles so that they can be closed, and offers the ability for |
| higher-level clients to close them (all at once, not individually). |
| */ |
| #if !defined(CMPP_CLOSE_DLLS) |
| #define CMPP_CLOSE_DLLS 1 |
| #endif |
| |
| /** Proxy for cmpp_malloc() which (A) is a no-op if ppCode |
| and (B) sets pp->err on OOM. |
| */ |
| CMPP_PRIVATE void * cmpp__malloc(cmpp* pp, cmpp_size_t n); |
| |
| /** |
| Internal-use-only flags for use with cmpp_d::flags. |
| |
| These supplement the ones from the public API's cmpp_d_e. |
| */ |
| enum cmpp_d_ext_e { |
| /** |
| Mask of flag bits from this enum and cmpp_d_e which are for |
| internal use only and are disallowed in client-side directives. |
| */ |
| cmpp_d_F_MASK_INTERNAL = ~cmpp_d_F_MASK, |
| |
| /** |
| If true, and if cmpp_d_F_ARGS_LIST is set, then cmpp_args_parse() |
| will pass its results to cmpp_args__not_simplify(). Only |
| directives which eval cmpp_arg expressions need this, and the |
| library does not expose the pieces for evaluating such |
| expressions. As such, this flag is for internal use only. This |
| only has an effect if cmpp_d_F_ARGS_LIST is also used. |
| */ |
| cmpp_d_F_NOT_SIMPLIFY = 0x10000, |
| |
| /** |
| Most directives are inert when they are seen in the "falsy" part |
| of an if/else. The callbacks for such directives are skipped, as |
| opposed to requiring each directive's callback to check whether |
| they should be skipping. This flag indicates that a directive |
| must always be run, even when skipping content (e.g. inside of an |
| #if 0 block). Only flow-control directives may have the |
| FLOW_CONTROL bit set. The library API does not expose enough of |
| its internals for client-defined directives to make flow-control |
| decisions. |
| |
| i really want to get rid of this flag but it seems to be a |
| necessary evil. |
| */ |
| cmpp_d_F_FLOW_CONTROL = 0x20000 |
| }; |
| |
| /** |
| A single directive line from an input stream. |
| */ |
| struct CmppDLine { |
| /** Line number in the source input. */ |
| cmpp_size_t lineNo; |
| /** Start of the line within its source input. */ |
| unsigned char const * zBegin; |
| /** One-past-the-end byte of the line. A virtual EOF. It will only |
| actually be NUL-terminated if it is the last line of the input |
| and that input has no trailing newline. */ |
| unsigned char const * zEnd; |
| }; |
| typedef struct CmppDLine CmppDLine; |
| #define CmppDLine_empty_m {0U,0,0} |
| |
| /** |
| A snippet from a string. |
| */ |
| struct CmppSnippet { |
| /* Start of the range. */ |
| unsigned char const *z; |
| /* Number of bytes. */ |
| unsigned int n; |
| }; |
| typedef struct CmppSnippet CmppSnippet; |
| #define CmppSnippet_empty_m {0,0} |
| |
| /** |
| CmppLvl represents one "level" of parsing, pushing one level |
| for each of `#if` and popping one for each `#/if`. |
| |
| These pieces are ONLY for use with flow-control directives. It's |
| not proven that they can be of any use to more than a single |
| flow-control directive. e.g. if we had a hypothetical #foreach, we |
| may need to extend this. |
| */ |
| struct CmppLvl { |
| #if 0 |
| /** |
| The directive on whose behalf this level was opened. |
| */ |
| cmpp_d const * d; |
| /** |
| Opaque directive-specific immutable state. It's provided as a way |
| for a directive to see whether the top of the stack is correct |
| after it processes inner directives. |
| */ |
| void const * state; |
| #endif |
| /** |
| Bitmask of CmppLvl_F_... |
| */ |
| cmpp_flag32_t flags; |
| /** |
| The directive line number which started this level. This is used for |
| reporting the starting lines of erroneously unclosed block |
| constructs. |
| */ |
| cmpp_size_t lineNo; |
| }; |
| typedef struct CmppLvl CmppLvl; |
| #define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U} |
| |
| /** |
| Declares struct T as a container for a list-of-MT. MT may be |
| pointer-qualified. cmpp__ListType_impl() with the same arguments |
| implements T_reserve() for basic list allocation. Cleanup, alas, is |
| MT-dependent. |
| */ |
| #define cmpp__ListType_decl(T,MT) \ |
| struct T { \ |
| MT * list; \ |
| cmpp_size_t n; \ |
| cmpp_size_t nAlloc; \ |
| }; \ |
| typedef struct T T; \ |
| int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min) |
| #define CMPP__MAX(X,Y) ((X)<=(Y) ? (X) : (Y)) |
| #define cmpp__ListType_impl(T,MT) \ |
| int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \ |
| return cmpp_array_reserve(pp, (void**)&li->list, min, \ |
| &li->nAlloc, sizeof(MT)); \ |
| } |
| |
| #define cmpp__LIST_T_empty_m {.list=0,.n=0,.nAlloc=0} |
| |
| /** |
| A dynamically-allocated list of CmppLvl objects. |
| */ |
| cmpp__ListType_decl(CmppLvlList,CmppLvl*); |
| #define CmppLvlList_empty_m cmpp__LIST_T_empty_m |
| CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx); |
| CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx); |
| CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl); |
| CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on); |
| CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl); |
| CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx); |
| |
| /** |
| A dynamically-allocated list of cmpp_b objects. |
| */ |
| cmpp__ListType_decl(cmpp_b_list,cmpp_b*); |
| #define cmpp_b_list_empty_m cmpp__LIST_T_empty_m |
| extern const cmpp_b_list cmpp_b_list_empty; |
| CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li); |
| //CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li); |
| //CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li); |
| /** |
| cmpp_b_list sorting policies. NULL entries must |
| always sort last. |
| */ |
| enum cmpp_b_list_e { |
| cmpp_b_list_UNSORTED, |
| /* Smallest first. */ |
| cmpp_b_list_ASC, |
| /* Largest first. */ |
| cmpp_b_list_DESC |
| }; |
| |
| /** |
| A dynamically-allocated list of cmpp_arg objects. Used by |
| cmmp_args. |
| */ |
| cmpp__ListType_decl(CmppArgList,cmpp_arg); |
| #define CmppArgList_empty_m cmpp__LIST_T_empty_m |
| |
| /** Allocate a new arg, owned by li, and return it (cleanly zeroed |
| out). Returns NULL and updates pp->err on error. */ |
| CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li); |
| |
| /** |
| The internal part of the cmpp_args interface. |
| */ |
| struct cmpp_args_pimpl { |
| /** |
| We need(?) a (cmpp*) here for finalization/recycling purposes. |
| */ |
| cmpp *pp; |
| bool isCall; |
| /** |
| Next entry in the free-list. |
| */ |
| cmpp_args_pimpl * nextFree; |
| /** Version 3 of the real args memory. */ |
| CmppArgList argli; |
| /** |
| cmpp_args_parse() copies each argument's bytes into here, |
| each one NUL-terminated. |
| */ |
| cmpp_b argOut; |
| }; |
| #define cmpp_args_pimpl_empty_m { \ |
| .pp = 0, \ |
| .isCall = false, \ |
| .nextFree = 0, \ |
| .argli = CmppArgList_empty_m, \ |
| .argOut = cmpp_b_empty_m \ |
| } |
| extern const cmpp_args_pimpl cmpp_args_pimpl_empty; |
| void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p); |
| |
| /** |
| The internal part of the cmpp_dx interface. |
| */ |
| struct cmpp_dx_pimpl { |
| /** Start of input. */ |
| unsigned const char * zBegin; |
| /** One-after-the-end of input. */ |
| unsigned const char * zEnd; |
| /** |
| Current input position. Generally speaking, only |
| cmpp_dx_delim_search() should update this, but it turns out that |
| the ability to rewind the input is necessary for looping |
| constructs, like #query, when they want to be able to include |
| other directives in their bodies. |
| */ |
| cmpp_dx_pos pos; |
| /** |
| Currently input line. |
| */ |
| CmppDLine dline; |
| /** Number of active #savepoints. */ |
| unsigned nSavepoint; |
| /** Current directive's args. */ |
| cmpp_args args; |
| /** |
| A stack of state used by #if and friends to inform the innards |
| that they must not generate output. This is largely historical |
| and could have been done differently had this code started as a |
| library instead of a monolithic app. |
| |
| TODO is to figure out how best to move this state completely into |
| the #if handler, rather than fiddle with this all throughout the |
| processing. We could maybe move this stack into CmppIfState? |
| */ |
| CmppLvlList dxLvl; |
| struct { |
| /** |
| A copy of this->d's input line which gets translated |
| slightly from its native form for futher processing. |
| */ |
| cmpp_b line; |
| /** |
| Holds the semi-raw input line, stripped only of backslash-escaped |
| newlines and leading spaces. This is primarily for debug output |
| but also for custom arg parsing for some directives. |
| */ |
| cmpp_b argsRaw; |
| } buf; |
| |
| /** |
| Record IDs for/from cmpp_[un]define_shadow(). |
| */ |
| struct { |
| /** ID for __FILE__. */ |
| int64_t sidFile; |
| /** Rowid for #include path entry. */ |
| int64_t ridInclPath; |
| } shadow; |
| |
| struct { |
| /** |
| Set when we're searching for directives so that we know whether |
| cmpp_out_expand() should count newlines. |
| */ |
| unsigned short countLines; |
| /** |
| True if the next directive is the start of a [call]. |
| */ |
| bool nextIsCall; |
| } flags; |
| }; |
| /** |
| Initializes or resets a. Returns non-0 on OOM. |
| */ |
| CMPP_PRIVATE int cmpp_args__init(cmpp *pp, cmpp_args *a); |
| |
| /** |
| If a has state then it's recycled for reuse, else this zeroes out a |
| except for a->pimpl, which is retained (but may be NULL). |
| */ |
| CMPP_PRIVATE void cmpp_args_reuse(cmpp_args *a); |
| |
| #define cmpp_dx_pimpl_empty_m { \ |
| .zBegin=0, .zEnd=0, \ |
| .pos=cmpp_dx_pos_empty_m, \ |
| .dline=CmppDLine_empty_m, \ |
| .nSavepoint=0, \ |
| .args = cmpp_args_empty_m, \ |
| .dxLvl = CmppLvlList_empty_m, \ |
| .buf = { \ |
| cmpp_b_empty_m, \ |
| cmpp_b_empty_m \ |
| }, \ |
| .shadow = { \ |
| .sidFile = 0, \ |
| .ridInclPath = 0 \ |
| }, \ |
| .flags = { \ |
| .countLines = 0, \ |
| .nextIsCall = false \ |
| } \ |
| } |
| |
| /** |
| A level of indirection for CmppDList in order to be able to |
| manage ownership of their name (string) lifetimes. |
| */ |
| struct CmppDList_entry { |
| /** this->d.name.z points to this, which is owned by the CmppDList |
| which manages this object. */ |
| char * zName; |
| /* Potential TODO: move d->id into here. That doesn't eliminate our |
| dependency on it, though. */ |
| cmpp_d d; |
| //cmpp_d_reg reg; |
| }; |
| typedef struct CmppDList_entry CmppDList_entry; |
| #define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/} |
| |
| /** |
| A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs. |
| */ |
| cmpp__ListType_decl(CmppDList,CmppDList_entry*); |
| #define CmppDList_empty_m cmpp__LIST_T_empty_m |
| |
| /** |
| State for keeping track of DLL handles, a.k.a. shared-object |
| handles, a.k.a. "soh". |
| |
| Instances of this must be either cleanly initialized by bitwise |
| copying CmppSohList_empty, memset() (or equivalent) them to 0, or |
| allocating them with CmppSohList_new(). |
| */ |
| cmpp__ListType_decl(CmppSohList,void*); |
| #define CmppSohList_empty_m cmpp__LIST_T_empty_m |
| |
| /** |
| Closes all handles which have been CmppSohList_append()ed to soli |
| and frees any memory it owns, but does not free soli (which might |
| be stack-allocated or part of another struct). |
| |
| Special case: if built without DLL-closing support then this |
| is no-op. |
| */ |
| CMPP_PRIVATE void CmppSohList_close(CmppSohList *soli); |
| |
| /** |
| Operators and operator policies for use with X=Y-format keys. This |
| is legacy stuff, actually, but some of the #define management still |
| needs it. |
| */ |
| #define CmppKvp_op_map(E) \ |
| E(none,"") \ |
| E(eq1,"=") |
| |
| enum CmppKvp_op_e { |
| #define E(N,S) CmppKvp_op_ ## N, |
| CmppKvp_op_map(E) |
| #undef E |
| }; |
| typedef enum CmppKvp_op_e CmppKvp_op_e; |
| |
| /** |
| Result type for CmppKvp_parse(). |
| */ |
| struct CmppKvp { |
| /* Key part of the kvp. */ |
| CmppSnippet k; |
| /* Key part of the kvp. Might be empty. */ |
| CmppSnippet v; |
| /* Operator part of kvp, if any. */ |
| CmppKvp_op_e op; |
| }; |
| typedef struct CmppKvp CmppKvp; |
| extern const CmppKvp CmppKvp_empty; |
| |
| /** |
| Parses X or X=Y into p. Sets pp's error state on error. |
| |
| If nKey is negative then strlen() is used to calculate it. |
| |
| The third argument specifies whether/how to permit/treat the '=' |
| part of X=Y. |
| */ |
| CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p, |
| unsigned char const *zKey, |
| cmpp_ssize_t nKey, |
| CmppKvp_op_e opPolicy); |
| |
| |
| /** |
| Stack of POD values. Intended for use with cmpp at-token and |
| undefined key policies. |
| */ |
| #define cmpp__PodList_decl(ST,ET) \ |
| struct ST { \ |
| /* current stack index */ \ |
| cmpp_size_t n; \ |
| cmpp_size_t na; \ |
| ET * stack; \ |
| }; typedef struct ST ST; \ |
| void ST ## _wipe(ST * s, ET v); \ |
| int ST ## _push(cmpp *pp, ST * s, ET v); \ |
| void ST ## _set(ST * s, ET v); \ |
| void ST ## _finalize(ST * s); \ |
| void ST ## _pop(ST *s); \ |
| int ST ## _reserve(cmpp *, ST *, cmpp_size_t min) |
| |
| #define cmpp__PodList_impl(ST,ET) \ |
| void ST ## _wipe(ST * const s, ET v){ \ |
| if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na); \ |
| s->n = 0; \ |
| } \ |
| int ST ## _reserve(cmpp * const pp, ST * const s, \ |
| cmpp_size_t min){ \ |
| return cmpp_array_reserve(pp, (void**)&s->stack, min>0 \ |
| ? min : (s->n \ |
| ? (s->n==s->na-1 \ |
| ? s->na*2 : s->n+1) \ |
| : 8), \ |
| &s->na, sizeof(ET)); \ |
| } \ |
| int ST ## _push(cmpp * const pp, ST * s, ET v){ \ |
| if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v; \ |
| return ppCode; \ |
| } \ |
| void ST ## _set(ST * s, ET v){ \ |
| assert(s->n); \ |
| if( 0== ST ## _reserve(NULL, s, 0) ){ \ |
| s->stack[s->n] = v; \ |
| } \ |
| } \ |
| ET ST ## _get(ST const * const s){ \ |
| assert(s->na && s->na >=s->n); \ |
| return s->stack[s->n]; \ |
| } \ |
| void ST ## _pop(ST *s){ \ |
| assert(s->n); \ |
| if(s->n) --s->n; \ |
| } \ |
| void ST ## _finalize(ST *s){ \ |
| cmpp_mfree(s->stack); \ |
| s->stack = NULL; \ |
| s->n = s->na = 0; \ |
| } |
| |
| cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e); |
| cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e); |
| |
| #define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH |
| #define cmpp__policy(PP,WHICH) \ |
| cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n] |
| |
| /** |
| A "delimiter" object. That is, the "#" referred to in the libcmpp |
| docs. It's also outfitted for a second delimiter so that it can be |
| used for the opening/closing delimiters of @tokens@. |
| */ |
| struct cmpp__delim { |
| /** |
| Bytes of the directive delimiter/prefix or the @token@ opening |
| delimiter. Owned elsewhere but often points at this->zOwns. |
| */ |
| CmppSnippet open; |
| /** |
| Closing @token@ delimiter. This has no meaning for the directive |
| delimiter. |
| */ |
| CmppSnippet close; |
| /** |
| Memory, owned by this object, for this->open and this->close. In |
| the latter case, it's one string with both delimiters encoded in |
| it. |
| */ |
| unsigned char * zOwns; |
| }; |
| typedef struct cmpp__delim cmpp__delim; |
| #define cmpp__delim_empty_m { \ |
| .open={ \ |
| .z=(unsigned char*)CMPP_DEFAULT_DELIM, \ |
| .n=sizeof(CMPP_DEFAULT_DELIM)-1 \ |
| }, \ |
| .close=CmppSnippet_empty_m, \ |
| .zOwns=0 \ |
| } |
| |
| extern const cmpp__delim cmpp__delim_empty; |
| void cmpp__delim_cleanup(cmpp__delim *d); |
| |
| /** |
| A dynamically-allocated list of cmpp__delim objects. |
| */ |
| cmpp__ListType_decl(cmpp__delim_list,cmpp__delim); |
| #define cmpp__delim_list_empty_m {0,0,0} |
| extern const cmpp__delim_list cmpp__delim_list_empty; |
| |
| CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li); |
| static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){ |
| return li->n ? li->list+(li->n-1) : NULL; |
| } |
| static inline void cmpp__delim_list_pop(cmpp__delim_list *li){ |
| assert(li->n); |
| if( li->n ) cmpp__delim_cleanup(li->list + --li->n); |
| } |
| static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){ |
| while( li->n ) cmpp__delim_cleanup(li->list + --li->n); |
| } |
| |
| /** |
| An untested experiment: an output buffer proxy. Integrating this |
| fully would require some surgery, but it might also inspire me to |
| do the same with input and stream it rather than slurp it all at |
| once. |
| */ |
| #define CMPP__OBUF 0 |
| |
| typedef struct cmpp__obuf cmpp__obuf; |
| #if CMPP__OBUF |
| /** |
| An untested experiment. |
| */ |
| struct cmpp__obuf { |
| /** Start of the output buffer. */ |
| unsigned char * begin; |
| /** One-after-the-end of this->begin. Virtual EOF. */ |
| unsigned char const * end; |
| /** Current write position. Must initially be |
| this->begin. */ |
| unsigned char * cursor; |
| /** |
| True if this object owns this->begin, which must have been |
| allocated using cmpp_malloc() or cmpp_realloc(). |
| */ |
| bool ownsMemory; |
| /** Propagating result code. */ |
| int rc; |
| /** |
| The output channel to buffer for. Flushing |
| */ |
| cmpp_outputer dest; |
| }; |
| |
| #define cmpp__obuf_empty_m { \ |
| .begin=0, .end=0, .cursor=0, .ownsMemory=false, \ |
| .rc=0, .dest=cmpp_outputer_empty_m \ |
| } |
| extern const cmpp__obuf cmpp__obuf_empty; |
| extern const cmpp_outputer cmpp_outputer_obuf; |
| #endif /* CMPP__OBUF */ |
| /** |
| The main public-API context type for this library. |
| */ |
| struct cmpp_pimpl { |
| /* Internal workhorse. */ |
| struct { |
| sqlite3 * dbh; |
| /** |
| Optional filename. Memory is owned by this object. |
| */ |
| char * zName; |
| } db; |
| /** |
| Current directive context. It's const, primarily to help protect |
| cmpp_dx_f()'s from inadvertent side effects of changes which |
| lower-level APIs might make to it. Maybe it shouldn't be: if it |
| were not then we could update dx->zDelim from |
| cmpp__delimiter_set(). |
| */ |
| cmpp_dx const * dx; |
| /* Output channel. */ |
| cmpp_outputer out; |
| /** |
| Delimiters version 2. |
| */ |
| struct { |
| /** |
| Directive delimiter. |
| */ |
| cmpp__delim_list d; |
| /** |
| @token@ delimiters. |
| */ |
| cmpp__delim_list at; |
| } delim; |
| struct { |
| |
| #define CMPP__SEL_V_FROM(N) \ |
| "(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \ |
| " ORDER BY source LIMIT 1)" |
| |
| /** |
| One entry for each distinct query used by cmpp: E(X,SQL), where |
| X is the member's name and SQL is its SQL. |
| */ |
| #define CMPP_SAVEPOINT_NAME "_cmpp_" |
| #define CmppStmt_map(E) \ |
| E(sdefIns, \ |
| "INSERT INTO " \ |
| CMPP__DB_MAIN_NAME ".sdef" \ |
| "(t,k,v) VALUES(?1,?2,?3) RETURNING id") \ |
| E(defIns, \ |
| "INSERT OR REPLACE INTO " \ |
| CMPP__DB_MAIN_NAME ".def" \ |
| "(t,k,v) VALUES(?1,?2,?3)") \ |
| E(defDel, \ |
| "DELETE FROM " \ |
| CMPP__DB_MAIN_NAME ".def" \ |
| " WHERE k GLOB ?1") \ |
| E(sdefDel, \ |
| "DELETE FROM " \ |
| CMPP__DB_MAIN_NAME ".sdef" \ |
| " WHERE k=?1 AND id>=?2") \ |
| E(defHas, \ |
| "SELECT 1 FROM " \ |
| CMPP__DB_MAIN_NAME ".vdef" \ |
| " WHERE k = ?1") \ |
| E(defGet, \ |
| "SELECT source,t,k,v FROM " \ |
| CMPP__DB_MAIN_NAME ".vdef" \ |
| " WHERE k = ?1 ORDER BY source LIMIT 1") \ |
| E(defGetBool, \ |
| "SELECT cmpp_truthy(v) FROM " \ |
| CMPP__DB_MAIN_NAME ".vdef" \ |
| " WHERE k = ?1" \ |
| " ORDER BY source LIMIT 1") \ |
| E(defGetInt, \ |
| "SELECT CAST(v AS INTEGER)" \ |
| " FROM " CMPP__DB_MAIN_NAME ".vdef" \ |
| " WHERE k = ?1" \ |
| " ORDER BY source LIMIT 1") \ |
| E(defSelAll, "SELECT t,k,v" \ |
| " FROM " CMPP__DB_MAIN_NAME ".vdef" \ |
| " ORDER BY source, k") \ |
| E(inclIns," INSERT OR FAIL INTO " \ |
| CMPP__DB_MAIN_NAME ".incl(" \ |
| " file,srcFile, srcLine" \ |
| ") VALUES(?,?,?)") \ |
| E(inclDel, "DELETE FROM " \ |
| CMPP__DB_MAIN_NAME ".incl WHERE file=?") \ |
| E(inclHas, "SELECT 1 FROM " \ |
| CMPP__DB_MAIN_NAME ".incl WHERE file=?") \ |
| E(inclPathAdd, "INSERT INTO " \ |
| CMPP__DB_MAIN_NAME ".inclpath(priority,dir) " \ |
| "VALUES(coalesce(?1,0),?2) " \ |
| "ON CONFLICT DO NOTHING " \ |
| "RETURNING rowid /*xlates to 0 on conflict*/") \ |
| E(inclPathRmId, "DELETE FROM " \ |
| CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 " \ |
| "RETURNING rowid") \ |
| E(inclSearch, \ |
| "SELECT ?1 fn WHERE cmpp_file_exists(fn) " \ |
| "UNION ALL SELECT fn FROM (" \ |
| " SELECT replace(dir||'/'||?1, '//','/') AS fn " \ |
| " FROM " CMPP__DB_MAIN_NAME ".inclpath" \ |
| " WHERE cmpp_file_exists(fn) " \ |
| " ORDER BY priority DESC, rowid LIMIT 1" \ |
| ")") \ |
| E(cmpVV, "SELECT cmpp_compare(?1,?2)") \ |
| E(cmpDV, \ |
| "SELECT cmpp_compare(" \ |
| CMPP__SEL_V_FROM(1) ", ?2" \ |
| ")") \ |
| E(cmpVD, \ |
| "SELECT cmpp_compare(" \ |
| "?1," CMPP__SEL_V_FROM(2) \ |
| ")") \ |
| E(cmpDD, \ |
| "SELECT cmpp_compare(" \ |
| CMPP__SEL_V_FROM(1) \ |
| "," \ |
| CMPP__SEL_V_FROM(2) \ |
| ")") \ |
| E(dbAttach, \ |
| "ATTACH ?1 AS ?2") \ |
| E(dbDetach, \ |
| "DETACH ?1") \ |
| E(spBegin, "SAVEPOINT " CMPP_SAVEPOINT_NAME) \ |
| E(spRollback, \ |
| "ROLLBACK TO SAVEPOINT " CMPP_SAVEPOINT_NAME) \ |
| E(spRelease, \ |
| "RELEASE SAVEPOINT " CMPP_SAVEPOINT_NAME) \ |
| E(insTtype, \ |
| "INSERT INTO " CMPP__DB_MAIN_NAME ".ttype" \ |
| "(t,n,s) VALUES(?1,?2,?3)") \ |
| E(selPathSearch, \ |
| /* sqlite.org/forum/forumpost/840c98a8e87c2207 */ \ |
| "WITH path(basename, sep, ext, path) AS (\n" \ |
| " select\n" \ |
| " ?1 basename,\n" \ |
| " ?2 sep,\n" \ |
| " ?3 ext,\n" \ |
| " ?4 path\n" \ |
| "),\n" \ |
| "pathsplit(i, l, c, r) AS (\n" \ |
| "-- i = sequential ID\n" \ |
| "-- l = Length remaining\n" \ |
| "-- c = text remaining\n" \ |
| "-- r = current unpacked value\n" \ |
| " SELECT 1,\n" \ |
| " length(p.path)+length(p.sep),\n" \ |
| " p.path||p.sep, ''\n" \ |
| " FROM path p\n" \ |
| " UNION ALL\n" \ |
| " SELECT i+1, instr( c, p.sep ) l,\n" \ |
| " substr( c, instr( c, p.sep ) + 1) c,\n" \ |
| " trim( substr( c, 1,\n" \ |
| " instr( c, p.sep) - 1) ) r\n" \ |
| " FROM pathsplit, path p\n" \ |
| " WHERE l > 0\n" \ |
| "),\n" \ |
| "thefile (f) AS (\n" \ |
| " select basename f FROM path\n" \ |
| " union all\n" \ |
| " select basename||ext\n" \ |
| " from path where ext is not null\n" \ |
| ")\n" \ |
| "select 0 i, replace(f,'//','/') AS fn\n" \ |
| "from thefile where cmpp_file_exists(fn)\n" \ |
| "union all\n" \ |
| "select i, replace(r||'/'||f,'//','/') fn\n" \ |
| "from pathsplit, thefile\n" \ |
| "where r<>'' and cmpp_file_exists(fn)\n" \ |
| "order by i\n" \ |
| "limit 1;") |
| |
| /* trivia: selPathSearch (^^^) was generated using |
| cmpp's #c-code directive. */ |
| |
| #define E(N,S) sqlite3_stmt * N; |
| CmppStmt_map(E) |
| #undef E |
| |
| } stmt; |
| |
| /** Error state. */ |
| struct { |
| /** Result code. */ |
| int code; |
| /** Error string owned by this object. */ |
| char * zMsg; |
| /** Either this->zMsg or an external error string. */ |
| char const * zMsgC; |
| } err; |
| |
| /** State for SQL tracing. */ |
| struct { |
| bool expandSql; |
| cmpp_size_t counter; |
| cmpp_outputer out; |
| } sqlTrace; |
| |
| struct { |
| /** If set properly, cmpp_dtor() will free this |
| object, else it will not. */ |
| void const * allocStamp; |
| /** |
| How many dirs we believe are in the #include search list. We |
| only do this for the sake of the historical "if no path was |
| added, assume '.'" behavior. This really ought to go away. |
| */ |
| unsigned nIncludeDir; |
| /** |
| The current depth of cmpp_process_string() calls. We do this so |
| the directory part of #include'd files can get added to the |
| #include path and be given a higher priority than previous |
| include path entries in the stack. |
| */ |
| int nDxDepth; |
| /* Number of active #savepoints. */ |
| unsigned nSavepoint; |
| /* If >0, enables certain debugging output. */ |
| char doDebug; |
| /* If true, chomp() files read via -Fx=file. */ |
| unsigned char chompF; |
| /* Flags passed to cmpp_ctor(). */ |
| cmpp_flag32_t newFlags; |
| |
| /** |
| An ugly hack for getting cmpp_d_register() to get |
| syntactically-illegal directive names, like "@policy", |
| to register. |
| */ |
| bool isInternalDirectiveReg; |
| /** |
| True if the next directive is the start of a [call]. This is |
| used for: |
| |
| 1) To set cmpp_dx::isCall, which is useful in certain |
| directives. |
| |
| 2) So that the cmpp_dx object created for the call can inherit |
| the line number from its parent context. That's significant |
| for error reporting. |
| |
| 3) So that #2's cmpp_dx object can communicate that flag to |
| cmpp_dx_next(). |
| */ |
| bool nextIsCall; |
| |
| /** |
| True until the cmpp's (sometimes) lazy init has been run. This |
| is essentially a kludge to work around a wrench cmpp_reset() |
| throws into cmpp state. Maybe we should just remove |
| cmpp_reset() from the interface, since error recovery in this |
| context is not really a thing. |
| */ |
| bool needsLazyInit; |
| } flags; |
| |
| /** Policies. */ |
| struct { |
| /** @token@-parsing policy. */ |
| PodList__atpol at; |
| /** Policy towards referencing undefined symbols. */ |
| PodList__unpol un; |
| } policy; |
| |
| /** |
| Directive state. |
| */ |
| struct { |
| /** |
| Runtime-installed directives. |
| */ |
| CmppDList list; |
| /** |
| Directive autoloader/auto-registerer. |
| */ |
| cmpp_d_autoloader autoload; |
| } d; |
| |
| struct { |
| /** |
| List of DLL handles opened by cmpp_module_extract(). |
| */ |
| CmppSohList sohList; |
| /** |
| Search path for DLLs, delimited by this->pathSep. |
| */ |
| cmpp_b path; |
| /** |
| File extension for DLLs. |
| */ |
| char const * soExt; |
| /** Separator char for this->path. */ |
| char pathSep; |
| } mod; |
| |
| struct { |
| /** |
| Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return(). |
| */ |
| cmpp_b_list buf; |
| /** How/whether this->list is sorted. */ |
| enum cmpp_b_list_e bufSort; |
| /** |
| Head of the free-list. |
| */ |
| cmpp_args_pimpl * argPimpl; |
| } recycler; |
| }; |
| |
| /** IDs Distinct for each cmpp::stmt member. */ |
| enum CmppStmt_e { |
| CmppStmt_none = 0, |
| #define E(N,S) CmppStmt_ ## N, |
| CmppStmt_map(E) |
| #undef E |
| }; |
| |
| static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){ |
| return cmpp__delim_list_get(&pp->pimpl->delim.d); |
| } |
| static inline char const * cmpp__pp_zdelim(cmpp const *pp){ |
| cmpp__delim const * const d = cmpp__pp_delim(pp); |
| return d ? (char const *)d->open.z : NULL; |
| } |
| #define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp) |
| #define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp) |
| |
| /** |
| Emit [z,(char*)z+n) to the given output channel if |
| (A) pOut->out is not NULL and (B) pp has no error state and (C) |
| n>0. On error, pp's error state is updated. Returns pp->err.code. |
| |
| Skip level is not honored. |
| */ |
| CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n); |
| |
| CMPP_PRIVATE void cmpp__err_clear(cmpp *pp); |
| |
| |
| /** |
| Initialize pp->db.dbh. If it's already open or ppCode!=0 |
| then ppCode is returned. |
| */ |
| int cmpp__db_init(cmpp *pp); |
| |
| /** |
| Returns the pp->pimpl->stmt.X corresponding to `which`, initializing it if |
| needed. If it returns NULL then either this was called when pp has |
| its error state set or this function will set the error state. |
| |
| If prepEvenIfErr is true then the ppCode check is bypassed, but it |
| will still fail if pp->pimpl->db is not opened or if the preparation itself |
| fails. |
| */ |
| sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which, |
| bool prepEvenIfErr); |
| |
| /** |
| Reminder to self: this must return an SQLITE_... code, not a |
| CMPP_RC_... code. |
| |
| On success it returns 0, SQLITE_ROW, or SQLITE_DONE. On error it |
| returns another non-0 SQLITE_... code and updates pp->pimpl->err. |
| |
| This is a no-op if called when pp has an error set, returning |
| SQLITE_ERROR. |
| |
| If resetIt is true, q is passed to cmpp__stmt_reset(), else the |
| caller must eventually reset it. |
| */ |
| int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt); |
| |
| /** Resets and clear bindings from q (if q is not NULL). */ |
| void cmpp__stmt_reset(sqlite3_stmt * const q); |
| |
| /** |
| Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or |
| SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err |
| is updated with pp->db's current error state. zMsgSuffix is an |
| optional prefix for the error message. |
| */ |
| int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsgSuffix); |
| |
| /* Proxy for sqlite3_bind_int64(). */ |
| int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); |
| |
| /** |
| Proxy for cmpp__bind_text() which encodes val as a string. |
| |
| For queries which compare values, it's important that they all have |
| the same type, so some cases where we might want an int needs to be |
| bound as text instead. See #query for one such case. |
| */ |
| int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val); |
| |
| /* Proxy for sqlite3_bind_null(). */ |
| int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col); |
| |
| /* Proxy for sqlite3_bind_text() which updates pp->err on error. */ |
| int cmpp__bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col, |
| unsigned const char * zStr); |
| |
| /* Proxy for sqlite3_bind_text() which updates pp->err on error. */ |
| int cmpp__bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col, |
| unsigned const char *zStr, cmpp_ssize_t len); |
| |
| /** |
| Adds zDir to the include path, using the given priority value (use |
| 0 except for the implicit cwd path which #include should (but does |
| not yet) set). If pRowid is not NULL then *pRowid gets set to |
| either 0 (if zDir was already in the path) or the row id of the |
| newly-inserted record, which can later be used to delete just that |
| entry. |
| |
| If this returns a non-zero value via pRowid, the caller is |
| obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(), |
| even if pp is in an error state. |
| |
| TODO: normalize zDir (at least remove trailing slashes) before |
| insertion to avoid that both a/b and a/b/ get be inserted. |
| */ |
| int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid); |
| |
| /** |
| Deletes the include path entry with the given rowid. This will make |
| make the attempt even if pp is in an error state but also retains |
| any existing error rather than overwriting it if this operation |
| somehow fails. Returns pp's error code. |
| |
| It is not an error for the given entry to not exist. |
| */ |
| int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid); |
| |
| |
| #if 0 |
| /** |
| Proxy for sqlite3_bind_text(). It uses sqlite3_str_vappendf() so |
| supports all of its formatting options. |
| */ |
| int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col, |
| const char * zFmt, ...); |
| #endif |
| |
| /** |
| Proxy for sqlite3_str_finish() which updates pp's error state if s |
| has error state. Returns s's string on success and NULL on |
| error. The returned string must eventualy be passed to |
| cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so |
| callers must check pp->err to see if NULL is an error. |
| |
| If n is not NULL then on success it is set to the byte length of |
| the returned string. |
| */ |
| char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n); |
| |
| /** |
| Searches pp's list of directives. If found, return it else return |
| NULL. See cmpp__d_search3(). |
| */ |
| cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName); |
| |
| /** |
| Flags for use with the final argument to |
| cmpp__d_search3(). |
| */ |
| enum cmpp__d_search3_e { |
| /** Internal delayed-registered directives. */ |
| cmpp__d_search3_F_DELAYED = 0x01, |
| /** Directive autoloader. */ |
| cmpp__d_search3_F_AUTOLOADER = 0x02, |
| /** Search for a DLL. */ |
| cmpp__d_search3_F_DLL = 0x04, |
| /** Options which do not trigger DLL lookup. */ |
| cmpp__d_search3_F_NO_DLL = 0 |
| | cmpp__d_search3_F_DELAYED |
| | cmpp__d_search3_F_AUTOLOADER, |
| /** All options. */ |
| cmpp__d_search3_F_ALL = 0 |
| | cmpp__d_search3_F_DELAYED |
| | cmpp__d_search3_F_AUTOLOADER |
| | cmpp__d_search3_F_DLL |
| }; |
| |
| /** |
| Like cmpp__d_search() but if no match is found then it will search |
| through its other options and, if found, register it. |
| |
| The final argument specifies where to search. cmpp__d_search() |
| always checked first. After that, depending on "what", the search |
| order is: (1) internal delayed-load modules, (2) autoloader, (3) |
| DLL. |
| |
| This may update pp's error state, in which case it will return |
| NULL. |
| */ |
| cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName, |
| cmpp_flag32_t what); |
| |
| /** |
| Sets pp's error state (A) if it's not set already and (B) if |
| !cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is |
| legal (to support legacy CLI pieces). Returns ppCode. |
| */ |
| int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey, |
| cmpp_ssize_t nKey, |
| bool permitEqualSign); |
| |
| /** |
| Appends DLL handle soh to soli. Returns 0 on success, CMPP_RC_OOM |
| on error. If pp is not NULL then its error state is updated as |
| well. |
| |
| Results are undefined if soli was not cleanly initialized (by |
| copying CmppSohList_empty or using CmppSohList_new()). |
| |
| Special case: if built without DLL-closing support, this is a no-op |
| returning 0. |
| */ |
| int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh); |
| |
| /** True if arg is of type cmpp_TT_Word and it looks like it |
| _might_ be a filename or flag argument. Might. */ |
| bool cmpp__arg_wordIsPathOrFlag(cmpp_arg const * const arg); |
| |
| /** |
| Helper for #query and friends. Binds aVal's value to column bindNdx |
| of q. |
| |
| It expands cmpp_TT_StringAt and cmpp_TT_Word aVal. cmpp_TT_String |
| and cmpp_TT_Int are bound as strings. A cmpp_TT_GroupParen aVal is |
| eval'ed as an integer and that int gets bound as a string. |
| |
| This function strictly binds everything as strings, even if the |
| value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that |
| comparison queries will work as expected. |
| |
| Returns ppCode. |
| */ |
| int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * q, |
| int bindNdx, cmpp_arg const * aVal); |
| |
| /** |
| Helper for #query's bind X part, where aGroup is that X. |
| |
| A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be |
| either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have |
| non-empty content. cmpp_TT_GroupBrace treats it as a list of values |
| to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt |
| column in one of these forms: |
| |
| :bindName -> value |
| $bindName -> value |
| |
| Each LHS refers to a same-named binding in q's SQL, including the |
| ':' or '$' prefix. (SQLite supports an '@' prefix but we don't |
| allow it here to avoid confusion with cmpp_TT_StringAt tokens.) |
| |
| Each bound value is passed to cmpp__bind_arg() for processing. |
| |
| On success, each aGroup entry is bound to q. On error q's state is |
| unspecified. Returns ppCode. |
| |
| See cmpp__bind_arg() for notes about the bind data type. |
| */ |
| int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q, |
| cmpp_arg const * const aGroup); |
| |
| /** |
| Returns true if the given key is already in the `#define` list, |
| else false. Sets pp's error state on db error. |
| |
| nName is the length of the key part of zName (which might have |
| a following =y part. If it's negative, strlen() is used to |
| calculate it. |
| */ |
| int cmpp_has(cmpp *pp, const char * zName, cmpp_ssize_t nName); |
| |
| /** |
| Returns true if the given key is already in the `#define` list, and |
| it has a truthy value (is not empty and not equal to '0'), else |
| false. If zName contains an '=' then only the part preceding that |
| is used as the key. |
| |
| nName is the length of zName, or <0 to use strlen() to figure |
| it out. |
| |
| Updates ppCode on error. |
| */ |
| int cmpp__get_bool(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName); |
| |
| /** |
| Fetches the given define. If found, sets *pOut to it, else pOut is |
| unmodified. Returns pp->err.code. If bRequire is true and no entry |
| is found p->err.code is updated. |
| */ |
| int cmpp__get_int(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, int *pOut); |
| |
| /** |
| Searches for a define where (k GLOB zName). If one is found, a copy |
| of it is assigned to *zVal (the caller must eventually db_free() |
| it), *nVal (if nVal is not NULL) is assigned its strlen, and |
| returns non-0. If no match is found, 0 is returned and neither |
| *zVal nor *nVal are modified. If more than one result matches, a |
| fatal error is triggered. |
| |
| It is legal for *zVal to be NULL (and *nVal to be 0) if it returns |
| non-0. That just means that the key was defined with no value part. |
| |
| Fixme: return 0 on success and set output *gotOne=0|1. |
| */ |
| int cmpp__get(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, |
| unsigned char **zVal, unsigned int *nVal); |
| |
| /** |
| Like cmp__get() but puts its output in os. |
| */ |
| int cmpp__get_b(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, cmpp_b * os, |
| bool enforceUndefPolicy); |
| |
| |
| /** |
| Helper for #query and friends. |
| |
| It expects that q has just been stepped. For each column in the |
| row, it sets a define named after the column. If q has row data |
| then the values come from there. If q has no row then: if |
| defineIfNoRow is true then it defines each column name to an empty |
| value else it defines nothing. |
| */ |
| int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, |
| bool defineIfNoRow); |
| |
| /** Start a new savepoint for dx. */ |
| int cmpp__dx_sp_begin(cmpp_dx * const dx); |
| /** Commit and close dx's current savepoint. */ |
| int cmpp__dx_sp_commit(cmpp_dx * const dx); |
| /** Roll back and close dx's current savepoint. */ |
| int cmpp__dx_sp_rollback(cmpp_dx * const dx); |
| |
| /** |
| Append's dx's file/line information to sstr. It returns void |
| because that's how sqlite3_str_appendf() and friends work. |
| */ |
| void cmpp__dx_append_script_info(cmpp_dx const * dx, |
| sqlite3_str * sstr); |
| |
| /** |
| If zName matches one of the delayed-load directives, that directive |
| is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if |
| no match is found, but pp's error state is not updated in that |
| case. If a match is found and registration fails, that result code |
| will propagate via pp. |
| */ |
| int cmpp__d_delayed_load(cmpp *pp, char const *zName); |
| |
| void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent); |
| |
| /** |
| Like cmpp_tt_cstr(), but if bSymbolName is false then it returns |
| the higher-level token name, which is NULL for most token types. |
| */ |
| char const * cmpp__tt_cstr(int tt, bool bSymbolName); |
| |
| /** |
| Expects **zPos to be one of ('(' '{' '[' '"' '\'') and zEnd to be |
| the logical EOF for *zPos. |
| |
| This looks for a matching closing token, accounting for nesting. On |
| success, returns 0 and sets *zPos to the closing character. |
| |
| On error it update's pp's error state and returns that code. pp may |
| be NULL. |
| |
| If pNl is not NULL then *pNl is incremented for each '\n' character |
| seen while looking for the closing character. |
| */ |
| int cmpp__find_closing2(cmpp *pp, |
| unsigned char const **zPos, |
| unsigned char const *zEnd, |
| cmpp_size_t *pNl); |
| |
| #define cmpp__find_closing(PP,Z0,Z1) \ |
| cmpp__find_closing2(PP, Z0, Z1, NULL) |
| |
| static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_ssize_t n){ |
| return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n; |
| } |
| static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_ssize_t n){ |
| return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n; |
| } |
| |
| /** |
| If ppCode is not set and pol resolves to cmpp_atpol_OFF then this |
| updates ppCode with a message about the lack of support for |
| at-strings. If cmpp_atpol_CURRENT==pol then pp's current policy is |
| checked. Returns ppCode. |
| */ |
| int cmpp__StringAtIsOk(cmpp * const pp, cmpp_atpol_e pol); |
| |
| /** |
| "define"s zKey to zVal, recording the value type as tType. |
| */ |
| int cmpp__define2(cmpp *pp, |
| unsigned char const * zKey, |
| cmpp_ssize_t nKey, |
| unsigned char const *zVal, |
| cmpp_ssize_t nVal, |
| cmpp_tt tType); |
| |
| /** |
| Evals pArgs's arguments as an integer expression. On success, sets |
| *pResult to the value. |
| |
| Returns ppCode. |
| */ |
| int cmpp__args_evalToInt(cmpp_dx * dx, cmpp_args const *pArgs, |
| int * pResult); |
| |
| /** Passes the contents of arg through to cmpp__args_evalToInt(). */ |
| int cmpp__arg_evalSubToInt(cmpp_dx *dx, cmpp_arg const *arg, |
| int * pResult); |
| |
| /** |
| Evaluated arg as an integer/bool, placing the result in *pResult |
| and setting *pNext to the first argument to arg's right which this |
| routine did not consume. Non-0 on error and all that. |
| */ |
| int cmpp__arg_toBool(cmpp_dx * dx, cmpp_arg const *arg, |
| int * pResult, cmpp_arg const **pNext); |
| |
| /** |
| If thisTtype==cmpp_TT_AnyType or thisTtype==arg->ttype and arg->z |
| looks like it might contain an at-string then os is re-used to hold |
| the @token@-expanded version of arg's content. os is unconditionally |
| passed to cmpp_b_reuse() before it begines work. |
| |
| It uses the given atPolicy to determine whether or not the content |
| is expanded, as per cmpp_dx_out_expand(). |
| |
| Returns 0 on success. If it expands content then *pExp is set to |
| os->z, else *pExp is set to arg->z. If nExp is not NULL then *nExp |
| gets set to the length of *pExp (geither os->n or arg->n). |
| |
| Returns ppCode. |
| |
| Much later: what does this give us that cmpp_arg_to_b() |
| doesn't? Oh - that one calls into this one. i.e. this one is |
| lower-level. |
| */ |
| int cmpp__arg_expand_ats(cmpp_dx const * const dx, |
| cmpp_b * os, |
| cmpp_atpol_e atPolicy, |
| cmpp_arg const * const arg, |
| cmpp_tt thisTtype, |
| unsigned char const **pExp, |
| cmpp_size_t * nExp); |
| |
| typedef struct cmpp_argOp cmpp_argOp; |
| typedef void (*cmpp_argOp_f)(cmpp_dx *dx, |
| cmpp_argOp const *op, |
| cmpp_arg const *vLhs, |
| cmpp_arg const **pvRhs, |
| int *pResult); |
| struct cmpp_argOp { |
| int ttype; |
| /* 1 or 2 */ |
| unsigned char arity; |
| /* 0=none/left, 1=right (unary ops only) */ |
| signed char assoc; |
| cmpp_argOp_f xCall; |
| }; |
| |
| cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt); |
| |
| |
| bool cmpp__is_int(unsigned char const *z, unsigned n, |
| int *pOut); |
| bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut); |
| |
| char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p); |
| char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p); |
| |
| /** |
| Uncerimoniously bitwise-replaces pp's output channel with oNew. It |
| does _not_ clean up the previous channel, on the assumption that |
| the caller is taking any necessary measures. |
| |
| Apropos necessary measures for cleanup: if oPrev is not NULL, |
| *oPrev is set to a bitwise copy of the previous channel. |
| |
| Intended usage: |
| |
| ``` |
| cmpp_outputer oMine = cmpp_outputer_b; |
| cmpp_b bMine = cmpp_b_empty; |
| cmpp_outputer oOld = {0}; |
| oMine.state = &bMine; |
| cmpp_outputer_swap(pp, &myOut, &oOld); |
| ...do some work then ALWAYS do... |
| cmpp_outputer_swap(pp, &oOld, &oMine); |
| ``` |
| |
| Because this involves bitwise copying, care must be taken with |
| stream state, e.g. bMine.z (above) can be reallocated, so we have |
| to be sure to swap it back before using bMine again. |
| */ |
| void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew, |
| cmpp_outputer *oPrev); |
| |
| /** |
| Init code which is usually run as part of the ctor but may have to |
| be run later, after cmpp_reset(). We can't run it from cmpp_reset() |
| because that could leave post-reset in an error state, which is |
| icky. This call is a no-op after the first. |
| */ |
| int cmpp__lazy_init(cmpp *pp); |
| |
| CMPP_NORETURN void cmpp__fatalv_base(char const *zFile, int line, |
| char const *zFmt, va_list); |
| #define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__) |
| CMPP_NORETURN void cmpp__fatal_base(char const *zFile, int line, |
| char const *zFmt, ...); |
| #define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__) |
| |
| /** |
| Outputs a printf()-formatted message to stderr. |
| */ |
| void g_stderr(char const *zFmt, ...); |
| #define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__) |
| #define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg) |
| #if 0 |
| #define g_debug(PP,lvl,pfexpr) (void)0 |
| #else |
| #define g_debug(PP,lvl,pfexpr) \ |
| if(lvl<=(PP)->pimpl->flags.doDebug) { \ |
| if( (PP)->pimpl->dx ){ \ |
| g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ", \ |
| (PP)->pimpl->dx->sourceName, \ |
| (PP)->pimpl->dx->pimpl->dline.lineNo); \ |
| } \ |
| g_stderr("%s():%d: ", \ |
| __func__,__LINE__); \ |
| g_stderr pfexpr; \ |
| } (void)0 |
| #endif |
| |
| /** Returns true if zFile is readable, else false. */ |
| bool cmpp__file_is_readable(char const *zFile); |
| |
| #define ustr_c(X) ((unsigned char const *)X) |
| #define ustr_nc(X) ((unsigned char *)X) |
| #define ppCode pp->pimpl->err.code |
| #define dxppCode dx->ppCode |
| #define cmpp__pi(PP) cmpp_pimpl * const pi = PP->pimpl |
| #define cmpp__dx_pi(DX) cmpp_dx_pimpl * const dpi = DX->pimpl |
| #define serr(...) cmpp_err_set(pp, CMPP_RC_SYNTAX, __VA_ARGS__) |
| #define dxserr(...) cmpp_err_set(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__) |
| #define cmpp__li_reserve1_size(li,nInitial) \ |
| (li->n ? (li->n==li->nAlloc ? li->nAlloc * 2 : li->n+1) : nInitial) |
| |
| #define MARKER(pfexp) \ |
| do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ |
| printf pfexp; \ |
| } while(0) |
| |
| #endif /* include guard */ |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the core of libcmpp (it used to house all of it, |
| ** but the library grew beyond the confines of a single file). |
| ** |
| ** See the accompanying c-pp.h and README.md and/or c-pp.h for more |
| ** details. |
| */ |
| #include "sqlite3.h" |
| |
| char const * cmpp_version(void){ return CMPP_VERSION; } |
| |
| const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m; |
| const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m; |
| const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m; |
| const cmpp_outputer cmpp_outputer_FILE = { |
| .out = cmpp_output_f_FILE, |
| .flush = cmpp_flush_f_FILE, |
| .cleanup = cmpp_outputer_cleanup_f_FILE, |
| .state = NULL |
| }; |
| const cmpp_b_list cmpp_b_list_empty = |
| cmpp_b_list_empty_m; |
| const cmpp_outputer cmpp_outputer_b = { |
| .out = cmpp_output_f_b, |
| .flush = 0, |
| .cleanup = cmpp_outputer_cleanup_f_b, |
| .state = NULL |
| }; |
| |
| /** |
| Default delimiters for @tokens@. |
| */ |
| static const cmpp__delim delimAtDefault = { |
| .open = { .z = ustr_c("@"), .n = 1 }, |
| .close = { .z = ustr_c("@"), .n = 1 }, |
| .zOwns = NULL |
| }; |
| |
| static const cmpp_api_thunk cmppApiMethods = { |
| #define A(V) |
| #define V(N,T,V) .N = V, |
| #define F(N,T,P) .N = cmpp_ ##N, |
| #define O(N,T) .N = &cmpp_ ##N, |
| cmpp_api_thunk_map(A,V,F,O) |
| #undef F |
| #undef O |
| #undef V |
| #undef A |
| }; |
| |
| /* Fatally exits the app with the given printf-style message. */ |
| |
| CMPP__EXPORT(bool, cmpp_isspace)(int ch){ |
| return ' '==ch || '\t'==ch; |
| } |
| |
| //CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){} |
| static inline int cmpp_isnl(unsigned char const * z, unsigned char const *zEnd){ |
| //assert(z<zEnd && "Caller-level pointer mis-traversal"); |
| switch( z<zEnd ? *z : 0 ){ |
| case 0: return 0; |
| case '\r': return ((z+1<zEnd) && '\n'==z[1]) ? 2 : 0; |
| default: return '\n'==*z; |
| } |
| } |
| |
| static inline bool cmpp_issnl(int ch){ |
| /* TODO: replace this in line with cmpp_isnl(), but it needs |
| a new interface for that. It's only used in two places and they |
| have different traversal directions, so we can probably |
| get rid of this and do the direct CRNL check in each of those |
| places. */ |
| return ' '==ch || '\t'==ch || '\n'==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<zEnd && cmpp_isspace(*z) ) ++z; |
| *p = z; |
| } |
| |
| CMPP__EXPORT(void, cmpp_skip_snl)( unsigned char const **p, |
| unsigned char const *zEnd ){ |
| unsigned char const * z = *p; |
| /* FIXME: CRNL. */ |
| while( z<zEnd && cmpp_issnl(*z) ) ++z; |
| *p = z; |
| } |
| |
| CMPP__EXPORT(void, cmpp_skip_space_trailing)( unsigned char const *zBegin, |
| unsigned char const **p ){ |
| assert( *p >= zBegin ); |
| unsigned char const * z = *p; |
| while( z>zBegin && cmpp_isspace(z[-1]) ) --z; |
| *p = z; |
| } |
| |
| CMPP__EXPORT(void, cmpp_skip_snl_trailing)( unsigned char const *zBegin, |
| unsigned char const **p ){ |
| assert( *p >= zBegin ); |
| unsigned char const * z = *p; |
| /* FIXME: CRNL. */ |
| while( z>zBegin && cmpp_issnl(*z) ) --z; |
| *p = z; |
| } |
| |
| /* Set pp's error state. */ |
| static int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list); |
| /** |
| Sets pp's error state. |
| */ |
| CMPP__EXPORT(int, cmpp_err_set)(cmpp *pp, int rc, char const *zFmt, ...); |
| #define cmpp__err cmpp_err_set |
| #define cmpp_dx_err cmpp_dx_err_set |
| |
| /* Open/close pp's output channel. */ |
| static int cmpp__out_fopen(cmpp *pp, const char *zName); |
| static void cmpp__out_close(cmpp *pp); |
| |
| #define CmppKvp_empty_m \ |
| {CmppSnippet_empty_m,CmppSnippet_empty_m,CmppKvp_op_none} |
| const CmppKvp CmppKvp_empty = CmppKvp_empty_m; |
| |
| /* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just |
| supported cmpp_FILE input. */ |
| typedef struct FileWrapper FileWrapper; |
| struct FileWrapper { |
| /* File's name. */ |
| char const *zName; |
| /* cmpp_FILE handle. */ |
| cmpp_FILE * pFile; |
| /* Where FileWrapper_slurp() stores the file's contents. */ |
| unsigned char * zContent; |
| /* Size of this->zContent, as set by FileWrapper_slurp(). */ |
| cmpp_size_t nContent; |
| }; |
| #define FileWrapper_empty_m {0,0,0,0} |
| static const FileWrapper FileWrapper_empty = FileWrapper_empty_m; |
| |
| /** |
| Proxy for cmpp_fclose() and frees all memory owned by p. It is not |
| an error if p is already closed. |
| */ |
| static void FileWrapper_close(FileWrapper * p); |
| |
| /** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */ |
| static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode); |
| |
| /* Populates p->zContent and p->nContent from p->pFile. */ |
| //static int FileWrapper_slurp(FileWrapper * p, int bCloseFile ); |
| |
| /** |
| If p->zContent ends in \n or \r\n, that part is replaced with 0 and |
| p->nContent is adjusted. Returns true if it chomps, else false. |
| */ |
| static bool FileWrapper_chomp(FileWrapper * p); |
| |
| /* |
| ** Outputs a printf()-formatted message to stderr. |
| */ |
| static void g_stderrv(char const *zFmt, va_list); |
| |
| CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){ |
| switch((cmpp_rc_e)rc){ |
| #define E(N,V,H) case N: return # N; |
| cmpp_rc_e_map(E) |
| #undef E |
| } |
| return NULL; |
| } |
| |
| CMPP__EXPORT(void, cmpp_mfree)(void *p){ |
| /* This MUST be a proxy for sqlite3_free() because allocate memory |
| exclusively using sqlite3_malloc() and friends. */ |
| sqlite3_free(p); |
| } |
| |
| CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){ |
| return sqlite3_realloc64(p, n); |
| } |
| |
| CMPP__EXPORT(void *, cmpp_malloc)(size_t n){ |
| #if 1 |
| return sqlite3_malloc64(n); |
| #else |
| void * p = sqlite3_malloc64(n); |
| if( p ) memset(p, 0, n); |
| return p; |
| #endif |
| } |
| |
| cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){ |
| cmpp_FILE *f; |
| if(zName && ('-'==*zName && !zName[1])){ |
| f = (strchr(zMode, 'w') || strchr(zMode,'+')) |
| ? stdout |
| : stdin |
| ; |
| }else{ |
| f = fopen(zName, zMode); |
| } |
| return f; |
| } |
| |
| void cmpp_fclose( cmpp_FILE * f ){ |
| if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){ |
| fclose(f); |
| } |
| } |
| |
| int cmpp_slurp(cmpp_input_f fIn, void *sIn, |
| unsigned char **pOut, cmpp_size_t * nOut){ |
| unsigned char zBuf[1024 * 16]; |
| unsigned char * pDest = 0; |
| unsigned nAlloc = 0; |
| unsigned nOff = 0; |
| int rc = 0; |
| cmpp_size_t nr = 0; |
| while( 0==rc ){ |
| nr = sizeof(zBuf); |
| if( (rc = fIn(sIn, zBuf, &nr)) ){ |
| break; |
| } |
| if(nr>0){ |
| if(nAlloc < nOff + nr + 1){ |
| nAlloc = nOff + nr + 1; |
| pDest = cmpp_mrealloc(pDest, nAlloc); |
| } |
| memcpy(pDest + nOff, zBuf, nr); |
| nOff += nr; |
| }else{ |
| break; |
| } |
| } |
| if( 0==rc ){ |
| if(pDest) pDest[nOff] = 0; |
| *pOut = pDest; |
| *nOut = nOff; |
| }else{ |
| cmpp_mfree(pDest); |
| } |
| return rc; |
| } |
| |
| void FileWrapper_close(FileWrapper * p){ |
| if(p->pFile) cmpp_fclose(p->pFile); |
| if(p->zContent) cmpp_mfree(p->zContent); |
| *p = FileWrapper_empty; |
| } |
| |
| int FileWrapper_open(FileWrapper * p, const char * zName, |
| const char * zMode){ |
| FileWrapper_close(p); |
| if( (p->pFile = cmpp_fopen(zName, zMode)) ){ |
| p->zName = zName; |
| return 0; |
| }else{ |
| return cmpp_errno_rc(errno, CMPP_RC_IO); |
| } |
| } |
| |
| int FileWrapper_slurp(FileWrapper * p, int bCloseFile){ |
| assert(!p->zContent); |
| assert(p->pFile); |
| int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile, |
| &p->zContent, &p->nContent); |
| if( bCloseFile ){ |
| cmpp_fclose(p->pFile); |
| p->pFile = 0; |
| } |
| return rc; |
| } |
| |
| CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){ |
| if( *n && '\n'==z[*n-1] ){ |
| z[--*n] = 0; |
| if( *n && '\r'==z[*n-1] ){ |
| z[--*n] = 0; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool FileWrapper_chomp(FileWrapper * p){ |
| return cmpp_chomp(p->zContent, &p->nContent); |
| } |
| |
| |
| #if 0 |
| /** |
| Returns the number newline characters between the given starting |
| point and inclusive ending point. Results are undefined if zFrom is |
| greater than zTo. |
| */ |
| static unsigned cmpp__count_lines(unsigned char const * zFrom, |
| unsigned char const *zTo); |
| |
| unsigned cmpp__count_lines(unsigned char const * zFrom, |
| unsigned char const *zTo){ |
| unsigned ln = 0; |
| assert(zFrom && zTo); |
| assert(zFrom <= zTo); |
| for(; zFrom < zTo; ++zFrom){ |
| if((unsigned char)'\n' == *zFrom) ++ln; |
| } |
| return ln; |
| } |
| #endif |
| |
| char const * cmpp__tt_cstr(int tt, bool bSymbolName){ |
| switch(tt){ |
| #define E(N,TOK) case cmpp_TT_ ## N: \ |
| return bSymbolName ? "cmpp_TT_" #N : TOK; |
| cmpp_tt_map(E) |
| #undef E |
| } |
| return NULL; |
| } |
| |
| char const * cmpp_tt_cstr(int tt){ |
| return cmpp__tt_cstr(tt, true); |
| } |
| |
| /** Flags and constants related to CmppLvl. */ |
| enum CmppLvl_e { |
| /** |
| Flag indicating that all ostensible output for a CmpLevel should |
| be elided. This also suppresses non-flow-control directives from |
| being processed. |
| */ |
| CmppLvl_F_ELIDE = 0x01, |
| /** |
| Mask of CmppLvl::flags which are inherited when |
| CmppLvl_push() is used. |
| */ |
| CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE |
| }; |
| |
| //static const CmppDLine CmppDLine_empty = CmppDLine_empty_m; |
| |
| /** Free all memory owned by li but does not free li. */ |
| static void CmppLvlList_cleanup(CmppLvlList *li); |
| |
| /** |
| Allocate a list entry, owned by li, and return it (cleanly zeroed |
| out). Returns NULL and updates pp->err on error. It is expected |
| that the caller will populate the entry's zName using |
| sqlite3_mprintf() or equivalent. |
| */ |
| static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li); |
| |
| /** Returns the most-recently-appended element of li back to li's |
| free-list. It expects to receive that value as a sanity-checking |
| measure and may fail fatally of that's not upheld. */ |
| static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl); |
| |
| static const cmpp_dx_pimpl cmpp_dx_pimpl_empty = |
| cmpp_dx_pimpl_empty_m; |
| |
| #define cmpp_dx_empty_m { \ |
| .pp=0, \ |
| .d=0, \ |
| .sourceName=0, \ |
| .args={ \ |
| .z=0, .nz=0, \ |
| .argc=0, .arg0=0 \ |
| }, \ |
| .pimpl = 0 \ |
| } |
| |
| const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m; |
| #define cmpp_d_empty_m {{0,0},0,0,cmpp_d_impl_empty_m} |
| //static const cmpp_d cmpp_d_empty = cmpp_d_empty_m; |
| |
| static const CmppDList_entry CmppDList_entry_empty = |
| CmppDList_entry_empty_m; |
| |
| /** Free all memory owned by li but does not free li. */ |
| static void CmppDList_cleanup(CmppDList *li); |
| /** |
| Allocate a list entry, owned by li, and return it (cleanly zeroed |
| out). Returns NULL and updates pp->err on error. It is expected |
| that the caller will populate the entry's zName using |
| sqlite3_mprintf() or equivalent. |
| */ |
| static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li); |
| /** Returns the most-recently-appended element of li back to li's |
| free-list. */ |
| static void CmppDList_unappend(CmppDList *li); |
| /** Resets li's list for re-use but does not free it. Returns li. */ |
| //static CmppDList * CmppDList_reuse(CmppDList *li); |
| static CmppDList_entry * CmppDList_search(CmppDList const * li, |
| char const *zName); |
| |
| /** Reset dx and free any memory it may own. */ |
| static void cmpp_dx_cleanup(cmpp_dx * const dx); |
| /** |
| Reset some of dx's parsing-related state in prep for fetching the |
| next line. |
| */ |
| static void cmpp_dx__reset(cmpp_dx * const dx); |
| |
| /* Returns dx's current directive. */ |
| static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){ |
| return dx->d; |
| } |
| |
| static const cmpp_pimpl cmpp_pimpl_empty = { |
| .db = { |
| .dbh = 0, |
| .zName = 0 |
| }, |
| .dx = 0, |
| .out = cmpp_outputer_empty_m, |
| .delim = { |
| .d = cmpp__delim_list_empty_m, |
| .at = cmpp__delim_list_empty_m |
| }, |
| .stmt = { |
| #define E(N,S) .N = 0, |
| CmppStmt_map(E) |
| #undef E |
| }, |
| .err = { |
| .code = 0, |
| .zMsg = 0, |
| .zMsgC = 0 |
| }, |
| .sqlTrace = { |
| .expandSql = false, |
| .counter = 0, |
| .out = cmpp_outputer_empty_m |
| }, |
| .flags = { |
| .allocStamp = 0, |
| .nIncludeDir = 0, |
| .nDxDepth = 0, |
| .nSavepoint = 0, |
| .doDebug = 0, |
| .chompF = 0, |
| .newFlags = 0, |
| .isInternalDirectiveReg = false, |
| .nextIsCall = false, |
| .needsLazyInit = true |
| }, |
| .policy = { |
| .at = {0,0,0}, |
| .un = {0,0,0} |
| }, |
| .d = { |
| .list = CmppDList_empty_m, |
| .autoload = cmpp_d_autoloader_empty_m |
| }, |
| .mod = { |
| .sohList = CmppSohList_empty_m, |
| .path = cmpp_b_empty_m, |
| .soExt = CMPP_PLATFORM_EXT_DLL, |
| /* Yes, '*'. It makes sense in context. */ |
| .pathSep = '*' |
| // 0x1e /* "record separator" */ doesn't work. Must be non-ctrl. |
| }, |
| .recycler = { |
| .buf = cmpp_b_list_empty_m, |
| .bufSort = cmpp_b_list_UNSORTED, |
| .argPimpl = NULL |
| } |
| }; |
| |
| #if 0 |
| static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){ |
| return cmpp__out2(pp, &pp->out, z, n); |
| } |
| #endif |
| |
| /** |
| Returns an approximate cmpp_tt for the given SQLITE_... value from |
| sqlite3_column_type() or sqlite3_value_type(). |
| */ |
| static cmpp_tt cmpp__tt_for_sqlite(int sqType); |
| |
| /** |
| Init code which is usually run as part of the ctor but may have to |
| be run later, after cmpp_reset(). We can't run it from cmpp_reset() |
| because that could leave post-reset in an error state, which is |
| icky. |
| */ |
| int cmpp__lazy_init(cmpp *pp){ |
| if( !ppCode && pp->pimpl->flags.needsLazyInit ){ |
| pp->pimpl->flags.needsLazyInit = false; |
| cmpp__delim_list * li = &pp->pimpl->delim.d; |
| if( !li->n ) cmpp_delimiter_push(pp, NULL); |
| li = &pp->pimpl->delim.at; |
| if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL); |
| #if defined(CMPP_CTOR_INSTANCE_INIT) |
| if( !ppCode ){ |
| extern int CMPP_CTOR_INSTANCE_INIT(cmpp*); |
| int const rc = CMPP_CTOR_INSTANCE_INIT(pp); |
| if( rc && !ppCode ){ |
| cmpp__err(pp, rc, |
| "Initialization via CMPP_CTOR_INSTANCE_INIT() failed " |
| "with code %d/%s.", rc, cmpp_rc_cstr(rc) ); |
| } |
| } |
| #endif |
| } |
| return ppCode; |
| } |
| |
| static void cmpp__wipe_policies(cmpp *pp){ |
| if( 0==ppCode ){ |
| PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0); |
| PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0); |
| if( 0==ppCode ){ |
| PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT); |
| PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT); |
| } |
| } |
| } |
| |
| CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_cfg const * cfg){ |
| cmpp_pimpl * pi = 0; |
| cmpp * pp = 0; |
| void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi)); |
| if( mv ){ |
| if( !cfg ){ |
| static const cmpp_ctor_cfg dfltCfg = {0}; |
| cfg = &dfltCfg; |
| } |
| cmpp const x = { |
| .api = &cmppApiMethods, |
| .pimpl = (cmpp_pimpl*)((unsigned char *)mv + sizeof(cmpp)) |
| /* ^^^ (T const * const) members */ |
| }; |
| memcpy(mv, &x, sizeof(x)) |
| /* FWIW, i'm convinced that this is a legal way to transfer |
| these const-pointers-to-const. If not, we'll need to change |
| those cmpp members from (T const * const) to (T const *). */; |
| pp = mv; |
| assert(pp->api == &cmppApiMethods); |
| assert(pp->pimpl); |
| pi = pp->pimpl; |
| *pOut = pp; |
| *pi = cmpp_pimpl_empty; |
| assert( pi->flags.needsLazyInit ); |
| pi->flags.newFlags = cfg->flags; |
| pi->flags.allocStamp = &cmpp_pimpl_empty; |
| if( cfg->dbFile ){ |
| pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile); |
| cmpp_check_oom(pp, pi->db.zName); |
| } |
| if( 0==ppCode ){ |
| cmpp__wipe_policies(pp); |
| cmpp__lazy_init(pp); |
| } |
| } |
| return pp ? ppCode : CMPP_RC_OOM; |
| } |
| |
| CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){ |
| cmpp__pi(pp); |
| cmpp_outputer_cleanup(&pi->sqlTrace.out); |
| pi->sqlTrace.out = cmpp_outputer_empty; |
| if( pi->d.autoload.dtor ){ |
| pi->d.autoload.dtor(pi->d.autoload.state); |
| } |
| pi->d.autoload = cmpp_pimpl_empty.d.autoload; |
| cmpp_b_clear(&pi->mod.path); |
| if( pi->stmt.spRelease && pi->stmt.spRollback ){ |
| /* Cleanly kill all savepoint levels. This is truly superfluous, |
| as they'll all be rolled back (if the db is persistent) or |
| nuked (if using a :memory: db) momentarily. However, we'll |
| eventually need this for a partial-clear operation which leaves |
| the db and custom directives intact. For now it lives here but |
| will eventually move to wherever that ends up being. |
| |
| 2025-11-16: or not. It's fine here, really. |
| */ |
| sqlite3_reset(pi->stmt.spRelease); |
| while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){ |
| sqlite3_reset(pi->stmt.spRollback); |
| sqlite3_step(pi->stmt.spRollback); |
| sqlite3_reset(pi->stmt.spRelease); |
| } |
| } |
| cmpp__out_close(pp); |
| CmppDList_cleanup(&pi->d.list); |
| #define E(N,S) \ |
| if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;} |
| CmppStmt_map(E) (void)0; |
| #undef E |
| if( pi->db.dbh ){ |
| if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){ |
| sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL) |
| /* ignoring error */; |
| } |
| sqlite3_close(pi->db.dbh); |
| pi->db.dbh = 0; |
| } |
| cmpp__delim_list_reuse(&pi->delim.d); |
| cmpp__delim_list_reuse(&pi->delim.at); |
| //why? cmpp_b_list_reuse(&pi->cache.buf); |
| cmpp__err_clear(pp); |
| {/* Zero out pi but save some pieces for later, when pp is |
| cmpp_dtor()'d */ |
| cmpp_pimpl const tmp = *pi; |
| *pi = cmpp_pimpl_empty; |
| pi->db = tmp.db /* restore db.zName */; |
| pi->recycler = tmp.recycler; |
| pi->policy = tmp.policy; |
| pi->delim = tmp.delim; |
| pi->mod.sohList = tmp.mod.sohList; |
| cmpp__wipe_policies(pp); |
| pi->flags.allocStamp = tmp.flags.allocStamp; |
| pi->flags.newFlags = tmp.flags.newFlags; |
| pi->flags.needsLazyInit = true; |
| } |
| } |
| |
| static void cmpp__delim_list_cleanup(cmpp__delim_list *li); |
| |
| CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){ |
| if( pp ){ |
| cmpp__pi(pp); |
| cmpp_reset(pp); |
| cmpp_mfree(pi->db.zName); |
| PodList__atpol_finalize(&cmpp__epol(pp,at)); |
| assert(!cmpp__epol(pp,at).na); |
| PodList__unpol_finalize(&cmpp__epol(pp,un)); |
| assert(!cmpp__epol(pp,un).na); |
| cmpp_b_list_cleanup(&pi->recycler.buf); |
| cmpp__delim_list_cleanup(&pi->delim.d); |
| cmpp__delim_list_cleanup(&pi->delim.at); |
| for( cmpp_args_pimpl * apNext = 0, |
| * ap = pi->recycler.argPimpl; |
| ap; ap = apNext ){ |
| apNext = ap->nextFree; |
| ap->nextFree = 0; |
| cmpp_args_pimpl_cleanup(ap); |
| cmpp_mfree(ap); |
| } |
| CmppSohList_close(&pi->mod.sohList); |
| if( &cmpp_pimpl_empty==pi->flags.allocStamp ){ |
| pi->flags.allocStamp = 0; |
| cmpp_mfree(pp); |
| } |
| } |
| } |
| |
| CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){ |
| return pp ? 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) : false; |
| } |
| |
| /** Sets ppCode if m is NULL. Returns ppCode. */ |
| CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){ |
| int rc; |
| if( pp ){ |
| if( !m ){ |
| //assert(!"oom"); |
| cmpp__err(pp, CMPP_RC_OOM, 0); |
| } |
| rc = ppCode; |
| }else{ |
| rc = m ? 0 : CMPP_RC_OOM; |
| } |
| return rc; |
| } |
| |
| //CxMPP_WASM_EXPORT |
| void *cmpp__malloc(cmpp *pp, cmpp_size_t n){ |
| void *p = 0; |
| if( 0==ppCode ){ |
| p = cmpp_malloc(n); |
| cmpp_check_oom(pp, p); |
| } |
| return p; |
| } |
| |
| /** |
| If ppCode is not 0 then it flushes pp's output channel. If that |
| fails, it sets ppCode. Returns ppCode. |
| */ |
| static int cmpp__flush(cmpp *pp){ |
| if( !ppCode && pp->pimpl->out.flush ){ |
| int const rc = pp->pimpl->out.flush(pp->pimpl->out.state); |
| if( rc && !ppCode ){ |
| cmpp_err_set(pp, rc, "Flush failed."); |
| } |
| } |
| return ppCode; |
| } |
| |
| void cmpp__out_close(cmpp *pp){ |
| cmpp__flush(pp)/*ignoring result*/; |
| cmpp_outputer_cleanup(&pp->pimpl->out); |
| pp->pimpl->out = cmpp_pimpl_empty.out; |
| } |
| |
| int cmpp__out_fopen(cmpp *pp, const char *zName){ |
| cmpp__out_close(pp); |
| if( !ppCode ){ |
| cmpp_FILE * const f = cmpp_fopen(zName, "wb"); |
| if( f ){ |
| ppCode = 0; |
| pp->pimpl->out = cmpp_outputer_FILE; |
| pp->pimpl->out.state = f; |
| pp->pimpl->out.name = zName; |
| }else{ |
| ppCode = cmpp__err( |
| pp, cmpp_errno_rc(errno, CMPP_RC_IO), |
| "Error opening file %s", zName |
| ); |
| } |
| } |
| return ppCode; |
| } |
| |
| static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw, |
| const char * zName, |
| const char * zMode){ |
| int const rc = FileWrapper_open(fw, zName, zMode); |
| if( rc ){ |
| cmpp__err(pp, rc, "Error %s opening file [%s] " |
| "with mode [%s]", |
| cmpp_rc_cstr(rc), zName, zMode); |
| } |
| return ppCode; |
| } |
| |
| static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){ |
| assert( fw->pFile ); |
| int const rc = FileWrapper_slurp(fw, 1); |
| if( rc ){ |
| cmpp__err(pp, rc, "Error %s slurping file %s", |
| cmpp_rc_cstr(rc), fw->zName); |
| } |
| return ppCode; |
| } |
| |
| void g_stderrv(char const *zFmt, va_list va){ |
| vfprintf(0 ? stdout : stderr, zFmt, va); |
| } |
| |
| void g_stderr(char const *zFmt, ...){ |
| va_list va; |
| va_start(va, zFmt); |
| g_stderrv(zFmt, va); |
| va_end(va); |
| } |
| |
| CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){ |
| return (char const *)cmpp__dx_zdelim(dx); |
| } |
| |
| int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, |
| void const *z, cmpp_size_t n){ |
| assert( pOut ); |
| if( !ppCode && pOut->out && n ){ |
| int const rc = pOut->out(pOut->state, z, n); |
| if( rc ){ |
| cmpp__err(pp, rc, |
| "Write of %" CMPP_SIZE_T_PFMT |
| " bytes to output stream failed.", n); |
| } |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_out_raw)(cmpp_dx * dx, void const *z, cmpp_size_t n){ |
| if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode; |
| return cmpp__out2(dx->pp, &dx->pp->pimpl->out, z, n); |
| } |
| |
| CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){ |
| assert( out ); |
| if( !ppCode && zFmt && *zFmt && out->out ){ |
| char * s = sqlite3_vmprintf(zFmt, va); |
| if( 0==cmpp_check_oom(pp, s) ){ |
| cmpp__out2(pp, out, s, cmpp__strlen(s, -1)); |
| } |
| cmpp_mfree(s); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){ |
| assert( out ); |
| if( !ppCode && zFmt && *zFmt && out->out ){ |
| va_list va; |
| va_start(va, zFmt); |
| cmpp_outfv2(pp, out, zFmt, va); |
| va_end(va); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){ |
| return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va); |
| } |
| |
| CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){ |
| if( !ppCode ){ |
| va_list va; |
| va_start(va, zFmt); |
| cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va); |
| va_end(va); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_outf)(cmpp_dx *dx, char const *zFmt, ...){ |
| if( !dxppCode && zFmt && *zFmt && dx->pp->pimpl->out.out ){ |
| va_list va; |
| va_start(va, zFmt); |
| cmpp_outfv(dx->pp, zFmt, va); |
| va_end(va); |
| } |
| return dxppCode; |
| } |
| |
| static int cmpp__affirm_undef_policy(cmpp *pp, |
| unsigned char const *zName, |
| cmpp_size_t nName){ |
| if( 0==ppCode |
| && cmpp_unpol_ERROR==cmpp__policy(pp,un) ){ |
| cmpp__err(pp, CMPP_RC_NOT_DEFINED, |
| "Key '%.*s' was not found and the undefined-value " |
| "policy is 'error'.", |
| (int)nName, zName); |
| } |
| return ppCode; |
| } |
| |
| static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut, |
| unsigned char const * zFrom, |
| cmpp_size_t n, cmpp_atpol_e atPolicy){ |
| enum state_e { |
| /* looking for @token@ opening @ */ |
| state_opening, |
| /* looking for @token@ closing @ */ |
| state_closing |
| }; |
| cmpp__pi(pp); |
| if( ppCode ) return ppCode; |
| if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at); |
| assert( cmpp_atpol_invalid!=atPolicy ); |
| unsigned char const *zLeft = zFrom; |
| unsigned char const * const zEnd = zFrom + n; |
| unsigned char const *z = |
| (cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy) |
| ? zEnd |
| : zLeft; |
| unsigned char const chEol = (unsigned char)'\n'; |
| cmpp__delim const * delim = |
| cmpp__delim_list_get(&pp->pimpl->delim.at); |
| if( !delim && z<zEnd ){ |
| return cmpp_err_set(pp, CMPP_RC_ERROR, |
| "@token@ delimiter stack is empty."); |
| } |
| enum state_e state = state_opening; |
| cmpp_b * const bCall = cmpp_b_borrow(pp); |
| cmpp_b * const bVal = cmpp_b_borrow(pp); |
| if( !bCall || !bVal ) return ppCode; |
| if(0) g_warn("looking to expand from %d bytes: [%.*s]", (int)n, |
| (int)n,zLeft); |
| if( !pOut ){ |
| if( 0 && atPolicy!=cmpp__policy(pp,at) ){ |
| /* This might be too strict. It was initially in place to ensure |
| that we did not _accidentally_ do @token@ parsing to the main |
| output channel. We frequently use it internally on other |
| output channels to buffer/build results. |
| |
| Advantage to removing this check: #query could impose |
| its own at-policy without having to use an intermediary |
| buffer. |
| |
| Disadvantage: less protection against accidentally |
| @-filtering the output when we shouldn't. |
| */ |
| return cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "%s(): when sending to the default " |
| "output channel, the @policy must be " |
| "cmpp_atpol_CURRENT.", __func__); |
| } |
| pOut = &pi->out; |
| } |
| assert( pi->dx ? !cmpp_dx_is_eliding(pi->dx) : 1 ); |
| |
| #define tflush \ |
| if(z>zEnd) z=zEnd; \ |
| if(zLeft<z){ \ |
| if(0) g_warn("flush %d [%.*s]", (int)(z-zLeft), (int)(z-zLeft), zLeft); \ |
| cmpp__out2(pp, pOut, zLeft, (z-zLeft)); \ |
| } zLeft = z |
| cmpp_dx_pimpl * const dxp = pp->pimpl->dx ? pp->pimpl->dx->pimpl : NULL; |
| for( ; z<zEnd && 0==ppCode; ++z ){ |
| zLeft = z; |
| for( ;z<zEnd && 0==ppCode; ++z ){ |
| again: |
| if( chEol==*z ){ |
| #if 0 |
| broken; |
| if( dxp && dxp->flags.countLines ){ |
| ++dxp->lineNo; |
| } |
| #endif |
| state = state_opening; |
| continue; |
| } |
| if( state_opening==state ){ |
| if( z + delim->open.n < zEnd |
| && 0==memcmp(z, delim->open.z, delim->open.n) ){ |
| tflush; |
| z += delim->open.n; |
| if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft); |
| if( 0 ){ |
| g_warn("\nzLeft..=[%s]\nz=[%s]", zLeft, z); |
| } |
| state = state_closing; |
| #if 1 |
| /* Handle call of @[directive ...args]@ |
| |
| i'm not a huge fan of this syntax, but that may go away |
| if we replace the single-char separator with a pair of |
| opening/closing delimiters. |
| */ |
| if( z<zEnd && '['==*z ){ |
| unsigned char const * zb = z; |
| cmpp_size_t nl = 0; |
| //g_warn("Scanning: <<%.*s>>", (int)(zEnd-zb), zb); |
| if( cmpp__find_closing2(pp, &zb, zEnd, &nl) ){ |
| break; |
| } |
| //g_warn("Found: <<%.*s>>", (int)(zb+1-z), z); |
| if( zb + delim->close.n >= zEnd |
| || 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){ |
| serr("Expecting '%s' after closing ']'.", delim->close.z); |
| break; |
| } |
| if( nl && dxp && dxp->flags.countLines ){ |
| dxp->pos.lineNo +=nl; |
| } |
| cmpp_call_str(pp, z+delim->open.n, |
| (zb - z - delim->open.n), |
| cmpp_b_reuse(bCall), 0); |
| if( 0==ppCode ){ |
| cmpp__out2(pp, pOut, bCall->z, bCall->n); |
| state = state_opening; |
| zLeft = z = zb + delim->close.n + 1; |
| //g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z); |
| } |
| } |
| #endif |
| if( z>=zEnd ) break; |
| goto again /* avoid adjusting z again */; |
| } |
| }else{/*we're looking for delim->closer*/ |
| assert( state_closing==state ); |
| if( z + delim->close.n <= zEnd |
| && 0==memcmp(z, delim->close.z, delim->close.n ) ){ |
| /* process the ... part of @...@ */ |
| assert( state_closing==state ); |
| assert( zLeft<z ); |
| assert( z<=zEnd ); |
| if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft); |
| if( 0 ) g_warn("zLeft..=%s", zLeft); |
| assert( 0==memcmp(zLeft, delim->open.z, delim->open.n) ); |
| unsigned char const *zKey = |
| zLeft + delim->open.n; |
| cmpp_ssize_t const nKey = z - zLeft - delim->open.n; |
| if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey); |
| assert( nKey>= 0 ); |
| if( !nKey ){ |
| serr("Empty key is not permitted in %s...%s.", |
| delim->open.z, delim->close.z); |
| break; |
| } |
| if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){ |
| if(0){ |
| g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n, |
| (int)bVal->n, bVal->z); |
| } |
| if( bVal->n ){ |
| cmpp__out2(pp, pOut, bVal->z, bVal->n); |
| }else{ |
| /* Elide it */ |
| } |
| zLeft = z + delim->close.n; |
| assert( zLeft<=zEnd ); |
| }else if( !ppCode ){ |
| assert( !bVal->n ); |
| /* No matching define . */ |
| switch( atPolicy ){ |
| case cmpp_atpol_ELIDE: zLeft = z + delim->close.n; break; |
| case cmpp_atpol_RETAIN: tflush; break; |
| case cmpp_atpol_ERROR: |
| cmpp__err(pp, CMPP_RC_NOT_DEFINED, |
| "Undefined %skey%s: %.*s", |
| delim->open.z, delim->close.z, nKey, zKey); |
| break; |
| case cmpp_atpol_invalid: |
| case cmpp_atpol_CURRENT: |
| case cmpp_atpol_OFF: |
| assert(!"this shouldn't be reachable" ); |
| cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d", |
| atPolicy); |
| break; |
| } |
| }/* process @...@ */ |
| state = state_opening; |
| assert( z<=zEnd ); |
| }/*matched a closer*/ |
| }/*state_closer==state*/ |
| assert( z<=zEnd ); |
| }/*per-line loop*/ |
| }/*outer loop*/ |
| #if 0 |
| if( 0==ppCode && state_closer==state ){ |
| serr("Opening '%s' found without a closing '%s'.", |
| delim->open.z, delim->close.z); |
| } |
| #endif |
| tflush; |
| #undef tflush |
| cmpp_b_return(pp, bCall); |
| cmpp_b_return(pp, bVal); |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_out_expand)(cmpp_dx const * const dx, |
| cmpp_outputer * pOut, |
| unsigned char const * zFrom, |
| cmpp_size_t n, |
| cmpp_atpol_e atPolicy){ |
| if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode; |
| return cmpp__out_expand(dx->pp, pOut, zFrom, n, atPolicy); |
| } |
| |
| CmppLvl * CmppLvl_get(cmpp_dx const *dx){ |
| return dx->pimpl->dxLvl.n |
| ? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1] |
| : 0; |
| } |
| |
| static const CmppLvl CmppLvl_empty = CmppLvl_empty_m; |
| |
| CmppLvl * CmppLvl_push(cmpp_dx *dx){ |
| CmppLvl * p = 0; |
| if( !dxppCode ){ |
| CmppLvl * const pPrev = CmppLvl_get(dx); |
| p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl); |
| if( p ){ |
| *p = CmppLvl_empty; |
| p->lineNo = dx->pimpl->dline.lineNo; |
| //p->d = dx->d; |
| if( pPrev ){ |
| p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags); |
| //if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE; |
| } |
| } |
| } |
| return p; |
| } |
| |
| void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){ |
| CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl); |
| } |
| |
| void CmppLvl_elide(CmppLvl *lvl, bool on){ |
| if( on ) lvl->flags |= CmppLvl_F_ELIDE; |
| else lvl->flags &= ~CmppLvl_F_ELIDE; |
| } |
| |
| bool CmppLvl_is_eliding(CmppLvl const *lvl){ |
| return lvl && !!(lvl->flags & CmppLvl_F_ELIDE); |
| } |
| |
| #if 0 |
| void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){ |
| CmppLvl_elide(CmppLvl_get(dx), on); |
| } |
| #endif |
| |
| bool cmpp_dx_is_eliding(cmpp_dx const *dx){ |
| return CmppLvl_is_eliding(CmppLvl_get(dx)); |
| } |
| |
| |
| char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n){ |
| char * z = 0; |
| int const rc = sqlite3_str_errcode(s); |
| cmpp__db_rc(pp, rc, "sqlite3_str_errcode()"); |
| if(0==rc){ |
| int const nStr = sqlite3_str_length(s); |
| if(n) *n = nStr; |
| z = sqlite3_str_finish(s); |
| if( !z ){ |
| assert( 0==nStr && "else rc!=0" ); |
| } |
| }else{ |
| cmpp_mfree( sqlite3_str_finish(s) ); |
| } |
| return z; |
| } |
| |
| int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val){ |
| return ppCode |
| ? ppCode |
| : cmpp__db_rc(pp, sqlite3_bind_int64(pStmt, col, val), |
| "from cmpp__bind_int()"); |
| } |
| |
| int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, |
| int64_t val){ |
| unsigned char buf[32]; |
| snprintf((char *)buf, sizeof(buf), "%" PRIi64, val); |
| return cmpp__bind_textn(pp, pStmt, col, buf, -1); |
| } |
| |
| int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col){ |
| return ppCode |
| ? ppCode |
| : cmpp__db_rc(pp, sqlite3_bind_null(pStmt, col), |
| "from cmpp__bind_null()"); |
| } |
| |
| static int cmpp__bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col, |
| unsigned const char * zStr, cmpp_ssize_t n, |
| void (*dtor)(void *)){ |
| if( 0==ppCode ){ |
| cmpp__db_rc( |
| pp, (zStr && n) |
| ? sqlite3_bind_text(pStmt, col, |
| (char const *)zStr, |
| (int)n, dtor) |
| : sqlite3_bind_null(pStmt, col), |
| sqlite3_sql(pStmt) |
| ); |
| } |
| return ppCode; |
| } |
| |
| int cmpp__bind_textn(cmpp *pp, sqlite3_stmt *pStmt, int col, |
| unsigned const char * zStr, cmpp_ssize_t n){ |
| return cmpp__bind_textx(pp, pStmt, col, zStr, (int)n, |
| SQLITE_TRANSIENT); |
| } |
| |
| int cmpp__bind_text(cmpp *pp, sqlite3_stmt *pStmt, int col, |
| unsigned const char * zStr){ |
| return cmpp__bind_textn(pp, pStmt, col, zStr, -1); |
| } |
| |
| #if 0 |
| int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col, |
| const char * zFmt, ...){ |
| if( 0==p->err.code ){ |
| int rc; |
| sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh); |
| int n = 0; |
| char * z; |
| va_list va; |
| if( !str ) return ppCode; |
| va_start(va,zFmt); |
| sqlite3_str_vappendf(str, zFmt, va); |
| va_end(va); |
| z = cmpp_str_finish(str, &n); |
| cmpp__db_rc( |
| pp, z |
| ? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free) |
| : sqlite3_bind_null(pStmt, col), |
| sqlite3_sql(pStmt) |
| ); |
| cmpp_mfree(z); |
| } |
| return p->err.code; |
| } |
| #endif |
| |
| void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, |
| char const *zName){ |
| cmpp__pi(pp); |
| cmpp_outputer_cleanup(&pi->out); |
| if( out ) pi->out = *out; |
| else pi->out = cmpp_outputer_empty; |
| pi->out.name = zName; |
| } |
| |
| void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew, |
| cmpp_outputer *oPrev){ |
| if( oPrev ){ |
| *oPrev = pp->pimpl->out; |
| } |
| pp->pimpl->out = *oNew; |
| } |
| |
| #if 0 |
| static void delim__list_dump(cmpp const *pp){ |
| cmpp__delim_list const *li = &pp->pimpl->delim.d; |
| if( li->n ){ |
| g_warn0("delimiter stack:"); |
| for(cmpp_size_t i = 0; i < li->n; ++i ){ |
| g_warn("#%d: %s", (int)i, li->list[i].z); |
| } |
| } |
| |
| } |
| #endif |
| |
| static bool cmpp__valid_delim(cmpp * const pp, |
| char const *z, |
| char const *zEnd){ |
| char const * const zB = z; |
| for( ; z < zEnd; ++z ){ |
| if( *z<33 || 127==*z ){ |
| cmpp_err_set(pp, CMPP_RC_SYNTAX, |
| "Delimiters may not contain " |
| "control characters."); |
| return false; |
| } |
| } |
| if( zB==z ){ |
| cmpp_err_set(pp, CMPP_RC_SYNTAX, |
| "Delimiters may not be empty."); |
| } |
| return z>zB; |
| } |
| |
| CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){ |
| if( ppCode ) return ppCode; |
| unsigned n; |
| if( zDelim ){ |
| n = cmpp__strlen(zDelim, -1); |
| if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){ |
| return ppCode; |
| }else if( n>12 /* arbitrary but seems sensible enough */ ){ |
| return cmpp__err(pp, CMPP_RC_MISUSE, |
| "Invalid delimiter (too long): %s", zDelim); |
| } |
| } |
| cmpp__pi(pp); |
| if( pi->delim.d.n ){ |
| cmpp__delim * const delim = cmpp__pp_delim(pp); |
| if( !cmpp_check_oom(pp, delim) ){ |
| cmpp__delim_cleanup(delim); |
| if( zDelim ){ |
| delim->open.n = n; |
| delim->open.z = delim->zOwns = |
| (unsigned char*)sqlite3_mprintf("%.*s", n, zDelim); |
| cmpp_check_oom(pp, delim->zOwns); |
| }else{ |
| assert( delim->open.z ); |
| assert( !delim->zOwns ); |
| assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 ); |
| } |
| } |
| }else{ |
| assert(!"Cannot set delimiter on an empty stack!"); |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "Directive delimter stack is empty."); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){ |
| cmpp__delim const * d = cmpp__pp_delim(pp); |
| if( !d ) d = &cmpp__delim_empty; |
| *zDelim = (char const *)d->open.z; |
| } |
| |
| CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){ |
| cmpp__delim * const d = |
| cmpp__delim_list_push(pp, &pp->pimpl->delim.d); |
| if( d && cmpp_delimiter_set(pp, zDelim) ){ |
| cmpp__delim_list_pop(&pp->pimpl->delim.d); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){ |
| cmpp__delim_list * const li = &pp->pimpl->delim.d; |
| if( li->n ){ |
| //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp)); |
| cmpp__delim_list_pop(li); |
| if( 0 && li->n ){ |
| g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp)); |
| } |
| }else if( !ppCode ){ |
| assert(!"Attempt to pop an empty delimiter stack."); |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "Cannot pop an empty delimiter stack."); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp, |
| char const *zOpen, |
| char const *zClose){ |
| if( 0==ppCode ){ |
| cmpp__pi(pp); |
| cmpp__delim * const d = pi->delim.at.n |
| ? &pi->delim.at.list[pi->delim.at.n-1] |
| : NULL; |
| assert( d ); |
| if( !d ){ |
| return cmpp__err(pp, CMPP_RC_MISUSE, |
| "@token@ delimiter stack is currently empty."); |
| } |
| if( 0==zOpen ){ |
| zOpen = (char const *)delimAtDefault.open.z; |
| zClose = (char const *)delimAtDefault.close.z; |
| }else if( 0==zClose ){ |
| zClose = zOpen; |
| } |
| cmpp_size_t const nO = cmpp__strlen(zOpen, -1); |
| cmpp_size_t const nC = cmpp__strlen(zClose, -1); |
| assert( zOpen && zClose ); |
| if( !cmpp__valid_delim(pp, zOpen, zOpen+nO) |
| || !cmpp__valid_delim(pp, zClose, zClose+nC) ){ |
| return ppCode; |
| } |
| cmpp_b b = cmpp_b_empty |
| /* Don't use cmpp_b_borrow() here because we'll unconditionally |
| transfer ownership of b.z to d. */; |
| if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){ |
| #ifndef NDEBUG |
| unsigned char const * const zReallocCheck = b.z; |
| #endif |
| /* Copy the open/close tokens to a single string to simplify |
| management. */ |
| cmpp_b_append4(pp, &b, zOpen, nO); |
| cmpp_b_append_ch(&b, '\0'); |
| cmpp_b_append4(pp, &b, zClose, nC); |
| assert( zReallocCheck==b.z |
| && "Else buffer was not properly pre-sized" ); |
| cmpp__delim_cleanup(d); |
| d->open.z = b.z; |
| d->open.n = nO; |
| d->close.z = d->open.z + nO + 1/*NUL*/; |
| d->close.n = nC; |
| d->zOwns = b.z; |
| b = cmpp_b_empty /* transfer memory ownership */; |
| } |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen, |
| char const *zClose){ |
| cmpp__delim * const d = |
| cmpp__delim_list_push(pp, &pp->pimpl->delim.at); |
| if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){ |
| cmpp__delim_list_pop(&pp->pimpl->delim.at); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){ |
| cmpp__delim_list * const li = &pp->pimpl->delim.at; |
| if( li->n ){ |
| //g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp)); |
| cmpp__delim_list_pop(li); |
| }else if( !ppCode ){ |
| assert(!"Attempt to pop an empty @token@ delim stack."); |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "Cannot pop an empty @token@ delimiter stack."); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp, |
| char const **zOpen, |
| char const **zClose){ |
| cmpp__delim const * d |
| = cmpp__delim_list_get(&pp->pimpl->delim.at); |
| assert( d ); |
| if( !d ) d = &delimAtDefault; |
| if( zClose ) *zClose = (char const *)d->close.z; |
| if( zOpen ) *zOpen = (char const *)d->open.z; |
| } |
| |
| #define cmpp__scan_int2(SZ,PFMT,Z,N,TGT) \ |
| (N<SZ) && 1==sscanf((char const *)Z, "%" #SZ PFMT, TGT) |
| |
| bool cmpp__is_int(unsigned char const *z, unsigned n, |
| int *pOut){ |
| int d = 0; |
| return cmpp__scan_int2(16, PRIi32, z, n, pOut ? pOut : &d); |
| } |
| |
| bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut){ |
| int64_t d = 0; |
| return cmpp__scan_int2(24, PRIi64, z, n, pOut ? pOut : &d); |
| } |
| #undef cmpp__scan_int2 |
| |
| /** |
| 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_ssize_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__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__bind_textx(pp, q, 3, fw.zContent, |
| (cmpp_ssize_t)fw.nContent, sqlite3_free); |
| fw.zContent = 0 /* transferred ownership */; |
| fw.nContent = 0; |
| }else{ |
| cmpp__bind_null(pp, q, 2); |
| } |
| cmpp__step(pp, q, true); |
| g_debug(pp,2,("define: %s%s%s\n", |
| kvp.k.z, |
| kvp.v.z ? " with value " : "", |
| kvp.v.z ? (char const *)kvp.v.z : "")); |
| } |
| FileWrapper_close(&fw); |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_has)(cmpp *pp, const char * zName, cmpp_ssize_t nName){ |
| int rc = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false); |
| if( q ){ |
| nName = cmpp__strlen(zName, nName); |
| cmpp__bind_textn(pp, q, 1, ustr_c(zName), nName); |
| if(SQLITE_ROW == cmpp__step(pp, q, true)){ |
| rc = 1; |
| }else{ |
| rc = 0; |
| } |
| g_debug(pp,1,("has [%s] ?= %d\n",zName, rc)); |
| } |
| return rc; |
| } |
| |
| int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_ssize_t nName){ |
| int rc = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false); |
| if( q ){ |
| nName = cmpp__strlenu(zName, nName); |
| cmpp__bind_textn(pp, q, 1, zName, nName); |
| assert(0==ppCode); |
| if(SQLITE_ROW == cmpp__step(pp, q, false)){ |
| rc = sqlite3_column_int(q, 0); |
| }else{ |
| rc = 0; |
| cmpp__affirm_undef_policy(pp, zName, nName); |
| } |
| cmpp__stmt_reset(q); |
| } |
| return rc; |
| } |
| |
| int cmpp__get_int(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, int *pOut ){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false); |
| if( q ){ |
| nName = cmpp__strlenu(zName, nName); |
| cmpp__bind_textn(pp, q, 1, zName, nName); |
| assert(0==ppCode); |
| if(SQLITE_ROW == cmpp__step(pp, q, false)){ |
| *pOut = sqlite3_column_int(q,0); |
| }else{ |
| cmpp__affirm_undef_policy(pp, zName, nName); |
| } |
| cmpp__stmt_reset(q); |
| } |
| return ppCode; |
| } |
| |
| int cmpp__get_b(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, cmpp_b * os, bool enforceUndefPolicy){ |
| int rc = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false); |
| if( q ){ |
| nName = cmpp__strlenu(zName, nName); |
| cmpp__bind_textn(pp, q, 1, zName, nName); |
| int n = 0; |
| if(SQLITE_ROW == cmpp__step(pp, q, false)){ |
| const unsigned char * z = sqlite3_column_text(q, 3); |
| n = sqlite3_column_bytes(q, 3); |
| cmpp_b_append4(pp, os, z, (cmpp_size_t)n); |
| rc = 1; |
| }else{ |
| if( enforceUndefPolicy ){ |
| cmpp__affirm_undef_policy(pp, zName, nName); |
| } |
| rc = 0; |
| } |
| cmpp__stmt_reset(q); |
| g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n", |
| nName, zName, rc, os->n, os->z)); |
| } |
| return rc; |
| } |
| |
| int cmpp__get(cmpp *pp, unsigned const char * zName, |
| cmpp_ssize_t nName, unsigned char **zVal, |
| unsigned int *nVal){ |
| int rc = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false); |
| if( q ){ |
| nName = cmpp__strlenu(zName, nName); |
| cmpp__bind_textn(pp, q, 1, zName, nName); |
| int n = 0; |
| if(SQLITE_ROW == cmpp__step(pp, q, false)){ |
| const unsigned char * z = sqlite3_column_text(q, 3); |
| n = sqlite3_column_bytes(q, 3); |
| if( nVal ) *nVal = (unsigned)n; |
| *zVal = ustr_nc(sqlite3_mprintf("%.*s", n, z)) |
| /* TODO? Return NULL for the n==0 case? */; |
| if( n && cmpp_check_oom(pp, *zVal) ){ |
| assert(!*zVal); |
| }else{ |
| rc = 1; |
| } |
| }else{ |
| cmpp__affirm_undef_policy(pp, zName, nName); |
| rc = 0; |
| } |
| cmpp__stmt_reset(q); |
| g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n", |
| nName, zName, rc, |
| *zVal ? n : 0, |
| *zVal ? (char const *)*zVal : "<NULL>")); |
| } |
| return rc; |
| } |
| |
| CMPP__EXPORT(int, cmpp_undef)(cmpp *pp, const char * zKey, |
| unsigned int *nRemoved){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDel, false); |
| if( q ){ |
| unsigned int const n = strlen(zKey); |
| cmpp__bind_textn(pp, q, 1, ustr_c(zKey), (cmpp_ssize_t)n); |
| cmpp__step(pp, q, true); |
| if( nRemoved ){ |
| *nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh); |
| } |
| g_debug(pp,2,("undefine: %.*s\n",n, zKey)); |
| } |
| return ppCode; |
| } |
| |
| int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid){ |
| if( pRowid ) *pRowid = 0; |
| if( !ppCode && zDir && *zDir ){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false); |
| if( q ){ |
| /* TODO: normalize zDir before insertion so that a/b and a/b/ |
| are equivalent. The relavent code is in another tree, |
| awaiting a decision on whether to import it or re-base cmpp |
| on top of that library (which would, e.g., replace cmpp_b |
| with that one, which is more mature). |
| */ |
| cmpp__bind_int(pp, q, 1, priority); |
| cmpp__bind_textn(pp, q, 2, ustr_c(zDir), -1); |
| int const rc = cmpp__step(pp, q, false); |
| if( SQLITE_ROW==rc ){ |
| ++pp->pimpl->flags.nIncludeDir; |
| if( pRowid ){ |
| *pRowid = sqlite3_column_int64(q, 0); |
| } |
| } |
| cmpp__stmt_reset(q); |
| /*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s", |
| rc, pRowid ? *pRowid : 0, priority, zDir);*/ |
| g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir)); |
| } |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){ |
| return cmpp__include_dir_add(pp, zDir, 0, NULL); |
| } |
| |
| int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true); |
| if( q ){ |
| /* Hoop-jumping to allow this to work even if pp's in an error |
| state. */ |
| int rc = sqlite3_bind_int64(q, 1, rowid); |
| if( 0==rc ){ |
| rc = sqlite3_step(q); |
| if( SQLITE_ROW==rc ){ |
| --pp->pimpl->flags.nIncludeDir; |
| rc = 0; |
| }else if( SQLITE_DONE==rc ){ |
| rc = 0; |
| } |
| } |
| if( rc && !ppCode ){ |
| cmpp__db_rc(pp, rc, sqlite3_sql(q)); |
| } |
| cmpp__stmt_reset(q); |
| g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid)); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){ |
| #if CMPP_ENABLE_DLLS |
| if( !ppCode ){ |
| cmpp_b * const ob = &pp->pimpl->mod.path; |
| if( !zDirs && !ob->n ){ |
| zDirs = getenv("CMPP_MODULE_PATH"); |
| if( !zDirs ){ |
| zDirs = CMPP_MODULE_PATH; |
| } |
| } |
| if( !zDirs || !*zDirs ) return 0; |
| char const * z = zDirs; |
| char const * const zEnd = zDirs + strlen(zDirs); |
| if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){ |
| unsigned char * zo = ob->z + ob->n; |
| unsigned i = 0; |
| for( ; z < zEnd && !ppCode; ++z ){ |
| switch( *z ){ |
| case CMPP_PATH_SEPARATOR: |
| *zo++ = pp->pimpl->mod.pathSep; |
| break; |
| default: |
| if( 1==++i && ob->n ){ |
| cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep); |
| } |
| *zo++ = *z; |
| break; |
| } |
| } |
| *zo = 0; |
| ob->n = (zo - ob->z); |
| } |
| } |
| return ppCode; |
| #else |
| return CMPP_RC_UNSUPPORTED; |
| #endif |
| } |
| |
| CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){ |
| if( 0==ppCode ){ |
| cmpp__pi(pp); |
| if( pi->db.dbh ){ |
| return cmpp__err(pp, CMPP_RC_MISUSE, |
| "DB name cannot be set after db initialization."); |
| } |
| if( zName ){ |
| char * const z = sqlite3_mprintf("%s", zName); |
| if( 0==cmpp_check_oom(pp, z) ){ |
| cmpp_mfree(pi->db.zName); |
| pi->db.zName = z; |
| } |
| }else{ |
| cmpp_mfree(pi->db.zName); |
| pi->db.zName = 0; |
| } |
| } |
| return ppCode; |
| } |
| |
| bool cmpp__is_legal_key(unsigned char const *zName, |
| cmpp_size_t n, |
| unsigned char const **zErrPos, |
| bool equalIsLegal){ |
| if( !n || n>64/*arbitrary*/ ){ |
| if( zErrPos ) *zErrPos = 0; |
| return false; |
| } |
| unsigned char const * z = zName; |
| unsigned char const * const zEnd = zName + n; |
| for( ; z<zEnd; ++z ){ |
| if( !((*z>='a' && *z<='z') |
| || (*z>='A' && *z<='Z') |
| || (z>zName && |
| ('-'==*z |
| /* This is gonna bite us if we extend the expresions to |
| support +/-. Expressions currently parse X=Y (no |
| spaces) as the three tokens X = Y, but we'd need to |
| require a space between X-Y in expressions because |
| '-' is a legal symbol character. i've looked at |
| making '-' illegal but it's just too convenient for |
| use in define keys. Once one is used to |
| tcl-style-naming of stuff, it's painful to have to go |
| back to snake_case. |
| */ |
| || (*z>='0' && *z<='9'))) |
| || (*z>='.' && *z<='/') |
| || (*z==':') |
| || (*z=='_') |
| || (equalIsLegal && z>zName && '='==*z) |
| || (*z & 0x80) |
| ) ){ |
| if( zErrPos ) *zErrPos = z; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool cmpp_is_legal_key(unsigned char const *zName, |
| cmpp_size_t n, |
| unsigned char const **zErrPos){ |
| return cmpp__is_legal_key(zName, n, zErrPos, false); |
| } |
| |
| int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey, |
| cmpp_ssize_t nKey, bool permitEqualSign){ |
| if( !ppCode ){ |
| unsigned char const *zAt = 0; |
| nKey = cmpp__strlenu(zKey, nKey); |
| if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){ |
| cmpp__err(pp, CMPP_RC_SYNTAX, |
| "Illegal character 0x%02x in key [%.*s]", |
| (int)*zAt, nKey, zKey); |
| } |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(bool, cmpp_next_chunk)(unsigned char const **zPos, |
| unsigned char const *zEnd, |
| unsigned char chSep, |
| cmpp_size_t *pCounter){ |
| assert( zPos ); |
| assert( *zPos ); |
| assert( zEnd ); |
| if( *zPos >= zEnd ) return false; |
| unsigned char const * z = *zPos; |
| while( z<zEnd ){ |
| if( chSep==*z ){ |
| ++z; |
| if( pCounter ) ++*pCounter; |
| break; |
| } |
| ++z; |
| } |
| if( *zPos==z ) return false; |
| *zPos = z; |
| return true; |
| } |
| |
| /** |
| Scans dx for the next newline. It updates ln to contain the result, |
| which includes the trailing newline unless EOF is hit before a |
| newline. |
| |
| Returns true if it has input, false at EOF. It has no error |
| conditions beyond invalid dx->pimpl state, which "doesn't happen". |
| */ |
| //static |
| bool cmpp__dx_next_line(cmpp_dx * const dx, CmppDLine *ln){ |
| assert( !dxppCode ); |
| cmpp_dx_pimpl * const dxp = dx->pimpl; |
| if(!dxp->pos.z) dxp->pos.z = dxp->zBegin; |
| assert( dxp->zEnd ); |
| if( dxp->pos.z>=dxp->zEnd ){ |
| return false; |
| } |
| assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n') |
| && "Else we've mismanaged something."); |
| cmpp__dx_pi(dx); |
| ln->lineNo = dpi->pos.lineNo; |
| ln->zBegin = dpi->pos.z; |
| ln->zEnd = ln->zBegin; |
| return cmpp_next_chunk(&ln->zEnd, dpi->zEnd, (unsigned char)'\n', |
| &dpi->pos.lineNo); |
| } |
| |
| /** |
| Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any |
| non-delimiter output found along the way to dx->pp's output |
| channel. |
| |
| This updates dx->pimpl->pos.z and dx->pimpl->pos.lineNo as it goes. |
| |
| If a delimiter is found, it sets *gotOne to true and updates |
| dx->pimpl->dline to point to the remainder of that line. On no match |
| *gotOne will be false and EOF will have been reached. |
| |
| Returns dxppCode. If it returns non-0 then the state of dx's |
| tokenization pieces are unspecified. i.e. it's illegal to call this |
| again without a reset. |
| */ |
| static int cmpp_dx_delim_search(cmpp_dx * const dx, bool * gotOne){ |
| if( dxppCode ) return dxppCode; |
| cmpp_dx_pimpl * const dxp = dx->pimpl; |
| if(!dxp->pos.z) dxp->pos.z = dxp->zBegin; |
| if( dxp->pos.z>=dxp->zEnd ){ |
| *gotOne = false; |
| return 0; |
| } |
| assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n') |
| && "Else we've mismanaged something."); |
| cmpp__pi(dx->pp); |
| cmpp__delim const * const delim = cmpp__dx_delim(dx); |
| if(!delim) { |
| return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "The directive delimiter stack is empty."); |
| } |
| unsigned char const * const zD = delim->open.z; |
| unsigned short const nD = delim->open.n; |
| unsigned char const * const zEnd = dxp->zEnd; |
| unsigned char const * zLeft = dxp->pos.z; |
| unsigned char const * z = zLeft; |
| assert(zD); |
| assert(nD); |
| #if 0 |
| assert( 0==*zEnd && "Else we'll misinteract with strcspn()" ); |
| if( *zEnd ){ |
| return cmpp_dx_err(dx, CMPP_RC_RANGE, |
| "Input must be NUL-terminated."); |
| } |
| #endif |
| ++dxp->flags.countLines; |
| while( z<zEnd && '\n'==*z ){ |
| /* Skip leading newlines. We have to delay the handling of |
| leading whitepace until later so that: |
| |
| | #if |
| |^^ those two spaces do not get emitted. |
| */ |
| ++dxp->pos.lineNo; |
| ++z; |
| } |
| #define tflush \ |
| if( z>zEnd ) z=zEnd; \ |
| if( z>zLeft && cmpp_dx_out_expand(dx, &pi->out, zLeft, \ |
| (cmpp_size_t)(z-zLeft), \ |
| cmpp_atpol_CURRENT) ){ \ |
| --dxp->flags.countLines; \ |
| return dxppCode; \ |
| } zLeft = z |
| |
| CmppDLine * const dline = &dxp->dline; |
| bool atBOL = true /* At the start of a line? Successful calls to |
| this always end at either BOL or EOF. */; |
| if( 0 ){ |
| g_warn("scanning... <<%.*s...>>", |
| (zEnd-z)>20?20:(zEnd-z), z); |
| } |
| while( z<zEnd && !dxppCode ){ |
| if( !atBOL ){ |
| /* We're continuing the scan of a line on which the first bytes |
| didn't match a delimiter. */ |
| while( z<zEnd ){ |
| while((z<zEnd && '\n'==*z) |
| || (z+1<zEnd && '\r'==*z && '\n'==z[1]) ){ |
| ++dxp->pos.lineNo; |
| z += 1 + ('\r'==*z); |
| atBOL = true; |
| } |
| if( atBOL ){ |
| break; |
| } |
| ++z; |
| } |
| if( !atBOL ) break; |
| } |
| if( 0 ){ |
| g_warn("at BOL... <<%.*s...>>", |
| (zEnd-z) > 20 ? 20 : (zEnd-z), z); |
| } |
| |
| /* We're at BOL. Check for a delimiter with optional leading |
| spaces. */ |
| tflush; |
| cmpp_skip_space(&z, zEnd); |
| int const skip = cmpp_isnl(z, zEnd); |
| if( skip ){ |
| /* Special case: a line comprised solely of whitespace. If we |
| don't catch this here, we won't recognize a delimiter which |
| starts on the next line. */ |
| tflush; |
| z += skip; |
| ++dxp->pos.lineNo; |
| continue; |
| } |
| if( 0 ){ |
| g_warn("at BOL... <<%.*s...>>", |
| (zEnd-z) > 20 ? 20 : (zEnd-z), z); |
| } |
| if( z + nD>zEnd ){ |
| /* Too short for a delimiter. We'll catch the z+nD==zEnd corner |
| case in a moment. */ |
| z = zEnd; |
| break; |
| } |
| if( memcmp(z, zD, nD) ){ |
| /* Not a delimiter. Keep trying. */ |
| atBOL = false; |
| ++z; |
| continue; |
| } |
| |
| /* z now points to a delimiter which sits at the start of a line |
| (ignoring leading spaces). */ |
| z += nD /* skip the delimiter */; |
| cmpp_skip_space(&z, zEnd) /* skip spaces immediately following |
| the delimiter. */; |
| if( z>=zEnd || cmpp_isnl(z, zEnd) ){ |
| dxserr("No directive name found after %s.", zD); |
| /* We could arguably treat this as no match and pass this line |
| through as-is but that currently sounds like a pothole. */ |
| break; |
| } |
| /* Set up dx->pimpl->dline to encompass the whole directive line sans |
| delimiter and leading spaces. */ |
| dline->zBegin = z |
| /* dx->pimpl->dline starts at the directive name and extends until the |
| next EOL/EOF. We don't yet know if it's a legal directive |
| name - cmpp_dx_next() figures that part out. */; |
| dline->lineNo = dxp->pos.lineNo; |
| /* Now find the end of the line or EOF, accounting for |
| backslash-escaped newlines and _not_ requiring backslashes to |
| escape newlines inside of {...}, (...), or [...]. We could also |
| add the double-quotes to this, but let's start without that. */ |
| bool keepGoing = true; |
| zLeft = z; |
| while( keepGoing && z<zEnd ){ |
| switch( *z ){ |
| case '(': case '{': case '[':{ |
| zLeft = z; |
| if( cmpp__find_closing2(dx->pp, &z, zEnd, &dxp->pos.lineNo) ){ |
| --dxp->flags.countLines; |
| return dxppCode; |
| } |
| ++z /* group-closing character */; |
| /* |
| Sidebar: this only checks top-level groups. It is |
| possible that an inner group is malformed, e.g.: |
| |
| { ( } |
| |
| It's also possible that that's perfectly legal for a |
| specific use case. |
| |
| Such cases will, if they're indeed syntax errors, be |
| recognized as such in the arguments-parsing |
| steps. Catching them here would require that we |
| recursively validate all of [zLeft,z) for group |
| constructs, whereas that traversal happens as a matter of |
| course in argument parsing. It would also require the |
| assumption that such constructs are not legal, which is |
| invalid once we start dealing with free-form input like |
| #query SQL. |
| */ |
| break; |
| } |
| case '\n': |
| assert( z!=dline->zBegin && "Checked up above" ); |
| if( '\\'==z[-1] |
| || (z>zLeft+1 && '\r'==z[-1] && '\\'==z[-2]) ){ |
| /* Backslash-escaped newline. */ |
| ++z; |
| }else{ |
| /* EOL for this directive. */ |
| keepGoing = false; |
| } |
| ++dxp->pos.lineNo; |
| break; |
| default: |
| ++z; |
| } |
| } |
| assert( z==zEnd || '\n'==*z ); |
| dline->zEnd = z; |
| dxp->pos.z = dline->zEnd + 1 |
| /* For the next call to this function, skip the trailing newline |
| or EOF */; |
| assert( dline->zBegin < dline->zEnd && "Was checked above" ); |
| if( 0 ){ |
| g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin), |
| (dline->zEnd-dline->zBegin), dline->zBegin); |
| } |
| *gotOne = true; |
| assert( !dxppCode ); |
| --dxp->flags.countLines; |
| return 0; |
| } |
| /* No directives found. We're now at EOL or EOF. Flush any pending |
| LHS content. */ |
| tflush; |
| dx->pimpl->pos.z = z; |
| *gotOne = false; |
| return dxppCode; |
| #undef tflush |
| } |
| |
| int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey, |
| cmpp_ssize_t nKey, CmppKvp_op_e opPolicy){ |
| if(ppCode) return ppCode; |
| char chEq = 0; |
| char opLen = 0; |
| *p = CmppKvp_empty; |
| p->k.z = zKey; |
| p->k.n = cmpp__strlenu(zKey, nKey); |
| switch( opPolicy ){ |
| case CmppKvp_op_none:// break; |
| case CmppKvp_op_eq1: |
| chEq = '='; |
| opLen = 1; |
| break; |
| default: |
| assert(!"don't use these"); |
| /* no longer todo: ==, !=, <=, <, >, >= */ |
| chEq = '='; |
| opLen = 1; |
| break; |
| } |
| assert( chEq ); |
| p->op = CmppKvp_op_none; |
| unsigned const char * const zEnd = p->k.z + p->k.n; |
| for(unsigned const char * zPos = p->k.z ; *zPos && zPos<zEnd ; ++zPos) { |
| if( chEq==*zPos ){ |
| if( CmppKvp_op_none==opPolicy ){ |
| cmpp__err(pp, CMPP_RC_SYNTAX, |
| "Illegal operator in key: %s", zKey); |
| }else{ |
| p->op = CmppKvp_op_eq1; |
| p->k.n = (unsigned)(zPos - ustr_c(zKey)); |
| zPos += opLen; |
| assert( zPos <= zEnd ); |
| p->v.z = zPos; |
| p->v.n = (unsigned)(zEnd - zPos); |
| } |
| break; |
| } |
| } |
| cmpp__legal_key_check(pp, p->k.z, p->k.n, false); |
| return ppCode; |
| } |
| |
| int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired, |
| cmpp_size_t * nAlloc, unsigned sizeOfEntry){ |
| int rc = pp ? ppCode : 0; |
| if( 0==rc && nDesired > *nAlloc ){ |
| cmpp_size_t const nA = nDesired < 10 ? 10 : nDesired; |
| void * const p = cmpp_mrealloc(*list, sizeOfEntry * nA); |
| rc = cmpp_check_oom(pp, p); |
| if( p ){ |
| memset((unsigned char *)p + |
| (sizeOfEntry * *nAlloc), 0, |
| sizeOfEntry * (nA - *nAlloc)); |
| *list = p; |
| *nAlloc = nA; |
| } |
| } |
| return rc; |
| } |
| |
| CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li){ |
| CmppLvl * p = 0; |
| assert( li->list ? li->nAlloc : 0==li->nAlloc ); |
| if( 0==ppCode |
| && 0==CmppLvlList_reserve(pp, li, |
| cmpp__li_reserve1_size(li,5)) ){ |
| p = li->list[li->n]; |
| if( !p ){ |
| p = cmpp__malloc(pp, sizeof(*p)); |
| } |
| if( p ){ |
| li->list[li->n++] = p; |
| *p = CmppLvl_empty; |
| } |
| } |
| return p; |
| } |
| |
| void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li, |
| CmppLvl * const lvl){ |
| assert( li->n ); |
| if( li->n ){ |
| if( lvl==li->list[li->n-1] ){ |
| *lvl = CmppLvl_empty; |
| cmpp_mfree(lvl); |
| li->list[--li->n] = 0; |
| }else{ |
| if( pp ){ |
| cmpp_err_set(pp, CMPP_RC_ASSERT, |
| "Misuse of %s(): not passed the top of the stack. " |
| "The CmppLvl stack is now out of whack.", |
| __func__); |
| }else{ |
| cmpp__fatal("Misuse of %s(): not passed the top of the stack", |
| __func__); |
| } |
| /* do not free it - CmppLvlList_cleanup() will catch it. */ |
| } |
| } |
| } |
| |
| void CmppLvlList_cleanup(CmppLvlList *li){ |
| const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m; |
| while( li->nAlloc ){ |
| cmpp_mfree( li->list[--li->nAlloc] ); |
| } |
| cmpp_mfree(li->list); |
| *li = CmppLvlList_empty; |
| } |
| |
| static inline void CmppDList_entry_clean(CmppDList_entry * const e){ |
| if( e->d.impl.dtor ){ |
| e->d.impl.dtor( e->d.impl.state ); |
| } |
| cmpp_mfree(e->zName); |
| *e = CmppDList_entry_empty; |
| } |
| |
| #if 0 |
| CmppDList * CmppDList_reuse(CmppDList *li){ |
| while( li->n ){ |
| CmppDList_entry_clean( li->list[--li->n] ); |
| } |
| return li; |
| } |
| #endif |
| |
| void CmppDList_cleanup(CmppDList *li){ |
| static const CmppDList CmppDList_empty = CmppDList_empty_m; |
| while( li->n ){ |
| CmppDList_entry_clean( li->list[--li->n] ); |
| cmpp_mfree( li->list[li->n] ); |
| li->list[li->n] = 0; |
| } |
| cmpp_mfree(li->list); |
| *li = CmppDList_empty; |
| } |
| |
| void CmppDList_unappend(CmppDList *li){ |
| assert( li->n ); |
| if( li->n ){ |
| CmppDList_entry_clean(li->list[--li->n]); |
| } |
| } |
| |
| |
| /** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */ |
| static |
| int CmppDList_entry_cmp_pp(const void *p1, const void *p2){ |
| CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1; |
| CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2; |
| return eL->d.name.n==eR->d.name.n |
| ? memcmp(eL->d.name.z, eR->d.name.z, eL->d.name.n) |
| : strcmp((char const *)eL->d.name.z, |
| (char const *)eR->d.name.z); |
| } |
| |
| static void CmppDList_sort(CmppDList * const li){ |
| if( li->n>1 ){ |
| qsort(li->list, li->n, sizeof(CmppDList_entry*), |
| CmppDList_entry_cmp_pp); |
| } |
| } |
| |
| CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){ |
| CmppDList_entry * p = 0; |
| assert( li->list ? li->nAlloc : 0==li->nAlloc ); |
| if( 0==ppCode |
| && 0==cmpp_array_reserve(pp, (void **)&li->list, |
| cmpp__li_reserve1_size(li, 15), |
| &li->nAlloc, sizeof(p)) ){ |
| p = li->list[li->n]; |
| if( !p ){ |
| li->list[li->n] = p = cmpp__malloc(pp, sizeof(*p)); |
| } |
| if( p ){ |
| ++li->n; |
| *p = CmppDList_entry_empty; |
| } |
| } |
| return p; |
| } |
| |
| CmppDList_entry * CmppDList_search(CmppDList const * li, |
| char const *zName){ |
| if( li->n > 2 ){ |
| CmppDList_entry const key = { |
| .d = { |
| .name = { |
| .z = zName, |
| .n = strlen(zName) |
| } |
| } |
| }; |
| CmppDList_entry const * pKey = &key; |
| CmppDList_entry ** pRv |
| = bsearch(&pKey, li->list, li->n, sizeof(li->list[0]), |
| CmppDList_entry_cmp_pp); |
| //g_warn("search in=%s out=%s", zName, (pRv ? (*pRv)->d.name.z : "<null>")); |
| return pRv ? *pRv : 0; |
| }else{ |
| cmpp_size_t const nName = cmpp__strlen(zName, -1); |
| for( cmpp_size_t i = 0; i < li->n; ++i ){ |
| CmppDList_entry * const e = li->list[i]; |
| if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){ |
| //g_warn("search in=%s out=%s", zName, e->d.name.z); |
| return e; |
| } |
| } |
| return 0; |
| } |
| } |
| |
| void cmpp__delim_cleanup(cmpp__delim *d){ |
| cmpp__delim const dd = cmpp__delim_empty_m; |
| cmpp_mfree(d->zOwns); |
| *d = dd; |
| assert(!d->zOwns); |
| assert(d->open.z); |
| assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM)); |
| assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1); |
| } |
| |
| cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){ |
| cmpp__delim * p = 0; |
| assert( li->list ? li->nAlloc : 0==li->nAlloc ); |
| if( 0==ppCode |
| && 0==cmpp_array_reserve(pp, (void **)&li->list, |
| cmpp__li_reserve1_size(li,4), |
| &li->nAlloc, sizeof(cmpp__delim)) ){ |
| p = &li->list[li->n++]; |
| *p = cmpp__delim_empty; |
| } |
| return p; |
| } |
| |
| void cmpp__delim_list_cleanup(cmpp__delim_list *li){ |
| while( li->nAlloc ) cmpp__delim_cleanup(li->list + --li->nAlloc); |
| cmpp_mfree(li->list); |
| *li = cmpp__delim_list_empty; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){ |
| if( dxppCode ) return dxppCode; |
| |
| CmppDLine * const tok = &dx->pimpl->dline; |
| if( !dx->pimpl->zBegin ){ |
| *pGotOne = false; |
| return 0; |
| } |
| assert(dx->pimpl->zEnd); |
| assert(dx->pimpl->zEnd > dx->pimpl->zBegin); |
| *pGotOne = false; |
| cmpp_dx__reset(dx); |
| bool foundDelim = false; |
| if( cmpp_dx_delim_search(dx, &foundDelim) || !foundDelim ){ |
| return dxppCode; |
| } |
| if( cmpp_args__init(dx->pp, &dx->pimpl->args) ){ |
| return dxppCode; |
| } |
| cmpp_skip_space( &tok->zBegin, tok->zEnd ); |
| g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n", |
| tok->lineNo, |
| (int)(tok->zEnd-tok->zBegin), tok->zBegin)); |
| /* Normalize the directive's line and parse arguments */ |
| const unsigned lineLen = (unsigned)(tok->zEnd - tok->zBegin); |
| if(!lineLen){ |
| return cmpp_dx_err(dx, CMPP_RC_SYNTAX, |
| "Line #%u has no directive after %s", |
| tok->lineNo, cmpp_dx_delim(dx)); |
| } |
| unsigned char const * zi = tok->zBegin /* Start of input */; |
| unsigned char const * ziEnd = tok->zEnd /* Input EOF */; |
| cmpp_b * const bufLine = |
| cmpp_b_reuse(&dx->pimpl->buf.line) |
| /* Slightly-transformed copy of the input. */; |
| if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){ |
| return dxppCode; |
| } |
| unsigned char * zo = bufLine->z /* Start of output */; |
| unsigned char const * const zoEnd = |
| zo + bufLine->nAlloc /* Output EOF. */; |
| g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n", |
| tok->lineNo, lineLen, lineLen, tok->zBegin)); |
| //memset(bufLine->z, 0, bufLine->nAlloc); |
| #define out(CH) if(zo==zoEnd) break; (*zo++)=CH |
| /* |
| bufLine is now populated with a copy of the whole input line. |
| Now normalize that buffer a bit before trying to parse it. |
| */ |
| unsigned char const * zEsc = 0; |
| cmpp_dx_pimpl * const pimpl = dx->pimpl; |
| for( ; zi<ziEnd && *zi && zo<zoEnd; |
| ++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. |
| */ |
| 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."); |
| } |
| *zo = 0; |
| #undef out |
| bufLine->n = (cmpp_size_t)(zo - bufLine->z); |
| if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z); |
| /* Line has now been normalized into bufLine->z. */ |
| for( zo = bufLine->z; zo<zoEnd && *zo; ++zo ){ |
| /* NULL-terminate the directive so we can search for it. */ |
| if( cmpp_isspace(*zo) ){ |
| *zo = 0; |
| break; |
| } |
| } |
| unsigned char * const zDirective = bufLine->z; |
| dx->d = cmpp__d_search3(dx->pp, (char const *)zDirective, |
| cmpp__d_search3_F_ALL); |
| if( dxppCode ){ |
| return dxppCode; |
| }else if(!dx->d){ |
| return cmpp_dx_err(dx, CMPP_RC_NOT_FOUND, |
| "Unknown directive at line %" |
| CMPP_SIZE_T_PFMT ": %.*s\n", |
| (unsigned)tok->lineNo, |
| (int)bufLine->n, bufLine->z); |
| } |
| assert( zDirective == bufLine->z ); |
| const bool isCall |
| = dx->pimpl->args.pimpl->isCall |
| = dx->pimpl->flags.nextIsCall; |
| dx->pimpl->flags.nextIsCall = false; |
| if( isCall ){ |
| if( cmpp_d_F_NO_CALL & dx->d->flags ){ |
| return cmpp_dx_err(dx, CMPP_RC_SYNTAX, |
| "%s%s cannot be used in a [call] context.", |
| cmpp_dx_delim(dx), |
| dx->d->name.z); |
| } |
| }else if( cmpp_d_F_CALL_ONLY & dx->d->flags ){ |
| return cmpp_dx_err(dx, CMPP_RC_TYPE, |
| "'%s' is a call-only directive, " |
| "not legal here.", dx->d->name.z); |
| } |
| if( bufLine->n > dx->d->name.n ){ |
| dx->args.z = zDirective + dx->d->name.n + 1; |
| assert( dx->args.z > bufLine->z ); |
| assert( dx->args.z <= bufLine->z+bufLine->n ); |
| dx->args.nz = cmpp__strlenu(dx->args.z, -1); |
| assert( bufLine->nAlloc > dx->args.nz ); |
| }else{ |
| dx->args.z = ustr_c("\0"); |
| dx->args.nz = 0; |
| } |
| if( 0 ){ |
| g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>", |
| bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z); |
| } |
| cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz); |
| if(0){ |
| g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz, |
| (int)dx->args.nz, dx->args.z); |
| } |
| assert( !pimpl->buf.argsRaw.n ); |
| if( dx->args.nz ){ |
| if( 0 ){ |
| g_warn("lineLen=%u zargs len=%u: [%.*s]\n", |
| (unsigned)lineLen, |
| (int)dx->args.nz, (int)dx->args.nz, |
| dx->args.z |
| ); |
| } |
| if( cmpp_b_append4(dx->pp, &pimpl->buf.argsRaw, |
| dx->args.z, dx->args.nz) ){ |
| return dxppCode; |
| } |
| } |
| assert( !pimpl->args.arg0 ); |
| assert( !pimpl->args.argc ); |
| assert( !pimpl->args.pimpl->argOut.n ); |
| assert( !pimpl->args.pimpl->argli.n ); |
| assert( dx->args.z ); |
| if( //1 || //pleases valgrind. Well, it did at one point. |
| !cmpp_dx_is_eliding(dx) || 0!=(cmpp_d_F_FLOW_CONTROL & dx->d->flags) ){ |
| if( cmpp_d_F_ARGS_LIST & dx->d->flags ){ |
| cmpp_dx_args_parse(dx, &pimpl->args); |
| }else if( cmpp_d_F_ARGS_RAW & dx->d->flags ){ |
| /* Treat rest of line as one token */ |
| cmpp_arg * const arg = |
| CmppArgList_append(dx->pp, &pimpl->args.pimpl->argli); |
| if( !arg ) return dxppCode; |
| pimpl->args.arg0 = arg; |
| pimpl->args.argc = 1; |
| arg->ttype = cmpp_TT_RawLine; |
| arg->z = pimpl->buf.argsRaw.z; |
| arg->n = pimpl->buf.argsRaw.n; |
| //g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z); |
| } |
| } |
| if( 0==dxppCode ){ |
| dx->args.arg0 = pimpl->args.arg0; |
| dx->args.argc = pimpl->args.argc; |
| } |
| *pGotOne = true; |
| return dxppCode; |
| } |
| |
| CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx){ |
| return dx->pimpl->args.pimpl->isCall; |
| } |
| |
| CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r, |
| cmpp_d ** dOut){ |
| CmppDList_entry * e1 = 0, * e2 = 0; |
| bool const isCallOnly = |
| (cmpp_d_F_CALL_ONLY & r->opener.flags); |
| if( ppCode ){ |
| goto end; |
| } |
| if( (cmpp_d_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags)) |
| && (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) ){ |
| cmpp__err(pp, CMPP_RC_ACCESS, |
| "Directive %s%s flag cmpp_d_F_NOT_IN_SAFE_MODE is set " |
| "and the preprocessor is running in safe mode.", |
| cmpp__pp_zdelim(pp), r->name); |
| goto end; |
| } |
| if( isCallOnly && r->closer.f ){ |
| cmpp__err(pp, CMPP_RC_MISUSE, |
| "Call-only directives may not have a closing directive."); |
| goto end; |
| } |
| #if 0 |
| if( pp->pimpl->dx ){ |
| cmpp__err(pp, CMPP_RC_MISUSE, |
| "Directives may not be added while a " |
| "directive is running." |
| /* because that might reallocate being-run directives. |
| 2025-10-25: that's since been resolved but we need a |
| use case before enabling this. |
| */); |
| goto end; |
| } |
| #endif |
| if( !pp->pimpl->flags.isInternalDirectiveReg |
| && !cmpp_is_legal_key(ustr_c(r->name), |
| cmpp__strlen(r->name,-1), NULL) ){ |
| cmpp__err(pp, CMPP_RC_RANGE, |
| "\"%s\" is not a legal directive name.", r->name); |
| goto end; |
| } |
| if( cmpp__d_search(pp, r->name) ){ |
| cmpp__err(pp, CMPP_RC_ALREADY_EXISTS, |
| "Directive name '%s' is already in use.", |
| r->name); |
| goto end; |
| } |
| e1 = CmppDList_append(pp, &pp->pimpl->d.list); |
| if( !e1 ) goto end; |
| e1->d.impl.callback = r->opener.f; |
| e1->d.impl.state = r->state; |
| e1->d.impl.dtor = r->dtor; |
| if( pp->pimpl->flags.isInternalDirectiveReg ){ |
| e1->d.flags = r->opener.flags; |
| }else{ |
| e1->d.flags = r->opener.flags & cmpp_d_F_MASK; |
| } |
| e1->zName = sqlite3_mprintf("%s", r->name); |
| if( 0==cmpp_check_oom(pp, e1->zName) ){ |
| //e1->reg = *r; e1->reg.zName = e1->zName; |
| e1->d.name.z = e1->zName; |
| e1->d.name.n = strlen(e1->zName); |
| if( r->closer.f |
| && (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){ |
| e2->d.impl.callback = r->closer.f; |
| e2->d.impl.state = r->state; |
| if( pp->pimpl->flags.isInternalDirectiveReg ){ |
| e2->d.flags = r->closer.flags; |
| }else{ |
| e2->d.flags = r->closer.flags & cmpp_d_F_MASK; |
| } |
| e1->d.closer = &e2->d; |
| e2->zName = sqlite3_mprintf("/%s", r->name); |
| if( 0==cmpp_check_oom(pp, e2->zName) ){ |
| e2->d.name.z = e2->zName; |
| e2->d.name.n = e1->d.name.n + 1; |
| } |
| } |
| } |
| |
| end: |
| if( ppCode ){ |
| if( e2 ) CmppDList_unappend(&pp->pimpl->d.list); |
| if( e1 ) CmppDList_unappend(&pp->pimpl->d.list); |
| else if( r->dtor ){ |
| r->dtor( r->state ); |
| } |
| }else{ |
| CmppDList_sort(&pp->pimpl->d.list); |
| if( dOut ){ |
| *dOut = &e1->d; |
| } |
| if( 0 ){ |
| g_warn("Registered: %s%s%s", e1->zName, |
| e2 ? " and " : "", |
| e2 ? e2->zName : ""); |
| } |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os, |
| cmpp_d const * const * const dClosers, |
| unsigned nClosers, |
| cmpp_flag32_t flags){ |
| assert( !dxppCode ); |
| bool gotOne = false; |
| cmpp_outputer const oldOut = dx->pp->pimpl->out; |
| bool const allowOtherDirectives = |
| (flags & cmpp_dx_consume_F_PROCESS_OTHER_D); |
| cmpp_d const * const d = cmpp_dx_d(dx); |
| cmpp_size_t const lineNo = dx->pimpl->dline.lineNo; |
| bool const pushAt = (cmpp_dx_consume_F_RAW & flags); |
| if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){ |
| return dxppCode; |
| } |
| if( os ){ |
| dx->pp->pimpl->out = *os; |
| } |
| while( 0==dxppCode |
| && 0==cmpp_dx_next(dx, &gotOne) |
| /* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){ |
| if( !gotOne ){ |
| dxserr("No closing directive found for " |
| "%s%s opened on line %" CMPP_SIZE_T_PFMT ".", |
| cmpp_dx_delim(dx), d->name.z, lineNo); |
| }else{ |
| cmpp_d const * const d2 = cmpp_dx_d(dx); |
| gotOne = false; |
| for( unsigned i = 0; !gotOne && i < nClosers; ++i ){ |
| gotOne = d2==dClosers[i]; |
| } |
| //g_warn("gotOne=%d d2=%s", gotOne, d2->name.z); |
| if( gotOne ) break; |
| else if( !allowOtherDirectives ){ |
| dxserr("%s%s at line %" CMPP_SIZE_T_PFMT |
| " may not contain %s%s.", |
| cmpp_dx_delim(dx), d->name.z, lineNo, |
| cmpp_dx_delim(dx), d2->name.z); |
| }else{ |
| cmpp_dx_process(dx); |
| } |
| } |
| } |
| if( pushAt ){ |
| cmpp_atpol_pop(dx->pp); |
| } |
| if( os ){ |
| dx->pp->pimpl->out = oldOut; |
| } |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b, |
| cmpp_d const * const * dClosers, |
| unsigned nClosers, cmpp_flag32_t flags){ |
| cmpp_outputer oss = cmpp_outputer_b; |
| oss.state = b; |
| return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags); |
| } |
| |
| char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){ |
| again: |
| switch(p){ |
| case cmpp_atpol_CURRENT:{ |
| if( pp ){ |
| assert( p!=cmpp__policy(pp, at) ); |
| p = cmpp__policy(pp, at); |
| pp = 0; |
| goto again; |
| } |
| return NULL; |
| } |
| case cmpp_atpol_invalid: return NULL; |
| case cmpp_atpol_OFF: return "off"; |
| case cmpp_atpol_RETAIN: return "retain"; |
| case cmpp_atpol_ELIDE: return "elide"; |
| case cmpp_atpol_ERROR: return "error"; |
| } |
| return NULL; |
| } |
| |
| cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){ |
| cmpp_atpol_e rv = cmpp_atpol_invalid; |
| if( 0==strcmp(z, "retain") ) rv = cmpp_atpol_RETAIN; |
| else if( 0==strcmp(z, "elide") ) rv = cmpp_atpol_ELIDE; |
| else if( 0==strcmp(z, "error") ) rv = cmpp_atpol_ERROR; |
| else if( 0==strcmp(z, "off") ) rv = cmpp_atpol_OFF; |
| if( pp ){ |
| if( cmpp_atpol_invalid==rv |
| && 0==strcmp(z, "current") ){ |
| rv = cmpp__policy(pp,at); |
| }else if( cmpp_atpol_invalid==rv ){ |
| cmpp__err(pp, CMPP_RC_RANGE, |
| "Invalid @ policy value: %s." |
| " Try one of retain|elide|error|off|current.", z); |
| }else{ |
| cmpp__policy(pp,at) = rv; |
| } |
| } |
| return rv; |
| } |
| |
| int cmpp__StringAtIsOk(cmpp * pp, cmpp_atpol_e pol){ |
| if( 0==ppCode ){ |
| if( pol==cmpp_atpol_CURRENT ) pol=cmpp__policy(pp,at); |
| if(cmpp_atpol_OFF==pol ){ |
| cmpp_err_set(pp, CMPP_RC_UNSUPPORTED, |
| "@policy is \"off\", so cannot use @\"strings\"."); |
| } |
| } |
| return ppCode; |
| } |
| |
| cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e) |
| cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e) |
| |
| int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){ |
| if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at); |
| assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." ); |
| if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol) |
| && 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){ |
| PodList__atpol_pop(&cmpp__epol(pp,at)); |
| } |
| return ppCode; |
| } |
| |
| void cmpp_atpol_pop(cmpp * pp){ |
| assert( cmpp__epol(pp,at).n ); |
| if( cmpp__epol(pp,at).n ){ |
| PodList__atpol_pop(&cmpp__epol(pp,at)); |
| }else if( !ppCode ){ |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "%s() called when no cmpp_atpol_push() is active.", |
| __func__); |
| } |
| } |
| |
| int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){ |
| if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol) |
| && cmpp_unpol_set(pp, pol)/*for validation*/ ){ |
| PodList__unpol_pop(&cmpp__epol(pp,un)); |
| } |
| return ppCode; |
| } |
| |
| void cmpp_unpol_pop(cmpp * pp){ |
| assert( cmpp__epol(pp,un).n ); |
| if( cmpp__epol(pp,un).n ){ |
| PodList__unpol_pop(&cmpp__epol(pp,un)); |
| }else if( !ppCode ){ |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "%s() called when no cmpp_unpol_push() is active.", |
| __func__); |
| } |
| } |
| |
| CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){ |
| return cmpp__epol(pp,at).na |
| ? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT; |
| } |
| |
| CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){ |
| if( 0==ppCode ){ |
| switch(pol){ |
| case cmpp_atpol_OFF: |
| case cmpp_atpol_RETAIN: |
| case cmpp_atpol_ELIDE: |
| case cmpp_atpol_ERROR: |
| assert(cmpp__epol(pp,at).na); |
| cmpp__policy(pp,at) = pol; |
| break; |
| case cmpp_atpol_CURRENT: |
| break; |
| default: |
| cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d", |
| (int)pol); |
| } |
| } |
| return ppCode; |
| } |
| |
| |
| char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){ |
| (void)pp; |
| switch(p){ |
| case cmpp_unpol_NULL: return "null"; |
| case cmpp_unpol_ERROR: return "error"; |
| case cmpp_unpol_invalid: return NULL; |
| } |
| return NULL; |
| } |
| |
| cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp, |
| char const *z){ |
| cmpp_unpol_e rv = cmpp_unpol_invalid; |
| if( 0==strcmp(z, "null") ) rv = cmpp_unpol_NULL; |
| else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR; |
| if( pp ){ |
| if( cmpp_unpol_invalid==rv |
| && 0==strcmp(z, "current") ){ |
| rv = cmpp__policy(pp,un); |
| }else if( cmpp_unpol_invalid==rv ){ |
| cmpp__err(pp, CMPP_RC_RANGE, |
| "Invalid undefined key policy value: %s." |
| " Try one of null|error.", z); |
| }else{ |
| cmpp_unpol_set(pp, rv); |
| } |
| } |
| return rv; |
| } |
| |
| CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){ |
| return cmpp__epol(pp,un).na |
| ? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT; |
| } |
| |
| CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){ |
| if( 0==ppCode ){ |
| switch(pol){ |
| case cmpp_unpol_NULL: |
| case cmpp_unpol_ERROR: |
| cmpp__policy(pp,un) = pol; |
| break; |
| default: |
| cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d", |
| (int)pol); |
| } |
| } |
| return ppCode; |
| } |
| |
| /** |
| Reminders to self re. savepoint tracking: |
| |
| cmpp_dx tracks per-input-source savepoints. We always want |
| savepoints which are created via scripts to be limited to that |
| script. cmpp instances, on the other hand, don't care about that. |
| |
| Thus we have two different APIs for starting/ending savepoints. |
| */ |
| CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){ |
| if( 0==ppCode ){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true); |
| assert( q || !"db init would have otherwise failed"); |
| if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ |
| ++pp->pimpl->flags.nSavepoint; |
| } |
| } |
| return ppCode; |
| } |
| |
| int cmpp__dx_sp_begin(cmpp_dx * const dx){ |
| if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){ |
| ++dx->pimpl->nSavepoint; |
| } |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){ |
| /* Remember that rollback must (mostly) ignore the |
| pending error state. */ |
| if( !pp->pimpl->flags.nSavepoint ){ |
| if( 0==ppCode ){ |
| cmpp__err(pp, CMPP_RC_MISUSE, |
| "Cannot roll back: no active savepoint"); |
| } |
| }else{ |
| sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true); |
| assert( q || !"db init would have otherwise failed"); |
| if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ |
| q = cmpp__stmt(pp, CmppStmt_spRelease, true); |
| if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ |
| --pp->pimpl->flags.nSavepoint; |
| } |
| } |
| } |
| return ppCode; |
| } |
| |
| int cmpp__dx_sp_rollback(cmpp_dx * const dx){ |
| /* Remember that rollback must (mostly) ignore the pending error state. */ |
| if( !dx->pimpl->nSavepoint ){ |
| if( 0==dxppCode ){ |
| cmpp_dx_err(dx, CMPP_RC_MISUSE, |
| "Cannot roll back: no active savepoint"); |
| } |
| }else{ |
| cmpp_sp_rollback(dx->pp); |
| --dx->pimpl->nSavepoint; |
| } |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){ |
| if( 0==ppCode ){ |
| if( !pp->pimpl->flags.nSavepoint ){ |
| cmpp__err(pp, CMPP_RC_MISUSE, |
| "Cannot commit: no active savepoint"); |
| }else{ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true); |
| assert( q || !"db init would have otherwise failed"); |
| if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){ |
| --pp->pimpl->flags.nSavepoint; |
| } |
| } |
| }else{ |
| cmpp_sp_rollback(pp); |
| } |
| return ppCode; |
| } |
| |
| int cmpp__dx_sp_commit(cmpp_dx * const dx){ |
| if( 0==dxppCode ){ |
| if( !dx->pimpl->nSavepoint ){ |
| cmpp_dx_err(dx, CMPP_RC_MISUSE, |
| "Cannot commit: no active savepoint"); |
| }else if( 0==cmpp_sp_commit(dx->pp) ){ |
| --dx->pimpl->nSavepoint; |
| } |
| } |
| return dxppCode; |
| } |
| |
| static void cmpp_dx_pimpl_reuse(cmpp_dx_pimpl *p){ |
| #if 0 |
| /* no: we need most of the state to remain |
| intact. */ |
| cmpp_dx_pimpl const tmp = *p; |
| *p = cmpp_dx_pimpl_empty; |
| p->buf = tmp.buf; |
| p->args = tmp.args; |
| #endif |
| cmpp_b_reuse(&p->buf.line); |
| cmpp_b_reuse(&p->buf.argsRaw); |
| cmpp_args_reuse(&p->args); |
| } |
| |
| void cmpp_dx_pimpl_cleanup(cmpp_dx_pimpl *p){ |
| cmpp_b_clear(&p->buf.line); |
| cmpp_b_clear(&p->buf.argsRaw); |
| cmpp_args_cleanup(&p->args); |
| *p = cmpp_dx_pimpl_empty; |
| } |
| |
| void cmpp_dx__reset(cmpp_dx * const dx){ |
| dx->args = cmpp_dx_empty.args; |
| cmpp_dx_pimpl_reuse(dx->pimpl); |
| dx->d = 0; |
| //no: dx->sourceName = 0; |
| } |
| |
| void cmpp_dx_cleanup(cmpp_dx * const dx){ |
| unsigned prev = 0; |
| CmppLvlList_cleanup(&dx->pimpl->dxLvl); |
| while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){ |
| prev = dx->pimpl->nSavepoint; |
| cmpp__dx_sp_rollback(dx); |
| } |
| cmpp_dx_pimpl_cleanup(dx->pimpl); |
| memset(dx, 0, sizeof(*dx)); |
| } |
| |
| int cmpp__find_closing2(cmpp *pp, |
| unsigned char const **zPos, |
| unsigned char const *zEnd, |
| cmpp_size_t * pNl){ |
| unsigned char const * z = *zPos; |
| unsigned char const opener = *z; |
| unsigned char closer = 0; |
| switch(opener){ |
| case '(': closer = ')'; break; |
| case '[': closer = ']'; break; |
| case '{': closer = '}'; break; |
| case '"': case '\'': closer = opener; break; |
| default: |
| return cmpp__err(pp, CMPP_RC_MISUSE, |
| "Invalid starting char (0x%x) for %s()", |
| (int)opener, __func__); |
| } |
| int count = 1; |
| for( ++z; z < zEnd; ++z ){ |
| if( closer == *z && 0==--count ){ |
| /* Have to check this first for the case of "" and ''. */ |
| break; |
| }else if( opener == *z ){ |
| ++count; |
| }else if( pNl && '\n'==*z ){ |
| ++*pNl; |
| } |
| } |
| if( closer!=*z ){ |
| if( 0 ){ |
| g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z, |
| (zEnd - *zPos), *zPos); |
| } |
| //assert(!"here"); |
| cmpp__err(pp, CMPP_RC_SYNTAX, |
| "Unbalanced %c%c: %.*s", |
| opener, closer, |
| (int)(z-*zPos), *zPos); |
| }else{ |
| if( 0 ){ |
| g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos); |
| } |
| *zPos = z; |
| } |
| return ppCode; |
| } |
| |
| cmpp_tt cmpp__tt_for_sqlite(int sqType){ |
| cmpp_tt rv; |
| switch( sqType ){ |
| case SQLITE_INTEGER: rv = cmpp_TT_Int; break; |
| case SQLITE_NULL: rv = cmpp_TT_Null; break; |
| default: rv = cmpp_TT_String; break; |
| } |
| return rv; |
| } |
| |
| int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q, |
| bool defineIfNoRow){ |
| if( 0==ppCode ){ |
| int const nCol = sqlite3_column_count(q); |
| assert( sqlite3_data_count(q)>0 || defineIfNoRow); |
| /* Create a #define for each column */ |
| bool const hasRow = sqlite3_data_count(q)>0; |
| for( int i = 0; !ppCode && i < nCol; ++i ){ |
| char const * const zCol = sqlite3_column_name(q, i); |
| if( hasRow ){ |
| unsigned char const * const zVal = sqlite3_column_text(q, i); |
| int const nVal = sqlite3_column_bytes(q, i); |
| cmpp_tt const ttype = |
| cmpp__tt_for_sqlite(sqlite3_column_type(q,i)); |
| cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype); |
| }else if(defineIfNoRow){ |
| cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_TT_Null); |
| }else{ |
| break; |
| } |
| } |
| } |
| return ppCode; |
| } |
| |
| cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){ |
| cmpp_d const * d = 0;//cmpp__d_search(zName); |
| if( !d ){ |
| CmppDList_entry const * e = |
| CmppDList_search(&pp->pimpl->d.list, zName); |
| if( e ) d = &e->d; |
| } |
| return d; |
| } |
| |
| cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName, |
| cmpp_flag32_t what){ |
| cmpp_d const * d = cmpp__d_search(pp, zName); |
| if( !d ){ |
| CmppDList_entry const * e = 0; |
| if( cmpp__d_search3_F_DELAYED & what ){ |
| int rc = cmpp__d_delayed_load(pp, zName); |
| if( 0==rc ){ |
| e = CmppDList_search(&pp->pimpl->d.list, zName); |
| }else if( CMPP_RC_NO_DIRECTIVE!=rc ){ |
| assert( ppCode ); |
| return NULL; |
| } |
| } |
| if( !e |
| && (cmpp__d_search3_F_AUTOLOADER & what) |
| && pp->pimpl->d.autoload.f |
| && 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){ |
| e = CmppDList_search(&pp->pimpl->d.list, zName); |
| } |
| #if CMPP_D_MODULE |
| if( !e |
| && !ppCode |
| && (cmpp__d_search3_F_DLL & what) ){ |
| char * z = sqlite3_mprintf("libcmpp-d-%s", zName); |
| cmpp_check_oom(pp, z); |
| int rc = cmpp_module_load(pp, z, NULL); |
| sqlite3_free(z); |
| if( rc ){ |
| if( CMPP_RC_NOT_FOUND==rc ){ |
| cmpp__err_clear(pp); |
| } |
| return NULL; |
| } |
| e = CmppDList_search(&pp->pimpl->d.list, zName); |
| } |
| #endif |
| if( e ) d = &e->d; |
| } |
| return d; |
| } |
| |
| int cmpp_dx_process(cmpp_dx * const dx){ |
| if( 0==dxppCode ){ |
| cmpp_d const * const d = cmpp_dx_d(dx); |
| assert( d ); |
| if( !cmpp_dx_is_eliding(dx) || (d->flags & cmpp_d_F_FLOW_CONTROL) ){ |
| if( (cmpp_d_F_NOT_IN_SAFEMODE & d->flags) |
| && (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.newFlags) ){ |
| cmpp_dx_err(dx, CMPP_RC_ACCESS, |
| "Directive %s%s is disabled by safe mode.", |
| cmpp_dx_delim(dx), dx->d->name.z); |
| }else{ |
| assert(d->impl.callback); |
| d->impl.callback(dx); |
| } |
| } |
| } |
| return dxppCode; |
| } |
| |
| |
| static void cmpp_dx__setup_include_path(cmpp_dx * dx){ |
| /* Add the leading dir part of dx->sourceName as the |
| highest-priority include path. It gets removed |
| in cmpp_dx__teardown(). */ |
| assert( dx->sourceName ); |
| enum { BufSize = 512 * 4 }; |
| unsigned char buf[BufSize] = {0}; |
| unsigned char *z = &buf[0]; |
| cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1); |
| if( n > (unsigned)BufSize-1 ) return; |
| memcpy(z, dx->sourceName, n); |
| buf[n] = 0; |
| cmpp_ssize_t i = n - 1; |
| for( ; i > 0; --i ){ |
| if( '/'==z[i] || '\\'==z[i] ){ |
| z[i] = 0; |
| n = i; |
| break; |
| } |
| } |
| if( n>(cmpp_size_t)i ){ |
| /* No path separator found. Assuming '.'. This is intended to |
| replace the historical behavior of automatically adding '.' if |
| no -I flags are used. Potential TODO is getcwd() here instead |
| of using '.' */ |
| n = 1; |
| buf[0] = '.'; |
| buf[1] = 0; |
| } |
| int64_t rowid = 0; |
| cmpp__include_dir_add(dx->pp, (char const*)buf, |
| dx->pp->pimpl->flags.nDxDepth, |
| &rowid); |
| if( rowid ){ |
| //g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z); |
| dx->pimpl->shadow.ridInclPath = rowid; |
| } |
| } |
| |
| static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx, |
| unsigned char const * zIn, |
| cmpp_ssize_t nIn){ |
| if( 0==ppCode ){ |
| assert( dx->sourceName ); |
| assert( dx->pimpl ); |
| assert( pp==dx->pp ); |
| nIn = cmpp__strlenu(zIn, nIn); |
| if( !nIn ) return 0; |
| pp->pimpl->dx = dx; |
| dx->pimpl->zBegin = zIn; |
| dx->pimpl->zEnd = zIn + nIn; |
| cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName, |
| &dx->pimpl->shadow.sidFile); |
| ++dx->pp->pimpl->flags.nDxDepth; |
| cmpp_dx__setup_include_path(dx); |
| } |
| return ppCode; |
| } |
| |
| static void cmpp_dx__teardown(cmpp_dx *dx){ |
| if( dx->pimpl->shadow.ridInclPath>0 ){ |
| cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath); |
| dx->pimpl->shadow.ridInclPath = 0; |
| } |
| if( dx->pimpl->shadow.sidFile ){ |
| cmpp_define_unshadow(dx->pp, "__FILE__", |
| dx->pimpl->shadow.sidFile); |
| } |
| --dx->pp->pimpl->flags.nDxDepth; |
| cmpp_dx_cleanup(dx); |
| } |
| |
| CMPP__EXPORT(int, cmpp_process_string)( |
| cmpp *pp, const char * zName, |
| unsigned char const * zIn, |
| cmpp_ssize_t nIn |
| ){ |
| if( !zName ) zName = ""; |
| if( 0==cmpp__db_init(pp) ){ |
| cmpp_dx const * const oldDx = pp->pimpl->dx; |
| cmpp_dx_pimpl dxp = cmpp_dx_pimpl_empty; |
| cmpp_dx dx = { |
| .pp = pp, |
| .sourceName = ustr_c(zName), |
| .args = cmpp_dx_empty.args, |
| .pimpl = &dxp |
| }; |
| dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall; |
| pp->pimpl->flags.nextIsCall = false; |
| if( dxp.flags.nextIsCall ){ |
| assert( pp->pimpl->dx ); |
| dxp.pos.lineNo = pp->pimpl->dx->pimpl->pos.lineNo; |
| } |
| bool gotOne = false; |
| (void)cmpp__stmt(pp, CmppStmt_sdefIns, true); |
| (void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true); |
| (void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true); |
| (void)cmpp__stmt(pp, CmppStmt_sdefDel, true) |
| /* hack: ensure that those queries are allocated now, as an |
| error in processing may keep them from being created |
| later. We might want to rethink the |
| prepare-no-statements-on-error bits, but will have to go back |
| and fix routines which currently rely on that. */; |
| cmpp_dx__setup(pp, &dx, zIn, nIn); |
| while(0==ppCode |
| && 0==cmpp_dx_next(&dx, &gotOne) |
| && gotOne){ |
| cmpp_dx_process(&dx); |
| } |
| if(0==ppCode && 0!=dx.pimpl->dxLvl.n){ |
| CmppLvl const * const lv = CmppLvl_get(&dx); |
| cmpp_dx_err(&dx, CMPP_RC_SYNTAX, |
| "Input ended inside an unterminated nested construct " |
| "opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName, |
| lv ? lv->lineNo : (cmpp_size_t)0); |
| } |
| cmpp_dx__teardown(&dx); |
| pp->pimpl->dx = oldDx; |
| } |
| if( !ppCode ){ |
| cmpp_outputer_flush(&pp->pimpl->out) |
| /* We're going to ignore a result code just this once. */; |
| } |
| return ppCode; |
| } |
| |
| int cmpp_process_file(cmpp *pp, const char * zName){ |
| if( 0==ppCode ){ |
| FileWrapper fw = FileWrapper_empty; |
| if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb") |
| && 0==cmpp__FileWrapper_slurp(pp, &fw) ){ |
| cmpp_process_string(pp, zName, fw.zContent, fw.nContent); |
| } |
| FileWrapper_close(&fw); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName, |
| cmpp_input_f src, void * srcState){ |
| if( 0==ppCode ){ |
| cmpp_b * const os = cmpp_b_borrow(pp); |
| int const rc = os |
| ? cmpp_stream(src, srcState, cmpp_output_f_b, os) |
| : ppCode; |
| if( 0==rc ){ |
| cmpp_process_string(pp, zName, os->z, os->n); |
| }else{ |
| cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName); |
| } |
| cmpp_b_return(pp, os); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_call_str)( |
| cmpp *pp, unsigned char const * z, cmpp_ssize_t n, |
| cmpp_b * dest, cmpp_flag32_t flags |
| ){ |
| if( ppCode ) return ppCode; |
| cmpp_args args = cmpp_args_empty; |
| cmpp_b * const b = cmpp_b_borrow(pp); |
| cmpp_b * const bo = cmpp_b_borrow(pp); |
| cmpp_outputer oB = cmpp_outputer_b; |
| if( !b || !bo ) return ppCode; |
| cmpp__pi(pp); |
| oB.state = bo; |
| oB.name = pi->out.name;//"[call]"; |
| n = cmpp__strlenu(z, n); |
| //g_warn("calling: <<%.*s>>", (int)n, z); |
| unsigned char const * zEnd = z+n; |
| cmpp_skip_snl(&z, zEnd); |
| cmpp_skip_snl_trailing(z, &zEnd); |
| n = (zEnd-z); |
| if( !n ){ |
| cmpp_err_set(pp, CMPP_RC_SYNTAX, |
| "Empty [call] is not permitted."); |
| goto end; |
| } |
| //g_warn("calling: <<%.*s>>", (int)n, z); |
| cmpp__delim const * const delim = cmpp__pp_delim(pp); |
| assert(delim); |
| if( (cmpp_size_t)n<=delim->open.n |
| || 0!=memcmp(z, delim->open.z, delim->open.n) ){ |
| /* If it doesn't start with the current delimiter, |
| prepend one. */ |
| cmpp_b_reserve3(pp, b, delim->open.n + n + 2); |
| cmpp_b_append4(pp, b, delim->open.z, delim->open.n); |
| } |
| cmpp_b_append4(pp, b, z, n); |
| if( !ppCode ){ |
| cmpp_outputer oOld = cmpp_outputer_empty; |
| pi->flags.nextIsCall = true |
| /* Convey (indirectly) that the first cmpp_dx_next() call made |
| via cmpp_process_string() is a call context. */; |
| cmpp__outputer_swap(pp, &oB, &oOld); |
| cmpp_process_string(pp, (char*)b->z, b->z, b->n); |
| cmpp__outputer_swap(pp, &oOld, &oB); |
| assert( !pi->flags.nextIsCall || ppCode ); |
| pi->flags.nextIsCall = false; |
| } |
| if( !ppCode ){ |
| unsigned char const * zz = bo->z; |
| unsigned char const * zzEnd = bo->z + bo->n; |
| if( cmpp_call_F_TRIM_ALL & flags ){ |
| cmpp_skip_snl(&zz, zzEnd); |
| cmpp_skip_snl_trailing(zz, &zzEnd); |
| }else if( 0==(cmpp_call_F_NO_TRIM & flags) ){ |
| cmpp_b_chomp(bo); |
| zzEnd = bo->z + bo->n; |
| } |
| if( (zzEnd-zz) ){ |
| cmpp_b_append4(pp, dest, zz, (zzEnd-zz)); |
| } |
| } |
| end: |
| cmpp_b_return(pp, b); |
| cmpp_b_return(pp, bo); |
| cmpp_args_cleanup(&args); |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){ |
| switch(errNo){ |
| /* Please expand on this as tests/use cases call for it... */ |
| case 0: |
| return 0; |
| case EINVAL: |
| return CMPP_RC_MISUSE; |
| case ENOMEM: |
| return CMPP_RC_OOM; |
| case EROFS: |
| case EACCES: |
| case EBUSY: |
| case EPERM: |
| case EDQUOT: |
| case EAGAIN: |
| case ETXTBSY: |
| return CMPP_RC_ACCESS; |
| case EISDIR: |
| case ENOTDIR: |
| return CMPP_RC_TYPE; |
| case ENAMETOOLONG: |
| case ELOOP: |
| case ERANGE: |
| return CMPP_RC_RANGE; |
| case ENOENT: |
| case ESRCH: |
| return CMPP_RC_NOT_FOUND; |
| case EEXIST: |
| case ENOTEMPTY: |
| return CMPP_RC_ALREADY_EXISTS; |
| case EIO: |
| return CMPP_RC_IO; |
| default: |
| return dflt; |
| } |
| } |
| |
| int cmpp_flush_f_FILE(void * _FILE){ |
| return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0; |
| } |
| |
| int cmpp_output_f_FILE( void * state, |
| void const * src, cmpp_size_t n ){ |
| return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout)) |
| ? 0 : CMPP_RC_IO; |
| } |
| |
| int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){ |
| int const fd = *((int*)state); |
| ssize_t const wn = write(fd, src, n); |
| return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0; |
| } |
| |
| int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){ |
| cmpp_FILE * f = state; |
| cmpp_size_t const rn = *n; |
| *n = (cmpp_size_t)fread(dest, 1, rn, f); |
| return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO); |
| } |
| |
| int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){ |
| int const fd = *((int*)state); |
| ssize_t const rn = read(fd, dest, *n); |
| if( rn<0 ){ |
| return cmpp_errno_rc(errno, CMPP_RC_IO); |
| }else{ |
| *n = (cmpp_size_t)rn; |
| return 0; |
| } |
| } |
| |
| void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){ |
| if( self->state ){ |
| cmpp_fclose( self->state ); |
| self->name = NULL; |
| self->state = NULL; |
| } |
| } |
| |
| CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){ |
| if( self->state ) cmpp_b_clear(self->state); |
| } |
| |
| CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){ |
| return o->out ? o->out(o->state, p, n) : 0; |
| } |
| |
| CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){ |
| return o->flush ? o->flush(o->state) : 0; |
| } |
| |
| CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){ |
| if( o->cleanup ){ |
| o->cleanup( o ); |
| } |
| } |
| |
| CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState, |
| cmpp_output_f outF, void * outState ){ |
| int rc = 0; |
| enum { BufSize = 1024 * 4 }; |
| unsigned char buf[BufSize]; |
| cmpp_size_t rn = BufSize; |
| while( 0==rc |
| && (rn==BufSize) |
| && (0==(rc=inF(inState, buf, &rn))) ){ |
| if(rn) rc = outF(outState, buf, rn); |
| } |
| return rc; |
| } |
| |
| void cmpp__fatalv_base(char const *zFile, int line, |
| char const *zFmt, va_list va){ |
| cmpp_FILE * const fp = stderr; |
| fflush(stdout); |
| fprintf(fp, "\n%s:%d: ", zFile, line); |
| if(zFmt && *zFmt){ |
| vfprintf(fp, zFmt, va); |
| fputc('\n', fp); |
| } |
| fflush(fp); |
| exit(1); |
| } |
| |
| void cmpp__fatal_base(char const *zFile, int line, |
| char const *zFmt, ...){ |
| va_list va; |
| va_start(va, zFmt); |
| cmpp__fatalv_base(zFile, line, zFmt, va); |
| va_end(va); |
| } |
| |
| CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){ |
| if( zMsg && ppCode ) *zMsg = pp->pimpl->err.zMsg; |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){ |
| int const rc = ppCode; |
| if( rc ){ |
| *zMsg = pp->pimpl->err.zMsg; |
| pp->pimpl->err = cmpp_pimpl_empty.err; |
| } |
| return rc; |
| } |
| |
| //CMPP_WASM_EXPORT |
| void cmpp__err_clear(cmpp *pp){ |
| cmpp_mfree(pp->pimpl->err.zMsg); |
| pp->pimpl->err = cmpp_pimpl_empty.err; |
| } |
| |
| CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){ |
| return pp ? pp->pimpl->err.code : 0; |
| } |
| |
| CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){ |
| *pos = dx->pimpl->pos; |
| } |
| |
| CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){ |
| dx->pimpl->pos = *pos; |
| } |
| |
| |
| //CMPP_WASM_EXPORT |
| void cmpp__dx_append_script_info(cmpp_dx const * dx, |
| sqlite3_str * const sstr){ |
| sqlite3_str_appendf( |
| sstr, |
| "%s%s@ %s line %" CMPP_SIZE_T_PFMT, |
| dx->d ? dx->d->name.z : "", |
| dx->d ? " " : "", |
| (dx->sourceName |
| && 0==strcmp("-", (char const *)dx->sourceName)) |
| ? "<stdin>" |
| : (char const *)dx->sourceName, |
| dx->pimpl->dline.lineNo |
| ); |
| } |
| |
| int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list va){ |
| if( pp ){ |
| cmpp__err_clear(pp); |
| ppCode = rc; |
| if( 0==rc ) return rc; |
| if( CMPP_RC_OOM==rc ){ |
| oom: |
| pp->pimpl->err.zMsgC = "An allocation failed."; |
| return pp->pimpl->err.code = CMPP_RC_OOM; |
| } |
| assert( !pp->pimpl->err.zMsg ); |
| if( pp->pimpl->dx || (zFmt && *zFmt) ){ |
| sqlite3_str * sstr = 0; |
| sstr = sqlite3_str_new(pp->pimpl->db.dbh); |
| if( pp->pimpl->dx ){ |
| cmpp__dx_append_script_info(pp->pimpl->dx, sstr); |
| sqlite3_str_append(sstr, ": ", 2); |
| } |
| if( zFmt && *zFmt ){ |
| sqlite3_str_vappendf(sstr, zFmt, va); |
| }else{ |
| sqlite3_str_appendf(sstr, "No error info provided."); |
| } |
| pp->pimpl->err.zMsgC = |
| pp->pimpl->err.zMsg = sqlite3_str_finish(sstr); |
| if( !pp->pimpl->err.zMsg ){ |
| goto oom; |
| } |
| }else{ |
| pp->pimpl->err.zMsgC = "No error info provided."; |
| } |
| rc = ppCode; |
| } |
| return rc; |
| } |
| |
| //CMPP_WASM_EXPORT no - variadic |
| int cmpp_err_set(cmpp *pp, int rc, |
| char const *zFmt, ...){ |
| if( pp ){ |
| va_list va; |
| va_start(va, zFmt); |
| rc = cmpp__errv(pp, rc, zFmt, va); |
| va_end(va); |
| } |
| return rc; |
| } |
| |
| const cmpp_d_autoloader cmpp_d_autoloader_empty = |
| cmpp_d_autoloader_empty_m; |
| |
| CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){ |
| if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state); |
| if( pNew ) pp->pimpl->d.autoload = *pNew; |
| else pp->pimpl->d.autoload = cmpp_d_autoloader_empty; |
| } |
| |
| CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){ |
| *pOld = pp->pimpl->d.autoload; |
| pp->pimpl->d.autoload = cmpp_d_autoloader_empty; |
| } |
| |
| //CMPP_WASM_EXPORT no - variadic |
| int cmpp_dx_err_set(cmpp_dx *dx, int rc, |
| char const *zFmt, ...){ |
| va_list va; |
| va_start(va, zFmt); |
| rc = cmpp__errv(dx->pp, rc, zFmt, va); |
| va_end(va); |
| return rc; |
| } |
| |
| CMPP__EXPORT(int, cmpp_err_set1)(cmpp *pp, int rc, char const *zMsg){ |
| return cmpp_err_set(pp, rc, (zMsg && *zMsg) ? "%s" : 0, zMsg); |
| } |
| |
| //no: CMPP_WASM_EXPORT |
| char * cmpp_path_search(cmpp *pp, |
| char const *zPath, |
| char pathSep, |
| char const *zBaseName, |
| char const *zExt){ |
| char * zrc = 0; |
| if( !ppCode ){ |
| sqlite3_stmt * const q = |
| cmpp__stmt(pp, CmppStmt_selPathSearch, false); |
| if( q ){ |
| unsigned char sep[2] = {pathSep, 0}; |
| cmpp__bind_text(pp, q, 1, ustr_c(zBaseName)); |
| cmpp__bind_text(pp, q, 2, sep); |
| cmpp__bind_text(pp, q, 3, ustr_c((zExt ? zExt : ""))); |
| cmpp__bind_text(pp, q, 4, ustr_c((zPath ? zPath: ""))); |
| int const dbrc = cmpp__step(pp, q, false); |
| if( SQLITE_ROW==dbrc ){ |
| unsigned char const * s = sqlite3_column_text(q, 1); |
| zrc = sqlite3_mprintf("%s", s); |
| cmpp_check_oom(pp, zrc); |
| } |
| cmpp__stmt_reset(q); |
| } |
| } |
| return zrc; |
| } |
| |
| #if CMPP__OBUF |
| int cmpp__obuf_flush(cmpp__obuf * b){ |
| if( 0==b->rc && b->cursor > b->begin ){ |
| if( b->dest.out ){ |
| b->rc = b->dest.out(b->dest.state, b->begin, |
| b->cursor-b->begin); |
| } |
| b->cursor = b->begin; |
| } |
| if( 0==b->rc && b->dest.flush ){ |
| b->rc = b->dest.flush(b->dest.state); |
| } |
| return b->rc; |
| } |
| |
| void cmpp__obuf_cleanup(cmpp__obuf * b){ |
| if( b ){ |
| cmpp__obuf_flush(b);/*ignoring result*/; |
| if( b->ownsMemory ){ |
| cmpp_mfree(b->begin); |
| } |
| *b = cmpp__obuf_empty; |
| } |
| } |
| |
| int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n){ |
| assert( b ); |
| if( n && !b->rc && b->dest.out ){ |
| assert( b->end ); |
| assert( b->cursor ); |
| assert( b->cursor <= b->end ); |
| assert( b->end>b->begin ); |
| if( b->cursor + n >= b->end ){ |
| if( 0==cmpp_flush_f_obuf(b) ){ |
| if( b->cursor + n >= b->end ){ |
| /* Go ahead and write it all */ |
| b->rc = b->dest.out(b->dest.state, src, n); |
| }else{ |
| goto copy_it; |
| } |
| } |
| }else{ |
| copy_it: |
| memcpy(b->cursor, src, n); |
| b->cursor += n; |
| } |
| } |
| return b->rc; |
| } |
| |
| int cmpp_flush_f_obuf(void * b){ |
| return cmpp__obuf_flush(b); |
| } |
| |
| int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n){ |
| return cmpp__obuf_write(state, src, n); |
| } |
| |
| void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o){ |
| cmpp__obuf_cleanup(o->state); |
| } |
| #endif /* CMPP__OBUF */ |
| |
| //cmpp__ListType_impl(cmpp__delim_list,cmpp__delim) |
| //cmpp__ListType_impl(CmppDList,CmppDList_entry*) |
| //cmpp__ListType_impl(CmppSohList,void*) |
| cmpp__ListType_impl(CmppArgList,cmpp_arg) |
| cmpp__ListType_impl(cmpp_b_list,cmpp_b*) |
| cmpp__ListType_impl(CmppLvlList,CmppLvl*) |
| |
| /** |
| Expects that *ndx points to the current argv entry and that it is a |
| flag which expects a value. This function checks for --flag=val and |
| (--flag val) forms. If a value is found then *ndx is adjusted (if |
| needed) to point to the next argument after the value and *zVal is |
| * pointed to the value. If no value is found then it returns false. |
| */ |
| static bool get_flag_val(int argc, |
| char const * const * argv, int * ndx, |
| char const **zVal){ |
| char const * zEq = strchr(argv[*ndx], '='); |
| if( zEq ){ |
| *zVal = zEq+1; |
| return 1; |
| }else if(*ndx+1>=argc){ |
| return 0; |
| }else{ |
| *zVal = argv[++*ndx]; |
| return 1; |
| } |
| } |
| |
| static |
| bool cmpp__arg_is_flag( char const *zFlag, char const *zArg, |
| char const **zValIfEqX ); |
| bool cmpp__arg_is_flag( char const *zFlag, char const *zArg, |
| char const **zValIfEqX ){ |
| if( zValIfEqX ) *zValIfEqX = 0; |
| if( 0==strcmp(zFlag, zArg) ) return true; |
| char const * z = strchr(zArg,'='); |
| if( z && z>zArg ){ |
| /* compare the part before the '=' */ |
| if( 0==strncmp(zFlag, zArg, z-zArg) ){ |
| if( !zFlag[z-zArg] ){ |
| if( zValIfEqX ) *zValIfEqX = z+1; |
| return true; |
| } |
| /* Else it was a prefix match. */ |
| } |
| } |
| return false; |
| } |
| |
| void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false); |
| if( q ){ |
| while( SQLITE_ROW==sqlite3_step(q) ){ |
| int const tt = sqlite3_column_int(q, 0); |
| unsigned char const * zK = sqlite3_column_text(q, 1); |
| unsigned char const * zV = sqlite3_column_text(q, 2); |
| int const nK = sqlite3_column_bytes(q, 1); |
| int const nV = sqlite3_column_bytes(q, 2); |
| char const * zTt = cmpp__tt_cstr(tt, true); |
| if( tt && zTt ) zTt += 3; |
| else zTt = "String"; |
| fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "", |
| nK, zK, zTt, nV, zV); |
| } |
| cmpp__stmt_reset(q); |
| } |
| } |
| |
| /** |
| This is what was originally the main() of cmpp v1, back when it was |
| a monolithic app. It still serves as the driver for main() but is |
| otherwise unused. |
| */ |
| CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc, |
| char const * const * argv){ |
| if( ppCode ) return ppCode; |
| int nFile = 0 /* number of files/-e scripts seen */; |
| |
| #define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \ |
| cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \ |
| argv[i]); \ |
| break; \ |
| } |
| #define M(X) cmpp__arg_is_flag(X, zArg, &zVal) |
| #define ISFLAG(X) else if(M(X)) |
| #define ISFLAG2(X,Y) else if(M(X) || M(Y)) |
| #define NOVAL if( zVal ){ \ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \ |
| break; \ |
| } (void)0 |
| |
| #define open_output_if_needed \ |
| if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break |
| |
| cmpp__staticAssert(TT_None,0==(int)cmpp_TT_None); |
| cmpp__staticAssert(Mask1, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_FLOW_CONTROL); |
| cmpp__staticAssert(Mask2, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_NOT_SIMPLIFY); |
| cmpp__staticAssert(Mask3, 0==(cmpp_d_F_MASK_INTERNAL & cmpp_d_F_MASK)); |
| |
| for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){ |
| /** |
| Loop through the flags twice. The first time we just validate |
| and look for --help/-?. The second time we process the flags. |
| This approach allows us to easily chain multiple files and |
| flags: |
| |
| ./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y |
| |
| Which, it turns out, is a surprisingly useful way to work. |
| */ |
| #define DOIT if(1==doIt) |
| for(int i = 0; i < argc && 0==ppCode; ++i){ |
| char const * zVal = 0; |
| int isNoFlag = 0; |
| char const * zArg = argv[i]; |
| //g_stderr("i=%d zArg=%s\n", i, zArg); |
| zVal = 0; |
| while('-'==*zArg) ++zArg; |
| if(zArg==argv[i]/*not a flag*/){ |
| zVal = zArg; |
| goto do_infile; |
| } |
| //g_warn("zArg=%s", zArg); |
| if( 0==strncmp(zArg,"no-",3) ){ |
| zArg += 3; |
| isNoFlag = 1; |
| } |
| if( M("?") || M("help") ){ |
| NOVAL; |
| cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]); |
| break; |
| }else if('D'==*zArg){ |
| ++zArg; |
| if(!*zArg){ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D"); |
| }else DOIT { |
| cmpp_define_legacy(pp, zArg, 0); |
| } |
| }else if('F'==*zArg){ |
| ++zArg; |
| if(!*zArg){ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F"); |
| }else DOIT { |
| cmpp__set_file(pp, ustr_c(zArg), -1); |
| } |
| } |
| ISFLAG("e"){ |
| ARGVAL; |
| DOIT { |
| ++nFile; |
| open_output_if_needed; |
| cmpp_process_string(pp, "-e script", |
| (unsigned char const *)zVal, -1); |
| } |
| }else if('U'==*zArg){ |
| ++zArg; |
| if(!*zArg){ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U"); |
| }else DOIT { |
| cmpp_undef(pp, zArg, NULL); |
| } |
| }else if('I'==*zArg){ |
| ++zArg; |
| if(!*zArg){ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I"); |
| }else DOIT { |
| cmpp_include_dir_add(pp, zArg); |
| } |
| }else if('L'==*zArg){ |
| ++zArg; |
| if(!*zArg){ |
| cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L"); |
| }else DOIT { |
| cmpp_module_dir_add(pp, zArg); |
| } |
| } |
| ISFLAG2("o","outfile"){ |
| ARGVAL; |
| DOIT { |
| cmpp__out_fopen(pp, zVal); |
| } |
| } |
| ISFLAG2("f","file"){ |
| ARGVAL; |
| do_infile: |
| DOIT { |
| if( !pp->pimpl->mod.path.z ){ |
| cmpp_module_dir_add(pp, NULL); |
| } |
| ++nFile; |
| if( 0 |
| && !pp->pimpl->flags.nIncludeDir |
| && cmpp_include_dir_add(pp, ".") ){ |
| break; |
| } |
| open_output_if_needed; |
| cmpp_process_file(pp, zVal); |
| } |
| } |
| ISFLAG("@"){ |
| NOVAL; |
| DOIT { |
| assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF ); |
| cmpp_atpol_set(pp, isNoFlag |
| ? cmpp_atpol_OFF |
| : cmpp_atpol_DEFAULT_FOR_FLAG); |
| } |
| } |
| ISFLAG("@policy"){ |
| ARGVAL; |
| cmpp_atpol_from_str(pp, zVal); |
| } |
| ISFLAG("debug"){ |
| NOVAL; |
| DOIT { |
| pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1; |
| } |
| } |
| ISFLAG2("u","undefined-policy"){ |
| ARGVAL; |
| cmpp_unpol_from_str(pp, zVal); |
| } |
| ISFLAG("sql-trace"){ |
| NOVAL; |
| /* Needs to be set before the start of the second pass, when |
| the db is inited. */ |
| DOIT { |
| pp->pimpl->sqlTrace.expandSql = false; |
| do_trace_flag: |
| cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out); |
| if( isNoFlag ){ |
| pp->pimpl->sqlTrace.out = cmpp_outputer_empty; |
| }else{ |
| pp->pimpl->sqlTrace.out = cmpp_outputer_FILE; |
| pp->pimpl->sqlTrace.out.state = stderr; |
| } |
| } |
| } |
| ISFLAG("sql-trace-x"){ |
| NOVAL; |
| DOIT { |
| pp->pimpl->sqlTrace.expandSql = true; |
| goto do_trace_flag; |
| } |
| } |
| ISFLAG("chomp-F"){ |
| NOVAL; |
| DOIT pp->pimpl->flags.chompF = !isNoFlag; |
| } |
| ISFLAG2("d","delimiter"){ |
| ARGVAL; |
| DOIT { |
| cmpp_delimiter_set(pp, zVal); |
| } |
| } |
| ISFLAG2("dd", "dump-defines"){ |
| DOIT { |
| cmpp_FILE * const fp = |
| /* tcl's exec treats output to stderr as failure. |
| If we use [exec -ignorestderr] then it instead replaces |
| stderr's output with its own message, invalidating |
| test expectations. */ |
| 1 ? stdout : stderr; |
| fprintf(fp, "All %sdefine entries:\n", |
| cmpp__pp_zdelim(pp)); |
| cmpp__dump_defines(pp, fp, 1); |
| } |
| } |
| #if !defined(CMPP_OMIT_D_DB) |
| ISFLAG2("db", "db-file"){ |
| /* Undocumented flag used for testing purposes. */ |
| ARGVAL; |
| DOIT { |
| cmpp_db_name_set(pp, zVal); |
| } |
| } |
| #endif |
| ISFLAG("version"){ |
| NOVAL; |
| #if !defined(CMPP_OMIT_FILE_IO) |
| fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n", |
| cmpp_version(), |
| sqlite3_libversion(), |
| sqlite3_sourceid()); |
| #endif |
| doIt = 100; |
| break; |
| } |
| #if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE) |
| ISFLAG("safe-mode"){ |
| if( i>0 ){ |
| cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "--%s, if used, must be the first argument.", |
| zArg); |
| break; |
| } |
| } |
| #endif |
| else{ |
| cmpp__err(pp,CMPP_RC_MISUSE, |
| "Unhandled flag: %s", argv[i]); |
| } |
| } |
| DOIT { |
| if(!nFile){ |
| /* We got no file arguments, so read from stdin. */ |
| if(0 |
| && !pp->pimpl->flags.nIncludeDir |
| && cmpp_include_dir_add(pp, ".") ){ |
| break; |
| } |
| open_output_if_needed; |
| cmpp_process_file(pp, "-"); |
| } |
| } |
| #undef DOIT |
| } |
| return ppCode; |
| #undef ARGVAL |
| #undef M |
| #undef ISFLAG |
| #undef ISFLAG2 |
| #undef NOVAL |
| #undef open_output_if_needed |
| } |
| |
| void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){ |
| #if defined(CMPP_OMIT_FILE_IO) |
| (void)zAppName; (void)fOut; |
| #else |
| fprintf(fOut, "%s version %s\nwith SQLite %s %s\n", |
| zAppName ? zAppName : "c-pp", |
| cmpp_version(), |
| sqlite3_libversion(), |
| sqlite3_sourceid()); |
| fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName); |
| fprintf(fOut, |
| "Flags and filenames may be in any order and " |
| "they are processed in that order.\n" |
| "\nFlags:\n"); |
| #define GAP " " |
| #define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D) |
| #if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE) |
| arg("--safe-mode", |
| "Disables preprocessing directives which use the filesystem " |
| "or invoke external processes. If used, it must be the first " |
| "argument."); |
| #endif |
| |
| arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n" |
| GAP "Because arguments are processed in order, this should\n" |
| GAP "normally be given before -f."); |
| arg("-f|--file FILE","Process FILE (default=- (stdin)).\n" |
| GAP "All non-flag arguments are assumed to be the input files."); |
| arg("-e SCRIPT", |
| "Treat SCRIPT as a complete c-pp input and process it.\n" |
| GAP "Doing anything marginally useful with this requires\n" |
| GAP "using it several times, once per directive. It will not\n" |
| GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for " |
| CMPP_DEFAULT_DELIM "expr, " |
| CMPP_DEFAULT_DELIM "assert, and " |
| CMPP_DEFAULT_DELIM "define."); |
| arg("-DXYZ[=value]","Define XYZ to the given value (default=1)."); |
| arg("-UXYZ","Undefine all defines matching glob XYZ."); |
| arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path."); |
| arg("-LXYZ","Add dir XYZ to the loadable module search path."); |
| arg("-FXYZ=filename", |
| "Define XYZ to the raw contents of the given file.\n" |
| GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n" |
| GAP "Maybe it should be. Or maybe we need a new flag for that."); |
| arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE " |
| "(default=" CMPP_DEFAULT_DELIM ")."); |
| arg("--@policy retain|elide|error|off", |
| "Specifies how to handle @tokens@ (default=off).\n" |
| GAP "off = do not look for @tokens@\n" |
| GAP "retain = parse @tokens@ and retain any undefined ones\n" |
| GAP "elide = parse @tokens@ and elide any undefined ones\n" |
| GAP "error = parse @tokens@ and error out for any undefined ones" |
| ); |
| arg("-u|--undefined-policy NAME", |
| "Sets the policy for how to handle references to undefined key:\n" |
| GAP "null = treat them as empty/falsy. This is the default.\n" |
| GAP "error = trigger an error. This should probably be " |
| "the default." |
| ); |
| arg("-@", "Equivalent to --@policy=error."); |
| arg("-no-@", "Equivalent to --@policy=off (the default)."); |
| arg("--sql-trace", "Send a trace of all SQL to stderr."); |
| arg("--sql-trace-x", |
| "Like --sql-trace but expand all bound values in the SQL."); |
| arg("--no-sql-trace", "Disable SQL tracing (default)."); |
| arg("--chomp-F", "One trailing newline is trimmed from files " |
| "read via -FXYZ=filename."); |
| arg("--no-chomp-F", "Disable --chomp-F (default)."); |
| #undef arg |
| #undef GAP |
| fputs("\nFlags which require a value accept either " |
| "--flag=value or --flag value. " |
| "The exceptions are that the -D... and -F... flags " |
| "require their '=' to be part of the flag (because they " |
| "are parsed elsewhere).\n\n",fOut); |
| #endif /*CMPP_OMIT_FILE_IO*/ |
| } |
| |
| #if defined(CMPP_MAIN) /* add main() */ |
| int main(int argc, char const * const * argv){ |
| int rc = 0; |
| cmpp * pp = 0; |
| cmpp_flag32_t newFlags = 0 |
| #if defined(CMPP_MAIN_SAFEMODE) |
| | cmpp_ctor_F_SAFEMODE |
| #endif |
| ; |
| cmpp_b bArgs = cmpp_b_empty; |
| sqlite3_config(SQLITE_CONFIG_URI,1); |
| { |
| /* Copy argv to a string so we can #define it. This has proven |
| helpful in testing, debugging, and output validation. */ |
| for( int i = 0; i < argc; ++i ){ |
| if( i ) cmpp_b_append_ch(&bArgs,' '); |
| cmpp_b_append(&bArgs, argv[i], strlen(argv[i])); |
| } |
| if( (rc = bArgs.errCode) ) goto end; |
| if( argc>1 && cmpp__arg_is_flag("--safe-mode", argv[1], NULL) ){ |
| newFlags |= cmpp_ctor_F_SAFEMODE; |
| --argc; |
| ++argv; |
| } |
| } |
| cmpp_ctor_cfg const cfg = { |
| .flags = newFlags |
| }; |
| rc = cmpp_ctor(&pp, &cfg); |
| if( rc ) goto end; |
| /** |
| Define CMPP_MAIN_INIT to the name of a function with the signature |
| |
| int (*)(cmpp*) |
| |
| to have it called here. The intent is that custom directives can |
| be installed this way without having to edit this code. |
| */ |
| #if defined(CMPP_MAIN_INIT) |
| extern int CMPP_MAIN_INIT(cmpp*); |
| if( 0!=(rc = CMPP_MAIN_INIT(pp)) ){ |
| g_warn0("Initialization via CMPP_MAIN_INIT() failed"); |
| goto end; |
| } |
| #endif |
| #if defined(CMPP_MAIN_AUTOLOADER) |
| { |
| extern int CMPP_MAIN_AUTOLOADER(cmpp*,char const *,void*); |
| cmpp_d_autoloader al = cmpp_d_autoloader_empty; |
| al.f = CMPP_MAIN_AUTOLOADER; |
| cmpp_d_autoloader_set(pp, &al); |
| } |
| #endif |
| if( cmpp_define_v2(pp, "c-pp::argv", (char*)bArgs.z) ) goto end; |
| cmpp_b_clear(&bArgs); |
| rc = cmpp_process_argv(pp, argc-1, argv+1); |
| switch( rc ){ |
| case 0: break; |
| case CMPP_RC_HELP: |
| rc = 0; |
| cmpp_process_argv_usage(argv[0], stdout); |
| break; |
| default: |
| break; |
| } |
| end: |
| cmpp_b_clear(&bArgs); |
| if( pp ){ |
| char const *zErr = 0; |
| rc = cmpp_err_get(pp, &zErr); |
| if( rc && CMPP_RC_HELP!=rc ){ |
| g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr); |
| } |
| cmpp_dtor(pp); |
| }else if( rc && CMPP_RC_HELP!=rc ){ |
| g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc)); |
| } |
| sqlite3_shutdown(); |
| return rc ? EXIT_FAILURE : EXIT_SUCCESS; |
| } |
| #endif /* CMPP_MAIN */ |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the cmpp_b-related parts of libcmpp. |
| */ |
| |
| const cmpp_b cmpp_b_empty = cmpp_b_empty_m; |
| |
| CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp, |
| cmpp_b * const os, |
| void const * src, |
| cmpp_size_t n){ |
| if( !ppCode && cmpp_b_append(os, src, n) ){ |
| cmpp_check_oom(pp, 0); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp, |
| cmpp_b * const os, |
| cmpp_size_t n){ |
| if( !ppCode && cmpp_b_reserve(os, n) ){ |
| cmpp_check_oom(pp, 0); |
| } |
| return ppCode; |
| } |
| |
| |
| CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){ |
| if( s->z ) cmpp_mfree(s->z); |
| *s = cmpp_b_empty; |
| } |
| |
| CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){ |
| if( s->z ){ |
| #if 1 |
| memset(s->z, 0, s->nAlloc) |
| /* valgrind pushes for this, which is curious because |
| cmpp_b_reserve[3]() memset()s new space to 0. |
| |
| Try the following without this block using one commit after |
| [5f9c31d1da1d] (that'll be the commit that this comment and #if |
| block were added): |
| |
| ##define foo |
| ##if not defined a |
| ##/if |
| ##query define {select ?1 a} bind [1] |
| |
| There's a misuse complaint about a jump depending on |
| uninitialized memory deep under cmpp__is_int(), in strlen(), on |
| the "define" argument of the ##query. It does not appear if |
| the lines above it are removed, which indicates that it's at |
| least semi-genuine. gcc v13.3.0, if it matters. |
| */; |
| #else |
| s->z[0] = 0; |
| #endif |
| s->n = 0; |
| } |
| s->errCode = 0; |
| return s; |
| } |
| |
| CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){ |
| if( l!=r ){ |
| cmpp_b const x = *l; |
| *l = *r; |
| *r = x; |
| } |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){ |
| if( 0==s->errCode && s->nAlloc < n ){ |
| void * const m = cmpp_mrealloc(s->z, s->nAlloc + n); |
| if( m ){ |
| memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc)) |
| /* valgrind convincingly recommends this. */; |
| s->z = m; |
| s->nAlloc += n; |
| }else{ |
| s->errCode = CMPP_RC_OOM; |
| } |
| } |
| return s->errCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src, |
| cmpp_size_t n){ |
| if(0==os->errCode){ |
| cmpp_size_t const nNeeded = os->n + n + 1; |
| if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){ |
| assert( CMPP_RC_OOM==os->errCode ); |
| return os->errCode; |
| } |
| memcpy(os->z + os->n, src, n); |
| os->n += n; |
| os->z[os->n] = 0; |
| if( 0 ) { |
| g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n, |
| (char const*) src, (int)os->n); |
| } |
| } |
| return os->errCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){ |
| if( 0==os->errCode |
| && (os->n+1<os->nAlloc |
| || 0==cmpp_b_reserve(os, os->n+2)) ){ |
| os->z[os->n++] = (unsigned char)ch; |
| os->z[os->n] = 0; |
| } |
| return os->errCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_append_i32)(cmpp_b * os, int32_t d){ |
| if( 0==os->errCode ){ |
| char buf[16] = {0}; |
| int const n = snprintf(buf, sizeof(buf), "%" PRIi32, d); |
| cmpp_b_append(os, buf, (unsigned)n); |
| } |
| return os->errCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_b_append_i64)(cmpp_b * os, int64_t d){ |
| if( 0==os->errCode ){ |
| char buf[32] = {0}; |
| int const n = snprintf(buf, sizeof(buf), "%" PRIi64, d); |
| cmpp_b_append(os, buf, (unsigned)n); |
| } |
| return os->errCode; |
| } |
| |
| CMPP__EXPORT(bool, cmpp_b_chomp)(cmpp_b * b){ |
| return cmpp_chomp(b->z, &b->n); |
| } |
| |
| CMPP__EXPORT(void, cmpp_b_list_cleanup)(cmpp_b_list *li){ |
| while( li->nAlloc ){ |
| cmpp_b * const b = li->list[--li->nAlloc]; |
| if(b){ |
| cmpp_b_clear(b); |
| cmpp_mfree(b); |
| } |
| } |
| cmpp_mfree(li->list); |
| *li = cmpp_b_list_empty; |
| } |
| |
| CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){ |
| while( li->n ){ |
| cmpp_b * const b = li->list[li->n--]; |
| if(b) cmpp_b_reuse(b); |
| } |
| } |
| |
| static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){ |
| cmpp_b * p = 0; |
| assert( li->list ? li->nAlloc : 0==li->nAlloc ); |
| if( !cmpp_b_list_reserve(NULL, li, |
| cmpp__li_reserve1_size(li, 20)) ){ |
| p = li->list[li->n]; |
| if( p ){ |
| cmpp_b_reuse(p); |
| }else{ |
| p = cmpp_malloc(sizeof(*p)); |
| if( p ){ |
| li->list[li->n++] = p; |
| *p = cmpp_b_empty; |
| } |
| } |
| } |
| return p; |
| } |
| |
| /** |
| bsearch()/qsort() comparison for (cmpp_b**), sorting by size, |
| largest first and empty slots last. |
| */ |
| static int cmpp_b__cmp_desc(const void *p1, const void *p2){ |
| cmpp_b const * const eL = *(cmpp_b const **)p1; |
| cmpp_b const * const eR = *(cmpp_b const **)p2; |
| if( eL==eR ) return 0; |
| else if( !eL ) return 1; |
| else if (!eR ) return -1; |
| return (int)(/*largest first*/eL->nAlloc - eR->nAlloc); |
| } |
| |
| /** |
| bsearch()/qsort() comparison for (cmpp_b**), sorting by size, |
| smallest first and empty slots last. |
| */ |
| static int cmpp_b__cmp_asc(const void *p1, const void *p2){ |
| cmpp_b const * const eL = *(cmpp_b const **)p1; |
| cmpp_b const * const eR = *(cmpp_b const **)p2; |
| if( eL==eR ) return 0; |
| else if( !eL ) return 1; |
| else if (!eR ) return -1; |
| return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc); |
| } |
| |
| /** |
| Sort li's buffer list using the given policy. NULL entries always |
| sort last. This is a no-op of how == cmpp_b_list_UNSORTED or |
| li->n<2. |
| */ |
| static void cmpp_b_list__sort(cmpp_b_list * const li, |
| enum cmpp_b_list_e how){ |
| switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){ |
| case cmpp_b_list_UNSORTED: |
| break; |
| case cmpp_b_list_DESC: |
| qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc); |
| break; |
| case cmpp_b_list_ASC: |
| qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc); |
| break; |
| } |
| } |
| |
| CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){ |
| cmpp__pi(pp); |
| cmpp_b_list * const li = &pi->recycler.buf; |
| cmpp_b * b = 0; |
| if( cmpp_b_list_UNSORTED==pi->recycler.bufSort ){ |
| pi->recycler.bufSort = cmpp_b_list_DESC; |
| cmpp_b_list__sort(li, pi->recycler.bufSort); |
| assert( cmpp_b_list_UNSORTED!=pi->recycler.bufSort |
| || pi->recycler.buf.n<2 ); |
| } |
| for( cmpp_size_t i = 0; i < li->n; ++i ){ |
| b = li->list[i]; |
| if( b ){ |
| li->list[i] = 0; |
| assert( !b->n && |
| "Someone wrote to a buffer after giving it back" ); |
| if( i < li->n-1 ){ |
| pi->recycler.bufSort = cmpp_b_list_UNSORTED; |
| } |
| return cmpp_b_reuse(b); |
| } |
| } |
| /** |
| Allocate the list entry now and then remove the buffer from it to |
| "borrow" it. We allocate now, instead of in cmpp_b_return(), so |
| that that function has no OOM condition (handling it properly in |
| higher-level code would be a mess). |
| */ |
| b = cmpp_b_list_push(li); |
| if( 0==cmpp_check_oom(pp, b) ) { |
| assert( b==li->list[li->n-1] ); |
| li->list[li->n-1] = 0; |
| } |
| return b; |
| } |
| |
| CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){ |
| if( !b ) return; |
| cmpp__pi(pp); |
| cmpp_b_list * const li = &pi->recycler.buf; |
| for( cmpp_size_t i = 0; i < li->n; ++i ){ |
| if( !li->list[i] ){ |
| li->list[i] = cmpp_b_reuse(b); |
| pi->recycler.bufSort = cmpp_b_list_UNSORTED; |
| return; |
| } |
| } |
| assert( !"This shouldn't be possible - no slot in recycler.buf" ); |
| cmpp_b_clear(b); |
| cmpp_mfree(b); |
| } |
| |
| CMPP__EXPORT(int, cmpp_output_f_b)( |
| void * state, void const * src, cmpp_size_t n |
| ){ |
| if( state ){ |
| return cmpp_b_append(state, src, n); |
| } |
| return 0; |
| } |
| |
| #if CMPP__OBUF |
| int cmpp__obuf_flush(cmpp__obuf * b); |
| int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n); |
| void cmpp__obuf_cleanup(cmpp__obuf * b); |
| int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n); |
| int cmpp_flush_f_obuf(void * state); |
| void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o); |
| const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m; |
| const cmpp_outputer cmpp_outputer_obuf = { |
| .out = cmpp_output_f_obuf, |
| .flush = cmpp_flush_f_obuf, |
| .cleanup = cmpp_outputer_cleanup_f_obuf |
| }; |
| #endif |
| /* |
| ** 2025-11-07: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the db-related pieces of libcmpp. |
| */ |
| |
| /** |
| A proxy for sqlite3_prepare() which updates pp->pimpl->err on error. |
| */ |
| static int cmpp__prepare(cmpp *pp, sqlite3_stmt **pStmt, |
| const char * zSql, ...){ |
| /* We need for pp->pimpl->stmt.sp* to work regardless of pending errors so |
| that we can, when appropriate, create the rollback statements. */ |
| sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh); |
| char * z = 0; |
| int n = 0; |
| va_list va; |
| assert( pp->pimpl->db.dbh ); |
| va_start(va, zSql); |
| sqlite3_str_vappendf(str, zSql, va); |
| va_end(va); |
| z = cmpp_str_finish(pp, str, &n); |
| if( z ){ |
| int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, z, n, pStmt, 0); |
| cmpp__db_rc(pp, rc, z); |
| sqlite3_free(z); |
| } |
| return ppCode; |
| } |
| |
| sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which, |
| bool prepEvenIfErr){ |
| if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL; |
| sqlite3_stmt ** q = 0; |
| char const * zSql = 0; |
| switch(which){ |
| default: |
| cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which); |
| return NULL; |
| #define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break; |
| CmppStmt_map(E) |
| #undef E |
| } |
| assert( q ); |
| assert( zSql && *zSql ); |
| if( !*q && (!ppCode || prepEvenIfErr) ){ |
| cmpp__prepare(pp, q, "%s", zSql); |
| } |
| return *q; |
| } |
| |
| void cmpp__stmt_reset(sqlite3_stmt * const q){ |
| if( q ){ |
| sqlite3_clear_bindings(q); |
| sqlite3_reset(q); |
| } |
| } |
| |
| static inline int cmpp__stmt_is_sp(cmpp const * const pp, |
| sqlite3_stmt const * const q){ |
| return q==pp->pimpl->stmt.spBegin |
| || q==pp->pimpl->stmt.spRelease |
| || q==pp->pimpl->stmt.spRollback; |
| } |
| |
| int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){ |
| int rc = SQLITE_ERROR; |
| assert( q ); |
| if( !ppCode || cmpp__stmt_is_sp(pp,q) ){ |
| rc = sqlite3_step(q); |
| cmpp__db_rc(pp, rc, sqlite3_sql(q)); |
| } |
| if( resetIt /* even if ppCode!=0 */ ) cmpp__stmt_reset(q); |
| assert( 0!=rc ); |
| return rc; |
| } |
| |
| |
| /** |
| Expects an SQLITE_... result code and returns an approximate match |
| from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE |
| as non-errors, returning 0 for those. |
| */ |
| static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode); |
| int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){ |
| (void)db; |
| int rc = 0; |
| switch(sqliteCode & 0xff){ |
| case SQLITE_ROW: |
| case SQLITE_DONE: |
| case SQLITE_OK: rc = 0; break; |
| case SQLITE_NOMEM: rc = CMPP_RC_OOM; break; |
| case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break; |
| case SQLITE_TOOBIG: |
| case SQLITE_FULL: |
| case SQLITE_RANGE: rc = CMPP_RC_RANGE; break; |
| case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break; |
| case SQLITE_PERM: |
| case SQLITE_AUTH: |
| case SQLITE_BUSY: |
| case SQLITE_LOCKED: |
| case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break; |
| case SQLITE_CANTOPEN: |
| case SQLITE_IOERR: rc = CMPP_RC_IO; break; |
| case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break; |
| default: |
| //MARKER(("sqlite3_errcode()=0x%04x\n", rc)); |
| rc = CMPP_RC_DB; break; |
| } |
| return rc; |
| } |
| |
| int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsg){ |
| switch(dbRc){ |
| case 0: |
| case SQLITE_DONE: |
| case SQLITE_ROW: |
| return 0; |
| default: |
| return cmpp_err_set( |
| pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc), |
| "SQLite error #%d: %s%s%s", |
| dbRc, |
| pp->pimpl->db.dbh |
| ? sqlite3_errmsg(pp->pimpl->db.dbh) |
| : "<no db handle>", |
| zMsg ? ": " : "", |
| zMsg ? zMsg : "" |
| ); |
| } |
| } |
| |
| /** |
| The base "define" impl. Requires q to be an INSERT for one of the |
| define tables and have the (t,k,v) columns set up to bind to ?1, |
| ?2, and ?3. |
| */ |
| static |
| int cmpp__define_impl(cmpp * const pp, |
| sqlite3_stmt * const q, |
| unsigned char const * zKey, |
| cmpp_ssize_t nKey, |
| unsigned char const *zVal, |
| cmpp_ssize_t nVal, |
| int tType, |
| bool resetStmt){ |
| if( 0==ppCode){ |
| assert( q ); |
| nKey = cmpp__strlenu(zKey, nKey); |
| nVal = cmpp__strlenu(zVal, nVal); |
| if( 0==cmpp__bind_textn(pp, q, 2, zKey, (int)nKey) |
| && 0==cmpp__bind_int(pp, q, 1, tType) ){ |
| //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq); |
| /* TODO? if tType==cmpp_TT_Blob, bind it as a blob */ |
| if( zVal ){ |
| if( nVal ){ |
| cmpp__bind_textn(pp, q, 3, zVal, (int)nVal); |
| }else{ |
| /* Arguable */ |
| cmpp__bind_null(pp, q, 3); |
| } |
| }else{ |
| cmpp__bind_int(pp, q, 3, 1); |
| } |
| cmpp__step(pp, q, resetStmt); |
| g_debug(pp,2,("define: %s [%s]=[%.*s]\n", |
| cmpp_tt_cstr(tType), zKey, (int)nVal, zVal)); |
| } |
| } |
| return ppCode; |
| } |
| |
| int cmpp__define2(cmpp *pp, |
| unsigned char const * zKey, |
| cmpp_ssize_t nKey, |
| unsigned char const *zVal, |
| cmpp_ssize_t nVal, |
| cmpp_tt tType){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false); |
| if( q ){ |
| cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true); |
| } |
| return ppCode; |
| } |
| |
| /** |
| The legacy variant of define() which accepts X=Y in zKey. This |
| continues to exist because it's convenient for passing args from |
| main(). |
| */ |
| static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal, |
| cmpp_tt ttype ){ |
| |
| if(ppCode) return ppCode; |
| CmppKvp kvp = CmppKvp_empty; |
| if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1, |
| zVal |
| ? CmppKvp_op_none |
| : CmppKvp_op_eq1) ) { |
| return ppCode; |
| } |
| if( kvp.v.z ){ |
| if( zVal ){ |
| assert(!"cannot happen - CmppKvp_op_none will prevent it"); |
| return cmpp_err_set(pp, CMPP_RC_MISUSE, |
| "Cannot assign two values to [%.*s] [%.*s] [%s]", |
| kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal); |
| } |
| }else{ |
| kvp.v.z = (unsigned char const *)zVal; |
| kvp.v.n = zVal ? (int)strlen(zVal) : 0; |
| } |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false); |
| if( !q ) return ppCode; |
| int64_t intCheck = 0; |
| switch( ttype ){ |
| case cmpp_TT_Unknown: |
| if(kvp.v.n){ |
| if( cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ |
| ttype = cmpp_TT_Int; |
| if( '+'==*kvp.v.z ){ |
| ++kvp.v.z; |
| --kvp.v.n; |
| } |
| }else{ |
| ttype = cmpp_TT_String; |
| } |
| }else if( kvp.v.z ){ |
| ttype = cmpp_TT_String; |
| }else{ |
| ttype = cmpp_TT_Int; |
| intCheck = 1 /* No value ==> value of 1. */; |
| } |
| break; |
| case cmpp_TT_Int: |
| if( !cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){ |
| ttype = cmpp_TT_String; |
| } |
| break; |
| default: |
| break; |
| } |
| if( 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, kvp.k.n) |
| && 0==cmpp__bind_int(pp, q, 1, ttype) ){ |
| //g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq); |
| switch( ttype ){ |
| case cmpp_TT_Int: |
| cmpp__bind_int(pp, q, 3, intCheck); |
| break; |
| case cmpp_TT_Null: |
| cmpp__bind_null(pp, q, 3); |
| break; |
| default: |
| cmpp__bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n); |
| break; |
| } |
| cmpp__step(pp, q, true); |
| g_debug(pp,2,("define: [%.*s]=[%.*s]\n", |
| kvp.k.n, kvp.k.z, |
| kvp.v.n, kvp.v.z)); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){ |
| return cmpp__define_legacy(pp, zKey, zVal, cmpp_TT_Unknown); |
| } |
| |
| CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){ |
| return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1, |
| cmpp_TT_String); |
| } |
| |
| static |
| int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey, |
| cmpp_ssize_t nKey, |
| unsigned char const *zVal, |
| cmpp_ssize_t nVal, |
| int ttype, |
| int64_t * pId){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false); |
| if( q ){ |
| if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false) |
| && pId ){ |
| *pId = sqlite3_column_int64(q, 0); |
| assert( *pId ); |
| } |
| cmpp__stmt_reset(q); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey, |
| char const *zVal, int64_t *pId){ |
| assert( pId ); |
| return cmpp__define_shadow(pp, ustr_c(zKey), -1, |
| ustr_c(zVal), -1, cmpp_TT_String, pId); |
| } |
| |
| static |
| int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey, |
| cmpp_ssize_t nKey, int64_t id){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false); |
| if( q ){ |
| cmpp__bind_textn(pp, q, 1, zKey, (int)nKey); |
| cmpp__bind_int(pp, q, 2, id); |
| cmpp__step(pp, q, true); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){ |
| return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id); |
| } |
| |
| /* |
| ** This sqlite3_trace_v2() callback outputs tracing info using |
| ** ((cmpp*)c)->sqlTrace.pFile. |
| */ |
| static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){ |
| switch(dx){ |
| case SQLITE_TRACE_STMT:{ |
| char const * const zSql = x; |
| cmpp * const pp = c; |
| cmpp__pi(pp); |
| if(pi->sqlTrace.out.out){ |
| char * const zExp = pi->sqlTrace.expandSql |
| ? sqlite3_expanded_sql((sqlite3_stmt*)p) |
| : 0; |
| sqlite3_str * const s = sqlite3_str_new(pi->db.dbh); |
| if( pi->dx ){ |
| cmpp__dx_append_script_info(pi->dx, s); |
| sqlite3_str_appendchar(s, 1, ':'); |
| sqlite3_str_appendchar(s, 1, ' '); |
| } |
| sqlite3_str_appendall(s, zExp ? zExp : zSql); |
| sqlite3_str_appendchar(s, 1, '\n'); |
| int const n = sqlite3_str_length(s); |
| if( n ){ |
| char * const z = sqlite3_str_finish(s); |
| if( z ){ |
| cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n); |
| sqlite3_free(z); |
| } |
| } |
| sqlite3_free(zExp); |
| } |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| #include <sys/stat.h> |
| /* |
| ** sqlite3 UDF which returns true if its argument refers to an |
| ** accessible file, else false. |
| */ |
| static void cmpp__udf_file_exists( |
| sqlite3_context *context, |
| int argc, |
| sqlite3_value **argv |
| ){ |
| const char *zName; |
| (void)(argc); /* Unused parameter */ |
| zName = (const char*)sqlite3_value_text(argv[0]); |
| if( 0!=zName ){ |
| struct stat sb; |
| sqlite3_result_int(context, stat(zName, &sb) |
| ? 0 |
| : S_ISREG(sb.st_mode)); |
| } |
| } |
| |
| static void cmpp__udf_truthy( |
| sqlite3_context *context, |
| int argc, |
| sqlite3_value **argv |
| ){ |
| (void)(argc); /* Unused parameter */ |
| assert(1==argc); |
| int buul = 0; |
| sqlite3_value * const sv = argv[0]; |
| switch( sqlite3_value_type(sv) ){ |
| case SQLITE_NULL: |
| break; |
| case SQLITE_FLOAT: |
| buul = 0.0!=sqlite3_value_double(sv); |
| break; |
| case SQLITE_INTEGER: |
| buul = 0!=sqlite3_value_int(sv); |
| break; |
| case SQLITE_TEXT: |
| case SQLITE_BLOB:{ |
| int const n = sqlite3_value_bytes(sv); |
| if( n>1 ) buul = 1; |
| else if( 1==n ){ |
| const char *z = |
| (const char*)sqlite3_value_text(sv); |
| buul = z |
| ? 0!=strcmp(z,"0") |
| : 0; |
| } |
| } |
| } |
| sqlite3_result_int(context, buul); |
| } |
| |
| /** |
| SQLite3 UDF which compares its two arguments using memcmp() |
| semantics. NULL will compare equal to NULL, but less than anything |
| else. |
| */ |
| static void cmpp__udf_compare( |
| sqlite3_context *context, |
| int argc, |
| sqlite3_value **argv |
| ){ |
| (void)(argc); /* Unused parameter */ |
| assert(2==argc); |
| sqlite3_value * const v1 = argv[0]; |
| sqlite3_value * const v2 = argv[1]; |
| unsigned char const * const z1 = sqlite3_value_text(v1); |
| unsigned char const * const z2 = sqlite3_value_text(v2); |
| int const n1 = sqlite3_value_bytes(v1); |
| int const n2 = sqlite3_value_bytes(v2); |
| int rv; |
| if( !z1 ){ |
| rv = z2 ? -1 : 0; |
| }else if( !z2 ){ |
| rv = 1; |
| }else{ |
| rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2); |
| } |
| if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv); |
| sqlite3_result_int(context, rv); |
| } |
| |
| int cmpp__db_init(cmpp *pp){ |
| cmpp__pi(pp); |
| if( pi->db.dbh || ppCode ) return ppCode; |
| int rc; |
| char * zErr = 0; |
| const char * zDrops = |
| "BEGIN EXCLUSIVE;" |
| "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;" |
| "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;" |
| "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;" |
| "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;" |
| "DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;" |
| "DROP VIEW IF EXISTS " CMPP__DB_MAIN_NAME ".vdef;" |
| "COMMIT;" |
| ; |
| const char * zSchema = |
| "BEGIN EXCLUSIVE;" |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".def(" |
| /* ^^^ defines */ |
| "t INTEGER DEFAULT NULL," |
| /*^^ type: cmpp_tt or NULL */ |
| "k TEXT PRIMARY KEY NOT NULL," |
| "v TEXT DEFAULT NULL" |
| ") WITHOUT ROWID;" |
| |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".incl(" |
| /* ^^^ files currently being included */ |
| "file TEXT PRIMARY KEY NOT NULL," |
| "srcFile TEXT DEFAULT NULL," |
| "srcLine INTEGER DEFAULT 0" |
| ") WITHOUT ROWID;" |
| |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath(" |
| /* ^^^ include path. We use (ORDER BY priority DESC, rowid) to |
| make their priority correct. priority should only be set by the |
| #include directive for its cwd entry. */ |
| "priority INTEGER DEFAULT 0," /* higher sorts first */ |
| "dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE" |
| ");" |
| |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath(" |
| /* ^^^ module path. We use ORDER BY ROWID to make their |
| priority correct. */ |
| "dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE" |
| ");" |
| |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".predef(" |
| /* ^^^ pre-defines */ |
| "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */ |
| "k TEXT PRIMARY KEY NOT NULL," |
| "v TEXT DEFAULT NULL" |
| ") WITHOUT ROWID;" |
| "INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)" |
| " VALUES(NULL,'cmpp::version','" CMPP_VERSION "')" |
| ";" |
| |
| /** |
| sdefs - "scoped defines" or "shadow defines". The problem these |
| solve is the one of supporting a __FILE__ define in cmpp input |
| sources, such that it remains valid both before and after an |
| #include, but has a new name in the scope of an #include. We |
| can't use savepoints for that because they're a nuclear option |
| affecting _all_ #defines in the #include'd file, whereas we |
| normally want #defines to stick around across files. |
| |
| See cmpp_define_shadow() and cmpp_define_unshadow(). |
| */ |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef(" |
| "id INTEGER PRIMARY KEY AUTOINCREMENT," |
| "t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */ |
| "k TEXT NOT NULL," |
| "v TEXT DEFAULT NULL" |
| ");" |
| |
| /** |
| vdef is a view consolidating the various #define stores. It's |
| intended to be used for all general-purpose fetching of defines |
| and it orders the results such that the library's defines |
| supercede all others, then scoped keys, then client-level |
| defines. |
| |
| To push a new sdef we simply insert into sdef. Then vdef will |
| order the newest sdef before any entry from the def table. |
| */ |
| "CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS" |
| " SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef" |
| /* ------^^^^ sorts before numbers */ |
| " UNION ALL" |
| " SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef" |
| /* ^^^^ sorts newest of matching keys first */ |
| " UNION ALL" |
| " SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def" |
| " ORDER BY 1, 3" |
| ";" |
| |
| #if 0 |
| "CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype(" |
| /* ^^^ token types */ |
| "t INTEGER PRIMARY KEY NOT NULL," |
| /*^^ type: cmpp_tt */ |
| "n TEXT NOT NULL," |
| /*^^ cmpp_TT_... name. */ |
| "s TEXT DEFAULT NULL" |
| /* Symbolic or directive name, if any. */ |
| ");" |
| #endif |
| |
| "COMMIT;" |
| "BEGIN EXCLUSIVE;" |
| ; |
| cmpp__err_clear(pp); |
| int openFlags = SQLITE_OPEN_READWRITE; |
| if( pi->db.zName ){ |
| openFlags |= SQLITE_OPEN_CREATE; |
| } |
| rc = sqlite3_open_v2( |
| pi->db.zName ? pi->db.zName : ":memory:", |
| &pi->db.dbh, openFlags, 0); |
| if(rc){ |
| cmpp__db_rc(pp, rc, pi->db.zName |
| ? pi->db.zName |
| : ":memory:"); |
| sqlite3_close(pi->db.dbh); |
| pi->db.dbh = 0; |
| assert(ppCode); |
| return rc; |
| } |
| sqlite3_busy_timeout(pi->db.dbh, 5000); |
| sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME, |
| CMPP__DB_MAIN_NAME); |
| rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT, |
| cmpp__db_sq3TraceV2, pp); |
| if( cmpp__db_rc(pp, rc, "Installing tracer failed") ){ |
| goto end; |
| } |
| //g_warn("Schema:\n%s\n",zSchema); |
| struct { |
| /* SQL UDFs */ |
| char const * const zName; |
| void (*xUdf)(sqlite3_context *,int,sqlite3_value **); |
| int arity; |
| int flags; |
| } aFunc[] = { |
| { |
| .zName = "cmpp_file_exists", |
| .xUdf = cmpp__udf_file_exists, |
| .arity = 1, |
| .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY |
| }, |
| { |
| .zName = "cmpp_truthy", |
| .xUdf = cmpp__udf_truthy, |
| .arity = 1, |
| .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC |
| }, |
| { |
| .zName = "cmpp_compare", |
| .xUdf = cmpp__udf_compare, |
| .arity = 2, |
| .flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC |
| } |
| }; |
| assert( 0==rc ); |
| for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){ |
| rc = sqlite3_create_function( |
| pi->db.dbh, aFunc[i].zName, aFunc[i].arity, |
| aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0 |
| ); |
| } |
| if( cmpp__db_rc(pp, rc, "UDF registration failed.") ){ |
| return ppCode; |
| } |
| if( pi->db.zName ){ |
| /* Drop all cmpp tables when using a persistent db so that we are |
| not beholden to a given structure. TODO: a config flag to |
| toggle this. */ |
| rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr); |
| } |
| if( !rc ){ |
| rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr); |
| } |
| |
| if( !rc ){ |
| extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *); |
| rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL); |
| } |
| |
| if(rc){ |
| if( zErr ){ |
| cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc), |
| "SQLite error #%d initializing DB: %s", rc, zErr); |
| sqlite3_free(zErr); |
| }else{ |
| cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc), |
| "SQLite error #%d initializing DB", rc); |
| } |
| goto end; |
| } |
| |
| while(0){ |
| /* Insert the ttype mappings. We don't yet make use of this but |
| only for lack of a use case ;). */ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false); |
| if( !q ) goto end; |
| #define E(N,STR) \ |
| cmpp__bind_int(pp, q, 1, cmpp_TT_ ## N); \ |
| cmpp__bind_textn(pp, q, 2, \ |
| ustr_c("cmpp_TT_ " # N), sizeof("cmpp_TT_" # N)-1); \ |
| if( STR ) cmpp__bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \ |
| else cmpp__bind_null(pp, q, 3); \ |
| if( SQLITE_DONE!=cmpp__step(pp, q, true) ) return ppCode; |
| cmpp_tt_map(E) |
| #undef E |
| sqlite3_finalize(q); |
| pi->stmt.insTtype = 0; |
| break; |
| } |
| |
| end: |
| if( !ppCode ){ |
| /* |
| ** Keep us from getting in the situation later that delayed |
| ** preparation if one of the savepoint statements fails (e.g. due |
| ** to OOM or memory corruption). |
| */ |
| cmpp__stmt(pp, CmppStmt_spBegin, false); |
| cmpp__stmt(pp, CmppStmt_spRelease, false); |
| cmpp__stmt(pp, CmppStmt_spRollback, false); |
| cmpp__lazy_init(pp); |
| } |
| return ppCode; |
| } |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the core cmpp_dx_f() implementations of libcmpp. |
| */ |
| |
| static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){ |
| return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' may only be used once.", |
| arg->z); |
| } |
| |
| /* No-op cmpp_dx_f() impl. */ |
| static void cmpp_dx_f_noop(cmpp_dx *dx){ |
| (void)dx; |
| } |
| |
| /** |
| cmpp_kav_each_f() impl for use by #define {k->v}. |
| */ |
| static int cmpp_kav_each_f_define__group( |
| cmpp_dx *dx, |
| unsigned char const *zKey, cmpp_size_t nKey, |
| unsigned char const *zVal, cmpp_size_t nVal, |
| void* callbackState |
| ){ |
| if( (callbackState==dx) |
| && cmpp_has(dx->pp, (char const*)zKey, nKey) ){ |
| return dxppCode; |
| } |
| return cmpp__define2(dx->pp, zKey, nKey, zVal, nVal, cmpp_TT_String); |
| } |
| |
| /* #error impl. */ |
| static void cmpp_dx_f_error(cmpp_dx *dx){ |
| const char *zBegin = (char const *)dx->args.z; |
| unsigned n = (unsigned)dx->args.nz; |
| if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){ |
| ++zBegin; |
| n -= 2; |
| } |
| if( n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_ERROR, "%.*s", n, zBegin); |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_ERROR, "(no additional info)"); |
| } |
| } |
| |
| /* Impl. for #define. */ |
| static void cmpp_dx_f_define(cmpp_dx *dx){ |
| cmpp_d const * const d = dx->d; |
| assert(d); |
| if( !dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting one or more arguments"); |
| return; |
| } |
| cmpp_arg const * aKey = 0; |
| int argNdx = 0; |
| int nChomp = 0; |
| unsigned nHeredoc = 0; |
| unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */; |
| bool ifNotDefined = false /* true if '?' arg */; |
| cmpp_arg const *aAppend = 0; |
| #define checkIsDefined(ARG) \ |
| if(ifNotDefined && (cmpp_has(dx->pp, (char const*)ARG->z, ARG->n) \ |
| || dxppCode)) break |
| |
| for( cmpp_arg const * arg = dx->args.arg0; |
| 0==dxppCode && arg; |
| arg = arg->next, ++argNdx ){ |
| //g_warn("arg=%s", arg->z); |
| if( 0==argNdx && cmpp_arg_equals(arg, "?") ){ |
| /* Only set the key if it's not already defined. */ |
| ifNotDefined = true; |
| continue; |
| } |
| switch( arg->ttype ){ |
| case cmpp_TT_ShiftL3: |
| ++nChomp; |
| /* fall through */ |
| case cmpp_TT_ShiftL: |
| if( arg->next || argNdx<1 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Ill-placed '%s'.", arg->z); |
| }else if( arg->n >= sizeof(acHeredoc)-1 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_RANGE, |
| "Heredoc name is too large."); |
| }else if( !aKey ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Missing key before %s.", |
| cmpp__tt_cstr(cmpp_TT_ShiftL, false)); |
| }else{ |
| assert( aKey ); |
| nHeredoc = aKey->n; |
| memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/); |
| } |
| break; |
| case cmpp_TT_OpEq: |
| if( 1 /*seenEq || argNdx!=1 || !arg->next*/ ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Ill-placed '%s'.", arg->z); |
| break; |
| } |
| continue; |
| case cmpp_TT_StringAt: |
| if( cmpp__StringAtIsOk(dx->pp, cmpp_atpol_CURRENT) ){ |
| break; |
| } |
| /* fall through */ |
| case cmpp_TT_Int: |
| case cmpp_TT_String: |
| case cmpp_TT_Word: |
| if( cmpp_arg_isflag(arg,"-chomp") ){ |
| ++nChomp; |
| break; |
| } |
| if( cmpp_arg_isflag(arg,"-append") |
| || cmpp_arg_isflag(arg,"-a") ){ |
| aAppend = arg->next; |
| if( !aAppend ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting argument for %s", |
| arg->z); |
| } |
| arg = aAppend; |
| break; |
| } |
| if( aKey ){ |
| /* This is the second arg - the value */ |
| checkIsDefined(aKey); |
| cmpp_b * const os = cmpp_b_borrow(dx->pp); |
| cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0; |
| while( os ){ |
| if( ba ){ |
| cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false); |
| if( dxppCode ) break; |
| if( 0 ){ |
| g_warn("key=%s\n", aKey->z); |
| g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z); |
| } |
| } |
| if( cmpp_arg_to_b(dx, arg, os, |
| cmpp_arg_to_b_F_BRACE_CALL) ) break; |
| cmpp_b * const which = (ba && ba->n) ? ba : os; |
| if( which==ba && os->n ){ |
| if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n); |
| cmpp_b_append4(dx->pp, ba, os->z, os->n); |
| } |
| cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n, |
| arg->ttype); |
| if( 0 ){ |
| g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z); |
| g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z); |
| } |
| break; |
| } |
| cmpp_b_return(dx->pp, os); |
| cmpp_b_return(dx->pp, ba); |
| aKey = 0; |
| }else if( cmpp_TT_Word!=arg->ttype ){ |
| cmpp_dx_err_set(dx, CMPP_RC_TYPE, |
| "Expecting a define-name token here."); |
| }else if( arg->next ){ |
| aKey = arg; |
| }else{ |
| /* No value = a value of 1. */ |
| checkIsDefined(arg); |
| cmpp__define2(dx->pp, arg->z, arg->n, |
| ustr_c("1"), 1, cmpp_TT_Int); |
| } |
| break; |
| case cmpp_TT_GroupSquiggly: |
| assert( !acHeredoc[0] ); |
| if( (ifNotDefined ? argNdx>1 : argNdx>0) || arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "{...} must be the only argument.") |
| /* This is for simplicity's sake. */; |
| }else{ |
| cmpp_kav_each(dx, arg->z, arg->n, |
| cmpp_kav_each_f_define__group, |
| ifNotDefined ? dx : NULL, |
| cmpp_kav_each_F_NOT_EMPTY |
| | cmpp_kav_each_F_CALL_VAL |
| | cmpp_kav_each_F_PARENS_EXPR |
| //TODO cmpp_kav_each_F_IF_UNDEF |
| ); |
| } |
| aKey = 0; |
| break; |
| case cmpp_TT_GroupParen:{ |
| if( !aKey ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "(...) is not permitted as a key."); |
| break; |
| } |
| checkIsDefined(aKey); |
| int d = 0; |
| if( 0==cmpp__arg_evalSubToInt(dx, arg, &d) ){ |
| char exprBuf[32] = {0}; |
| cmpp_size_t nVal = |
| (cmpp_size_t)snprintf(&exprBuf[0], |
| sizeof(exprBuf), "%d", d); |
| assert(nVal>0); |
| cmpp__define2(dx->pp, aKey->z, aKey->n, |
| ustr_c(&exprBuf[0]), nVal, cmpp_TT_Int); |
| } |
| break; |
| } |
| case cmpp_TT_GroupBrace:{ |
| if( !aKey ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "[...] is not permitted as a key."); |
| break; |
| } |
| checkIsDefined(aKey); |
| cmpp_b * const b = cmpp_b_borrow(dx->pp); |
| if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){ |
| cmpp__define2(dx->pp, aKey->z, aKey->n, |
| b->z, b->n, cmpp_TT_AnyType); |
| } |
| cmpp_b_return(dx->pp, b); |
| break; |
| } |
| default: |
| // TODO: treat (...) as an expression |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unhandled arg type %s: %s", |
| cmpp__tt_cstr(arg->ttype, true), arg->z); |
| break; |
| } |
| } |
| if( 0==nHeredoc && nChomp ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "-chomp can only be used with <<."); |
| } |
| if( 0==dxppCode && nHeredoc ){ |
| // Process (#define KEY <<) |
| cmpp_b * const os = cmpp_b_borrow(dx->pp); |
| assert( dx->d->closer ); |
| if( os && |
| 0==cmpp_dx_consume_b(dx, os, &dx->d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D) ){ |
| while( nChomp-- && cmpp_b_chomp(os) ){} |
| g_debug(dx->pp,2,("define heredoc: [%s]=[%.*s]\n", |
| acHeredoc, (int)os->n, os->z)); |
| if( !ifNotDefined |
| || !cmpp_has(dx->pp, (char const*)acHeredoc, nHeredoc) ){ |
| cmpp__define2( |
| dx->pp, acHeredoc, nHeredoc, os->z, os->n, cmpp_TT_String |
| ); |
| } |
| } |
| cmpp_b_return(dx->pp, os); |
| } |
| #undef checkIsDefined |
| return; |
| } |
| |
| /* Impl. for #undef */ |
| static void cmpp_dx_f_undef(cmpp_dx *dx){ |
| if( !dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting one or more arguments"); |
| return; |
| } |
| cmpp_d const * const d = dx->d; |
| for( cmpp_arg const * arg = dx->args.arg0; |
| 0==dxppCode && arg; |
| arg = arg->next ){ |
| if( 0 ){ |
| g_stderr(" %s: %s %p n=%d %.*s\n", d->name.z, |
| cmpp__tt_cstr(arg->ttype, true), arg->z, |
| (int)arg->n, (int)arg->n, arg->z); |
| } |
| if( cmpp_TT_Word==arg->ttype ){ |
| #if 0 |
| /* Too strict? */ |
| if( 0==cmpp__legal_key_check(dx->pp, arg->z, |
| (cmpp_ssize_t)arg->n, false) ) { |
| cmpp_undef(dx->pp, (char const *)arg->z); |
| } |
| #else |
| cmpp_undef(dx->pp, (char const *)arg->z, NULL); |
| #endif |
| }else{ |
| cmpp_err_set(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s", |
| d->name.z, arg->z); |
| } |
| } |
| } |
| |
| /* Impl. for #once. */ |
| static void cmpp_dx_f_once(cmpp_dx *dx){ |
| cmpp_d const * const d = dx->d; |
| assert(d); |
| assert(d->closer); |
| if( dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting no arguments"); |
| return; |
| } |
| cmpp_dx_pimpl * const dxp = dx->pimpl; |
| cmpp_b * const b = cmpp_b_borrow(dx->pp); |
| if( !b ) return; |
| cmpp_b_append_ch(b, '#'); |
| cmpp_b_append4(dx->pp, b, d->name.z, d->name.n); |
| cmpp_b_append_ch(b, ':'); |
| cmpp__get_b(dx->pp, ustr_c("__FILE__"), 8, b, true) |
| /* Wonky return semantics. */; |
| if( b->errCode |
| || dxppCode |
| || cmpp_b_append_ch(b, ':') |
| || cmpp_b_append_i32(b, (int)dxp->pos.lineNo) ){ |
| goto end; |
| } |
| //g_debug(dx->pp,1,("#once key: %s", b->z)); |
| int const had = cmpp_has(dx->pp, (char const *)b->z, b->n); |
| if( dxppCode ) goto end; |
| else if( had ){ |
| CmppLvl * const lvl = CmppLvl_push(dx); |
| if( lvl ){ |
| CmppLvl_elide(lvl, true); |
| cmpp_outputer devNull = cmpp_outputer_empty; |
| cmpp_dx_consume(dx, &devNull, &d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D); |
| CmppLvl_pop(dx, lvl); |
| } |
| }else if( !cmpp_define_v2(dx->pp, (char const*)b->z, "1") ){ |
| cmpp_dx_consume(dx, NULL, &d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D); |
| } |
| end: |
| cmpp_b_return(dx->pp, b); |
| return; |
| } |
| |
| |
| /* Impl. for #/define, /#query, /#pipe. */ |
| CMPP__EXPORT(void, cmpp_dx_f_dangling_closer)(cmpp_dx *dx){ |
| cmpp_d const * const d = dx->d; |
| char const * const zD = cmpp_dx_delim(dx); |
| dxserr("%s%s used without its opening directive.", |
| zD, d->name.z); |
| } |
| |
| #ifndef CMPP_OMIT_D_INCLUDE |
| static int cmpp__including_has(cmpp *pp, unsigned const char * zName){ |
| int rc = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false); |
| if( q && 0==cmpp__bind_text(pp, q, 1, zName) ){ |
| if(SQLITE_ROW == cmpp__step(pp, q, true)){ |
| rc = 1; |
| }else{ |
| rc = 0; |
| } |
| g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc)); |
| } |
| return rc; |
| } |
| |
| /** |
| Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of |
| the `#include` dirs (cmpp_include_dir_add()). If no file match is |
| found, NULL is returned. Memory must eventually be passed to |
| cmpp_mfree() to free it. |
| */ |
| static char * cmpp__include_search(cmpp *pp, unsigned const char * zKey, |
| int * nVal){ |
| char * zName = 0; |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false); |
| if( nVal ) *nVal = 0; |
| if( q && 0==cmpp__bind_text(pp, q, 1, zKey) ){ |
| int const rc = cmpp__step(pp, q, false); |
| if(SQLITE_ROW==rc){ |
| const unsigned char * z = sqlite3_column_text(q, 0); |
| int const n = sqlite3_column_bytes(q,0); |
| zName = n ? sqlite3_mprintf("%.*s", n, z) : 0; |
| if( n ) cmpp_check_oom(pp, zName); |
| if( nVal ) *nVal = n; |
| } |
| cmpp__stmt_reset(q); |
| } |
| return zName; |
| } |
| |
| /** |
| Removes zKey from the currently-being-`#include`d list |
| list. |
| */ |
| static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false); |
| if( q ){ |
| cmpp__bind_text(pp, q, 1, ustr_c(zKey)); |
| cmpp__step(pp, q, true); |
| g_debug(pp,2,("incl rm [%s]\n", zKey)); |
| } |
| return ppCode; |
| } |
| |
| #if 0 |
| /* |
| ** Sets pp's error state if the `#include` list contains the given |
| ** key. |
| */ |
| static int cmpp__including_check(cmpp *pp, const char * zKey); |
| int cmpp__including_check(cmpp *pp, const char * zName){ |
| if( !ppCode ){ |
| if(cmpp__including_has(pp, zName)){ |
| cmpp__err(pp, CMPP_RC_MISUSE, |
| "Recursive include detected: %s\n", zName); |
| } |
| } |
| return ppCode; |
| } |
| #endif |
| |
| |
| /** |
| Adds the given filename to the list of being-`#include`d files, |
| using the given source file name and line number of error reporting |
| purposes. If recursion is later detected. |
| */ |
| static int cmpp__including_add(cmpp *pp, unsigned const char * zKey, |
| unsigned const char * zSrc, cmpp_size_t srcLine){ |
| sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false); |
| if( q ){ |
| cmpp__bind_text(pp, q, 1, zKey); |
| cmpp__bind_text(pp, q, 2, zSrc); |
| cmpp__bind_int(pp, q, 3, srcLine); |
| cmpp__step(pp, q, true); |
| g_debug(pp,2,("is-including-file add [%s] from [%s]:%" |
| CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine)); |
| } |
| return ppCode; |
| } |
| |
| /* Impl. for #include. */ |
| static void cmpp_dx_f_include(cmpp_dx *dx){ |
| char * zResolved = 0; |
| int nResolved = 0; |
| cmpp_b * const ob = cmpp_b_borrow(dx->pp); |
| bool raw = false; |
| cmpp_args args = cmpp_args_empty; |
| if( !ob || cmpp_dx_args_clone(dx, &args) ){ |
| goto end; |
| } |
| assert(args.pimpl && args.pimpl->pp==dx->pp); |
| cmpp_arg const * arg = args.arg0; |
| for( ; arg; arg = arg->next){ |
| #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) |
| FLAG("-raw"){ |
| raw = true; |
| continue; |
| } |
| break; |
| #undef FLAG |
| } |
| if( !arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, |
| "Expecting at least one filename argument."); |
| } |
| for( ; !dxppCode && arg; arg = arg->next ){ |
| cmpp_flag32_t a2bf = cmpp_arg_to_b_F_BRACE_CALL; |
| if( cmpp_TT_Word==arg->ttype && cmpp__arg_wordIsPathOrFlag(arg) ){ |
| a2bf |= cmpp_arg_to_b_F_NO_DEFINES; |
| } |
| if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(ob), a2bf) ){ |
| break; |
| } |
| //g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved); |
| if(!raw && cmpp__including_has(dx->pp, ob->z)){ |
| /* Note that different spellings of the same filename |
| ** will elude this check, but that seems okay, as different |
| ** spellings means that we're not re-running the exact same |
| ** invocation. We might want some other form of multi-include |
| ** protection, rather than this, however. There may well be |
| ** sensible uses for recursion. */ |
| cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Recursive include of file: %s", |
| ob->z); |
| break; |
| } |
| cmpp_mfree(zResolved); |
| nResolved = 0; |
| zResolved = cmpp__include_search(dx->pp, ob->z, &nResolved); |
| if(!zResolved){ |
| if( !dxppCode ){ |
| cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "file not found: %s", ob->z); |
| } |
| break; |
| } |
| if( raw ){ |
| if( !dx->pp->pimpl->out.out ) break; |
| FILE * const fp = cmpp_fopen(zResolved, "r"); |
| if( fp ){ |
| int const rc = cmpp_stream(cmpp_input_f_FILE, fp, |
| dx->pp->pimpl->out.out, |
| dx->pp->pimpl->out.state); |
| if( rc ){ |
| cmpp_dx_err_set(dx, rc, "Unknown error streaming file %s.", |
| arg->z); |
| } |
| cmpp_fclose(fp); |
| }else{ |
| cmpp_dx_err_set(dx, cmpp_errno_rc(errno, CMPP_RC_IO), |
| "Unknown error opening file %s.", arg->z); |
| } |
| }else{ |
| cmpp__including_add(dx->pp, ob->z, ustr_c(dx->sourceName), |
| dx->pimpl->dline.lineNo); |
| cmpp_process_file(dx->pp, zResolved); |
| cmpp__include_rm(dx->pp, ob->z); |
| } |
| } |
| end: |
| cmpp_mfree(zResolved); |
| cmpp_args_cleanup(&args); |
| cmpp_b_return(dx->pp, ob); |
| } |
| #endif /* #ifndef CMPP_OMIT_D_INCLUDE */ |
| |
| /** |
| cmpp_dx_f() callback state for cmpp_dx_f_if(): pointers to the |
| various directives of that family. |
| */ |
| struct CmppIfState { |
| cmpp_d * dIf; |
| cmpp_d * dElif; |
| cmpp_d * dElse; |
| cmpp_d const * dEndif; |
| }; |
| typedef struct CmppIfState CmppIfState; |
| |
| /* Version 2 of #if. */ |
| static void cmpp_dx_f_if(cmpp_dx *dx){ |
| /* Reminder to self: |
| |
| We need to be able to recurse, even in skip mode, for #if nesting |
| to work. That's not great because it means we are evaluating |
| stuff we ideally should be skipping over, but it's keeping the |
| current tests working as-is. We can/do, however, avoid evaluating |
| expressions and such when recursing via skip mode. If we can |
| eliminate that here, by keeping track of the #if stack depth, |
| then we can possibly eliminate the whole CmppLvl_F_ELIDE |
| flag stuff. |
| |
| The more convoluted version 1 #if (which this replaced not hours |
| ago) kept track of the skip state across a separate directive |
| function for #if and #/if. That was more complex but did avoid |
| having to recurse into #if in order to straighten out #elif and |
| #else. Update: tried a non-recursive variant involving moving |
| this function's gotTruth into the CmppLvl object() and |
| managing the CmppLvl stack here, but it just didn't want to |
| work for me and i was too tired to figure out why. |
| */ |
| int gotTruth = 0 /*expr result*/; |
| CmppIfState const * const cis = dx->d->impl.state; |
| cmpp_d const * dClosers[] = { |
| cis->dElif, cis->dElse, cis->dEndif |
| }; |
| CmppLvl * lvl = 0; |
| CmppDLine const dline = dx->pimpl->dline; |
| cmpp_args args = cmpp_args_empty; |
| char delim[20] = {0}; |
| #define skipOn CmppLvl_elide((lvl), true) |
| #define skipOff CmppLvl_elide((lvl), false) |
| |
| assert( dx->d==cis->dIf ); |
| if( !dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an expression."); |
| return; |
| } |
| snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx)); |
| delim[sizeof(delim)-1] = 0; |
| lvl = CmppLvl_push(dx); |
| if( !lvl ) goto end; |
| if( cmpp_dx_is_eliding(dx) ){ |
| gotTruth = 1; |
| }else if( cmpp__args_evalToInt(dx, &dx->pimpl->args, &gotTruth) ){ |
| goto end; |
| }else if( !gotTruth ){ |
| skipOn; |
| } |
| |
| cmpp_d const * dPrev = dx->d; |
| cmpp_outputer devNull = cmpp_outputer_empty; |
| while( !dxppCode ){ |
| dPrev = dx->d; |
| bool const isFinal = dPrev==cis->dElse |
| /* true if expecting an #/if. */; |
| if( cmpp_dx_consume(dx, |
| CmppLvl_is_eliding(lvl) ? &devNull : NULL, |
| isFinal ? &cis->dEndif : dClosers, |
| isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]), |
| cmpp_dx_consume_F_PROCESS_OTHER_D) ){ |
| break; |
| } |
| cmpp_d const * const d2 = dx->d; |
| if( !d2 ){ |
| dxserr("Reached end of input in an untermined %s%s opened " |
| "at line %" CMPP_SIZE_T_PFMT ".", |
| delim, cis->dIf->name.z, dline.lineNo); |
| } |
| if( d2==cis->dEndif ){ |
| break; |
| }else if( isFinal ){ |
| assert(!"cannot happen - caught by consume()"); |
| dxserr("Expecting %s%s to close %s%s.", |
| delim, cis->dEndif->name.z, |
| delim, dPrev->name.z); |
| break; |
| }else if( gotTruth ){ |
| skipOn; |
| continue; |
| }else if( d2==cis->dElif ){ |
| if( 0==cmpp_dx_args_parse(dx, &args) |
| && 0==cmpp__args_evalToInt(dx, &args, &gotTruth) ){ |
| if( gotTruth ) skipOff; |
| else skipOn; |
| } |
| continue; |
| }else{ |
| assert( d2==cis->dElse |
| && "Else (haha!) we cannot have gotten here" ); |
| skipOff; |
| continue; |
| } |
| assert(!"unreachable"); |
| } |
| |
| #undef skipOff |
| #undef skipOn |
| end: |
| cmpp_args_cleanup(&args); |
| if( lvl ){ |
| bool const lvlIsOk = CmppLvl_get(dx)==lvl; |
| CmppLvl_pop(dx, lvl); |
| if( !lvlIsOk && !dxppCode ){ |
| assert(!"i naively believe that this is not possible"); |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, |
| "Mis-terminated %s%s opened at line " |
| "%" CMPP_SIZE_T_PFMT ".", |
| delim, cis->dIf->name.z, dline.lineNo); |
| } |
| } |
| return; |
| } |
| |
| /* Version 2 of #elif, #else, and #/if. */ |
| static void cmpp_dx_f_if_dangler(cmpp_dx *dx){ |
| CmppIfState const * const cis = dx->d->impl.state; |
| char const *zDelim = cmpp_dx_delim(dx); |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, |
| "%s%s with no matching %s%s", |
| zDelim, dx->d->name.z, |
| zDelim, cis->dIf->name.z); |
| } |
| |
| static void cmpp__dump_sizeofs(cmpp_dx*dx){ |
| (void)dx; |
| #define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X)) |
| SO(cmpp); |
| SO(cmpp_api_thunk); |
| SO(cmpp_arg); |
| SO(cmpp_args); |
| SO(cmpp_args_pimpl); |
| SO(cmpp_b); |
| SO(cmpp_d); |
| SO(cmpp_d_reg); |
| SO(cmpp__delim); |
| SO(cmpp__delim_list); |
| SO(cmpp_dx); |
| SO(cmpp_dx_pimpl); |
| SO(cmpp_outputer); |
| SO(cmpp_pimpl); |
| SO(((cmpp_pimpl*)0)->stmt); |
| SO(((cmpp_pimpl*)0)->policy); |
| SO(CmppArgList); |
| SO(CmppDLine); |
| SO(CmppDList); |
| SO(CmppDList_entry); |
| SO(CmppLvl); |
| SO(CmppSnippet); |
| SO(PodList__atpol); |
| printf("cmpp_TT__last = %d\n", |
| cmpp_TT__last); |
| #undef SO |
| } |
| |
| |
| /* Impl. for #pragma. */ |
| static void cmpp_dx_f_pragma(cmpp_dx *dx){ |
| cmpp_arg const * arg = dx->args.arg0; |
| if(!arg){ |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting an argument"); |
| return; |
| }else if(arg->next){ |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Too many arguments"); |
| return; |
| } |
| const char * const zArg = (char const *)arg->z; |
| #define M(X) 0==strcmp(zArg,X) |
| if(M("defines")){ |
| cmpp__dump_defines(dx->pp, stderr, 1); |
| }else if(M("sizeof")){ |
| cmpp__dump_sizeofs(dx); |
| }else if(M("chomp-F")){ |
| dx->pp->pimpl->flags.chompF = 1; |
| }else if(M("no-chomp-F")){ |
| dx->pp->pimpl->flags.chompF = 0; |
| }else if(M("api-thunk")){ |
| /* Generate macros for CMPP_API_THUNK and friends from |
| cmpp_api_thunk_map. */ |
| char const * zName = "CMPP_API_THUNK_NAME"; |
| char buf[256]; |
| #define out(FMT,...) snprintf(buf, sizeof(buf), FMT,__VA_ARGS__); \ |
| cmpp_dx_out_raw(dx, buf, strlen(buf)) |
| if( 0 ){ |
| out("/* libcmpp API thunk. */\n" |
| "static cmpp_api_thunk const * %s = 0;\n" |
| "#define cmpp_api_init(PP) %s = (PP)->api\n", zName, zName); |
| } |
| #define A(V) \ |
| if(V<=cmpp_api_thunk_version) { \ |
| out("/* Thunk APIs which follow are available as of " \ |
| "version %d... */\n",V); \ |
| } |
| #define V(N,T,V) |
| #define F(N,T,P) out("#define cmpp_%s %s->%s\n", # N, zName, # N); |
| #define O(N,T) out("#define cmpp_%s (*%s->%s)\n", # N, zName, # N); |
| cmpp_api_thunk_map(A,V,F,O) |
| #undef V |
| #undef F |
| #undef O |
| #undef A |
| #undef out |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg); |
| } |
| #undef M |
| } |
| |
| /* Impl. for #savepoint. */ |
| static void cmpp_dx_f_savepoint(cmpp_dx *dx){ |
| if(!dx->args.arg0 || dx->args.arg0->next){ |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting one argument"); |
| }else{ |
| const char * const zArg = (const char *)dx->args.arg0->z; |
| #define M(X) else if( 0==strcmp(zArg,X) ) |
| if( 0 ){} |
| M("begin"){ |
| cmpp__dx_sp_begin(dx); |
| } |
| M("rollback"){ |
| cmpp__dx_sp_rollback(dx); |
| }M("commit"){ |
| cmpp__dx_sp_commit(dx); |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unknown savepoint option: %s", zArg); |
| } |
| } |
| #undef M |
| } |
| |
| /* #stderr impl. */ |
| static void cmpp_dx_f_stderr(cmpp_dx *dx){ |
| if(dx->args.z){ |
| g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName, |
| dx->pimpl->dline.lineNo, |
| (int)dx->args.nz, dx->args.z); |
| }else{ |
| cmpp_d const * d = dx->d; |
| g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n", |
| dx->sourceName, dx->pimpl->dline.lineNo, |
| cmpp_dx_delim(dx), d->name.z); |
| } |
| } |
| |
| /** |
| Manages both the @token@ policy and the delimiters. |
| |
| #@ ?push? policy NAME ?<<? |
| #@ ?push? delimiter OPEN CLOSE ?<<? |
| #@ ?push? policy NAME delimiter OPEN CLOSE ?<<? |
| #@ pop policy |
| #@ pop delimiter |
| #@ pop policy delimiter |
| #@ pop both |
| |
| Function call forms: |
| |
| [@ policy] |
| [@ delimiter] |
| */ |
| static void cmpp_dx_f_at(cmpp_dx *dx){ |
| cmpp_arg const * arg = dx->args.arg0; |
| if( !arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting arguments."); |
| return; |
| } |
| enum ops { op_none, op_set, op_push, op_pop, op_heredoc }; |
| enum popWhichE { pop_policy = 0x01, pop_delim = 0x02, |
| pop_both = pop_policy | pop_delim }; |
| enum ops op = op_none /* what to do */; |
| int popWhich = 0 /* what to pop */; |
| bool gotPolicy = false; |
| bool checkedCallForm = !cmpp_dx_is_call(dx); |
| cmpp_arg const * argDelimO = 0 /* @token@ opener */; |
| cmpp_arg const * argDelimC = 0 /* @token@ closer */; |
| cmpp__pi(dx->pp); |
| cmpp_atpol_e polNew = cmpp_atpol_get(dx->pp); |
| for( ; arg; arg = arg ? arg->next : NULL ){ |
| //g_warn("arg=%s", arg->z); |
| if( !checkedCallForm ){ |
| assert( cmpp_dx_is_call(dx) ); |
| checkedCallForm = true; |
| if( cmpp_arg_equals(arg, "policy") ){ |
| char const * z = |
| cmpp__atpol_name(dx->pp, cmpp__policy(dx->pp,at)); |
| if( z ){ |
| cmpp_dx_out_raw(dx, z, strlen(z)); |
| } |
| }else if( cmpp_arg_equals(arg, "delimiter") ){ |
| char const * zO = 0; |
| char const * zC = 0; |
| cmpp_atdelim_get(dx->pp, &zO, &zC); |
| if( zC ){ |
| cmpp_dx_out_raw(dx, zO, strlen(zO)); |
| cmpp_dx_out_raw(dx, " ", 1); |
| cmpp_dx_out_raw(dx, zC, strlen(zC)); |
| } |
| goto end; |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "In call form, '%s' expects one of " |
| "'policy' or delimiter'."); |
| } |
| goto end; |
| }/* checkedCallForm */ |
| if( !argDelimC && op_none==op ){ |
| /* Look for push|pop. */ |
| if( cmpp_arg_equals(arg, "pop") ){ |
| arg = arg->next; |
| if( !arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'pop' expects arguments of 'policy' " |
| "and/or 'delimiter' and/or 'both'."); |
| goto end; |
| } |
| for( ; arg; arg = arg->next ){ |
| if( 0==(pop_policy & popWhich) |
| && cmpp_arg_equals(arg, "policy") ){ |
| popWhich |= pop_policy; |
| }else if( 0==(pop_delim & popWhich) |
| && cmpp_arg_equals(arg, "delimiter") ){ |
| popWhich |= pop_delim; |
| }else if( 0==(pop_both & popWhich) |
| && cmpp_arg_equals(arg, "both") ){ |
| popWhich |= pop_both; |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Invalid argument to 'pop': ", arg->z); |
| goto end; |
| } |
| } |
| assert( !arg ); |
| op = op_pop; |
| break; |
| }/* pop */ |
| if( cmpp_arg_equals(arg, "push") ){ |
| op = op_push; |
| continue; |
| } |
| if( cmpp_arg_equals(arg, "set") ){ |
| /* set is implied if neither of push/pop are and we get |
| a policy name. */ |
| op = op_set; |
| continue; |
| } |
| /* Fall through */ |
| }/* !argDelimC && op_none==op */ |
| if( !gotPolicy && cmpp_arg_equals(arg, "policy") ){ |
| arg = arg->next; |
| if( !arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'policy' requires a policy name argument."); |
| goto end; |
| } |
| polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z); |
| if( cmpp_atpol_invalid==polNew ){ |
| cmpp_atpol_from_str(dx->pp, (char const*)arg->z) |
| /* Will set the error state to something informative. */; |
| goto end; |
| } |
| if( op_none==op ) op = op_set; |
| gotPolicy = true; |
| continue; |
| } |
| if( !argDelimC && cmpp_arg_equals(arg, "delimiter") ){ |
| assert( !argDelimO && !argDelimC ); |
| argDelimO = arg->next; |
| argDelimC = argDelimO ? argDelimO->next : NULL; |
| if( !argDelimC ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'delimiter' requires two arguments."); |
| goto end; |
| } |
| arg = argDelimC->next; |
| continue; |
| } |
| if( op_pop!=op ){ |
| if( cmpp_arg_equals(arg,"<<") ){ |
| if( arg->next ) { |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'%s' must be the final argument.", |
| arg->z); |
| goto end; |
| } |
| op = op_heredoc; |
| break; |
| } |
| } |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z); |
| return; |
| }/*arg collection*/ |
| |
| assert( !dxppCode ); |
| assert( cmpp_atpol_invalid!=polNew ); |
| |
| #define popcheck(LIST) \ |
| if(dxppCode) goto end; \ |
| if(!LIST.n) goto bad_pop |
| |
| if( op_pop==op ){ |
| assert( popWhich>0 && popWhich<=3 ); |
| if( pop_policy & popWhich ){ |
| popcheck(pi->policy.at); |
| cmpp_atpol_pop(dx->pp); |
| } |
| if( pop_delim & popWhich ){ |
| popcheck(pi->delim.at); |
| cmpp_atdelim_pop(dx->pp); |
| } |
| goto end; |
| } |
| |
| assert( op_set==op || op_push==op || op_heredoc==op ); |
| if( argDelimC ){ |
| /* Push or set the @token@ delimiters */ |
| if( 0 ){ |
| g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push", |
| argDelimO->z, argDelimC->z); |
| } |
| if( op_push==op || op_heredoc==op ){ |
| if( cmpp_atdelim_push(dx->pp, (char const*)argDelimO->z, |
| (char const*)argDelimC->z) ){ |
| goto end; |
| } |
| argDelimO = 0 /* Re-use argDelimC as a flag in case we need to |
| roll this back on an error below. */; |
| }else{ |
| assert( op_set==op ); |
| if( cmpp_atdelim_set(dx->pp, (char const*)argDelimO->z, |
| (char const*)argDelimC->z) ){ |
| goto end; |
| } |
| argDelimO = argDelimC = 0; |
| } |
| } |
| |
| assert( !dxppCode ); |
| assert( !argDelimO ); |
| if( op_heredoc==op ){ |
| if( cmpp_atpol_push(dx->pp, polNew) ){ |
| if( argDelimC ){ |
| popcheck(pi->delim.at); |
| cmpp_atdelim_pop(dx->pp); |
| } |
| }else{ |
| bool const pushedDelim = NULL!=argDelimC; |
| assert( dx->d->closer ); |
| cmpp_dx_consume(dx, NULL, &dx->d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D) |
| /* !Invalidates argDelimO and argDelimC! */; |
| popcheck(pi->policy.at); |
| cmpp_atpol_pop(dx->pp); |
| if( pushedDelim ) cmpp_atdelim_pop(dx->pp); |
| } |
| }else if( op_push==op ){ |
| if( cmpp_atpol_push(dx->pp, polNew) && argDelimC ){ |
| /* Roll back delimiter push */ |
| cmpp_atdelim_pop(dx->pp); |
| } |
| }else{ |
| assert( op_set==op ); |
| if( cmpp__policy(dx->pp,at)!=polNew ){ |
| cmpp_atpol_set(dx->pp, polNew); |
| } |
| } |
| end: |
| return; |
| bad_pop: |
| cmpp_dx_err_set(dx, CMPP_RC_RANGE, |
| "Cannot pop an empty stack."); |
| #undef popcheck |
| } |
| |
| |
| static void cmpp_dx_f_expr(cmpp_dx *dx){ |
| int rv = 0; |
| assert( dx->args.z ); |
| if( 0 ){ |
| g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc, |
| dx->args.arg0->n, dx->args.arg0->z); |
| g_stderr("%s() dx->args.z [%.*s]\n", __func__, |
| (int)dx->args.nz, dx->args.z); |
| } |
| if( !dx->args.argc ){ |
| dxserr("An empty expression is not permitted."); |
| return; |
| } |
| #if 0 |
| for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){ |
| g_stderr("got type=%s n=%u z=%.*s\n", |
| cmpp__tt_cstr(a->ttype, true), |
| (unsigned)a->n, (int)a->n, a->z); |
| } |
| #endif |
| if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){ |
| if( 'a'==dx->d->name.z[0] ){ |
| if( !rv ){ |
| cmpp_dx_err_set(dx, CMPP_RC_ASSERT, "Assertion failed: %s", |
| dx->pimpl->buf.argsRaw.z); |
| } |
| }else{ |
| char buf[60]; |
| snprintf(buf, sizeof(buf), "%d\n", rv); |
| cmpp_dx_out_raw(dx, buf, strlen(buf)); |
| } |
| } |
| } |
| |
| static void cmpp_dx_f_undef_policy(cmpp_dx *dx){ |
| cmpp_unpol_e up = cmpp_unpol_invalid; |
| int nSeen = 0; |
| cmpp_arg const * arg = dx->args.arg0; |
| enum ops { op_set, op_push, op_pop }; |
| enum ops op = op_set; |
| if( !dx->args.argc ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting one of: error, null"); |
| return; |
| } |
| again: |
| ++nSeen; |
| if( cmpp_arg_equals(arg,"error") ) up = cmpp_unpol_ERROR; |
| else if( cmpp_arg_equals(arg,"null") ) up = cmpp_unpol_NULL; |
| else if( 1==nSeen ){ |
| if( cmpp_arg_equals(arg, "push") ){ |
| if( !arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting argument to 'push'."); |
| return; |
| } |
| op = op_push; |
| arg = arg->next; |
| goto again; |
| }else if( cmpp_arg_equals(arg, "pop") ){ |
| if( arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Extra argument after 'pop': %s", |
| arg->next->z); |
| return; |
| } |
| op = op_pop; |
| } |
| } |
| if( op_pop!=op && cmpp_unpol_invalid==up ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unhandled undefined-policy '%s'." |
| " Try one of: error, null", |
| arg->z); |
| }else if( op_set==op ){ |
| cmpp_unpol_set(dx->pp, up); |
| }else if( op_push==op ){ |
| cmpp_unpol_push(dx->pp, up); |
| }else{ |
| assert( op_pop==op ); |
| if( !cmpp__epol(dx->pp,un).n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "No %s%s push is active.", |
| cmpp_dx_delim(dx), dx->d->name.z); |
| }else{ |
| cmpp_unpol_pop(dx->pp); |
| } |
| } |
| } |
| |
| #ifndef CMPP_OMIT_D_DB |
| /* Impl. for #attach. */ |
| static void cmpp_dx_f_attach(cmpp_dx *dx){ |
| if( 3!=dx->args.argc ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "%s expects: STRING as NAME", dx->d->name.z); |
| return; |
| } |
| cmpp_arg const * pNext = 0; |
| cmpp_b osDbFile = cmpp_b_empty; |
| cmpp_b osSchema = cmpp_b_empty; |
| for( cmpp_arg const * arg = dx->args.arg0; |
| 0==dxppCode && arg; |
| arg = pNext ){ |
| pNext = arg->next; |
| if( !osDbFile.n ){ |
| if( 0==cmpp_arg_to_b(dx, arg, &osDbFile, |
| cmpp_arg_to_b_F_BRACE_CALL) |
| && !osDbFile.n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Empty db file name is not permitted. " |
| "If '%s' is intended as a value, " |
| "it should be quoted.", arg->z); |
| break; |
| } |
| assert( pNext ); |
| if( !pNext || !cmpp_arg_equals(pNext, "as") ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting 'as' after db file name."); |
| break; |
| } |
| pNext = pNext->next; |
| }else if( !osSchema.n ){ |
| if( 0==cmpp_arg_to_b(dx, arg, &osSchema, |
| cmpp_arg_to_b_F_BRACE_CALL) |
| && !osSchema.n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Empty db schema name is not permitted." |
| "If '%s' is intended as a value, " |
| "it should be quoted.", |
| arg->z); |
| break; |
| } |
| } |
| } |
| if( dxppCode ) goto end; |
| if( !osSchema.n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing schema name."); |
| goto end; |
| } |
| sqlite3_stmt * const q = |
| cmpp__stmt(dx->pp, CmppStmt_dbAttach, false); |
| if( q ){ |
| cmpp__bind_textn(dx->pp, q, 1, osDbFile.z, osDbFile.n); |
| cmpp__bind_textn(dx->pp, q, 2, osSchema.z, osSchema.n); |
| cmpp__step(dx->pp, q, true); |
| } |
| end: |
| cmpp_b_clear(&osDbFile); |
| cmpp_b_clear(&osSchema); |
| } |
| |
| /* Impl. for #detach. */ |
| static void cmpp_dx_f_detach(cmpp_dx *dx){ |
| cmpp_d const * d = dx->d; |
| if( 1!=dx->args.argc ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "%s expects: NAME", d->name.z); |
| return; |
| } |
| cmpp_arg const * const arg = dx->args.arg0; |
| cmpp_b os = cmpp_b_empty; |
| if( cmpp_arg_to_b(dx, arg, &os, cmpp_arg_to_b_F_BRACE_CALL) ){ |
| goto end; |
| } |
| if( !os.n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Empty db schema name is not permitted."); |
| goto end; |
| } |
| sqlite3_stmt * const q = |
| cmpp__stmt(dx->pp, CmppStmt_dbDetach, false); |
| if( q ){ |
| cmpp__bind_textn(dx->pp, q, 1, os.z, os.n); |
| cmpp__step(dx->pp, q, true); |
| } |
| end: |
| cmpp_b_clear(&os); |
| } |
| #endif /* #ifndef CMPP_OMIT_D_DB */ |
| |
| static void cmpp_dx_f_delimiter(cmpp_dx *dx){ |
| cmpp_arg const * arg = dx->args.arg0; |
| enum ops { op_none, op_set, op_push, op_pop }; |
| enum ops op = op_none; |
| cmpp_arg const * argD = 0; |
| bool doHeredoc = false; |
| bool const isCall = cmpp_dx_is_call(dx); |
| for( ; arg; arg = arg->next ){ |
| if( op_none==op ){ |
| /* Look for push|pop. */ |
| if( cmpp_arg_equals(arg, "push") ){ |
| op = op_push; |
| continue; |
| }else if( cmpp_arg_equals(arg, "pop") ){ |
| if( arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'pop' expects no arguments."); |
| return; |
| } |
| op = op_pop; |
| break; |
| } |
| /* Fall through */ |
| } |
| if( !argD ){ |
| if( op_none==op ) op = op_set; |
| argD = arg; |
| continue; |
| }else if( !doHeredoc && cmpp_arg_equals(arg,"<<") ){ |
| if( isCall ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'%s' is not legal in [call] form.", arg->z); |
| return; |
| }else if( arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "'%s' must be the final argument.", arg->z); |
| return; |
| } |
| op = op_push; |
| doHeredoc = true; |
| continue; |
| } |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); |
| return; |
| } |
| if( op_pop==op ){ |
| cmpp_delimiter_pop(dx->pp); |
| }else if( !argD ){ |
| if( isCall ){ |
| cmpp__delim const * const del = cmpp__dx_delim(dx); |
| if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n); |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "No delimiter specified."); |
| } |
| return; |
| }else{ |
| char const * const z = |
| (0==strcmp("default",(char*)argD->z)) |
| ? NULL |
| : (char const*)argD->z; |
| if( op_push==op ){ |
| cmpp_delimiter_push(dx->pp, (char const*)argD->z); |
| }else{ |
| assert( op_set==op ); |
| if( doHeredoc ) cmpp_delimiter_push(dx->pp, z); |
| else cmpp_delimiter_set(dx->pp, z); |
| } |
| } |
| if( !cmpp_dx_err_check(dx) ){ |
| if( isCall ){ |
| cmpp__delim const * const del = cmpp__dx_delim(dx); |
| if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n); |
| }else if( doHeredoc ){ |
| assert( op_push==op ); |
| cmpp_dx_consume(dx, NULL, &dx->d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D); |
| cmpp_delimiter_pop(dx->pp); |
| } |
| } |
| } |
| |
| #ifndef NDEBUG |
| /* Experimenting grounds. */ |
| static void cmpp_dx_f_experiment(cmpp_dx *dx){ |
| void * st = dx->d->impl.state; |
| (void)st; |
| g_warn("raw args: %s", dx->pimpl->buf.argsRaw.z); |
| g_warn("argc=%u", dx->args.argc); |
| g_warn("isCall=%d\n", cmpp_dx_is_call(dx)); |
| if( 1 ){ |
| for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){ |
| g_stderr("got type=%s n=%u z=%.*s\n", |
| cmpp__tt_cstr(a->ttype, true), |
| (unsigned)a->n, (int)a->n, a->z); |
| } |
| } |
| if( 0 ){ |
| int rv = 0; |
| if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){ |
| g_stderr("expr result: %d\n", rv); |
| } |
| } |
| |
| if( 0 ){ |
| char const * zIn = "a strspn test @# and @"; |
| g_stderr("strlen : %u\n", (unsigned)strlen(zIn)); |
| g_stderr("strspn 1: %u, %u\n", |
| (unsigned)strspn(zIn, "#@"), |
| (unsigned)strspn(zIn, "@#")); |
| g_stderr("strcspn 2: %u, %u\n", |
| (unsigned)strcspn(zIn, "#@"), |
| (unsigned)strcspn(zIn, "@#")); |
| g_stderr("strcspn 3: %u, %u\n", |
| (unsigned)strcspn(zIn, "a strspn"), |
| (unsigned)strcspn(zIn, "nope")); |
| } |
| |
| if( 1 ){ |
| cmpp__dump_sizeofs(dx); |
| } |
| } |
| #endif /* #ifndef NDEBUG */ |
| |
| #ifndef CMPP_OMIT_D_DB |
| |
| /** |
| Helper for #query and friends. Expects arg to be an SQL value. If |
| arg->next is "bind" then this consumes the following two arguments( |
| "bind" BIND_ARG), where BIND_ARG must be one of either |
| cmpp_TT_GroupSquiggly or cmpp_TT_GroupBrace. |
| |
| If it returns 0 then: |
| |
| - If "bind" was found then *pBind is set to the BIND_ARG argument |
| and *pNext is set to the one after that. |
| |
| - Else *pBind is set to NULL and and *pNext is set to |
| arg->next. |
| |
| In either case, *pNext may be set to NULL. |
| */ |
| static |
| int cmpp__consume_sql_args(cmpp *pp, cmpp_arg const *arg, |
| cmpp_arg const **pBind, |
| cmpp_arg const **pNext){ |
| if( 0==ppCode ){ |
| *pBind = 0; |
| cmpp_arg const *pN = arg->next; |
| if( pN && cmpp_arg_equals(pN, "bind") ){ |
| pN = pN->next; |
| if( !pN || ( |
| cmpp_TT_GroupSquiggly!=pN->ttype |
| && cmpp_TT_GroupBrace!=pN->ttype |
| ) ){ |
| return serr("Expecting {...} or [...] after 'bind'."); |
| } |
| *pBind = pN; |
| *pNext = pN->next; |
| } else { |
| *pBind = 0; |
| *pNext = pN; |
| } |
| } |
| return ppCode; |
| } |
| |
| /** |
| cmpp_kav_each_f() impl for used by #query's `bind {...}` argument. |
| */ |
| static int cmpp_kav_each_f_query__bind( |
| cmpp_dx * const dx, |
| unsigned char const * const zKey, cmpp_size_t nKey, |
| unsigned char const * const zVal, cmpp_size_t nVal, |
| void * const callbackState |
| ){ |
| /* Expecting: :bindName -> bindValue */ |
| if( ':'!=zKey[0] && '$'!=zKey[0] /*&& '@'!=zKey[0]*/ ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Bind keys must start with ':' or '$'."); |
| }else{ |
| sqlite3_stmt * const q = callbackState; |
| assert( q ); |
| int const bindNdx = |
| sqlite3_bind_parameter_index(q, (char const*)zKey); |
| if( bindNdx ){ |
| cmpp__bind_textn(dx->pp, q, bindNdx, zVal, nVal); |
| }else{ |
| cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Invalid bind name: %.*s", |
| (int)nKey, zKey); |
| } |
| } |
| return dxppCode; |
| } |
| |
| int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q, |
| cmpp_arg const * const aGroup){ |
| if( dxppCode ) return dxppCode; |
| if( cmpp_TT_GroupSquiggly==aGroup->ttype ){ |
| return cmpp_kav_each( |
| dx, aGroup->z, aGroup->n, |
| cmpp_kav_each_f_query__bind, q, |
| cmpp_kav_each_F_NOT_EMPTY |
| | cmpp_kav_each_F_CALL_VAL |
| | cmpp_kav_each_F_PARENS_EXPR |
| ); |
| } |
| if( cmpp_TT_GroupBrace!=aGroup->ttype ){ |
| return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting {...} or [...] " |
| "for SQL binding list."); |
| } |
| int bindNdx = 0; |
| cmpp_args args = cmpp_args_empty; |
| cmpp_args_parse(dx, &args, aGroup->z, aGroup->n, 0); |
| if( !args.argc && !dxppCode ){ |
| cmpp_err_set(dx->pp, CMPP_RC_RANGE, |
| "Empty SQL bind list is not permitted."); |
| /* Keep going so we can clean up a partially-parsed args. */ |
| } |
| for( cmpp_arg const * aVal = args.arg0; |
| !dxppCode && aVal; |
| aVal = aVal->next ){ |
| ++bindNdx; |
| if( 0 ){ |
| g_warn("bind #%d %s <<%s>>", bindNdx, |
| cmpp__tt_cstr(aVal->ttype, true), aVal->z); |
| } |
| cmpp__bind_arg(dx, q, bindNdx, aVal); |
| } |
| cmpp_args_cleanup(&args); |
| return dxppCode; |
| } |
| |
| /** #query impl */ |
| static void cmpp_dx_f_query(cmpp_dx *dx){ |
| //cmpp_d const * d = cmpp_dx_d(dx); |
| if( !dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting one or more arguments"); |
| return; |
| } |
| cmpp * const pp = dx->pp; |
| sqlite3_stmt * q = 0; |
| cmpp_b * const obBody = cmpp_b_borrow(dx->pp); |
| cmpp_b * const sql = cmpp_b_borrow(dx->pp); |
| cmpp_outputer obNull = cmpp_outputer_empty; |
| //cmpp_b obBindArgs = cmpp_b_empty; |
| cmpp_args args = cmpp_args_empty |
| /* We need to copy the args or do some arg-type-specific work to |
| copy the memory for specific cases. */; |
| int nChomp = 0; |
| bool spStarted = false; |
| bool seenDefine = false; |
| bool batchMode = false; |
| cmpp_arg const * pNext = 0; |
| cmpp_arg const * aBind = 0; |
| cmpp_d const * const dNoRows = dx->d->impl.state; |
| cmpp_d const * const dClosers[2] = {dx->d->closer, dNoRows}; |
| |
| if( !obBody || !sql ) goto cleanup; |
| |
| assert( dNoRows ); |
| if( cmpp_dx_args_clone(dx, &args) ){ |
| goto cleanup; |
| } |
| //g_warn("args.argc=%d", args.argc); |
| for( cmpp_arg const * arg = args.arg0; |
| 0==dxppCode && arg; |
| arg = pNext ){ |
| //g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z); |
| pNext = arg->next; |
| if( cmpp_arg_equals(arg, "define") ){ |
| if( seenDefine ){ |
| cmpp__dx_err_just_once(dx, arg); |
| goto cleanup; |
| } |
| seenDefine = true; |
| continue; |
| } |
| if( cmpp_arg_equals(arg, "-chomp") ){ |
| ++nChomp; |
| continue; |
| } |
| if( cmpp_arg_equals(arg, "-batch") ){ |
| if( batchMode ){ |
| cmpp__dx_err_just_once(dx, arg); |
| goto cleanup; |
| } |
| batchMode = true; |
| continue; |
| } |
| if( !sql->n ){ |
| if( cmpp__consume_sql_args(pp, arg, &aBind, &pNext) ){ |
| goto cleanup; |
| } |
| if( cmpp_arg_to_b(dx, arg, sql, cmpp_arg_to_b_F_BRACE_CALL) ){ |
| goto cleanup; |
| } |
| //g_warn("SQL: <<%s>>", sql->z); |
| continue; |
| } |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z); |
| goto cleanup; |
| } |
| if( ppCode ) goto cleanup; |
| if( seenDefine ){ |
| if( nChomp ){ |
| serr("-chomp and define may not be used together."); |
| goto cleanup; |
| }else if( batchMode ){ |
| serr("-batch and define may not be used together."); |
| goto cleanup; |
| } |
| } |
| if( !sql->n ){ |
| serr("Expecting an SQL-string argument."); |
| goto cleanup; |
| } |
| |
| if( batchMode ){ |
| if( aBind ){ |
| serr("Bindable values may not be used with -batch."); |
| goto cleanup; |
| } |
| char *zErr = 0; |
| cmpp__pi(dx->pp); |
| int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr); |
| rc = cmpp__db_rc(dx->pp, rc, zErr); |
| sqlite3_free(zErr); |
| goto cleanup; |
| } |
| |
| if( cmpp__db_rc(pp, sqlite3_prepare_v2( |
| pp->pimpl->db.dbh, (char const *)sql->z, |
| (int)sql->n, &q, 0), 0) ){ |
| goto cleanup; |
| }else if( !q ){ |
| cmpp_err_set(pp, CMPP_RC_RANGE, |
| "Empty SQL is not permitted."); |
| goto cleanup; |
| } |
| //g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q)); |
| int const nCol = sqlite3_column_count(q); |
| if( !nCol ){ |
| cmpp_err_set(pp, CMPP_RC_RANGE, |
| "SQL does not have any result columns."); |
| goto cleanup; |
| } |
| if( !seenDefine ){ |
| if( cmpp_sp_begin(pp) ) goto cleanup; |
| spStarted = true; |
| } |
| |
| if( aBind && cmpp__bind_group(dx, q, aBind) ){ |
| goto cleanup; |
| } |
| |
| bool gotARow = false; |
| cmpp_dx_pos dxPosStart; |
| cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D; |
| cmpp_dx_pos_save(dx, &dxPosStart); |
| int const nChompOrig = nChomp; |
| while( 0==ppCode ){ |
| int const dbrc = cmpp__step(pp, q, false); |
| if( SQLITE_ROW==dbrc ){ |
| nChomp = nChompOrig; |
| gotARow = true; |
| if( cmpp__define_from_row(pp, q, false) ) break; |
| if( seenDefine ) break; |
| cmpp_dx_pos_restore(dx, &dxPosStart); |
| cmpp_b_reuse(obBody); |
| /* If it weren't for -chomp, we wouldn't need to |
| buffer this. */ |
| if( cmpp_dx_consume_b(dx, obBody, dClosers, |
| sizeof(dClosers)/sizeof(dClosers[0]), |
| consumeFlags) ){ |
| goto cleanup; |
| } |
| assert( dx->d == dClosers[0] || dx->d == dClosers[1] ); |
| while( nChomp-- && cmpp_b_chomp(obBody) ){} |
| if( obBody->n && cmpp_dx_out_raw(dx, obBody->z, obBody->n) ) break; |
| if( dx->d == dNoRows ){ |
| if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/, |
| consumeFlags) ){ |
| goto cleanup; |
| } |
| assert( dx->d == dClosers[0] ); |
| /* TODO? chomp? */ |
| } |
| continue; |
| } |
| if( 0==ppCode && seenDefine ){ |
| /* If we got here, there was no result row. */ |
| cmpp__define_from_row(pp, q, true); |
| } |
| break; |
| }/*result row loop*/ |
| cmpp__stmt_reset(q); |
| if( ppCode ) goto cleanup; |
| |
| while( !seenDefine && !gotARow ){ |
| /* No result rows. Skip past the body, emitting the #query:no-rows |
| content, if any. We disable @token processing for that first |
| step because (A) the output is not going anywhere, so no need |
| to expand it (noting that expanding may have side effects via |
| @[call...]@) and (B) the @tokens@ referring to this query's |
| results will not have been set because there was no row to set |
| them from, so @expanding@ them would fail. */ |
| cmpp_atpol_e const atpol = cmpp_atpol_get(dx->pp); |
| if( cmpp_atpol_set(dx->pp, cmpp_atpol_OFF) ) break; |
| cmpp_dx_consume(dx, &obNull, dClosers, |
| sizeof(dClosers)/sizeof(dClosers[0]), |
| consumeFlags); |
| cmpp_atpol_set(dx->pp, atpol); |
| if( dxppCode ) break; |
| assert( dx->d == dClosers[0] || dx->d == dClosers[1] ); |
| if( dx->d == dNoRows ){ |
| if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/, |
| consumeFlags) ){ |
| break; |
| } |
| assert( dx->d == dClosers[0] ); |
| /* TODO? chomp? */ |
| } |
| break; |
| } |
| |
| cleanup: |
| cmpp_args_cleanup(&args); |
| cmpp_b_return(dx->pp, obBody); |
| cmpp_b_return(dx->pp, sql); |
| sqlite3_finalize(q); |
| if( spStarted ) cmpp_sp_rollback(pp); |
| } |
| #endif /* #ifndef CMPP_OMIT_D_DB */ |
| |
| #ifndef CMPP_OMIT_D_PIPE |
| /** #pipe impl. */ |
| static void cmpp_dx_f_pipe(cmpp_dx *dx){ |
| //cmpp_d const * d = cmpp_dx_d(dx); |
| unsigned char const * zArgs = dx->args.z; |
| assert( dx->args.arg0->n == dx->args.nz ); |
| unsigned char const * const zArgsEnd = zArgs + dx->args.nz; |
| if( zArgs==zArgsEnd ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting a command and arguments to pipe."); |
| return; |
| } |
| cmpp_FILE * fpToChild = 0; |
| int nChompIn = 0, nChompOut = 0; |
| cmpp_b * const chout = cmpp_b_borrow(dx->pp); |
| cmpp_b * const cmd = cmpp_b_borrow(dx->pp); |
| cmpp_b * const body = cmpp_b_borrow(dx->pp); |
| cmpp_b * const bArg = cmpp_b_borrow(dx->pp) |
| /* arg parsing and the initial command name part of the |
| external command. */; |
| cmpp_args cmdArgs = cmpp_args_empty; |
| /* TODOs and FIXMEs: |
| |
| We need flags to optionally @token@-parse before and/or after |
| filtering. |
| */ |
| bool seenDD = false /* true if seen "--" or [...] */; |
| bool doCapture = true /* true if we need a closing /pipe */; |
| bool argsAsGroup = false /* true if args is [...] */; |
| bool dumpDebug = false; |
| cmpp_flag32_t popenFlags = 0; |
| cmpp_popen_t po = cmpp_popen_t_empty; |
| if( cmpp_b_reserve3(dx->pp, cmd, zArgsEnd-zArgs + 1) |
| || cmpp_b_reserve3(dx->pp, bArg, cmd->nAlloc) ){ |
| goto cleanup; |
| } |
| |
| unsigned char * zOut = bArg->z; |
| unsigned char const * const zOutEnd = bArg->z + bArg->nAlloc - 1; |
| while( 0==dxppCode ){ |
| cmpp_arg arg = cmpp_arg_empty; |
| zOut = bArg->z; |
| if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd, |
| &zOut, zOutEnd) ){ |
| goto cleanup; |
| } |
| if( cmpp_arg_equals(&arg, "--") ){ |
| zOut = bArg->z; |
| if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd, |
| &zOut, zOutEnd) ){ |
| goto cleanup; |
| } |
| if( !arg.n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting external command name " |
| "or [...] after --."); |
| goto cleanup; |
| } |
| do_arg_list: |
| seenDD = true; |
| cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| if( cmpp_TT_GroupBrace==arg.ttype ){ |
| argsAsGroup = true; |
| a2bFlags |= cmpp_arg_to_b_F_NO_BRACE_CALL; |
| }else if( cmpp__arg_wordIsPathOrFlag(&arg) ){ |
| /* If it looks like it is a path, do not |
| expand it as a word. */ |
| arg.ttype = cmpp_TT_String; |
| } |
| if( cmpp_arg_to_b(dx, &arg, cmd, a2bFlags) |
| || (!argsAsGroup && cmpp_b_append_ch(cmd, ' ')) ){ |
| goto cleanup; |
| } |
| //g_warn("command: [%s]=>%s", arg.z, cmd->z); |
| if( cmd->n<2 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_RANGE, |
| "Command name '%s' resolves to empty. " |
| "This is most commonly caused by not " |
| "quoting it but it can also mean that it " |
| "is an unknown define key.", arg.z); |
| goto cleanup; |
| } |
| //g_warn("arg=%s", arg.z); |
| //g_warn("cmd=%s", cmd->z); |
| break; |
| } |
| if( cmpp_TT_GroupBrace==arg.ttype ){ |
| goto do_arg_list; |
| } |
| #define FLAG(X)if( cmpp_arg_isflag(&arg, X) ) |
| FLAG("-no-input"){ |
| doCapture = false; |
| continue; |
| } |
| FLAG("-chomp-output"){ |
| ++nChompOut; |
| continue; |
| } |
| FLAG("-chomp"){ |
| ++nChompIn; |
| continue; |
| } |
| FLAG("-exec-direct"){ |
| popenFlags |= cmpp_popen_F_DIRECT; |
| continue; |
| } |
| FLAG("-path"){ |
| popenFlags |= cmpp_popen_F_PATH; |
| continue; |
| } |
| FLAG("-debug"){ |
| dumpDebug = true; |
| continue; |
| } |
| #undef FLAG |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unhandled argument: %s. %s%s requires -- " |
| "before its external command name.", |
| arg.z, cmpp_dx_delim(dx), |
| dx->d->name.z); |
| goto cleanup; |
| } |
| |
| if( !seenDD ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "%s%s requires a -- before the name of " |
| "its external app.", |
| cmpp_dx_delim(dx), dx->d->name.z); |
| goto cleanup; |
| } |
| |
| //g_warn("zArgs n=%u zArgs=%s", (unsigned)(zArgsEnd-zArgs), zArgs); |
| /* dx->pimpl->args gets overwritten by cmpp_dx_consume(), so we have to copy |
| the args. */ |
| if( argsAsGroup ){ |
| assert( cmd->z ); |
| if( cmpp_args_parse(dx, &cmdArgs, cmd->z, cmd->n, 0) ){ |
| goto cleanup; |
| } |
| }else{ |
| /* zArgs can have newlines in it. We need to strip those out |
| before passing it on. We elide them entirely, as opposed to |
| replacing them with a space. */ |
| cmpp_skip_snl(&zArgs, zArgsEnd); |
| if( cmpp_b_reserve3(dx->pp, cmd, cmd->n + (zArgsEnd-zArgs) + 1) ){ |
| goto cleanup; |
| } |
| unsigned char * zo = cmd->z + cmd->n; |
| unsigned char const *zi = zArgs; |
| #if !defined(NDEBUG) |
| unsigned char const * zoEnd = cmd->z + cmd->nAlloc; |
| #endif |
| for( ; zi<zArgsEnd; ++zi){ |
| if( '\n'!=*zi && '\r'!=*zi ) *zo++ = *zi; |
| } |
| assert( zoEnd > zo ); |
| *zo = 0; |
| cmd->n = zo - cmd->z; |
| } |
| assert( !dxppCode ); |
| |
| if( doCapture ){ |
| assert( dx->d->closer ); |
| if( cmpp_dx_consume_b(dx, body, &dx->d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D) ){ |
| goto cleanup; |
| } |
| while( nChompIn-- && cmpp_b_chomp(body) ){} |
| po.fpToChild = &fpToChild; |
| } |
| |
| if( dumpDebug ){ |
| g_warn("%s%s -debug: cmd argsAsGroup=%d n=%u z=%s", |
| cmpp_dx_delim(dx), dx->d->name.z, |
| (int)argsAsGroup, |
| (unsigned)cmd->n, cmd->z); |
| } |
| if( argsAsGroup ){ |
| cmpp_popen_args(dx, &cmdArgs, &po); |
| }else{ |
| unsigned char const * z = cmd->z; |
| //cmpp_skip_snl(&z, cmd->z + cmd->n); |
| cmpp_popen(dx->pp, z, popenFlags, &po); |
| } |
| if( dxppCode ) goto cleanup; |
| int rc = 0; |
| if( doCapture ){ |
| /* Bug: if body is too bug (no idea how much that is), this will |
| block while waiting on input from the child. This can easily |
| happen with #include -raw. */ |
| #if 0 |
| /* Failed attempt to work around it. */ |
| assert( fpToChild ); |
| enum { BufSize = 128 }; |
| unsigned char buf[BufSize]; |
| cmpp_size_t nLeft = body->n; |
| unsigned char const * z = body->z; |
| while( nLeft>0 && !dxppCode ){ |
| cmpp_size_t nWrite = nLeft < BufSize ? nLeft : BufSize; |
| g_warn("writing %u to child...", (unsigned)nWrite); |
| rc = cmpp_output_f_FILE(fpToChild, z, nWrite); |
| if( rc ){ |
| cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process."); |
| break; |
| } |
| z += nWrite; |
| nLeft -= nWrite; |
| fflush(fpToChild); |
| cmpp_size_t nRead = BufSize; |
| rc = cmpp_input_f_fd(&po.fdFromChild, &buf[0], &nRead); |
| if( rc ) goto err_reading; |
| cmpp_b_append4(dx->pp, &chout, buf, nRead);\ |
| } |
| if( !dxppCode ){ |
| g_warn0("reading from child..."); |
| rc = cmpp_stream( cmpp_input_f_fd, &po.fdFromChild, |
| cmpp_output_f_b, chout ); |
| if( rc ) goto err_reading; |
| } |
| g_warn0("I/O done"); |
| #else |
| //g_warn("writing %u bytes to child...", (unsigned)body->n); |
| rc = cmpp_output_f_FILE(fpToChild, body->z, body->n); |
| if( rc ){ |
| cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process."); |
| goto cleanup; |
| } |
| //g_warn("wrote %u bytes to child.", (unsigned)body->n); |
| fclose(fpToChild); |
| fpToChild = 0; |
| if( dxppCode ) goto cleanup; |
| goto stream_chout; |
| #endif |
| }else{ |
| stream_chout: |
| //g_warn0("waiting on child..."); |
| rc = cmpp_stream(cmpp_input_f_fd, &po.fdFromChild, |
| cmpp_output_f_b, chout); |
| //g_warn0("I/O done"); |
| if( rc ){ |
| //err_reading: |
| cmpp_dx_err_set(dx, rc, "Error reading stdout from piped process."); |
| goto cleanup; |
| } |
| } |
| while( nChompOut-- && cmpp_b_chomp(chout) ){} |
| //g_warn("Read in:\n%.*s", (int)chout->n, chout->z); |
| cmpp_dx_out_raw(dx, chout->z, chout->n); |
| |
| cleanup: |
| cmpp_args_cleanup(&cmdArgs); |
| cmpp_b_return(dx->pp, chout); |
| cmpp_b_return(dx->pp, cmd); |
| cmpp_b_return(dx->pp, body); |
| cmpp_b_return(dx->pp, bArg); |
| cmpp_pclose(&po); |
| } |
| #endif /* #ifndef CMPP_OMIT_D_PIPE */ |
| |
| /** |
| #sum ...args |
| |
| Emits the sum of its arguments, treating each as an |
| integer. Non-integer arguments are silently skipped. |
| */ |
| static void cmpp_dx_f_sum(cmpp_dx *dx){ |
| int64_t n = 0, i = 0; |
| cmpp_b b = cmpp_b_empty; |
| for( cmpp_arg const * arg = dx->args.arg0; |
| arg && !cmpp_dx_err_check(dx); arg = arg->next ){ |
| if( 0==cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&b), |
| cmpp_arg_to_b_F_BRACE_CALL) |
| && cmpp__is_int64(b.z, b.n, &i) ){ |
| n += i; |
| } |
| } |
| cmpp_b_append_i64(cmpp_b_reuse(&b), n); |
| cmpp_dx_out_raw(dx, b.z, b.n); |
| cmpp_b_clear(&b); |
| } |
| |
| /** |
| #arg ?flags? the-arg |
| |
| -trim-left |
| -trim-right |
| -trim: trim both sides |
| |
| It sends its arg to cmpp_arg_to_b() to expand it, optionally |
| trims the result, and emits that value. |
| |
| This directive is not expected to be useful except, perhaps in |
| testing cmpp itself. Its trim flags, in particular, aren't commonly |
| useful because #arg is only useful in a function call context and |
| those unconditionally trim their output. |
| */ |
| static void cmpp_dx_f_arg(cmpp_dx *dx){ |
| cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| bool trimL = false, trimR = false; |
| cmpp_arg const * arg = dx->args.arg0; |
| for( ; arg && !cmpp_dx_err_check(dx); arg = arg->next ){ |
| #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) |
| FLAG("-raw") { |
| a2bFlags = cmpp_arg_to_b_F_FORCE_STRING; |
| continue; |
| } |
| FLAG("-trim-left") { trimL=true; continue; } |
| FLAG("-trim-right") { trimR=true; continue; } |
| FLAG("-trim") { trimL=trimR=true; continue; } |
| #undef FLAG |
| break; |
| } |
| if( arg ){ |
| cmpp_b * const b = cmpp_b_borrow(dx->pp); |
| if( b && 0==cmpp_arg_to_b(dx, arg, b, a2bFlags) ){ |
| unsigned char const * zz = b->z; |
| unsigned char const * zzEnd = b->z + b->n; |
| if( trimL ) cmpp_skip_snl(&zz, zzEnd); |
| if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd); |
| if( zzEnd-zz ){ |
| cmpp_dx_out_raw(dx, zz, zzEnd-zz); |
| } |
| } |
| cmpp_b_return(dx->pp, b); |
| }else if( !cmpp_dx_err_check(dx) ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument."); |
| } |
| } |
| |
| /** |
| #join ?flags? ...args |
| |
| -s SEPARATOR: sets the separator for its RHS arguments. Default=space. |
| |
| -nl: append a newline (will be stripped by [call]s!). This is the default |
| when !cmpp_dx_is_call(dx). |
| |
| -nonl: do not append a newline. Default when dx->isCall. |
| */ |
| static void cmpp_dx_f_join(cmpp_dx *dx){ |
| cmpp_b * const b = cmpp_b_borrow(dx->pp); |
| cmpp_b * const bSep = cmpp_b_borrow(dx->pp); |
| cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| bool addNl = !cmpp_dx_is_call(dx); |
| int n = 0; |
| if( !b || !bSep ) goto end; |
| if( !dx->args.argc ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "%s%s expects ?flags? ...args", |
| cmpp_dx_delim(dx), dx->d->name.z); |
| goto end; |
| } |
| cmpp_b_append_ch(bSep, ' '); |
| cmpp_check_oom(dx->pp, bSep->z); |
| for( cmpp_arg const * arg = dx->args.arg0; arg |
| && !b->errCode |
| && !bSep->errCode |
| && !cmpp_dx_err_check(dx); |
| arg = arg->next ){ |
| #define FLAG(X)if( cmpp_arg_isflag(arg, X) ) |
| FLAG("-s"){ |
| if( !arg->next ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Missing SEPARATOR argument to -s."); |
| break; |
| } |
| cmpp_arg_to_b(dx, arg->next, |
| cmpp_b_reuse(bSep), |
| cmpp_arg_to_b_F_BRACE_CALL); |
| arg = arg->next; |
| continue; |
| } |
| //FLAG("-nl"){ addNl=true; continue; } |
| FLAG("-nonl"){ addNl=false; continue; } |
| #undef FLAG |
| if( n++ && cmpp_dx_out_raw(dx, bSep->z, bSep->n) ){ |
| break; |
| } |
| if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b), a2bFlags) ){ |
| break; |
| } |
| cmpp_dx_out_raw(dx, b->z, b->n); |
| } |
| if( !cmpp_dx_err_check(dx) ){ |
| if( !n ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting at least one argument."); |
| }else if( addNl ){ |
| cmpp_dx_out_raw(dx, "\n", 1); |
| } |
| } |
| end: |
| cmpp_b_return(dx->pp, b); |
| cmpp_b_return(dx->pp, bSep); |
| } |
| |
| |
| /* Impl. for #file */ |
| static void cmpp_dx_f_file(cmpp_dx *dx){ |
| if( !dx->args.arg0 ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting one or more arguments"); |
| return; |
| } |
| cmpp_d const * const d = dx->d; |
| enum e_op { |
| op_none, op_exists, op_join |
| }; |
| cmpp_b * const b0 = cmpp_b_borrow(dx->pp); |
| if( !b0 ) goto end; |
| enum e_op op = op_none; |
| cmpp_arg const * opArg = 0; |
| cmpp_arg const * arg = 0; |
| for( arg = dx->args.arg0; |
| 0==dxppCode && arg; |
| arg = arg->next ){ |
| if( op_none==op ){ |
| if( cmpp_arg_equals(arg, "exists") ){ |
| op = op_exists; |
| opArg = arg->next; |
| arg = opArg->next; |
| if( arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "%s%s exists: too many arguments", |
| cmpp_dx_delim(dx), d->name.z); |
| goto end; |
| } |
| break; |
| }else if( cmpp_arg_equals(arg, "join") ){ |
| op = op_join; |
| if( !arg->next ) goto missing_arg; |
| arg = arg->next; |
| break; |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unknown %s%s command: %s", |
| cmpp_dx_delim(dx), d->name.z, arg->z); |
| goto end; |
| } |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s unhandled argument: %s", |
| cmpp_dx_delim(dx), d->name.z, arg->z); |
| goto end; |
| } |
| } |
| switch( op ){ |
| case op_none: goto missing_arg; |
| case op_join: { |
| int i = 0; |
| cmpp_flag32_t const bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| for( ; arg; arg = arg->next, ++i ){ |
| if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b0), bFlags) |
| || (i && cmpp_dx_out_raw(dx, "/", 1)) |
| || (b0->n && cmpp_dx_out_raw(dx, b0->z, b0->n)) ){ |
| break; |
| } |
| } |
| cmpp_dx_out_raw(dx, "\n", 1); |
| break; |
| } |
| case op_exists: { |
| assert( opArg ); |
| bool const b = cmpp__file_is_readable((char const *)opArg->z); |
| cmpp_dx_out_raw(dx, b ? "1\n" : "0\n", 2); |
| break; |
| } |
| } |
| end: |
| cmpp_b_return(dx->pp, b0); |
| return; |
| missing_arg: |
| if( arg ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s %s: missing argument", |
| cmpp_dx_delim(dx), d->name.z, arg->z ); |
| }else{ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s: missing subcommand", |
| cmpp_dx_delim(dx), d->name.z); |
| } |
| goto end; |
| } |
| |
| |
| /** |
| #cmp LHS op RHS |
| */ |
| static void cmpp_dx_f_cmp(cmpp_dx *dx){ |
| cmpp_b * const bL = cmpp_b_borrow(dx->pp); |
| cmpp_b * const bR = cmpp_b_borrow(dx->pp); |
| cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| if( !bL || !!bR ) goto end; |
| for( cmpp_arg const * arg = dx->args.arg0; arg |
| && !cmpp_dx_err_check(dx); |
| arg = arg->next ){ |
| if( !bL->z ){ |
| cmpp_arg_to_b(dx, arg, bL, a2bFlags); |
| continue; |
| } |
| if( !bR->z ){ |
| cmpp_arg_to_b(dx, arg, bR, a2bFlags); |
| continue; |
| } |
| goto usage; |
| } |
| |
| if( cmpp_dx_err_check(dx) ) goto end; |
| if( !bL->z || !bR->z ){ |
| usage: |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Usage: LHS RHS"); |
| goto end; |
| } |
| assert( bL->z ); |
| assert( bR->z ); |
| char cbuf[20]; |
| int const cmp = strcmp((char*)bL->z, (char*)bR->z); |
| int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp); |
| assert(n>0); |
| cmpp_dx_out_raw(dx, cbuf, (cmpp_size_t)n); |
| |
| end: |
| cmpp_b_return(dx->pp, bL); |
| cmpp_b_return(dx->pp, bR); |
| } |
| |
| |
| #if 0 |
| /* Impl. for dummy placeholder. */ |
| static void cmpp_dx_f_todo(cmpp_dx *dx){ |
| cmpp_d const * d = cmpp_dx_d(dx); |
| g_warn("TODO: directive handler for %s", d->name.z); |
| } |
| #endif |
| |
| /** |
| If zName matches one of the delayed-load directives, that directive |
| is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if |
| no match is found, but pp's error state is not updated in that |
| case. If a match is found and registration fails, that result code |
| will propagate via pp. |
| */ |
| int cmpp__d_delayed_load(cmpp *pp, char const *zName){ |
| if( ppCode ) return ppCode; |
| int rc = CMPP_RC_NO_DIRECTIVE; |
| unsigned const nName = strlen(zName); |
| |
| pp->pimpl->flags.isInternalDirectiveReg = true; |
| |
| #define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME)) |
| #define M_OC(NAME) (M(NAME) || M("/" NAME)) |
| #define M_IF(NAME) if( M(NAME) ) |
| #define CF(X) cmpp_d_F_ ## X |
| #define F_A_RAW CF(ARGS_RAW) |
| #define F_A_LIST CF(ARGS_LIST) |
| #define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY) |
| #define F_UNSAFE cmpp_d_F_NOT_IN_SAFEMODE |
| #define F_NC cmpp_d_F_NO_CALL |
| #define F_CALL cmpp_d_F_CALL_ONLY |
| #define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \ |
| cmpp_d_reg SYMNAME = { \ |
| .name = NAME, \ |
| .opener = { \ |
| .f = OPENER, \ |
| .flags = OFLAGS \ |
| }, \ |
| .closer = { \ |
| .f = CLOSER, \ |
| .flags = CFLAGS \ |
| }, \ |
| .dtor = 0, \ |
| .state = 0 \ |
| } |
| |
| #define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS ) \ |
| DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \ |
| rc = cmpp_d_register(pp, &rReg, NULL); \ |
| goto end |
| |
| /* The #if family requires some hand-holding... */ |
| if( M_OC("if") || M("elif") || M("else") ) { |
| DREG0(rIf, "if", |
| cmpp_dx_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL), |
| cmpp_dx_f_if_dangler, 0); |
| DREG0(rElif, "elif", |
| cmpp_dx_f_if_dangler, F_NC, |
| 0, 0); |
| DREG0(rElse, "else", |
| cmpp_dx_f_if_dangler, F_NC, |
| 0, 0); |
| CmppIfState * const cis = cmpp__malloc(pp, sizeof(*cis)); |
| if( !cis ) goto end; |
| memset(cis, 0, sizeof(*cis)); |
| rIf.state = cis; |
| rIf.dtor = cmpp_mfree; |
| if( cmpp_d_register(pp, &rIf, &cis->dIf) |
| /* rIf must be first to avoid leaking cis on error */ |
| || cmpp_d_register(pp, &rElif, &cis->dElif) |
| || cmpp_d_register(pp, &rElse, &cis->dElse) ){ |
| rc = ppCode; |
| }else{ |
| assert( cis->dIf && cis->dElif && cis->dElse ); |
| assert( !cis->dEndif ); |
| assert( cis == cis->dIf->impl.state ); |
| assert( cmpp_mfree==cis->dIf->impl.dtor ); |
| cis->dElif->impl.state |
| = cis->dElse->impl.state |
| = cis; |
| cis->dElif->closer |
| = cis->dElse->closer |
| = cis->dEndif |
| = cis->dIf->closer; |
| rc = 0; |
| } |
| goto end; |
| }/* #if and friends */ |
| |
| /* Basic core directives... */ |
| #define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS) \ |
| if( M_OC(N) ){ \ |
| DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS); \ |
| } (void)0 |
| |
| M_IF_CORE("@", cmpp_dx_f_at, F_A_LIST, |
| cmpp_dx_f_dangling_closer, 0); |
| M_IF_CORE("arg", cmpp_dx_f_arg, F_A_LIST, 0, 0); |
| M_IF_CORE("assert", cmpp_dx_f_expr, F_EXPR, 0, 0); |
| M_IF_CORE("cmp", cmpp_dx_f_cmp, F_A_LIST, 0, 0); |
| M_IF_CORE("define", cmpp_dx_f_define, F_A_LIST, |
| cmpp_dx_f_dangling_closer, 0); |
| M_IF_CORE("delimiter", cmpp_dx_f_delimiter, F_A_LIST, |
| cmpp_dx_f_dangling_closer, 0); |
| M_IF_CORE("error", cmpp_dx_f_error, F_A_RAW, 0, 0); |
| M_IF_CORE("expr", cmpp_dx_f_expr, F_EXPR, 0, 0); |
| M_IF_CORE("join", cmpp_dx_f_join, F_A_LIST, 0, 0); |
| M_IF_CORE("once", cmpp_dx_f_once, F_A_LIST | F_NC, |
| cmpp_dx_f_dangling_closer, 0); |
| M_IF_CORE("pragma", cmpp_dx_f_pragma, F_A_LIST, 0, 0); |
| M_IF_CORE("savepoint", cmpp_dx_f_savepoint, F_A_LIST, 0, 0); |
| M_IF_CORE("stderr", cmpp_dx_f_stderr, F_A_RAW, 0, 0); |
| M_IF_CORE("sum", cmpp_dx_f_sum, F_A_LIST, 0, 0); |
| M_IF_CORE("undef", cmpp_dx_f_undef, F_A_LIST, 0, 0); |
| M_IF_CORE("undefined-policy", cmpp_dx_f_undef_policy, F_A_LIST, 0, 0); |
| M_IF_CORE("//", cmpp_dx_f_noop, F_A_RAW, 0, 0); |
| M_IF_CORE("file", cmpp_dx_f_file, |
| F_A_LIST | F_UNSAFE, 0, 0); |
| |
| #undef M_IF_CORE |
| |
| |
| /* Directives which can be disabled via build flags or |
| flags to cmpp_ctor()... */ |
| #define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \ |
| M_IF(NAME) { \ |
| if( 0==(FLAG & pp->pimpl->flags.newFlags) ) { \ |
| DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \ |
| } \ |
| goto end; \ |
| } |
| |
| #ifndef CMPP_OMIT_D_INCLUDE |
| M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE, |
| cmpp_dx_f_include, F_A_LIST | F_UNSAFE, |
| 0, 0); |
| #endif |
| |
| #ifndef CMPP_OMIT_D_PIPE |
| M_IF_FLAGGED("pipe", cmpp_ctor_F_NO_PIPE, |
| cmpp_dx_f_pipe, F_A_RAW | F_UNSAFE, |
| cmpp_dx_f_dangling_closer, 0); |
| #endif |
| |
| #ifndef CMPP_OMIT_D_DB |
| M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB, |
| cmpp_dx_f_attach, F_A_LIST | F_UNSAFE, |
| 0, 0); |
| M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB, |
| cmpp_dx_f_detach, F_A_LIST | F_UNSAFE, |
| 0, 0); |
| if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.newFlags) |
| && (M_OC("query") || M("query:no-rows")) ){ |
| DREG0(rQ, "query", cmpp_dx_f_query, F_A_LIST | F_UNSAFE, |
| cmpp_dx_f_dangling_closer, 0); |
| cmpp_d * dQ = 0; |
| rc = cmpp_d_register(pp, &rQ, &dQ); |
| if( 0==rc ){ |
| /* |
| It would be preferable to delay registration of query:no-rows |
| until we need it, but doing so causes an error when: |
| |
| |#if 0 |
| |#query |
| |... |
| |#query:no-rows HERE |
| |... |
| |#/query |
| |#/if |
| |
| Because query:no-rows won't have been registered, and unknown |
| directives are an error even in skip mode. Maybe they |
| shouldn't be. Maybe we should just skip them in skip mode. |
| That's only been an issue since doing delayed registration of |
| directives, so it's not come up until recently (as of |
| 2025-10-27). i was so hoping to be able to get _rid_ of skip |
| mode at some point. |
| */ |
| cmpp_d * dNoRows = 0; |
| cmpp_d_reg const rNR = { |
| .name = "query:no-rows", |
| .opener = { |
| .f = cmpp_dx_f_dangling_closer, |
| .flags = F_NC |
| } |
| }; |
| rc = cmpp_d_register(pp, &rNR, &dNoRows); |
| if( 0==rc ){ |
| dNoRows->closer = dQ->closer; |
| assert( !dQ->impl.state ); |
| dQ->impl.state = dNoRows; |
| } |
| } |
| goto end; |
| } |
| #endif /*CMPP_OMIT_D_DB*/ |
| |
| #if CMPP_D_MODULE |
| extern void cmpp_dx_f_module(cmpp_dx *); |
| M_IF_FLAGGED("module", cmpp_ctor_F_NO_MODULE, |
| cmpp_dx_f_module, F_A_LIST | F_UNSAFE, |
| 0, 0); |
| #endif |
| |
| #undef M_IF_FLAGGED |
| |
| #ifndef NDEBUG |
| M_IF("experiment"){ |
| DREG("experiment", cmpp_dx_f_experiment, |
| F_A_LIST | F_UNSAFE, 0, 0); |
| } |
| #endif |
| |
| end: |
| #undef DREG |
| #undef DREG0 |
| #undef F_EXPR |
| #undef F_A_RAW |
| #undef F_A_LIST |
| #undef F_UNSAFE |
| #undef F_NC |
| #undef F_CALL |
| #undef CF |
| #undef M |
| #undef M_OC |
| #undef M_IF |
| pp->pimpl->flags.isInternalDirectiveReg = false; |
| return ppCode ? ppCode : rc; |
| } |
| /* |
| ** 2026-02-07: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses filesystem-related APIs libcmpp. |
| */ |
| |
| #include <unistd.h> |
| |
| /** |
| There are APIs i'd _like_ to have here, but the readily-available |
| code for them BSD license, so can't be pasted in here. Examples: |
| |
| - Filename canonicalization. |
| |
| - Cross-platform getcwd() (see below). |
| |
| - Windows support. This requires, in addition to the different |
| filesystem APIs, converting strings into something it can use. |
| |
| All of that adds up to infrastructure... which already exists |
| elsewhere but can't be copied here while retaining this project's |
| license. |
| */ |
| |
| bool cmpp__file_is_readable(char const *zFile){ |
| return 0==access(zFile, R_OK); |
| } |
| |
| #if 0 |
| FILE *cmpp__fopen(const char *zName, const char *zMode){ |
| FILE *f; |
| if(zName && ('-'==*zName && !zName[1])){ |
| f = (strchr(zMode, 'w') || strchr(zMode,'+')) |
| ? stdout |
| : stdin |
| ; |
| }else{ |
| f = fopen(zName, zMode); |
| } |
| return f; |
| } |
| |
| void cmpp__fclose( FILE * f ){ |
| if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){ |
| fclose(f); |
| } |
| } |
| #endif |
| /* |
| ** 2025-11-07: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the arguments-handling-related pieces for libcmpp. |
| */ |
| |
| const cmpp_args_pimpl cmpp_args_pimpl_empty = |
| cmpp_args_pimpl_empty_m; |
| const cmpp_args cmpp_args_empty = cmpp_args_empty_m; |
| const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m; |
| |
| //just in case these ever get dynamic state |
| void cmpp_arg_cleanup(cmpp_arg *arg){ |
| if( arg ) *arg = cmpp_arg_empty; |
| } |
| |
| //just in case these ever get dynamic state |
| void cmpp_arg_reuse(cmpp_arg *arg){ |
| if( arg ) *arg = cmpp_arg_empty; |
| } |
| |
| /** Resets li's list for re-use but does not free it. Returns li. */ |
| static CmppArgList * CmppArgList_reuse(CmppArgList *li){ |
| for(cmpp_size_t n = li->nAlloc; n; ){ |
| cmpp_arg_reuse( &li->list[--n] ); |
| assert( !li->list[n].next ); |
| } |
| li->n = 0; |
| return li; |
| } |
| |
| /** Free all memory owned by li but does not free li. */ |
| void CmppArgList_cleanup(CmppArgList *li){ |
| const CmppArgList CmppArgList_empty = CmppArgList_empty_m; |
| while( li->nAlloc ){ |
| cmpp_arg_cleanup( &li->list[--li->nAlloc] ); |
| } |
| cmpp_mfree(li->list); |
| *li = CmppArgList_empty; |
| } |
| |
| /** Returns the most-recently-appended arg of li back to li's |
| free-list. */ |
| static void CmppArgList_unappend(CmppArgList *li){ |
| assert( li->n ); |
| if( li->n ){ |
| cmpp_arg_reuse( &li->list[--li->n] ); |
| } |
| } |
| |
| cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){ |
| cmpp_arg * p = 0; |
| assert( li->list ? li->nAlloc : 0==li->nAlloc ); |
| if( 0==ppCode |
| && 0==CmppArgList_reserve(pp, li, |
| cmpp__li_reserve1_size(li,10)) ){ |
| p = &li->list[li->n++]; |
| cmpp_arg_reuse( p ); |
| } |
| return p; |
| } |
| |
| void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p){ |
| assert( !p->nextFree ); |
| cmpp_b_clear(&p->argOut); |
| CmppArgList_cleanup(&p->argli); |
| *p = cmpp_args_pimpl_empty; |
| } |
| |
| static void cmpp_args_pimpl_reuse(cmpp_args_pimpl *p){ |
| assert( !p->nextFree ); |
| cmpp_b_reuse(&p->argOut); |
| CmppArgList_reuse(&p->argli); |
| assert( !p->argOut.n ); |
| assert( !p->argli.n ); |
| } |
| |
| static void cmpp_args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){ |
| if( p ){ |
| assert( p->pp ); |
| cmpp__pi(pp); |
| assert( !p->nextFree ); |
| cmpp_args_pimpl_reuse(p); |
| p->nextFree = pi->recycler.argPimpl; |
| pi->recycler.argPimpl = p; |
| } |
| } |
| |
| static cmpp_args_pimpl * cmpp_args_pimpl_borrow(cmpp *pp){ |
| cmpp__pi(pp); |
| cmpp_args_pimpl * p = 0; |
| if( pi->recycler.argPimpl ){ |
| p = pi->recycler.argPimpl; |
| pi->recycler.argPimpl = p->nextFree; |
| p->nextFree = 0; |
| p->pp = pp; |
| assert( !p->argOut.n && "Buffer was used when not borrowed" ); |
| }else{ |
| p = cmpp__malloc(pp, sizeof(*p)); |
| if( 0==cmpp_check_oom(pp, p) ) { |
| *p = cmpp_args_pimpl_empty; |
| p->pp = pp; |
| } |
| } |
| return p; |
| } |
| |
| CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){ |
| if( a ){ |
| if( a->pimpl ){ |
| cmpp * const pp = a->pimpl->pp; |
| assert( pp ); |
| if( pp ){ |
| cmpp_args_pimpl_return(pp, a->pimpl); |
| }else{ |
| cmpp_args_pimpl_cleanup(a->pimpl); |
| cmpp_mfree(a->pimpl); |
| } |
| } |
| *a = cmpp_args_empty; |
| } |
| } |
| |
| CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a){ |
| cmpp_args_pimpl * const p = a->pimpl; |
| if( p ) cmpp_args_pimpl_reuse(p); |
| *a = cmpp_args_empty; |
| a->pimpl = p; |
| } |
| |
| int cmpp_args__init(cmpp * pp, cmpp_args * a){ |
| if( 0==ppCode ){ |
| if( a->pimpl ){ |
| assert( a->pimpl->pp == pp ); |
| cmpp_args_reuse(a); |
| assert(! a->pimpl->argOut.n ); |
| assert( a->pimpl->pp == pp ); |
| }else{ |
| a->pimpl = cmpp_args_pimpl_borrow(pp); |
| assert( !a->pimpl || a->pimpl->pp==pp ); |
| } |
| } |
| return ppCode; |
| } |
| |
| /** |
| Declare cmpp_argOp_f_NAME(). |
| */ |
| #define cmpp_argOp_decl(NAME) \ |
| static void cmpp_argOp_f_ ## NAME (cmpp_dx *dx, \ |
| cmpp_argOp const *op, \ |
| cmpp_arg const *vLhs, \ |
| cmpp_arg const **pvRhs, \ |
| int *pResult) |
| cmpp_argOp_decl(compare); |
| |
| #if 0 |
| cmpp_argOp_decl(logical1); |
| cmpp_argOp_decl(logical2); |
| cmpp_argOp_decl(defined); |
| #endif |
| |
| static const struct { |
| const cmpp_argOp opAnd; |
| const cmpp_argOp opOr; |
| const cmpp_argOp opGlob; |
| const cmpp_argOp opNotGlob; |
| const cmpp_argOp opNot; |
| const cmpp_argOp opDefined; |
| #define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge) |
| #define E(NAME) const cmpp_argOp op ## NAME; |
| cmpp_argOps_cmp_map(E) |
| #undef E |
| } cmpp_argOps = { |
| .opAnd = { |
| .ttype = cmpp_TT_OpAnd, |
| .arity = 2, |
| .assoc = 0, |
| .xCall = 0//cmpp_argOp_f_logical2 |
| }, |
| .opOr = { |
| .ttype = cmpp_TT_OpOr, |
| .arity = 2, |
| .assoc = 0, |
| .xCall = 0//cmpp_argOp_f_logical2 |
| }, |
| .opGlob = { |
| .ttype = cmpp_TT_OpGlob, |
| .arity = 2, |
| .assoc = 0, |
| .xCall = 0//cmpp_argOp_f_glob |
| }, |
| .opNotGlob = { |
| .ttype = cmpp_TT_OpNotGlob, |
| .arity = 2, |
| .assoc = 0, |
| .xCall = 0//cmpp_argOp_f_glob |
| }, |
| .opNot = { |
| .ttype = cmpp_TT_OpNot, |
| .arity = 1, |
| .assoc = 1, |
| .xCall = 0//cmpp_argOp_f_logical1 |
| }, |
| .opDefined = { |
| .ttype = cmpp_TT_OpDefined, |
| .arity = 1, |
| .assoc = 1, |
| .xCall = 0//cmpp_argOp_f_defined |
| }, |
| /* Comparison ops... */ |
| #define E(NAME) .op ## NAME = { \ |
| .ttype = cmpp_TT_Op ## NAME, .arity = 2, .assoc = 0, \ |
| .xCall = cmpp_argOp_f_compare }, |
| cmpp_argOps_cmp_map(E) |
| #undef E |
| }; |
| |
| cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt){ |
| switch(tt){ |
| case cmpp_TT_OpAnd: return &cmpp_argOps.opAnd; |
| case cmpp_TT_OpOr: return &cmpp_argOps.opOr; |
| case cmpp_TT_OpGlob: return &cmpp_argOps.opGlob; |
| case cmpp_TT_OpNot: return &cmpp_argOps.opNot; |
| case cmpp_TT_OpDefined: return &cmpp_argOps.opDefined; |
| #define E(NAME) case cmpp_TT_Op ## NAME: return &cmpp_argOps.op ## NAME; |
| cmpp_argOps_cmp_map(E) |
| #undef E |
| default: return NULL; |
| } |
| } |
| #define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype) |
| |
| #if 0 |
| cmpp_argOp_decl(logical1){ |
| assert( cmpp_TT_OpNot==op->ttype ); |
| assert( !vRhs ); |
| assert( vLhs ); |
| if( 0==cmpp__arg_toBool(dx, vLhs, pResult) ){ |
| *pResult = !*pResult; |
| } |
| } |
| |
| cmpp_argOp_decl(logical2){ |
| assert( vRhs ); |
| assert( vLhs ); |
| int vL = 0; |
| int vR = 0; |
| if( 0==cmpp__arg_toBool(dx, vLhs, &vL) |
| && 0==cmpp__arg_toBool(dx, vRhs, &vR) ){ |
| switch( op->ttype ){ |
| case cmpp_TT_OpAnd: *pResult = vL && vR; break; |
| case cmpp_TT_OpOr: *pResult = vL || vR; break; |
| default: |
| cmpp__fatal("Cannot happen: illegal op mapping"); |
| } |
| } |
| } |
| |
| cmpp_argOp_decl(defined){ |
| assert( cmpp_TT_OpDefined==op->ttype ); |
| assert( !vRhs ); |
| assert( vLhs ); |
| if( cmpp_TT_Word==vLhs->ttype ){ |
| *pResult = cmpp_has(pp, (char const *)vLhs->z, vLhs->n); |
| if( !*pResult && vLhs->n>1 && '#'==vLhs->z[0] ){ |
| *pResult = !!cmpp__d_search3(pp, vLhs->z+1, |
| cmpp__d_search3_F_NO_DLL); |
| } |
| }else{ |
| cmpp__err(pp, CMPP_RC_TYPE, "Invalid token type %s for %s", |
| cmpp__tt_cstr(vLhs->ttype, true), |
| cmpp__tt_cstr(op->ttype, false)); |
| } |
| } |
| #endif |
| |
| #if 0 |
| static cmpp_argOp const * cmpp_argOp_isCompare(cmpp_tt tt){ |
| cmpp_argOp const * const p = cmpp_argOp_for_tt(tt); |
| switch( p ? p->ttype : cmpp_TT_None ){ |
| #define E(NAME) case cmpp_TT_Op ## NAME: return p; |
| cmpp_argOps_cmp_map(E) |
| #undef E |
| return p; |
| case cmpp_TT_None: |
| default: |
| return NULL; |
| } |
| } |
| #endif |
| |
| /** |
| An internal helper for cmpp_argOp_...(). It binds some value of |
| *paArg to column bindNdx of query q and sets *paArg to the next |
| argument to be consumed. This function expects that q is set up to |
| do the right thing when *paArg is a Word-type value (see |
| cmpp_argOp_f_compare()). |
| */ |
| static void cmpp_argOp__cmp_bind(cmpp_dx * const dx, |
| sqlite3_stmt * const q, |
| int bindNdx, |
| cmpp_arg const ** paArg){ |
| cmpp_arg const * const arg = *paArg; |
| assert(arg); |
| switch( dxppCode ? 0 : arg->ttype ){ |
| case 0: break; |
| case cmpp_TT_Word: |
| /* In this case, q is supposed to be set up to use |
| CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word |
| and performs the expansion to its value in the query. */ |
| cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, arg->n); |
| *paArg = arg->next; |
| break; |
| case cmpp_TT_StringAt: |
| case cmpp_TT_String: |
| case cmpp_TT_Int:{ |
| cmpp__bind_arg(dx, q, bindNdx, arg); |
| *paArg = arg->next; |
| break; |
| } |
| case cmpp_TT_OpNot: |
| case cmpp_TT_OpDefined: |
| case cmpp_TT_GroupParen:{ |
| int rv = 0; |
| if( 0==cmpp__arg_toBool(dx, arg, &rv, paArg) ){ |
| cmpp__bind_int(dx->pp, q, bindNdx, rv); |
| } |
| *paArg = arg->next; |
| break; |
| } |
| /* TODO? cmpp_TT_GroupParen */ |
| default: |
| cmpp_dx_err_set(dx, CMPP_RC_TYPE, |
| "Invalid argument type (%s) for the comparison " |
| "queries: %s", |
| cmpp_tt_cstr(arg->ttype), arg->z); |
| } |
| } |
| |
| /** |
| Internal helper for cmp_argOp_...(). |
| |
| Expects q to be a query with an integer in result column 0. This |
| steps/resets the query and applies the given comparison operator's |
| logic to column 0's value, placing the result of the operator in |
| *pResult. |
| |
| If q has no result row, a default value of 0 is assumed. |
| */ |
| static void cmpp_argOp__cmp_apply(cmpp * const pp, |
| cmpp_argOp const * const op, |
| sqlite3_stmt * const q, |
| int * const pResult){ |
| if( 0==ppCode ){ |
| int rc = cmpp__step(pp, q, false); |
| assert( SQLITE_ROW==rc || ppCode ); |
| if( SQLITE_ROW==rc ){ |
| rc = sqlite3_column_int(q, 0); |
| }else{ |
| rc = 0; |
| } |
| switch( op->ttype ){ |
| case 0: break; |
| case cmpp_TT_OpEq: *pResult = 0==rc; break; |
| case cmpp_TT_OpNeq: *pResult = 0!=rc; break; |
| case cmpp_TT_OpLt: *pResult = rc<0; break; |
| case cmpp_TT_OpLe: *pResult = rc<=0; break; |
| case cmpp_TT_OpGt: *pResult = rc>0; break; |
| case cmpp_TT_OpGe: *pResult = rc>=0; break; |
| default: |
| cmpp__fatal("Cannot happen: invalid arg mapping"); |
| } |
| } |
| cmpp__stmt_reset(q); |
| } |
| |
| /** |
| Applies *paRhs as the RHS of an integer binary operator, the LHS of |
| which is the lhs argument. The result is put in *pResult. On |
| success *paRhs is set to the next argument for the expression to |
| parse. |
| */ |
| static void cmpp_argOp_applyTo(cmpp_dx *dx, |
| cmpp_argOp const * const op, |
| int lhs, |
| cmpp_arg const ** paRhs, |
| int * pResult){ |
| sqlite3_stmt * q = 0; |
| cmpp_arg const * aRhs = *paRhs; |
| assert(aRhs); |
| q = cmpp_TT_Word==aRhs->ttype |
| ? cmpp__stmt(dx->pp, CmppStmt_cmpVD, false) |
| : cmpp__stmt(dx->pp, CmppStmt_cmpVV, false); |
| if( q ){ |
| char numbuf[32]; |
| int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs); |
| cmpp__bind_textn(dx->pp, q, 1, ustr_c(numbuf), nNum); |
| cmpp_argOp__cmp_bind(dx, q, 2, paRhs); |
| cmpp_argOp__cmp_apply(dx->pp, op, q, pResult); |
| } |
| } |
| |
| cmpp_argOp_decl(compare){ |
| cmpp_arg const * const vRhs = *pvRhs; |
| sqlite3_stmt * q = 0; |
| /* Select which query to use, depending on whether each |
| of the LHS/RHS are Word tokens. For Word tokens |
| the corresponding query columns get bound to |
| a subquery which resolves the word. Non-word |
| tokens get bound as-is. */ |
| if( cmpp_TT_Word==vLhs->ttype ){ |
| q = cmpp_TT_Word==vRhs->ttype |
| ? cmpp__stmt(dx->pp, CmppStmt_cmpDD, false) |
| : cmpp__stmt(dx->pp, CmppStmt_cmpDV, false); |
| if(0){ |
| g_warn("\nvLhs=%s %s\nvRhs=%s %s\n", |
| cmpp_tt_cstr(vLhs->ttype), vLhs->z, |
| cmpp_tt_cstr(vRhs->ttype), vRhs->z); |
| } |
| }else if( cmpp_TT_Word==vRhs->ttype ){ |
| q = cmpp__stmt(dx->pp, CmppStmt_cmpVD, false); |
| }else{ |
| q = cmpp__stmt(dx->pp, CmppStmt_cmpVV, false); |
| } |
| if( q ){ |
| //cmpp__bind_textn(pp, q, 1, vLhs->z, vLhs->n); |
| cmpp_argOp__cmp_bind(dx, q, 1, &vLhs); |
| cmpp_argOp__cmp_bind(dx, q, 2, pvRhs); |
| cmpp_argOp__cmp_apply(dx->pp, op, q, pResult); |
| } |
| } |
| |
| #undef cmpp_argOp_decl |
| |
| #if 0 |
| static inline int cmpp_dxt_isBinOp(cmpp_tt tt){ |
| cmpp_argOp const * const a = cmpp_argOp_for_tt(tt); |
| return a ? 2==a->arity : 0; |
| } |
| |
| static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){ |
| return tt==cmpp_TT_OpNot || cmpp_TT_OpDefined; |
| } |
| |
| static inline int cmpp_dxt_isGroup(cmpp_tt tt){ |
| return tt==cmpp_TT_GroupParen || tt==cmpp_TT_GroupBrace || cmpp_TT_GroupSquiggly; |
| } |
| #endif |
| |
| int cmpp__arg_evalSubToInt(cmpp_dx *dx, |
| cmpp_arg const *arg, |
| int * pResult){ |
| cmpp_args sub = cmpp_args_empty; |
| if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ |
| cmpp__args_evalToInt(dx, &sub, pResult); |
| } |
| cmpp_args_cleanup(&sub); |
| return dxppCode; |
| } |
| |
| int cmpp__args_evalToInt(cmpp_dx * const dx, |
| cmpp_args const *pArgs, |
| int * pResult){ |
| if( dxppCode ) return dxppCode; |
| |
| cmpp_arg const * pNext = 0; |
| cmpp_arg const * pPrev = 0; |
| int result = *pResult; |
| cmpp_b osL = cmpp_b_empty; |
| cmpp_b osR = cmpp_b_empty; |
| static int level = 0; |
| ++level; |
| |
| #define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__) |
| |
| //lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z); |
| for( cmpp_arg const *arg = pArgs->arg0; |
| arg && 0==dxppCode; |
| pPrev = arg, arg = pNext ){ |
| pNext = arg->next; |
| if( cmpp_TT_Noop==arg->ttype ){ |
| arg = pPrev /* help the following arg to DTRT */; |
| continue; |
| } |
| cmpp_argOp const * const thisOp = argOp(arg); |
| cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0; |
| if( 0 ){ |
| lout("arg: %s @%p %s\n", |
| cmpp__tt_cstr(arg->ttype, true), arg, arg->z); |
| if(1){ |
| if( pPrev ) lout(" prev arg: %s %s\n", |
| cmpp__tt_cstr(pPrev->ttype, true), pPrev->z); |
| if( pNext ) lout(" next arg: %s %s\n", |
| cmpp__tt_cstr(pNext->ttype, true), pNext->z); |
| } |
| } |
| if( thisOp ){ /* Basic validation */ |
| if( !pNext ){ |
| dxserr("Missing '%s' RHS.", |
| cmpp__tt_cstr(thisOp->ttype, false)); |
| break; |
| }else if( !pPrev && 2==thisOp->arity ){ |
| dxserr("Missing %s LHS.", |
| cmpp__tt_cstr(thisOp->ttype, false)); |
| break; |
| } |
| if( nextOp && nextOp->arity>1 ){ |
| dxserr("Invalid '%s' RHS: %s", arg->z, pNext->z); |
| break; |
| } |
| } |
| |
| switch( arg->ttype ){ |
| |
| case cmpp_TT_OpNot: |
| case cmpp_TT_OpDefined: |
| if( pPrev && !argOp(pPrev) ){ |
| cmpp_dx_err_set(dx, CMPP_RC_CANNOT_HAPPEN, |
| "We expected to have consumed '%s' by " |
| "this point.", |
| pPrev->z); |
| }else{ |
| cmpp__arg_toBool(dx, arg, &result, &pNext); |
| } |
| break; |
| |
| case cmpp_TT_OpAnd: |
| case cmpp_TT_OpOr:{ |
| assert( pNext ); |
| assert( pPrev ); |
| /* Reminder to self: we can't add short-circuiting of the RHS |
| right now because the handling of chained unary ops on the |
| RHS is handled via cmpp__arg_toBool(). */ |
| int rv = 0; |
| if( 0==cmpp__arg_toBool(dx, pNext, &rv, &pNext) ){ |
| if( cmpp_TT_OpAnd==arg->ttype ) result = result && rv; |
| else result = result || rv; |
| } |
| //g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0); |
| break; |
| } |
| |
| case cmpp_TT_OpNotGlob: |
| case cmpp_TT_OpGlob:{ |
| assert( pNext ); |
| assert( pPrev ); |
| assert( pNext!=arg ); |
| assert( pPrev!=arg ); |
| if( cmpp_arg_to_b(dx, pNext, &osL, 0) ){ |
| break; |
| } |
| unsigned char const * const zGlob = osL.z; |
| if( 0==cmpp_arg_to_b(dx, pPrev, &osR, 0) ){ |
| if( 0 ){ |
| g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z); |
| } |
| result = 0==sqlite3_strglob((char const *)zGlob, |
| (char const *)osR.z); |
| if( cmpp_TT_OpNotGlob==arg->ttype ){ |
| result = !result; |
| } |
| //g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result); |
| } |
| pNext = pNext->next; |
| break; |
| } |
| |
| #define E(NAME) case cmpp_TT_Op ## NAME: |
| cmpp_argOps_cmp_map(E) { |
| cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0; |
| if( prevOp ){ |
| /* Chained operators */ |
| cmpp_argOp_applyTo(dx, thisOp, result, &pNext, &result); |
| }else{ |
| assert( pNext ); |
| assert( pPrev ); |
| assert( thisOp ); |
| assert( thisOp->xCall ); |
| thisOp->xCall(dx, thisOp, pPrev, &pNext, &result); |
| } |
| break; |
| } |
| #undef E |
| |
| #define checkConsecutiveNonOps \ |
| if( pPrev && !argOp(pPrev) ){ \ |
| dxserr("Illegal consecutive non-operators: %s %s", \ |
| pPrev->z, arg->z); \ |
| break; \ |
| }(void)0 |
| |
| case cmpp_TT_Int: |
| case cmpp_TT_String: |
| checkConsecutiveNonOps; |
| if( !cmpp__is_int(arg->z, arg->n, &result) ){ |
| /* This is mostly for and/or ops. glob will reach back and |
| grab arg->z. */ |
| result = 0; |
| } |
| break; |
| case cmpp_TT_Word: |
| checkConsecutiveNonOps; |
| cmpp__get_int(dx->pp, arg->z, arg->n, &result); |
| break; |
| case cmpp_TT_GroupParen:{ |
| checkConsecutiveNonOps; |
| cmpp_args sub = cmpp_args_empty; |
| if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ |
| cmpp__args_evalToInt(dx, &sub, &result); |
| } |
| cmpp_args_cleanup(&sub); |
| break; |
| } |
| case cmpp_TT_GroupBrace:{ |
| checkConsecutiveNonOps; |
| cmpp_b b = cmpp_b_empty; |
| if( 0==cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0) ){ |
| cmpp__is_int(b.z, b.n, &result); |
| } |
| cmpp_b_clear(&b); |
| break; |
| } |
| #undef checkConsecutiveNonOps |
| default: |
| assert( arg->z ); |
| dxserr("Illegal expression token %s: %s", |
| cmpp__tt_cstr(arg->ttype, true), arg->z); |
| }/*switch(arg->ttype)*/ |
| }/* foreach arg */ |
| if( 0 ){ |
| lout("END %s() result=%d\n", __func__, result); |
| } |
| --level; |
| if( !dxppCode ){ |
| *pResult = result; |
| } |
| cmpp_b_clear(&osL); |
| cmpp_b_clear(&osR); |
| return dxppCode; |
| #undef lout |
| } |
| |
| #undef argOp |
| #undef cmpp_argOp_decl |
| |
| static inline cmpp_tt cmpp_dxt_is_group(cmpp_tt ttype){ |
| switch(ttype){ |
| case cmpp_TT_GroupParen: |
| case cmpp_TT_GroupBrace: |
| case cmpp_TT_GroupSquiggly: |
| return ttype; |
| default: |
| return cmpp_TT_None; |
| } |
| } |
| |
| int cmpp_args_parse(cmpp_dx * const dx, |
| cmpp_args * const pArgs, |
| unsigned char const * const zInBegin, |
| cmpp_ssize_t nIn, |
| cmpp_flag32_t flags){ |
| assert( zInBegin ); |
| unsigned char const * const zInEnd = |
| zInBegin + cmpp__strlenu(zInBegin, nIn); |
| |
| if( cmpp_args__init(dx->pp, pArgs) ) return dxppCode; |
| if( 0 ){ |
| g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin), |
| zInBegin); |
| } |
| unsigned char const * zPos = zInBegin; |
| cmpp_size_t const nBuffer = |
| /* Buffer size for our copy of the args. We need to know the |
| size before we start so that we can have each arg reliably |
| point back into this without it being reallocated during |
| parsing. */ |
| (cmpp_size_t)(zInEnd - zInBegin) |
| /* Plus we need one final NUL and one NUL byte per argument, but |
| we don't yet know how many arguments we will have, so let's |
| estimate... */ |
| + ((cmpp_size_t)(zInEnd - zInBegin))/3 |
| + 5/*fudge room*/; |
| cmpp_b * const buffer = &pArgs->pimpl->argOut; |
| assert( !buffer->n ); |
| if( cmpp_b_reserve3(dx->pp, buffer, nBuffer) ){ |
| return dxppCode; |
| } |
| unsigned char * zOut = buffer->z; |
| unsigned char const * const zOutEnd = zOut + buffer->nAlloc - 1; |
| cmpp_arg * prevArg = 0; |
| #if !defined(NDEBUG) |
| unsigned char const * const zReallocCheck = buffer->z; |
| #endif |
| |
| if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin), |
| zInBegin); |
| pArgs->arg0 = NULL; |
| pArgs->argc = 0; |
| for( int i = 0; zPos<zInEnd; ++i){ |
| //g_stderr("i=%d prevArg=%p\n",i, prevArg); |
| cmpp_arg * const arg = |
| CmppArgList_append(dx->pp, &pArgs->pimpl->argli); |
| if( !arg ) return dxppCode; |
| assert( pArgs->pimpl->argli.n ); |
| if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos); |
| if( cmpp_arg_parse(dx, arg, &zPos, zInEnd, &zOut, zOutEnd) ){ |
| if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos); |
| break; |
| } |
| if( 0 ){ |
| g_warn("#%d zPos=<<%.*s>>", i, (int)(zInEnd-zPos), zPos); |
| g_warn("#%d arg n=%u z=<<%.*s>> %s", i, (int)arg->n, (int)arg->n, arg->z, arg->z); |
| } |
| assert( zPos<=zInEnd ); |
| if( 0 ){ |
| g_stderr("ttype=%d %s n=%u z=%.*s\n", arg->ttype, |
| cmpp__tt_cstr(arg->ttype, true), |
| (unsigned)arg->n, (int)arg->n, arg->z); |
| } |
| if( cmpp_TT_Eof==arg->ttype ){ |
| CmppArgList_unappend(&pArgs->pimpl->argli); |
| break; |
| } |
| switch( 0==(flags & cmpp_args_F_NO_PARENS) |
| ? cmpp_dxt_is_group( arg->ttype ) |
| : 0 ){ |
| case cmpp_TT_GroupParen:{ |
| /* Sub-expression. We tokenize it here just to ensure that we |
| can, so we can fail earlier rather than later. This is why |
| we need a recycler for the cmpp_args buffer memory. */ |
| cmpp_args sub = cmpp_args_empty; |
| cmpp_args_parse(dx, &sub, arg->z, arg->n, flags); |
| //g_stderr("Parsed sub-expr: %s\n", sub.buffer.z); |
| cmpp_args_cleanup(&sub); |
| break; |
| } |
| case cmpp_TT_GroupBrace: |
| case cmpp_TT_GroupSquiggly: |
| default: break; |
| } |
| if( dxppCode ) break; |
| if( prevArg ){ |
| assert( !prevArg->next ); |
| prevArg->next = arg; |
| } |
| prevArg = arg; |
| }/*foreach input char*/ |
| //g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(dxppCode), pArgs->args.n); |
| if( 0==dxppCode ){ |
| pArgs->argc = pArgs->pimpl->argli.n; |
| assert( !pArgs->arg0 ); |
| if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list; |
| if( zOut<zInEnd ) *zOut = 0; |
| if( 0 ){ |
| for( cmpp_arg const * a = pArgs->arg0; a; a = a->next ){ |
| g_stderr(" got: %s %.*s\n", cmpp__tt_cstr(a->ttype, true), |
| a->n, a->z); |
| } |
| } |
| } |
| assert(zReallocCheck==buffer->z |
| && "Else buffer was reallocated, invalidating argN->z"); |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0, |
| cmpp_args * const dest){ |
| if( cmpp_args__init(pp, dest) || !a0 ) return ppCode; |
| cmpp_b * const ob = &dest->pimpl->argOut; |
| CmppArgList * const argli = &dest->pimpl->argli; |
| unsigned int i = 0; |
| cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */; |
| |
| assert( !ob->n ); |
| assert( !dest->arg0 ); |
| assert( !dest->argc ); |
| assert( !argli->n ); |
| |
| /* Preallocate ob->z to fit a copy of a0's args. */ |
| for( cmpp_arg const * a = a0; a; ++i, a = a->next ){ |
| nReserve += a->n + 1/*NUL byte*/; |
| } |
| if( cmpp_b_reserve3(pp, ob, nReserve+1) |
| || CmppArgList_reserve(pp, argli, i) ){ |
| goto end; |
| } |
| assert( argli->nAlloc>=i ); |
| i = 0; |
| #ifndef NDEBUG |
| unsigned char const * const zReallocCheck = ob->z; |
| #endif |
| for( cmpp_arg const * a = a0; a; ++i, a = a->next ){ |
| cmpp_arg * const aNew = &argli->list[i]; |
| aNew->n = a->n; |
| aNew->z = ob->z + ob->n; |
| aNew->ttype = a->ttype; |
| if( i ) argli->list[i-1].next = aNew; |
| assert( !a->z[a->n] && "Expecting a NUL byte there" ); |
| cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/); |
| if( 0 ){ |
| g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype), |
| (int)a->n, a->z, a->z); |
| } |
| assert( zReallocCheck==ob->z |
| && "This cannot fail: ob->z was pre-allocated" ); |
| } |
| dest->argc = i; |
| dest->arg0 = i ? &argli->list[0] : 0; |
| end: |
| if( ppCode ){ |
| cmpp_args_reuse(dest); |
| } |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){ |
| return cmpp_args_clone(dx->pp, dx->args.arg0, pOut); |
| } |
| |
| char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg){ |
| char * z = 0; |
| if( 0==ppCode ){ |
| z = sqlite3_mprintf("%s",arg->z); |
| cmpp_check_oom(pp, z); |
| } |
| return z; |
| } |
| |
| static cmpp_tt cmpp_tt_forWord(unsigned char const *z, unsigned n, |
| cmpp_tt dflt){ |
| static const struct { |
| #define E(NAME,STR) struct CmppSnippet NAME; |
| cmpp_tt_map(E) |
| #undef E |
| } ttStr = { |
| #define E(NAME,STR) \ |
| .NAME = {(unsigned char const *)STR,sizeof(STR)-1}, |
| cmpp_tt_map(E) |
| #undef E |
| }; |
| #define CASE(NAME) if( 0==memcmp(ttStr.NAME.z, z, n) ) return cmpp_TT_ ## NAME |
| switch( n ){ |
| case 1: |
| CASE(OpEq); |
| CASE(Plus); |
| CASE(Minus); |
| break; |
| case 2: |
| CASE(OpOr); |
| CASE(ShiftL); |
| //CASE(ShiftR); |
| //CASE(ArrowL); |
| CASE(ArrowR); |
| CASE(OpNeq); |
| CASE(OpLt); |
| CASE(OpLe); |
| CASE(OpGt); |
| CASE(OpGe); |
| break; |
| case 3: |
| CASE(OpAnd); |
| CASE(OpNot); |
| CASE(ShiftL3); |
| break; |
| case 4: |
| CASE(OpGlob); |
| break; |
| case 7: |
| CASE(OpDefined); |
| break; |
| #undef CASE |
| } |
| #if 0 |
| bool b = cmpp__is_int(z, n, NULL); |
| if( 1|| !b ){ |
| g_warn("is_int(%s)=%d", z, b); |
| } |
| return b ? cmpp_TT_Int : dflt; |
| #else |
| return cmpp__is_int(z, n, NULL) ? cmpp_TT_Int : dflt; |
| #endif |
| } |
| |
| int cmpp_arg_parse(cmpp_dx * const dx, cmpp_arg *pOut, |
| unsigned char const **pzIn, |
| unsigned char const *zInEnd, |
| unsigned char ** pzOut, |
| unsigned char const * zOutEnd){ |
| unsigned char const * zi = *pzIn; |
| unsigned char * zo = *pzOut; |
| cmpp_tt ttype = cmpp_TT_None; |
| |
| #if 0 |
| // trying to tickle valgrind |
| for(unsigned char const *x = zi; x < zInEnd; ++x ){ |
| assert(*x); |
| } |
| #endif |
| cmpp_arg_reuse( pOut ); |
| cmpp_skip_snl( &zi, zInEnd ); |
| if( zi>=zInEnd ){ |
| *pzIn = zi; |
| pOut->ttype = cmpp_TT_Eof; |
| return 0; |
| } |
| #define out(CH) if(zo>=zOutEnd) goto notEnoughOut; *zo++ = CH |
| #define eot_break if( cmpp_TT_None!=ttype ){ keepGoing = 0; break; } (void)0 |
| pOut->z = zo; |
| bool keepGoing = true; |
| for( ; keepGoing |
| && 0==dxppCode |
| && zi<zInEnd |
| && zo<zOutEnd; ){ |
| cmpp_tt ttOverride = cmpp_TT_None; |
| switch( (int)*zi ){ |
| case 0: keepGoing = false; break; |
| case ' ': case '\t': case '\n': case '\r': |
| eot_break; |
| cmpp_skip_snl( &zi, zInEnd ); |
| break; |
| case '-': |
| if ('>'==zi[1] ){ |
| ttype = cmpp_TT_ArrowR; |
| out(*zi++); |
| out(*zi++); |
| keepGoing = false; |
| }else{ |
| goto do_word; |
| } |
| break; |
| case '=': |
| eot_break; keepGoing = false; ttype = cmpp_TT_OpEq; out(*zi++); break; |
| #define opcmp(CH,TT,TTEQ,TTSHIFT,TTARROW) \ |
| case CH: eot_break; keepGoing = false; ttype = TT; out(*zi++); \ |
| if( zi<zInEnd && '='==*zi ){ out(*zi++); ttype = TTEQ; } \ |
| else if( zi<zInEnd && CH==*zi ){ out(*zi++); ttype = TTSHIFT; } \ |
| else if( (int)TTARROW && zi<zInEnd && '-'==*zi ){ out(*zi++); ttype = TTARROW; } |
| |
| opcmp('>',cmpp_TT_OpGt,cmpp_TT_OpGe,cmpp_TT_ShiftR,0) break; |
| opcmp('<',cmpp_TT_OpLt,cmpp_TT_OpLe,cmpp_TT_ShiftL,cmpp_TT_ArrowL) |
| if( cmpp_TT_ShiftL==ttype && zi<zInEnd && '<'==zi[0] ) { |
| out(*zi++); |
| ttype = cmpp_TT_ShiftL3; |
| } |
| break; |
| #undef opcmp |
| case '!': |
| eot_break; |
| keepGoing = false; |
| out(*zi++); |
| if( zi < zInEnd && '='==*zi ){ |
| ttype = cmpp_TT_OpNeq; |
| out('='); |
| ++zi; |
| }else{ |
| while( zi < zInEnd && '!'==*zi ){ |
| out(*zi++); |
| } |
| ttype = cmpp_TT_OpNot; |
| } |
| break; |
| case '@': |
| if( zi+2 >= zInEnd || ('"'!=zi[1] && '\''!=zi[1]) ){ |
| goto do_word; |
| } |
| //if( cmpp__StringAtIsOk(dx->pp) ) break; |
| ttOverride = cmpp_TT_StringAt; |
| ++zi /* consume opening '@' */; |
| //g_stderr("@-string override\n"); |
| /* fall through */ |
| case '"': |
| case '\'': { |
| /* Parse a string. We do not support backslash-escaping of any |
| sort here. Strings which themselves must contain quotes |
| should use the other quote type. */ |
| keepGoing = false; |
| if( cmpp_TT_None!=ttype ){ |
| cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, |
| "Misplaced quote character near: %.*s", |
| (int)(zi+1 - *pzIn), *pzIn); |
| break; |
| } |
| unsigned char const * zQuoteAt = zi; |
| if( cmpp__find_closing(dx->pp, &zQuoteAt, zInEnd) ){ |
| break; |
| } |
| assert( zi+1 <= zQuoteAt ); |
| assert( *zi == *zQuoteAt ); |
| if( (zQuoteAt - zi - 2) >= (zOutEnd-zo) ){ |
| goto notEnoughOut; |
| } |
| memcpy(zo, zi+1, zQuoteAt - zi - 1); |
| //g_warn("string=<<%.*s>>", (zQuoteAt-zi-1), zo); |
| zo += zQuoteAt - zi - 1; |
| zi = zQuoteAt + 1/* closing quote */; |
| ttype = (cmpp_TT_None==ttOverride ? cmpp_TT_String : ttOverride); |
| break; |
| } |
| case '[': |
| case '{': |
| case '(': { |
| /* Slurp these as a single token for later sub-parsing */ |
| keepGoing = false; |
| unsigned char const * zAt = zi; |
| if( cmpp__find_closing(dx->pp, &zi, zInEnd) ) break; |
| /* Transform the output, eliding the open/close characters and |
| trimming spaces. We need to keep newlines intact, as the |
| content may be free-form, intended for other purposes, e.g. |
| the #pipe or #query directives. */ |
| ttype = ('('==*zAt |
| ? cmpp_TT_GroupParen |
| : ('['==*zAt |
| ? cmpp_TT_GroupBrace |
| : cmpp_TT_GroupSquiggly)); |
| ++zAt /* consume opening brace */; |
| /* Trim leading and trailing space, but retain tabs and all but |
| the first and last newline. */ |
| cmpp_skip_space(&zAt, zi); |
| if( zAt<zInEnd ){ |
| if( '\n'==*zAt ) ++zAt; |
| else if(zAt+1<zInEnd && '\r'==*zAt && '\n'==zAt[1]) zAt+=2; |
| } |
| for( ; zAt<zi; ++zAt ){ |
| out(*zAt); |
| } |
| if(0) g_warn("parse1: group n=%u [%.*s]\n", |
| (zi-zAt), (zi-zAt), zAt); |
| while( zo>*pzOut && ' '==zo[-1] ) *--zo = 0; |
| if( zo>*pzOut && '\n'==zo[-1] ){ |
| *--zo = 0; |
| if( zo>*pzOut && '\r'==zo[-1] ){ |
| *--zo = 0; |
| } |
| } |
| ++zi /* consume the closer */; |
| break; |
| } |
| default: |
| ; do_word: |
| out(*zi++); |
| ttype = cmpp_TT_Word; |
| break; |
| } |
| //g_stderr("kg=%d char=%d %c\n", keepGoing, (int)*zi, *zi); |
| } |
| if( dxppCode ){ |
| /* problem already reported */ |
| }else if( zo>=zOutEnd-1 ){ |
| notEnoughOut: |
| cmpp_dx_err_set(dx, CMPP_RC_RANGE, |
| "Ran out of output space (%u bytes) while " |
| "parsing an argument", (unsigned)(zOutEnd-*pzOut)); |
| }else{ |
| pOut->n = (zo - *pzOut); |
| if( cmpp_TT_None==ttype ){ |
| pOut->ttype = cmpp_TT_Eof; |
| }else if( cmpp_TT_Word==ttype && pOut->n ){ |
| pOut->ttype = cmpp_tt_forWord(pOut->z, pOut->n, ttype); |
| }else{ |
| pOut->ttype = ttype; |
| } |
| *zo++ = 0; |
| *pzIn = zi; |
| *pzOut = zo; |
| switch( pOut->ttype ){ |
| case cmpp_TT_Int: |
| if( '+'==*pOut->z ){ /* strip leading + */ |
| ++pOut->z; |
| --pOut->n; |
| } |
| break; |
| default: |
| break; |
| } |
| if(0){ |
| g_stderr("parse1: %s n=%u <<%.*s>>", |
| cmpp__tt_cstr(pOut->ttype, true), pOut->n, |
| pOut->n, pOut->z); |
| } |
| } |
| #undef out |
| #undef eot_break |
| return dxppCode; |
| } |
| |
| int cmpp__arg_toBool(cmpp_dx * const dx, cmpp_arg const *arg, |
| int * pResult, cmpp_arg const **pNext){ |
| switch( dxppCode ? 0 : arg->ttype ){ |
| case 0: break; |
| |
| case cmpp_TT_Word: |
| *pNext = arg->next; |
| *pResult = cmpp__get_bool(dx->pp, arg->z, arg->n); |
| break; |
| |
| case cmpp_TT_Int: |
| *pNext = arg->next; |
| cmpp__is_int(arg->z, arg->n, pResult)/*was already validated*/; |
| break; |
| |
| case cmpp_TT_String: |
| case cmpp_TT_StringAt:{ |
| unsigned char const * z = 0; |
| cmpp_size_t n = 0; |
| cmpp_b os = cmpp_b_empty; |
| if( 0==cmpp__arg_expand_ats(dx, &os, cmpp_atpol_CURRENT, |
| arg, cmpp_TT_StringAt, &z, &n) ){ |
| *pNext = arg->next; |
| *pResult = n>0 && 0!=memcmp("0\0", z, 2); |
| } |
| cmpp_b_clear(&os); |
| break; |
| } |
| |
| case cmpp_TT_GroupParen:{ |
| *pNext = arg->next; |
| cmpp_args sub = cmpp_args_empty; |
| if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){ |
| cmpp__args_evalToInt(dx, &sub, pResult); |
| } |
| cmpp_args_cleanup(&sub); |
| break; |
| } |
| |
| case cmpp_TT_OpDefined: |
| if( !arg->next ){ |
| dxserr("Missing '%s' RHS.", arg->z); |
| }else if( cmpp_TT_Word!=arg->next->ttype ){ |
| dxserr( "Invalid '%s' RHS: %s", arg->z, arg->next->z); |
| }else{ |
| cmpp_arg const * aOperand = arg->next; |
| *pNext = aOperand->next; |
| if( aOperand->n>1 |
| && '#'==aOperand->z[0] |
| && !!cmpp__d_search3(dx->pp, (char const*)aOperand->z+1, |
| cmpp__d_search3_F_NO_DLL) ){ |
| *pResult = 1; |
| }else{ |
| *pResult = cmpp_has(dx->pp, (char const *)aOperand->z, |
| aOperand->n); |
| } |
| } |
| break; |
| |
| case cmpp_TT_OpNot:{ |
| assert( arg->next && "See cmpp_args__not_simplify()"); |
| assert( cmpp_TT_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()"); |
| if( 0==cmpp__arg_toBool(dx, arg->next, pResult, pNext) ){ |
| *pResult = !*pResult; |
| } |
| break; |
| } |
| |
| default: |
| dxserr("Invalid token type %s for %s(): %s", |
| cmpp__tt_cstr(arg->ttype, true), __func__, arg->z); |
| break; |
| } |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_arg_to_b)(cmpp_dx * const dx, cmpp_arg const *arg, |
| cmpp_b * ob, cmpp_flag32_t flags){ |
| /** |
| Reminder to self: this function specifically does not do any |
| expression evaluation of its arguments. Please avoid the |
| temptation to make it do so. Unless it proves necessary. Or |
| useful. Even then, though, consider the implications deeply |
| before doing so. |
| */ |
| switch( dxppCode |
| ? 0 |
| : ((cmpp_arg_to_b_F_FORCE_STRING & flags) |
| ? cmpp_TT_String : arg->ttype) ){ |
| |
| case 0: |
| break; |
| case cmpp_TT_Word: |
| if( 0==(flags & cmpp_arg_to_b_F_NO_DEFINES) ){ |
| cmpp__get_b(dx->pp, arg->z, arg->n, ob, true); |
| break; |
| } |
| goto theDefault; |
| case cmpp_TT_StringAt:{ |
| unsigned char const * z = 0; |
| cmpp_size_t n = 0; |
| if( 0 ){ |
| g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z); |
| } |
| if( 0==cmpp__arg_expand_ats(dx, ob, cmpp_atpol_CURRENT, arg, |
| cmpp_TT_StringAt, &z, &n) |
| && 0 ){ |
| g_warn("expanded at [%.*s] [%s]", (int)n, z, z); |
| g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z); |
| } |
| break; |
| } |
| case cmpp_TT_GroupBrace: |
| if( !(cmpp_arg_to_b_F_NO_BRACE_CALL & flags) |
| && (cmpp_arg_to_b_F_BRACE_CALL & flags) ){ |
| cmpp_call_str(dx->pp, arg->z, arg->n, ob, 0); |
| break; |
| } |
| /* fall through */ |
| default: { |
| theDefault: ; |
| cmpp_outputer oss = cmpp_outputer_b; |
| oss.state = ob;//no: cmpp_b_reuse(ob); Append instead. |
| cmpp__out2(dx->pp, &oss, arg->z, arg->n); |
| break; |
| } |
| } |
| return dxppCode; |
| } |
| |
| int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * const q, |
| int bindNdx, cmpp_arg const * const arg){ |
| |
| if( 0 ){ |
| g_warn("bind #%d %s <<%.*s>>", bindNdx, |
| cmpp__tt_cstr(arg->ttype, true), |
| (int)arg->n, arg->z); |
| } |
| switch( arg->ttype ){ |
| default: |
| case cmpp_TT_Int: |
| case cmpp_TT_String: |
| cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, (int)arg->n); |
| break; |
| |
| case cmpp_TT_Word: |
| case cmpp_TT_StringAt:{ |
| cmpp_b os = cmpp_b_empty; |
| if( 0==cmpp_arg_to_b(dx, arg, &os, 0) ){ |
| if( 0 ){ |
| g_warn("bind #%d <<%s>> => <<%.*s>>", |
| bindNdx, arg->z, (int)os.n, os.z); |
| } |
| cmpp__bind_textn(dx->pp, q, bindNdx, os.z, (int)os.n); |
| } |
| cmpp_b_clear(&os); |
| break; |
| } |
| |
| case cmpp_TT_GroupParen:{ |
| cmpp_args sub = cmpp_args_empty; |
| int i = 0; |
| if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) |
| && 0==cmpp__args_evalToInt(dx, &sub, &i) ){ |
| /* See comment above about cmpp_TT_Int. */ |
| cmpp__bind_int_text(dx->pp, q, bindNdx, i); |
| } |
| cmpp_args_cleanup(&sub); |
| break; |
| } |
| |
| case cmpp_TT_GroupBrace:{ |
| cmpp_b b = cmpp_b_empty; |
| cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0); |
| cmpp__bind_textn(dx->pp, q, bindNdx, b.z, b.n); |
| cmpp_b_clear(&b); |
| break; |
| } |
| |
| } |
| return dxppCode; |
| } |
| |
| /** |
| If a is in li->list, return its non-const pointer from li->list |
| (O(1)), else return NULL. |
| */ |
| static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){ |
| if( li->nAlloc && a>=li->list && a<(li->list + li->nAlloc) ){ |
| return li->list + (a - li->list); |
| } |
| return NULL; |
| } |
| |
| /** |
| To be called only by cmpp_dx_args_parse() and only if the current |
| directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY. |
| |
| Filter chains of "not" operators from pArgs, removing unnecessary |
| ones. Also collapse "not glob" into a single cmpp_TT_OpNotGlob argument. |
| Performs some basic validation as well to simplify downstream |
| operations. Returns p->err.code and is a no-op if that's set |
| before this is called. |
| */ |
| static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){ |
| cmpp_arg * pPrev = 0; |
| cmpp_arg * pNext = 0; |
| CmppArgList * const ali = &pArgs->pimpl->argli; |
| pArgs->argc = 0; |
| for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL; |
| arg && !ppCode; |
| pPrev=arg, arg = pNext ){ |
| pNext = CmppArgList_arg_nc(ali, arg->next); |
| assert( pNext || !arg->next ); |
| if( cmpp_TT_OpNot==arg->ttype ){ |
| if( !pNext ){ |
| serr("Missing '%s' RHS", arg->z); |
| break; |
| } |
| cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype); |
| if( nop && nop->arity>1 && cmpp_TT_OpGlob!=nop->ttype ){ |
| serr("Illegal '%s' RHS: binary '%s' operator", |
| arg->z, pNext->z); |
| break; |
| } |
| int bNeg = 1; |
| if( '!'==*arg->z ){ |
| /* odd number of ! == negate */ |
| bNeg = arg->n & 1; |
| } |
| while( pNext && cmpp_TT_OpNot==pNext->ttype ){ |
| bNeg = !bNeg; |
| arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next); |
| } |
| if( pNext && cmpp_TT_OpGlob==pNext->ttype ){ |
| /* Transform it to a cmpp_TT_OpNotGlob or cmpp_TT_OpGlob. */ |
| assert( pNext->z > arg->z + arg->n ); |
| arg->n = pNext->z + pNext->n - arg->z; |
| arg->next = pNext->next; |
| arg->ttype = bNeg |
| ? cmpp_TT_OpNotGlob |
| : pNext->ttype; |
| ++pArgs->argc; |
| }else if( pPrev ){ |
| if( bNeg ){ |
| ++pArgs->argc; |
| }else{ |
| /* Snip this node out. */ |
| pPrev->next = pNext; |
| } |
| }else{ |
| assert( 0==pArgs->argc ); |
| ++pArgs->argc; |
| if( !bNeg ){ |
| arg->ttype = cmpp_TT_Noop; |
| } |
| } |
| /* Potential bug in waiting/fixme: by eliding all nots we are |
| ** changing the behavior from forced coercion to bool to |
| ** coercion to whatever the LHS wants. */ |
| }else{ |
| ++pArgs->argc; |
| } |
| } |
| pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL; |
| return ppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx, |
| cmpp_args *args){ |
| if( !dxppCode |
| && 0==cmpp_args_parse(dx, args, dx->args.z, dx->args.nz, |
| cmpp_args_F_NO_PARENS) |
| && (cmpp_d_F_NOT_SIMPLIFY & dx->d->flags) ){ |
| cmpp_args__not_simplify(dx->pp, args); |
| } |
| return dxppCode; |
| } |
| |
| /* Helper for cmpp_kav_each() and friends. */ |
| static |
| int cmpp__each_parse_args(cmpp_dx *dx, |
| cmpp_args *args, |
| unsigned char const *zBegin, |
| cmpp_ssize_t nz, |
| cmpp_flag32_t flags){ |
| if( 0==cmpp_args_parse(dx, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){ |
| if( !args->argc |
| && (cmpp_kav_each_F_NOT_EMPTY & flags) ){ |
| cmpp_err_set(dx->pp, CMPP_RC_RANGE, |
| "Empty list is not permitted here."); |
| } |
| } |
| return dxppCode; |
| } |
| |
| /* Helper for cmpp_kav_each() and friends. */ |
| static |
| int cmpp__each_paren_expr(cmpp_dx *dx, cmpp_arg const * arg, |
| unsigned char * pOut, size_t nOut){ |
| cmpp_args sub = cmpp_args_empty; |
| int rc = cmpp_args_parse(dx, &sub, arg->z, arg->n, 0); |
| if( 0==rc ){ |
| int d = 0; |
| rc = cmpp__args_evalToInt(dx, &sub, &d); |
| if( 0==rc ){ |
| snprintf((char *)pOut, nOut, "%d", d); |
| } |
| } |
| cmpp_args_cleanup(&sub); |
| return rc; |
| } |
| |
| CMPP__EXPORT(int, cmpp_kav_each)(cmpp_dx *dx, |
| unsigned char const *zBegin, |
| cmpp_ssize_t nIn, |
| cmpp_kav_each_f callback, |
| void *callbackState, |
| cmpp_flag32_t flags){ |
| if( dxppCode ) return dxppCode; |
| /* Reminder to self: we cannot reuse internal buffers here because a |
| callback could recurse into this or otherwise use APIs which use |
| those same buffers. */ |
| cmpp_b bKey = cmpp_b_empty; |
| cmpp_b bVal = cmpp_b_empty; |
| bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags); |
| cmpp_args args = cmpp_args_empty; |
| unsigned char exprBuf[32] = {0}; |
| cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn); |
| unsigned char const * const zEnd = zBegin + nz; |
| cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_to_b() flags*/; |
| assert( zBegin ); |
| assert( zEnd ); |
| assert( zEnd>=zBegin ); |
| |
| if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){ |
| goto cleanup; |
| }else if( reqArrow && 0!=args.argc%3 ){ |
| cmpp_err_set(dx->pp, CMPP_RC_RANGE, |
| "Expecting a list of 3 tokens per entry: " |
| "KEY -> VALUE"); |
| }else if( !reqArrow && 0!=args.argc%2 ){ |
| cmpp_err_set(dx->pp, CMPP_RC_RANGE, |
| "Expecting a list of 2 tokens per entry: " |
| "KEY VALUE"); |
| } |
| if( cmpp_kav_each_F_CALL_KEY & flags ){ |
| a2bK |= cmpp_arg_to_b_F_BRACE_CALL; |
| flags |= cmpp_kav_each_F_EXPAND_KEY; |
| } |
| if( cmpp_kav_each_F_CALL_VAL & flags ){ |
| a2bV |= cmpp_arg_to_b_F_BRACE_CALL; |
| flags |= cmpp_kav_each_F_EXPAND_VAL; |
| } |
| cmpp_arg const * aNext = 0; |
| for( cmpp_arg const * aKey = args.arg0; |
| !dxppCode && aKey; |
| aKey = aNext ){ |
| aNext = aKey->next; |
| cmpp_arg const * aVal = aKey->next; |
| if( !aVal ){ |
| dxserr("Expecting %s after key '%s'.", |
| (reqArrow ? "->" : "a value"), |
| aKey->z); |
| break; |
| } |
| if( reqArrow ){ |
| if( cmpp_TT_ArrowR!=aVal->ttype ){ |
| dxserr("Expecting -> after key '%s'.", aKey->z); |
| break; |
| } |
| aVal = aVal->next; |
| if( !aVal ){ |
| dxserr("Expecting a value after '%s' ->.", aKey->z); |
| break; |
| } |
| } |
| //g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z); |
| /* Expand the key/value parts if needed... */ |
| unsigned char const *zKey; |
| unsigned char const *zVal; |
| cmpp_size_t nKey, nVal; |
| if( cmpp_kav_each_F_EXPAND_KEY & flags ){ |
| if( cmpp_arg_to_b(dx, aKey, cmpp_b_reuse(&bKey), |
| a2bK) ){ |
| break; |
| } |
| zKey = bKey.z; |
| nKey = bKey.n; |
| }else{ |
| zKey = aKey->z; |
| nKey = aKey->n; |
| } |
| if( cmpp_TT_GroupParen==aVal->ttype |
| && (cmpp_kav_each_F_PARENS_EXPR & flags) ){ |
| if( cmpp__each_paren_expr(dx, aVal, &exprBuf[0], |
| sizeof(exprBuf)-1) ){ |
| break; |
| } |
| zVal = &exprBuf[0]; |
| nVal = cmpp__strlenu(zVal, -1); |
| }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){ |
| if( cmpp_arg_to_b(dx, aVal, cmpp_b_reuse(&bVal), |
| a2bV) ){ |
| break; |
| } |
| zVal = bVal.z; |
| nVal = bVal.n; |
| }else{ |
| zVal = aVal->z; |
| nVal = aVal->n; |
| } |
| aNext = aVal->next; |
| if( 0!=callback(dx, zKey, nKey, zVal, nVal, callbackState) ){ |
| break; |
| } |
| } |
| cleanup: |
| cmpp_b_clear(&bKey); |
| cmpp_b_clear(&bVal); |
| cmpp_args_cleanup(&args); |
| return dxppCode; |
| } |
| |
| CMPP__EXPORT(int, cmpp_str_each)(cmpp_dx *dx, |
| unsigned char const *zBegin, |
| cmpp_ssize_t nIn, |
| cmpp_kav_each_f callback, void *callbackState, |
| cmpp_flag32_t flags){ |
| g_warn0("UNTESTED!"); |
| if( dxppCode ) return dxppCode; |
| /* Reminder to self: we cannot reuse internal buffers here because a |
| callback could recurse into this or otherwise use APIs which use |
| those same buffers. */ |
| cmpp_b ob = cmpp_b_empty; |
| cmpp_args args = cmpp_args_empty; |
| unsigned char exprBuf[32] = {0}; |
| cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn); |
| unsigned char const * const zEnd = zBegin + nz; |
| assert( zBegin ); |
| assert( zEnd ); |
| assert( zEnd>=zBegin ); |
| |
| if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){ |
| goto cleanup; |
| } |
| cmpp_arg const * aNext = 0; |
| for( cmpp_arg const * arg = args.arg0; |
| !dxppCode && arg; |
| arg = aNext ){ |
| aNext = arg->next; |
| //g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z); |
| /* Expand the key/value parts if needed... */ |
| unsigned char const *zVal; |
| cmpp_size_t nVal; |
| if( cmpp_TT_GroupParen==arg->ttype |
| && (cmpp_kav_each_F_PARENS_EXPR & flags) ){ |
| if( cmpp__each_paren_expr(dx, arg, &exprBuf[0], |
| sizeof(exprBuf)-1) ){ |
| break; |
| } |
| zVal = &exprBuf[0]; |
| nVal = cmpp__strlenu(zVal, -1); |
| }else if( cmpp_kav_each_F_EXPAND_VAL & flags ){ |
| if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&ob), 0) ){ |
| break; |
| } |
| zVal = ob.z; |
| nVal = ob.n; |
| }else{ |
| zVal = arg->z; |
| nVal = arg->n; |
| } |
| if( 0!=callback(dx, arg->z, arg->n, zVal, nVal, callbackState) ){ |
| break; |
| } |
| } |
| cleanup: |
| cmpp_b_clear(&ob); |
| cmpp_args_cleanup(&args); |
| return dxppCode; |
| } |
| |
| /** |
| Returns true if z _might_ be a cmpp_TT_StringAt, else false. It may have |
| false positives but won't have false negatives. |
| |
| This is only intended to be used on NUL-terminated strings, not a |
| pointer into a cmpp input source. |
| */ |
| static bool cmpp__might_be_atstring(unsigned char const *z){ |
| char const * const x = strchr((char const *)z, '@'); |
| return x && !!strchr(x+1, '@'); |
| } |
| |
| int cmpp__arg_expand_ats(cmpp_dx const * const dx, |
| cmpp_b * os, |
| cmpp_atpol_e atPolicy, |
| cmpp_arg const * const arg, |
| cmpp_tt thisTtype, |
| unsigned char const **pExp, |
| cmpp_size_t * nExp){ |
| assert( os ); |
| cmpp_b_reuse(os); |
| if( 0==dxppCode |
| && (cmpp_TT_AnyType==thisTtype || thisTtype==arg->ttype) |
| && cmpp__might_be_atstring(arg->z) |
| && 0==cmpp__StringAtIsOk(dx->pp, atPolicy) ){ |
| #if 0 |
| if( !os->nAlloc ){ |
| cmpp_b_reserve3(os, 128); |
| } |
| #endif |
| cmpp_outputer oos = cmpp_outputer_b; |
| oos.state = os; |
| assert( !os->n ); |
| if( !cmpp_dx_out_expand(dx, &oos, arg->z, arg->n, |
| atPolicy ) ){ |
| *pExp = os->z; |
| if( nExp ) *nExp = os->n; |
| if( 0 ){ |
| g_warn("os->n=%u os->z=[%.*s]\n", os->n, (int)os->n, |
| os->z); |
| } |
| |
| } |
| }else if( !dxppCode ){ |
| *pExp = arg->z; |
| if( nExp ) *nExp = arg->n; |
| } |
| return dxppCode; |
| } |
| |
| bool cmpp__arg_wordIsPathOrFlag( |
| cmpp_arg const * const arg |
| ){ |
| return cmpp_TT_Word==arg->ttype |
| && ('-'==(char)arg->z[0] |
| || strchr((char*)arg->z, '.') |
| || strchr((char*)arg->z, '-') |
| || strchr((char*)arg->z, '/') |
| || strchr((char*)arg->z, '\\')); |
| } |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the cmpp_popen() pieces. |
| */ |
| #if !defined(_POSIX_C_SOURCE) |
| # define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */ |
| #endif |
| #include <signal.h> |
| |
| const cmpp_popen_t cmpp_popen_t_empty = cmpp_popen_t_empty_m; |
| |
| #if CMPP_PLATFORM_IS_UNIX |
| #include <signal.h> |
| static int cmpp__err_errno(cmpp *pp, int errNo, char const *zContext){ |
| return cmpp_err_set(pp, cmpp_errno_rc(errNo, CMPP_RC_ERROR), |
| "errno #%d: %s", errNo, zContext); |
| } |
| #endif |
| |
| /** |
| Uses fork()/exec() to run a command in a separate process and open |
| a two-way stream to it. |
| |
| If azCmd is NULL then zCmd must contain the command to run and |
| any flags. It is passed as the 4th argument to |
| execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL). |
| |
| If azCmd is not NULL then it must be suitable for use as the 2nd |
| argument to execv(2). execv(X, azCmd) is used in this case, where |
| X is (zCmd ? zCmd : azCmd[0]). |
| |
| Flags: |
| |
| - cmpp_popen_F_DIRECT: if azCmd is NULL and flags has this bit set then |
| zCmd is instead passed to execl(zCmd, zCmd, NULL). That can only |
| work if zCmd is a single command without arguments. |
| cmpp_popen_F_DIRECT has no effect if azCmd is not NULL. |
| |
| - cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which |
| performs path lookup of its initial argument. |
| |
| On success: |
| |
| - po->fdFromChild is the child's stdout. Read from it to read from |
| the child. |
| |
| - If po->fpToChild is not NULL then *po->fpToChild is set to the |
| child's stdin. Write to it to send the child stuff. Be sure to |
| flush() and/or close() it to keep it from hanging forever. If |
| po->fpToChild is NULL then the stdin of the child is closed. |
| |
| - po->childPid will be set to the PID of the child process. |
| |
| On error: you know the drill. |
| |
| After calling this, the caller is obligated to pass po to |
| cmpp_pclose(). If the caller fcloses() *po->fpToChild then they |
| must set it to NULL so that passing it to cmpp_pclose() knows not |
| to close it. |
| |
| Bugs: because the command is run via /bin/sh -c ... we cannot tell |
| if it's actually found. All we can tell is that /bin/sh ran. |
| |
| Also: this doesn't capture stderr, so commands should redirect |
| stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is |
| a potential TODO without a current use case. |
| */ |
| static |
| int cmpp__popen_impl(cmpp *pp, unsigned char const *zCmd, |
| char * const * azCmd, cmpp_flag32_t flags, |
| cmpp_popen_t *po){ |
| #if !CMPP_PLATFORM_IS_UNIX |
| return cmpp__err(pp, CMPP_RC_UNSUPPORTED, |
| "Piping is not supported in this build."); |
| #else |
| if( ppCode ) return ppCode; |
| #define shut(P,N) close(P[N]) |
| /** Attribution: this impl is derived from one found in |
| the Fossil SCM. */ |
| int pin[2]; |
| int pout[2]; |
| |
| po->fdFromChild = -1; |
| if( po->fpToChild ) *po->fpToChild = 0; |
| if( pipe(pin)<0 ){ |
| return cmpp__err_errno(pp, errno, "pipe(in) failed"); |
| } |
| if( pipe(pout)<0 ){ |
| int const rc = cmpp__err_errno(pp, errno, |
| "pipe(out) failed"); |
| shut(pin,0); |
| shut(pin,1); |
| return rc; |
| } |
| po->childPid = fork(); |
| if( po->childPid<0 ){ |
| int const rc = cmpp__err_errno(pp, errno, "fork() failed"); |
| shut(pin,0); |
| shut(pin,1); |
| shut(pout,0); |
| shut(pout,1); |
| return rc; |
| } |
| signal(SIGPIPE,SIG_IGN); |
| if( po->childPid==0 ){ |
| /* The child process. */ |
| int fd; |
| close(0); |
| fd = dup(pout[0]); |
| if( fd!=0 ) { |
| cmpp__fatal("Error opening file descriptor 0."); |
| }; |
| shut(pout,0); |
| shut(pout,1); |
| close(1); |
| fd = dup(pin[1]); |
| if(fd!=1) { |
| cmpp__fatal("Error opening file descriptor 1."); |
| }; |
| shut(pin,0); |
| shut(pin,1); |
| if( azCmd ){ |
| if( pp->pimpl->flags.doDebug>1 ){ |
| for( int i = 0; azCmd[i]; ++i ){ |
| g_warn("execv arg[%d]=%s", i, azCmd[i]); |
| } |
| } |
| int (*exc)(const char *, char *const []) = |
| (cmpp_popen_F_PATH & flags) ? execvp : execv; |
| exc(zCmd ? (char*)zCmd : azCmd[0], azCmd); |
| cmpp__fatal("execv() failed"); |
| }else{ |
| g_debug(pp,2,("zCmd=%s\n", zCmd)); |
| int (*exc)(const char *, char const *, ...) = |
| (cmpp_popen_F_PATH & flags) ? execlp : execl; |
| if( cmpp_popen_F_DIRECT & flags ){ |
| exc((char*)zCmd, (char*)zCmd, (char*)0); |
| }else{ |
| exc("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0); |
| } |
| cmpp__fatal("execl() failed"); |
| } |
| /* not reached */ |
| }else{ |
| /* The parent process. */ |
| //cmpp_outputer_flush(&pp->pimpl->out.ch); |
| po->fdFromChild = pin[0]; |
| shut(pin,1); |
| shut(pout,0); |
| if( po->fpToChild ){ |
| *po->fpToChild = fdopen(pout[1], "w"); |
| if( !*po->fpToChild ){ |
| shut(pin,0); |
| shut(pout,1); |
| po->fdFromChild = -1; |
| cmpp__err_errno(pp, errno, |
| "Error opening child process's stdin " |
| "FILE handle from its descriptor."); |
| } |
| }else{ |
| shut(pout,1); |
| } |
| return ppCode; |
| } |
| #undef shut |
| #endif |
| } |
| |
| int cmpp_popen(cmpp *pp, unsigned char const *zCmd, |
| cmpp_flag32_t flags, cmpp_popen_t *po){ |
| return cmpp__popen_impl(pp, zCmd, NULL, flags, po); |
| } |
| |
| int cmpp_popenv(cmpp *pp, char * const * azCmd, |
| cmpp_flag32_t flags, cmpp_popen_t *po){ |
| return cmpp__popen_impl(pp, NULL, azCmd, flags, po); |
| } |
| |
| int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args, |
| cmpp_popen_t *po){ |
| #if !CMPP_PLATFORM_IS_UNIX |
| return cmpp__popen_impl(dx->pp, NULL, 0, po) /* will fail */; |
| #else |
| if( dxppCode ) return dxppCode; |
| enum { MaxArgs = 128 }; |
| char * argv[MaxArgs] = {0}; |
| cmpp_size_t offsets[MaxArgs] = {0}; |
| cmpp_b osAll = cmpp_b_empty; |
| cmpp_b os1 = cmpp_b_empty; |
| if( args->argc >= MaxArgs ){ |
| return cmpp_dx_err_set(dx, CMPP_RC_RANGE, |
| "Too many arguments (%d). Max is %d.", |
| args->argc, (int)MaxArgs); |
| } |
| int i = 0; |
| for(cmpp_arg const * a = args->arg0; |
| a; ++i, a = a->next ){ |
| offsets[i] = osAll.n; |
| cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL; |
| if( cmpp__arg_wordIsPathOrFlag(a) ){ |
| a2bFlags |= cmpp_arg_to_b_F_FORCE_STRING; |
| } |
| if( cmpp_arg_to_b(dx, a, cmpp_b_reuse(&os1), a2bFlags) |
| || cmpp_b_append4(dx->pp, &osAll, os1.z, os1.n+1/*NUL*/) ){ |
| goto end; |
| } |
| assert( osAll.n > offsets[i] ); |
| if( 0 ){ |
| g_warn("execv arg[%d] = %s => %s", i, a->z, |
| osAll.z+offsets[i]); |
| } |
| } |
| argv[i] = 0; |
| for( --i; i >= 0; --i ){ |
| argv[i] = (char*)(osAll.z + offsets[i]); |
| if( 0 ){ |
| g_warn("execv arg[%d] = %s", i, argv[i]); |
| } |
| } |
| end: |
| if( 0==dxppCode ){ |
| cmpp__popen_impl(dx->pp, NULL, argv, 0, po); |
| } |
| cmpp_b_clear(&osAll); |
| cmpp_b_clear(&os1); |
| return dxppCode; |
| #endif |
| } |
| |
| int cmpp_pclose(cmpp_popen_t *po){ |
| #if CMPP_PLATFORM_IS_UNIX |
| if( po->fdFromChild>=0 ) close(po->fdFromChild); |
| if( po->fpToChild && *po->fpToChild ) fclose(*po->fpToChild); |
| int const childPid = po->childPid; |
| *po = cmpp_popen_t_empty; |
| #if 1 |
| int wp, rc = 0; |
| if( childPid>0 ){ |
| //kill(childPid, SIGINT); // really needed? |
| do{ |
| wp = waitpid(childPid, &rc, WNOHANG); |
| if( wp>0 ){ |
| if( WIFEXITED(rc) ){ |
| rc = WEXITSTATUS(rc); |
| }else if( WIFSIGNALED(rc) ){ |
| rc = WTERMSIG(rc); |
| }else{ |
| rc = 0/*???*/; |
| } |
| } |
| } while( wp>0 ); |
| } |
| return rc; |
| #elif 0 |
| while( waitpid(childPid, NULL, WNOHANG)>0 ){} |
| #else |
| if( childPid>0 ){ |
| kill(childPid, SIGINT); // really needed? |
| waitpid((pid_t)childPid, NULL, WNOHANG); |
| }else{ |
| while( waitpid( (pid_t)0, NULL, WNOHANG)>0 ){} |
| } |
| #endif |
| #endif |
| } |
| /* |
| ** 2022-11-12: |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** * May you do good and not evil. |
| ** * May you find forgiveness for yourself and forgive others. |
| ** * May you share freely, never taking more than you give. |
| ** |
| ************************************************************************ |
| ** This file houses the module-loading pieces libcmpp. |
| */ |
| |
| #if CMPP_ENABLE_DLLS |
| static const CmppSohList CmppSohList_empty = |
| CmppSohList_empty_m; |
| #endif |
| |
| #if CMPP_ENABLE_DLLS |
| /** |
| If compiled without CMPP_ENABLE_DLLS defined to a true value |
| then this function always returns CMPP_RC_UNSUPPORTED and updates |
| the error state of its first argument with information about that |
| code. |
| |
| Its first argument is the controlling cmpp. It can actually be |
| NULL - it's only used for reporting error details. |
| |
| Its second argument is the name of a DLL file. |
| |
| Its third argument is the name of a symbol in the given DLL which |
| resolves to a cmpp_module pointer. This name may be NULL, |
| in which case a default symbol name of "cmpp_module1" is used |
| (which is only useful when plugins are built one per DLL). |
| |
| The fourth argument is the output pointer to store the |
| resulting module handle in. |
| |
| The fifth argument is an optional list to append the DLL's |
| native handle to. It may be NULL. |
| |
| This function tries to open a DLL named fname using the system's |
| DLL loader. If none is found, CMPP_RC_NOT_FOUND is returned and the |
| cmpp's error state is populated with info about the error. If |
| one is found, it looks for a symbol in the DLL: if symName is not |
| NULL and is not empty then the symbol "cmpp_module_symName" is |
| sought, else "cmpp_module". (e.g. if symName is "foo" then it |
| searches for a symbol names "cmpp_module_foo".) If no such symbol is |
| found then CMPP_RC_NOT_FOUND (again) is returned and the |
| cmpp's error state is populated, else the symbol is assumed to |
| be a (cmpp_module*) and *mod is assigned to it. |
| |
| All errors update pp's error state but all are recoverable. |
| |
| Returns 0 on success. |
| |
| On success: |
| |
| - `*mod` is set to the module object. Its ownship is kinda murky: it |
| lives in memory made available via the module loader. It remains |
| valid memory until the DLL is closed. The module might also |
| actually be statically linked with the application, in which case |
| it will live as long as the app. |
| |
| - If soli is not NULL then the native DLL handle is appended to it. |
| Allocation errors when appending the DLL handle to the target list |
| are ignored - failure to retain a DLL handle for closing later is |
| not considered critical (and it would be extraordinarily rare (and |
| closing them outside of late-/post-main() cleanup is ill-advised, |
| anyway)). |
| |
| @see cmpp_module_load() |
| @see CMPP_MODULE_DECL |
| @see CMPP_MODULE_IMPL2 |
| @see CMPP_MODULE_IMPL3 |
| @see CMPP_MODULE_IMPL_SOLO |
| @see CMPP_MODULE_REGISTER2 |
| @see CMPP_MODULE_REGISTER3 |
| */ |
| static |
| int cmpp__module_extract(cmpp * pp, |
| char const * dllFileName, |
| char const * symName, |
| cmpp_module const ** mod); |
| #endif |
| |
| #if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE) |
| # define CMPP_D_MODULE 1 |
| #else |
| # define CMPP_D_MODULE 0 |
| #endif |
| |
| #if CMPP_D_MODULE |
| /** |
| The #module directive: |
| |
| #module dll ?moduleName? |
| |
| Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a |
| directive module. |
| */ |
| //static |
| void cmpp_dx_f_module(cmpp_dx *dx) { |
| cmpp_arg const * aName = 0; |
| cmpp_b obDll = cmpp_b_empty; |
| for( cmpp_arg const *arg = dx->args.arg0; |
| arg; arg = arg->next ){ |
| //MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z)); |
| if( cmpp_dx_err_check(dx) ) goto end; |
| else if( !obDll.z ){ |
| cmpp_arg_to_b( |
| dx, arg, &obDll, |
| 0//cmpp_arg_to_b_F_NO_DEFINES |
| ); |
| continue; |
| }else if( !aName ){ |
| aName = arg; |
| continue; |
| } |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Unhandled argument: %s", arg->z); |
| goto end; |
| } |
| if( !obDll.z ){ |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, |
| "Expecting a DLL name argument."); |
| goto end; |
| } |
| cmpp_module_load(dx->pp, (char const *)obDll.z, |
| aName ? (char const *)aName->z : NULL); |
| end: |
| cmpp_b_clear(&obDll); |
| return; |
| #if 0 |
| missing_arg: |
| cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument after %s.", |
| arg->z); |
| return; |
| #endif |
| } |
| #endif /* #module */ |
| |
| /** |
| Module loader pedantic licensing note: Most of cmpp's |
| module-loading code was copied verbatim from another project[^1], |
| but was written by the same author who relicenses it in |
| cmpp. |
| |
| [^1]: https://fossil.wanderinghorse.net/r/cwal |
| */ |
| #if CMPP_ENABLE_DLLS |
| #if CMPP_HAVE_DLOPEN |
| typedef void * cmpp_soh; |
| # include <dlfcn.h> /* this actually has a different name on some platforms! */ |
| #elif CMPP_HAVE_LTDLOPEN |
| # include <ltdl.h> |
| typedef lt_dlhandle cmpp_soh; |
| #elif CMPP_ENABLE_DLLS |
| # error "We have no dlopen() impl for this configuration." |
| #endif |
| |
| static cmpp_soh cmpp__dlopen(char const * fname, |
| char const **errMsg){ |
| static int once = 0; |
| cmpp_soh soh = 0; |
| if(!once && ++once){ |
| #if CMPP_HAVE_DLOPEN |
| dlopen( 0, RTLD_NOW | RTLD_GLOBAL ); |
| #elif CMPP_HAVE_LTDLOPEN |
| lt_dlinit(); |
| lt_dlopen( 0 ); |
| #endif |
| } |
| #if CMPP_HAVE_DLOPEN |
| soh = dlopen(fname, RTLD_NOW | RTLD_GLOBAL); |
| #elif CMPP_HAVE_LTDLOPEN |
| soh = lt_dlopen(fname); |
| #endif |
| if(!soh && errMsg){ |
| #if CMPP_HAVE_DLOPEN |
| *errMsg = dlerror(); |
| #elif CMPP_HAVE_LTDLOPEN |
| *errMsg = lt_dlerror(); |
| #endif |
| } |
| return soh; |
| } |
| |
| static |
| cmpp_module const * cmpp__dlsym(cmpp_soh soh, |
| char const * mname){ |
| cmpp_module const ** sym = |
| #if CMPP_HAVE_DLOPEN |
| dlsym(soh, mname) |
| #elif CMPP_HAVE_LTDLOPEN |
| lt_dlsym(soh, mname) |
| #else |
| NULL |
| #endif |
| ; |
| return sym ? *sym : NULL; |
| } |
| |
| static void cmpp__dlclose(cmpp_soh soh){ |
| if( soh ) { |
| #if CMPP_CLOSE_DLLS |
| /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */ |
| #if CMPP_HAVE_DLOPEN |
| dlclose(soh); |
| #elif CMPP_HAVE_LTDLOPEN |
| lt_dlclose(soh); |
| #endif |
| #endif |
| } |
| } |
| #endif /* CMPP_ENABLE_DLLS */ |
| |
| #define CmppSohList_works (CMPP_ENABLE_DLLS && CMPP_CLOSE_DLLS) |
| |
| int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){ |
| #if CmppSohList_works |
| int const rc = cmpp_array_reserve(pp, (void**)&soli->list, |
| soli->n |
| ? (soli->n==soli->nAlloc |
| ? soli->nAlloc*2 |
| : soli->n+1) |
| : 8, |
| &soli->nAlloc, sizeof(void*)); |
| if( 0==rc ){ |
| soli->list[soli->n++] = soh; |
| } |
| return rc; |
| #else |
| (void)pp; (void)soli; (void)soh; |
| return 0; |
| #endif |
| } |
| |
| void CmppSohList_close(CmppSohList *s){ |
| #if CmppSohList_works |
| while( s->nAlloc ){ |
| if( s->list[--s->nAlloc] ){ |
| //MARKER(("closing soh %p\n", s->list[s->nAlloc])); |
| cmpp__dlclose(s->list[s->nAlloc]); |
| s->list[s->nAlloc] = 0; |
| } |
| } |
| cmpp_mfree(s->list); |
| *s = CmppSohList_empty; |
| #else |
| (void)s; |
| #endif |
| } |
| |
| #if 0 |
| /** |
| Passes soli to CmppSohList_close() then frees soli. Results are |
| undefined if soli is not NULL but was not returned from |
| CmppSohList_new(). |
| |
| Special case: if built without DLL-closing support, this is a no-op. |
| */ |
| //static void CmppSohList_free(CmppSohList *soli); |
| void CmppSohList_free(CmppSohList *s){ |
| if( s ){ |
| #if CmppSohList_works |
| CmppSohList_close(s); |
| cmpp_mfree(s); |
| #endif |
| } |
| } |
| |
| /** |
| Returns a new, cleanly-initialized CmppSohList or NULL |
| on allocation error. The returned instance must eventually be |
| passed to CmppSohList_free(). |
| |
| Special case: if built without DLL-closing support, this returns a |
| no-op singleton instance. |
| */ |
| //static CmppSohList * CmppSohList_new(void); |
| CmppSohList * CmppSohList_new(void){ |
| #if CmppSohList_works |
| CmppSohList * s = cmpp_malloc(sizeof(*s)); |
| if( s ) *s = CmppSohList_empty; |
| return s; |
| #else |
| static CmppSohList soli = CmppSohList_empty; |
| return &soli; |
| #endif |
| } |
| #endif |
| |
| #undef CmppSohList_works |
| |
| #if CMPP_ENABLE_DLLS |
| /** |
| Default entry point symbol name for loadable modules. This must |
| match the symbolic name defined by CMPP_MODULE_IMPL_SOLO(). |
| */ |
| static char const * const cmppModDfltSym = "cmpp_module1"; |
| |
| /** |
| Looks for a symbol in the given DLL handle. If symName is NULL or |
| empty, the symbol "cmpp_module" is used, else the symbols |
| ("cmpp_module__" + symName) is used. If it finds one, it casts it to |
| cmpp_module and returns it. On error it may update pp's |
| error state with the error information if pp is not NULL. |
| |
| Errors: |
| |
| - symName is too long. |
| |
| - cmpp__dlsym() lookup failure. |
| */ |
| static cmpp_module const * |
| cmpp__module_fish_out_entry_pt(cmpp * pp, |
| cmpp_soh soh, |
| char const * symName){ |
| enum { MaxLen = 128 }; |
| char buf[MaxLen] = {0}; |
| cmpp_size_t const slen = symName ? strlen(symName) : 0; |
| cmpp_module const * mod = 0; |
| if(slen > (MaxLen-20)){ |
| cmpp_err_set(pp, CMPP_RC_RANGE, |
| "DLL symbol name '%.*s' is too long. Max is %d.", |
| (int)slen, symName, (int)MaxLen-20); |
| }else{ |
| if(symName && *symName){ |
| snprintf(buf, MaxLen,"cmpp_module__%s", symName); |
| symName = &buf[0]; |
| }else{ |
| symName = cmppModDfltSym; |
| } |
| mod = cmpp__dlsym(soh, symName); |
| } |
| /*MARKER(("%s() [%s] ==> %p\n",__func__, symName, |
| (void const *)mod));*/ |
| return mod; |
| } |
| #endif/*CMPP_ENABLE_DLLS*/ |
| |
| #if CMPP_ENABLE_DLLS |
| /** |
| Tries to dlsym() the given cmpp_module symbol from the given |
| DLL handle. On success, 0 is returned and *mod is assigned to the |
| memory. On error, non-0 is returned and pp's error state may be |
| updated. |
| |
| Ownership of the returned module ostensibly lies with the first |
| argument, but that's not entirely true. If CMPP_CLOSE_DLLS is true |
| then a copy of the module's pointer is stored in the engine for |
| later closing. The memory itself is owned by the module loader, and |
| "should" stay valid until the DLL is closed. |
| */ |
| static int cmpp__module_get_sym(cmpp * pp, |
| cmpp_soh soh, |
| char const * symName, |
| cmpp_module const ** mod){ |
| |
| cmpp_module const * lm = 0; |
| int rc = cmpp_err_has(pp); |
| if( 0==rc ){ |
| lm = cmpp__module_fish_out_entry_pt(pp, soh, symName); |
| rc = cmpp_err_has(pp); |
| } |
| if(0==rc){ |
| if(lm){ |
| *mod = lm; |
| }else{ |
| cmpp__dlclose(soh); |
| rc = cmpp_err_set(pp, CMPP_RC_NOT_FOUND, |
| "Did not find module entry point symbol '%s'.", |
| symName ? symName : cmppModDfltSym); |
| } |
| } |
| return rc; |
| } |
| #endif/*CMPP_ENABLE_DLLS*/ |
| |
| #if !CMPP_ENABLE_DLLS |
| static int cmpp__err_no_dlls(cmpp * const pp){ |
| return cmpp_err_set(pp, CMPP_RC_UNSUPPORTED, |
| "No dlopen() equivalent is installed " |
| "for this build configuration."); |
| } |
| #endif |
| |
| #if CMPP_ENABLE_DLLS |
| //no: CMPP_WASM_EXPORT |
| int cmpp__module_extract(cmpp * pp, |
| char const * fname, |
| char const * symName, |
| cmpp_module const ** mod){ |
| int rc = cmpp_err_has(pp); |
| if( rc ) return rc; |
| else if( cmpp_is_safemode(pp) ){ |
| return cmpp_err_set(pp, CMPP_RC_ACCESS, |
| "Cannot use DLLs in safe mode."); |
| }else{ |
| cmpp_soh soh; |
| char const * errMsg = 0; |
| soh = cmpp__dlopen(fname, &errMsg); |
| if(soh){ |
| if( pp ){ |
| CmppSohList_append(NULL/*alloc error here can be ignored*/, |
| &pp->pimpl->mod.sohList, soh); |
| } |
| cmpp_module const * x = 0; |
| rc = cmpp__module_get_sym(pp, soh, symName, &x); |
| if(!rc && mod) *mod = x; |
| return rc; |
| }else{ |
| return errMsg |
| ? cmpp_err_set(pp, CMPP_RC_ERROR, "DLL open failed: %s", |
| errMsg) |
| : cmpp_err_set(pp, CMPP_RC_ERROR, |
| "DLL open failed for unknown reason."); |
| } |
| } |
| } |
| #endif |
| |
| //no: CMPP_WASM_EXPORT |
| int cmpp_module_load(cmpp * pp, char const * fname, |
| char const * symName){ |
| #if CMPP_ENABLE_DLLS |
| if( ppCode ){ |
| /* fall through */ |
| }else if( cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags ){ |
| cmpp_err_set(pp, CMPP_RC_ACCESS, |
| "%s() is disallowed in safe-mode."); |
| }else{ |
| cmpp__pi(pp); |
| char * zName = 0; |
| if( fname ){ |
| zName = cmpp_path_search(pp, (char const *)pi->mod.path.z, |
| pi->mod.pathSep, fname, |
| pi->mod.soExt); |
| if( !zName ){ |
| return cmpp_err_set(pp, CMPP_RC_NOT_FOUND, |
| "Did not find [%s] or [%s%s] " |
| "in search path [%s].", |
| fname, fname, pi->mod.soExt, |
| pi->mod.path.z); |
| } |
| } |
| cmpp_module const * mod = 0; |
| if( 0==cmpp__module_extract(pp, zName, symName, &mod) ){ |
| assert(mod); |
| assert(mod->init); |
| int const rc = mod->init(pp); |
| if( rc && !ppCode ){ |
| cmpp_err_set(pp, CMPP_RC_ERROR, |
| "Module %s::init() failed with code #%d/%s " |
| "without providing additional info.", |
| symName ? symName : "cmpp_module", |
| rc, cmpp_rc_cstr(rc)); |
| } |
| cmpp_mfree(zName); |
| } |
| } |
| return ppCode; |
| #else |
| (void)fname; (void)symName; |
| return cmpp__err_no_dlls(pp); |
| #endif |
| } |
| /* |
| ** 2015-08-18, 2023-04-28 |
| ** |
| ** The author disclaims copyright to this source code. In place of |
| ** a legal notice, here is a blessing: |
| ** |
| ** May you do good and not evil. |
| ** May you find forgiveness for yourself and forgive others. |
| ** May you share freely, never taking more than you give. |
| ** |
| ************************************************************************* |
| ** |
| ** This file demonstrates how to create a table-valued-function using |
| ** a virtual table. This demo implements the generate_series() function |
| ** which gives the same results as the eponymous function in PostgreSQL, |
| ** within the limitation that its arguments are signed 64-bit integers. |
| ** |
| ** Considering its equivalents to generate_series(start,stop,step): A |
| ** value V[n] sequence is produced for integer n ascending from 0 where |
| ** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 ) |
| ** for each produced value (independent of production time ordering.) |
| ** |
| ** All parameters must be either integer or convertable to integer. |
| ** The start parameter is required. |
| ** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) |
| ** The step parameter defaults to 1 and 0 is treated as 1. |
| ** |
| ** Examples: |
| ** |
| ** SELECT * FROM generate_series(0,100,5); |
| ** |
| ** The query above returns integers from 0 through 100 counting by steps |
| ** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total |
| ** of 21 rows. |
| ** |
| ** SELECT * FROM generate_series(0,100); |
| ** |
| ** Integers from 0 through 100 with a step size of 1. 101 rows. |
| ** |
| ** SELECT * FROM generate_series(20) LIMIT 10; |
| ** |
| ** Integers 20 through 29. 10 rows. |
| ** |
| ** SELECT * FROM generate_series(0,-100,-5); |
| ** |
| ** Integers 0 -5 -10 ... -100. 21 rows. |
| ** |
| ** SELECT * FROM generate_series(0,-1); |
| ** |
| ** Empty sequence. |
| ** |
| ** HOW IT WORKS |
| ** |
| ** The generate_series "function" is really a virtual table with the |
| ** following schema: |
| ** |
| ** CREATE TABLE generate_series( |
| ** value, |
| ** start HIDDEN, |
| ** stop HIDDEN, |
| ** step HIDDEN |
| ** ); |
| ** |
| ** The virtual table also has a rowid which is an alias for the value. |
| ** |
| ** Function arguments in queries against this virtual table are translated |
| ** into equality constraints against successive hidden columns. In other |
| ** words, the following pairs of queries are equivalent to each other: |
| ** |
| ** SELECT * FROM generate_series(0,100,5); |
| ** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; |
| ** |
| ** SELECT * FROM generate_series(0,100); |
| ** SELECT * FROM generate_series WHERE start=0 AND stop=100; |
| ** |
| ** SELECT * FROM generate_series(20) LIMIT 10; |
| ** SELECT * FROM generate_series WHERE start=20 LIMIT 10; |
| ** |
| ** The generate_series virtual table implementation leaves the xCreate method |
| ** set to NULL. This means that it is not possible to do a CREATE VIRTUAL |
| ** TABLE command with "generate_series" as the USING argument. Instead, there |
| ** is a single generate_series virtual table that is always available without |
| ** having to be created first. |
| ** |
| ** The xBestIndex method looks for equality constraints against the hidden |
| ** start, stop, and step columns, and if present, it uses those constraints |
| ** to bound the sequence of generated values. If the equality constraints |
| ** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step. |
| ** xBestIndex returns a small cost when both start and stop are available, |
| ** and a very large cost if either start or stop are unavailable. This |
| ** encourages the query planner to order joins such that the bounds of the |
| ** series are well-defined. |
| ** |
| ** Update on 2024-08-22: |
| ** xBestIndex now also looks for equality and inequality constraints against |
| ** the value column and uses those constraints as additional bounds against |
| ** the sequence range. Thus, a query like this: |
| ** |
| ** SELECT value FROM generate_series($SA,$EA) |
| ** WHERE value BETWEEN $SB AND $EB; |
| ** |
| ** Is logically the same as: |
| ** |
| ** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB)); |
| ** |
| ** Constraints on the value column can server as substitutes for constraints |
| ** on the hidden start and stop columns. So, the following two queries |
| ** are equivalent: |
| ** |
| ** SELECT value FROM generate_series($S,$E); |
| ** SELECT value FROM generate_series WHERE value BETWEEN $S and $E; |
| ** |
| */ |
| #if 0 |
| #include "sqlite3ext.h" |
| #else |
| #include "sqlite3.h" |
| #endif |
| #include <assert.h> |
| #include <string.h> |
| #include <limits.h> |
| #include <math.h> |
| |
| #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: value<VALUE |
| ** |
| ** This routine should initialize the cursor and position it so that it |
| ** is pointing at the first row, or pointing off the end of the table |
| ** (so that seriesEof() will return true) if the table is empty. |
| */ |
| static int seriesFilter( |
| sqlite3_vtab_cursor *pVtabCursor, |
| int idxNum, const char *idxStrUnused, |
| int argc, sqlite3_value **argv |
| ){ |
| series_cursor *pCur = (series_cursor *)pVtabCursor; |
| int iArg = 0; /* Arguments used so far */ |
| int i; /* Loop counter */ |
| sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */ |
| sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */ |
| sqlite3_int64 iLimit = 0; /* if >0, 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<argc; i++){ |
| if( sqlite3_value_type(argv[i])==SQLITE_NULL ){ |
| goto series_no_rows; |
| } |
| } |
| |
| /* Capture the three HIDDEN parameters to the virtual table and insert |
| ** default values for any parameters that are omitted. |
| */ |
| if( idxNum & 0x01 ){ |
| pCur->iOBase = sqlite3_value_int64(argv[iArg++]); |
| }else{ |
| pCur->iOBase = 0; |
| } |
| if( idxNum & 0x02 ){ |
| pCur->iOTerm = sqlite3_value_int64(argv[iArg++]); |
| }else{ |
| pCur->iOTerm = 0xffffffff; |
| } |
| if( idxNum & 0x04 ){ |
| pCur->iOStep = sqlite3_value_int64(argv[iArg++]); |
| if( pCur->iOStep==0 ) pCur->iOStep = 1; |
| }else{ |
| pCur->iOStep = 1; |
| } |
| |
| /* If there are constraints on the value column but there are |
| ** no constraints on the start, stop, and step columns, then |
| ** initialize the default range to be the entire range of 64-bit signed |
| ** integers. This range will contracted by the value column constraints |
| ** further below. |
| */ |
| if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){ |
| pCur->iOBase = SMALLEST_INT64; |
| } |
| if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){ |
| pCur->iOTerm = LARGEST_INT64; |
| } |
| pCur->iBase = pCur->iOBase; |
| pCur->iTerm = pCur->iOTerm; |
| if( pCur->iOStep>0 ){ |
| pCur->iStep = pCur->iOStep; |
| }else if( pCur->iOStep>SMALLEST_INT64 ){ |
| pCur->iStep = -pCur->iOStep; |
| }else{ |
| pCur->iStep = LARGEST_INT64; |
| pCur->iStep++; |
| } |
| pCur->bDesc = pCur->iOStep<0; |
| if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){ |
| goto series_no_rows; |
| } |
| if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){ |
| goto series_no_rows; |
| } |
| |
| /* Extract the LIMIT and OFFSET values, but do not apply them yet. |
| ** The range must first be constrained by the limits on value. |
| */ |
| if( idxNum & 0x20 ){ |
| iLimit = sqlite3_value_int64(argv[iArg++]); |
| if( idxNum & 0x40 ){ |
| iOffset = sqlite3_value_int64(argv[iArg++]); |
| } |
| } |
| |
| /* Narrow the range of iMin and iMax (the minimum and maximum outputs) |
| ** based on equality and inequality constraints on the "value" column. |
| */ |
| if( idxNum & 0x3380 ){ |
| if( idxNum & 0x0080 ){ /* value=X */ |
| if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| double r = sqlite3_value_double(argv[iArg++]); |
| if( r==seriesCeil(r) |
| && r>=(double)SMALLEST_INT64 |
| && r<=(double)LARGEST_INT64 |
| ){ |
| iMin = iMax = (sqlite3_int64)r; |
| }else{ |
| goto series_no_rows; |
| } |
| }else{ |
| iMin = iMax = sqlite3_value_int64(argv[iArg++]); |
| } |
| }else{ |
| if( idxNum & 0x0300 ){ /* value>X or value>=X */ |
| if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| double r = sqlite3_value_double(argv[iArg++]); |
| if( r<(double)SMALLEST_INT64 ){ |
| iMin = SMALLEST_INT64; |
| }else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){ |
| iMin = (sqlite3_int64)seriesCeil(r+1.0); |
| }else{ |
| iMin = (sqlite3_int64)seriesCeil(r); |
| } |
| }else{ |
| iMin = sqlite3_value_int64(argv[iArg++]); |
| if( (idxNum & 0x0200)!=0 ){ |
| if( iMin==LARGEST_INT64 ){ |
| goto series_no_rows; |
| }else{ |
| iMin++; |
| } |
| } |
| } |
| } |
| if( idxNum & 0x3000 ){ /* value<X or value<=X */ |
| if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ |
| double r = sqlite3_value_double(argv[iArg++]); |
| if( r>(double)LARGEST_INT64 ){ |
| iMax = LARGEST_INT64; |
| }else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){ |
| iMax = (sqlite3_int64)(r-1.0); |
| }else{ |
| iMax = (sqlite3_int64)seriesFloor(r); |
| } |
| }else{ |
| iMax = sqlite3_value_int64(argv[iArg++]); |
| if( idxNum & 0x2000 ){ |
| if( iMax==SMALLEST_INT64 ){ |
| goto series_no_rows; |
| }else{ |
| iMax--; |
| } |
| } |
| } |
| } |
| if( iMin>iMax ){ |
| goto series_no_rows; |
| } |
| } |
| |
| /* Try to reduce the range of values to be generated based on |
| ** constraints on the "value" column. |
| */ |
| if( pCur->bDesc==0 ){ |
| if( pCur->iBase<iMin ){ |
| sqlite3_uint64 span = span64(iMin,pCur->iBase); |
| pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); |
| if( pCur->iBase<iMin ){ |
| if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){ |
| goto series_no_rows; |
| } |
| pCur->iBase = add64(pCur->iBase, pCur->iStep); |
| } |
| } |
| if( pCur->iTerm>iMax ){ |
| pCur->iTerm = iMax; |
| } |
| }else{ |
| if( pCur->iBase>iMax ){ |
| sqlite3_uint64 span = span64(pCur->iBase,iMax); |
| pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); |
| if( pCur->iBase>iMax ){ |
| if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){ |
| goto series_no_rows; |
| } |
| pCur->iBase = sub64(pCur->iBase, pCur->iStep); |
| } |
| } |
| if( pCur->iTerm<iMin ){ |
| pCur->iTerm = iMin; |
| } |
| } |
| } |
| |
| /* Adjust iTerm so that it is exactly the last value of the series. |
| */ |
| if( pCur->bDesc==0 ){ |
| if( pCur->iBase>pCur->iTerm ){ |
| goto series_no_rows; |
| } |
| pCur->iTerm = sub64(pCur->iTerm, |
| span64(pCur->iTerm,pCur->iBase) % pCur->iStep); |
| }else{ |
| if( pCur->iBase<pCur->iTerm ){ |
| goto series_no_rows; |
| } |
| pCur->iTerm = add64(pCur->iTerm, |
| span64(pCur->iBase,pCur->iTerm) % pCur->iStep); |
| } |
| |
| /* Transform the series generator to output values in the requested |
| ** order. |
| */ |
| if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0) |
| || ((idxNum & 0x0010)!=0 && pCur->bDesc!=0) |
| ){ |
| sqlite3_int64 tmp = pCur->iBase; |
| pCur->iBase = pCur->iTerm; |
| pCur->iTerm = tmp; |
| pCur->bDesc = !pCur->bDesc; |
| } |
| |
| /* Apply LIMIT and OFFSET constraints, if any */ |
| assert( pCur->iStep!=0 ); |
| if( idxNum & 0x20 ){ |
| if( iOffset>0 ){ |
| if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){ |
| goto series_no_rows; |
| }else if( pCur->bDesc ){ |
| pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset); |
| }else{ |
| pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset); |
| } |
| } |
| if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){ |
| pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep); |
| } |
| } |
| pCur->iValue = pCur->iBase; |
| pCur->bDone = 0; |
| return SQLITE_OK; |
| |
| series_no_rows: |
| pCur->iBase = 0; |
| pCur->iTerm = 0; |
| pCur->iStep = 1; |
| pCur->bDesc = 0; |
| pCur->bDone = 1; |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** SQLite will invoke this method one or more times while planning a query |
| ** that uses the generate_series virtual table. This routine needs to create |
| ** a query plan for each invocation and compute an estimated cost for that |
| ** plan. |
| ** |
| ** In this implementation idxNum is used to represent the |
| ** query plan. idxStr is unused. |
| ** |
| ** The query plan is represented by bits in idxNum: |
| ** |
| ** 0x0001 start = $num |
| ** 0x0002 stop = $num |
| ** 0x0004 step = $num |
| ** 0x0008 output is in descending order |
| ** 0x0010 output is in ascending order |
| ** 0x0020 LIMIT $num |
| ** 0x0040 OFFSET $num |
| ** 0x0080 value = $num |
| ** 0x0100 value >= $num |
| ** 0x0200 value > $num |
| ** 0x1000 value <= $num |
| ** 0x2000 value < $num |
| ** |
| ** Only one of 0x0100 or 0x0200 will be returned. Similarly, only |
| ** one of 0x1000 or 0x2000 will be returned. If the 0x0080 is set, then |
| ** none of the 0xff00 bits will be set. |
| ** |
| ** The order of parameters passed to xFilter is as follows: |
| ** |
| ** * The argument to start= if bit 0x0001 is in the idxNum mask |
| ** * The argument to stop= if bit 0x0002 is in the idxNum mask |
| ** * The argument to step= if bit 0x0004 is in the idxNum mask |
| ** * The argument to LIMIT if bit 0x0020 is in the idxNum mask |
| ** * The argument to OFFSET if bit 0x0040 is in the idxNum mask |
| ** * The argument to value=, or value>= or value> if any of |
| ** bits 0x0380 are in the idxNum mask |
| ** * The argument to value<= or value< if either of bits 0x3000 |
| ** are in the mask |
| ** |
| */ |
| static int seriesBestIndex( |
| sqlite3_vtab *pVTab, |
| sqlite3_index_info *pIdxInfo |
| ){ |
| int i, j; /* Loop over constraints */ |
| int idxNum = 0; /* The query plan bitmask */ |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| int bStartSeen = 0; /* EQ constraint seen on the START column */ |
| #endif |
| int unusableMask = 0; /* Mask of unusable constraints */ |
| int nArg = 0; /* Number of arguments that seriesFilter() expects */ |
| int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET, |
| ** and value. aIdx[5] covers value=, value>=, and |
| ** value>, aIdx[6] covers value<= and value< */ |
| const struct sqlite3_index_constraint *pConstraint; |
| |
| /* This implementation assumes that the start, stop, and step columns |
| ** are the last three columns in the virtual table. */ |
| assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); |
| assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); |
| |
| aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1; |
| pConstraint = pIdxInfo->aConstraint; |
| for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ |
| int iCol; /* 0 for start, 1 for stop, 2 for step */ |
| int iMask; /* bitmask for those column */ |
| int op = pConstraint->op; |
| if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT |
| && op<=SQLITE_INDEX_CONSTRAINT_OFFSET |
| ){ |
| if( pConstraint->usable==0 ){ |
| /* do nothing */ |
| }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ |
| aIdx[3] = i; |
| idxNum |= 0x20; |
| }else{ |
| assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET ); |
| aIdx[4] = i; |
| idxNum |= 0x40; |
| } |
| continue; |
| } |
| if( pConstraint->iColumn<SERIES_COLUMN_START ){ |
| if( (pConstraint->iColumn==SERIES_COLUMN_VALUE || |
| pConstraint->iColumn==SERIES_COLUMN_ROWID) |
| && pConstraint->usable |
| ){ |
| switch( op ){ |
| case SQLITE_INDEX_CONSTRAINT_EQ: |
| case SQLITE_INDEX_CONSTRAINT_IS: { |
| idxNum |= 0x0080; |
| idxNum &= ~0x3300; |
| aIdx[5] = i; |
| aIdx[6] = -1; |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| bStartSeen = 1; |
| #endif |
| break; |
| } |
| case SQLITE_INDEX_CONSTRAINT_GE: { |
| if( idxNum & 0x0080 ) break; |
| idxNum |= 0x0100; |
| idxNum &= ~0x0200; |
| aIdx[5] = i; |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| bStartSeen = 1; |
| #endif |
| break; |
| } |
| case SQLITE_INDEX_CONSTRAINT_GT: { |
| if( idxNum & 0x0080 ) break; |
| idxNum |= 0x0200; |
| idxNum &= ~0x0100; |
| aIdx[5] = i; |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| bStartSeen = 1; |
| #endif |
| break; |
| } |
| case SQLITE_INDEX_CONSTRAINT_LE: { |
| if( idxNum & 0x0080 ) break; |
| idxNum |= 0x1000; |
| idxNum &= ~0x2000; |
| aIdx[6] = i; |
| break; |
| } |
| case SQLITE_INDEX_CONSTRAINT_LT: { |
| if( idxNum & 0x0080 ) break; |
| idxNum |= 0x2000; |
| idxNum &= ~0x1000; |
| aIdx[6] = i; |
| break; |
| } |
| } |
| } |
| continue; |
| } |
| iCol = pConstraint->iColumn - SERIES_COLUMN_START; |
| assert( iCol>=0 && iCol<=2 ); |
| iMask = 1 << iCol; |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){ |
| bStartSeen = 1; |
| } |
| #endif |
| if( pConstraint->usable==0 ){ |
| unusableMask |= iMask; |
| continue; |
| }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ |
| idxNum |= iMask; |
| aIdx[iCol] = i; |
| } |
| } |
| if( aIdx[3]==0 ){ |
| /* Ignore OFFSET if LIMIT is omitted */ |
| idxNum &= ~0x60; |
| aIdx[4] = 0; |
| } |
| for(i=0; i<7; i++){ |
| if( (j = aIdx[i])>=0 ){ |
| pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; |
| pIdxInfo->aConstraintUsage[j].omit = |
| !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3; |
| } |
| } |
| /* The current generate_column() implementation requires at least one |
| ** argument (the START value). Legacy versions assumed START=0 if the |
| ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES |
| ** to obtain the legacy behavior */ |
| #ifndef ZERO_ARGUMENT_GENERATE_SERIES |
| if( !bStartSeen ){ |
| sqlite3_free(pVTab->zErrMsg); |
| pVTab->zErrMsg = sqlite3_mprintf( |
| "first argument to \"generate_series()\" missing or unusable"); |
| return SQLITE_ERROR; |
| } |
| #endif |
| if( (unusableMask & ~idxNum)!=0 ){ |
| /* The start, stop, and step columns are inputs. Therefore if there |
| ** are unusable constraints on any of start, stop, or step then |
| ** this plan is unusable */ |
| return SQLITE_CONSTRAINT; |
| } |
| if( (idxNum & 0x03)==0x03 ){ |
| /* Both start= and stop= boundaries are available. This is the |
| ** the preferred case */ |
| pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); |
| pIdxInfo->estimatedRows = 1000; |
| if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ |
| if( pIdxInfo->aOrderBy[0].desc ){ |
| idxNum |= 0x08; |
| }else{ |
| idxNum |= 0x10; |
| } |
| pIdxInfo->orderByConsumed = 1; |
| } |
| }else if( (idxNum & 0x21)==0x21 ){ |
| /* We have start= and LIMIT */ |
| pIdxInfo->estimatedRows = 2500; |
| }else{ |
| /* If either boundary is missing, we have to generate a huge span |
| ** of numbers. Make this case very expensive so that the query |
| ** planner will work hard to avoid it. */ |
| pIdxInfo->estimatedRows = 2147483647; |
| } |
| pIdxInfo->idxNum = idxNum; |
| #ifdef SQLITE_INDEX_SCAN_HEX |
| pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX; |
| #endif |
| return SQLITE_OK; |
| } |
| |
| /* |
| ** This following structure defines all the methods for the |
| ** generate_series virtual table. |
| */ |
| static sqlite3_module seriesModule = { |
| 0, /* iVersion */ |
| 0, /* xCreate */ |
| seriesConnect, /* xConnect */ |
| seriesBestIndex, /* xBestIndex */ |
| seriesDisconnect, /* xDisconnect */ |
| 0, /* xDestroy */ |
| seriesOpen, /* xOpen - open a cursor */ |
| seriesClose, /* xClose - close a cursor */ |
| seriesFilter, /* xFilter - configure scan constraints */ |
| seriesNext, /* xNext - advance a cursor */ |
| seriesEof, /* xEof - check for end of scan */ |
| seriesColumn, /* xColumn - read data */ |
| seriesRowid, /* xRowid - read data */ |
| 0, /* xUpdate */ |
| 0, /* xBegin */ |
| 0, /* xSync */ |
| 0, /* xCommit */ |
| 0, /* xRollback */ |
| 0, /* xFindMethod */ |
| 0, /* xRename */ |
| 0, /* xSavepoint */ |
| 0, /* xRelease */ |
| 0, /* xRollbackTo */ |
| 0, /* xShadowName */ |
| 0 /* xIntegrity */ |
| }; |
| |
| #endif /* SQLITE_OMIT_VIRTUALTABLE */ |
| |
| #ifdef _WIN32 |
| __declspec(dllexport) |
| #endif |
| int sqlite3_series_init( |
| sqlite3 *db, |
| char **pzErrMsg, |
| const sqlite3_api_routines *pApi |
| ){ |
| int rc = SQLITE_OK; |
| (void)pApi; |
| #ifndef SQLITE_OMIT_VIRTUALTABLE |
| if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){ |
| *pzErrMsg = sqlite3_mprintf( |
| "generate_series() requires SQLite 3.8.12 or later"); |
| return SQLITE_ERROR; |
| } |
| rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0); |
| #endif |
| return rc; |
| } |
| #define CMPP_D_DEMO |
| /* |
| ** 2025-10-18: |
| ** |
| ** 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 contains demonstration client-side directives for the |
| ** c-pp API. |
| */ |
| #if defined(CMPP_D_DEMO) |
| /* Only when building with the main c-pp app. */ |
| #elif !defined(CMPP_MODULE_REGISTER1) |
| /** |
| Assume a standalone module build. Arrange for the default module |
| entry point to be installed so that cmpp_module_load() does not |
| require that the user know the entry point name. |
| */ |
| #define CMPP_MODULE_STANDALONE |
| #define CMPP_API_THUNK |
| #include "libcmpp.h" |
| #endif |
| |
| #include <stdlib.h> |
| #include <assert.h> |
| #include <string.h> |
| |
| /** |
| cmpp_d_autoload_f() impl for this file's directives and its close |
| friends. |
| */ |
| int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state); |
| /** |
| Registers demo and utility directives with pp. |
| */ |
| int cmpp_module__demo_register(cmpp *pp); |
| |
| /** |
| Simply says hello and emits info about its arguments. |
| */ |
| static void cmpp_dx_f_demo1(cmpp_dx *dx){ |
| cmpp_dx_outf(dx, "Hello from %s%s\n", |
| cmpp_dx_delim(dx), dx->d->name.z); |
| for( cmpp_arg const * a = dx->args.arg0; |
| 0==cmpp_dx_err_check(dx) && a; |
| a = a->next ){ |
| cmpp_dx_outf(dx, "arg type=%s n=%u z=%.*s\n", |
| cmpp_tt_cstr(a->ttype), |
| (unsigned)a->n, (int)a->n, a->z); |
| } |
| } |
| |
| /** |
| Internal helper for other directives. Emits an HTML <div> tag. If |
| passed any arguments, each is assumed to be a CSS class name and is |
| applied to the DIV. This does _not_ emit the lcosing DIV |
| tag. Returns 0 on success. |
| */ |
| static int divOpener(cmpp_dx *dx){ |
| cmpp_dx_out_raw(dx, "<div", 4); |
| int nClass = 0; |
| for( cmpp_arg const * a = dx->args.arg0; |
| 0==cmpp_dx_err_check(dx) && a; |
| a = a->next ){ |
| if( 1==++nClass ){ |
| cmpp_dx_out_raw(dx, " class='", 8); |
| }else{ |
| cmpp_dx_out_raw(dx, " ", 1); |
| } |
| cmpp_dx_out_raw(dx, a->z, a->n); |
| } |
| return cmpp_dx_out_raw(dx, nClass ? "'>" : ">", 1 + !!nClass); |
| } |
| |
| /** |
| Opens an HTML DIV tag, as per divOpener(). |
| */ |
| static void cmpp_dx_f_divOpen(cmpp_dx *dx){ |
| if( 0==divOpener(dx) ){ |
| int * const nDiv = dx->d->impl.state; |
| assert( nDiv ); |
| ++*nDiv; |
| } |
| } |
| |
| /** |
| Closes an HTML DIV tag which was opened by cmpp_dx_f_divOpen(). |
| */ |
| static void cmpp_dx_f_divClose(cmpp_dx *dx){ |
| int * const nDiv = dx->d->impl.state; |
| assert( nDiv ); |
| if( *nDiv > 0 ){ |
| --*nDiv; |
| }else{ |
| char const * const zDelim = cmpp_dx_delim(dx); |
| cmpp_dx_err_set( |
| dx, CMPP_RC_MISUSE, |
| "%s/%s was used without an opening %s%s directive", |
| zDelim, dx->d->name.z, zDelim, dx->d->name.z |
| ); |
| } |
| } |
| |
| /** |
| Another HTML DIV-inspired wrapper which consumes a block of input |
| and wraps it in a DIV. This is functionally the same as the |
| divOpen/divClose examples but demonstrates how to slurp up the |
| content between the open/close directives from within the opening |
| directive's callback. |
| */ |
| static void cmpp_dx_f_divWrapper(cmpp_dx *dx){ |
| if( divOpener(dx) ) return; |
| cmpp_b os = {0}; |
| if( 0==cmpp_dx_consume_b( |
| dx, &os, &dx->d->closer, 1, |
| cmpp_dx_consume_F_PROCESS_OTHER_D |
| ) |
| /* ^^^ says "read to the matching #/div" accounting for, and |
| processing, nested directives). The #/div closing tag is |
| identified by dx->d->closer. */ |
| ){ |
| cmpp_b_chomp( &os ); |
| /* Recall that most cmpp APIs become no-ops if dx->pp has an error |
| set, so we don't strictly need to error-check these calls: */ |
| cmpp_dx_out_raw(dx, os.z, os.n); |
| cmpp_dx_out_raw(dx, "</div>\n", 7); |
| } |
| cmpp_b_clear(&os); |
| } |
| |
| /** |
| A cmpp_d_autoload_f() impl for testing and demonstration |
| purposes. |
| */ |
| int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state){ |
| (void)state; |
| cmpp_api_init(pp); |
| |
| #define M(NAME) (0==strcmp(NAME,dname)) |
| #define MOC(NAME) (M(NAME) || M("/"NAME)) |
| |
| #define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \ |
| cmpp_d_reg SYMNAME = { \ |
| .name = NAME, \ |
| .opener = { \ |
| .f = OPENER, \ |
| .flags = OFLAGS \ |
| }, \ |
| .closer = { \ |
| .f = CLOSER, \ |
| .flags = CFLAGS \ |
| }, \ |
| .dtor = 0, \ |
| .state = 0 \ |
| } |
| |
| #define CHECK(NAME,CHECKCLOSER,OPENER,OFLAGS,CLOSER,CFLAGS) \ |
| if( M(NAME) || (CHECKCLOSER && M("/"NAME)) ){ \ |
| DREG0(reg,NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \ |
| return cmpp_d_register(pp, ®, NULL); \ |
| } (void)0 |
| |
| |
| CHECK("demo1", 0, cmpp_dx_f_demo1, cmpp_d_F_ARGS_LIST, 0, 0); |
| |
| CHECK("demo-div-wrapper", 1, cmpp_dx_f_divWrapper, |
| cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL, |
| cmpp_dx_f_dangling_closer, 0); |
| |
| if( MOC("demo-div") ){ |
| cmpp_d_reg const r = { |
| .name = "demo-div", |
| .opener = { |
| .f = cmpp_dx_f_divOpen, |
| .flags = cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL |
| }, |
| .closer = { |
| .f = cmpp_dx_f_divClose |
| }, |
| .state = cmpp_malloc(sizeof(int)), |
| .dtor = cmpp_mfree |
| }; |
| /* State for one of the custom directives. */; |
| int const rc = cmpp_d_register(pp, &r, NULL); |
| if( 0==rc ){ |
| *((int*)r.state) = 0 |
| /* else reg.state was freed by cmpp_d_register() */; |
| } |
| return rc; |
| } |
| |
| #undef M |
| #undef MOC |
| #undef CHECK |
| #undef DREG0 |
| return CMPP_RC_NO_DIRECTIVE; |
| } |
| |
| int cmpp_module__demo_register(cmpp *pp){ |
| cmpp_api_init(pp); |
| int rc; |
| #define X(D) \ |
| rc = cmpp_d_autoload_f_demos(pp, D, NULL); \ |
| if( rc && CMPP_RC_NO_DIRECTIVE!=rc ) goto end |
| X("demo1"); |
| X("demo-div"); |
| X("demo-div-wrapper"); |
| #undef X |
| end: |
| return cmpp_err_get(pp, NULL); |
| } |
| CMPP_MODULE_REGISTER1(demo); |
| #endif /* include guard */ |