1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-10-23 21:49:02 +03:00
Files
sqlite/ext/wasm/libcmpp.c
2026-09-30 09:17:12 +00:00

19025 lines
610 KiB
C

/**
This is the auto-generated "amalgamation build" of libcmpp. This C
file contains both the header and source file for c-pp,
a.k.a. libcmpp. It was amalgamated using:
./c-pp -I. -I./src -Dsrcdir=./src -o libcmpp.c ./tool/libcmpp.c-pp.c
with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC
*/
#if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED)
#define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED
#if !defined(_POSIX_C_SOURCE)
# define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
#endif
#define CMPP_AMALGAMATION
#if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED)
/**
This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated
using:
./c-pp -I. -I./src -Dsrcdir=./src -o libcmpp.h ./tool/libcmpp.c-pp.h
with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC
*/
#define CMPP_PACKAGE_NAME "libcmpp"
#define CMPP_LIB_VERSION "2.0.x"
#define CMPP_LIB_VERSION_HASH "4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46"
#define CMPP_LIB_VERSION_TIMESTAMP "2026-09-30 09:12:44.230 UTC"
#define CMPP_LIB_CONFIG_TIMESTAMP "2026-09-30 09:14 GMT"
#define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP
#define CMPP_PLATFORM_EXT_DLL ".so"
#define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp"
#if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_)
#define NET_WANDERINGHORSE_CMPP_H_INCLUDED_
/*
** 2022-11-12:
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** * May you do good and not evil.
** * May you find forgiveness for yourself and forgive others.
** * May you share freely, never taking more than you give.
**
************************************************************************
**
** The C-minus Preprocessor: C-like preprocessor. Why? Because C
** preprocessors _can_ process non-C code but generally make quite a
** mess of it. The purpose of this library is a customizable
** preprocessor suitable for use with arbitrary UTF-8-encoded text.
**
** The supported preprocessor directives are documented in the
** README.md hosted with this file (or see the link below).
**
** Any mention of "#" in the docs, e.g. "#if", is symbolic. The
** directive delimiter is configurable and defaults to "##". Define
** CMPP_DEFAULT_DELIM to a string when compiling to define the default
** at build-time.
**
** This API is presented as a library but was evolved from a
** monolithic app. Thus the library interface is likely still missing
** some pieces needed to make it more readily usable as a library.
**
** Author(s):
**
** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
**
** Canonical home:
**
** - <https://fossil.wanderinghorse.net/r/c-pp>
**
** One may see mentions of %amalgamation and %lite in some comments in
** this file and its implementation files. Those are preprocessor
** directives for this very preprocessor, used by the build process to
** filter out certain pieces to produce its amalgamation build
** and (derived from that) the "lite" build. Their placement is
** significant, especially when they're embedded within a multi-line C
** macro or at the end of the line with a C directive. In normal
** builds they're innocuous, but when passed through the "lightener"
** process, they activate (see Makefile.in keyword "%lite" and
** tool/libcmpp.c-pp.[ch]).
*/
/**
License note: the license headers have been stripped from other
files in this amalgamation build in order to save space. They all
have the same license as the one above but may have different
dates. The canonical sources are authoritative with regards to the
licenses of the code in each segment of this amalgamation.
*/
#if defined(CMPP_HAVE_AUTOCONFIG_H)
#include "libcmpp-autoconfig.h"
#endif
#if defined(HAVE_AUTOCONFIG_H)
#include "autoconfig.h"
#endif
#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
#ifdef _WIN32
# if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD)
# define CMPP_EXPORT extern
# elif defined(BUILD_libcmpp)
# define CMPP_EXPORT extern __declspec(dllexport)
# else
# define CMPP_EXPORT extern __declspec(dllimport)
# endif
#else
# define CMPP_EXPORT extern
#endif
/**
cmpp_FILE is a portability hack for WASM builds, where we want to
elide the (FILE*)-using pieces to avoid having a dependency on
Emscripten's POSIX I/O proxies. In all non-WASM builds it is
guaranteed to be an alias for FILE. On WASM builds it is guaranteed
to be an alias for void and the cmpp APIs which use it become
inoperative in WASM builds.
That said: the code does not yet support completely compiling out
(FILE*) dependencies, and may not be able to because canonical
sqlite3 (upon which it is based) depends heavily on file
descriptors and slightly on FILE handles.
*/
#if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__)
typedef void cmpp_FILE;
# define CMPP_PLATFORM_IS_WASM 1
#else
#include <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>
#include "sqlite3.h" /* sqlite3_str */
/**
For loadable modules to be able to portably access the cmpp API,
without requiring that their loading binary be linked with
-rdynamic, we need a "thunk". The API exposes cmpp_api_thunk for
that purpose. The following macros set up the thunk for a given
compilation unit. They are intended to only be used by loadable
modules, not generic client code.
Before including this header, define CMPP_API_THUNK with no value
and/or define CMPP_API_THUNK_NAME to a C symbol name. The latter
macro implies the former and defines the name of the static symbol
to be the local cmpp_api_thunk instance, defaulting to cmppApi.
The first line of a module's registration function should then be:
cmpp_api_init(pp);
where pp is the name of the sole argument to the registration
callback. After that is done, the cmpp_...() APIs may be used via
the macros defined elsewhere in this header, all of which route
through the thunk object.
*/
#if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME)
# if !defined(CMPP_API_THUNK)
# define CMPP_API_THUNK
# endif
# if !defined(CMPP_API_THUNK_NAME)
# define CMPP_API_THUNK_NAME cmppApi
# endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct cmpp__pimpl cmpp__pimpl;
typedef struct cmpp_api_thunk cmpp_api_thunk;
typedef struct cmpp_outputer cmpp_outputer;
typedef struct cmpp_b cmpp_b;
typedef struct cmpp_arg cmpp_arg;
typedef void cmpp_itch;
/**
32-bit flag bitmask type. This typedef exists primarily to improve
legibility of function signatures and member structs by conveying
their intent for use as flags instead of result codes or lengths.
*/
typedef uint32_t cmpp_flag32_t;
/**
An X-macro which invokes its argument (a macro name) to expand to
all possible values of cmpp_rc_e entries. The macro name passed to
it is invoked once for each entry and passed 3 arguments: the enum
entry's full name (CMPP_RC_...), its integer value, and a terse
informational string.
*/
#define cmpp_rc_e_map(E) \
E(CMPP_RC_OK, 0, \
"The quintessential not-an-error value.") \
E(CMPP_RC_ERROR, 100, \
"Generic/unknown error.") \
E(CMPP_RC_NYI, 101, \
"A placeholder return value for not yet implemented functions.") \
E(CMPP_RC_OOM, 102, \
"Out of memory.") \
E(CMPP_RC_MISUSE, 103, \
"API misuse (invalid args)") \
E(CMPP_RC_RANGE, 104, \
"A range was violated.") \
E(CMPP_RC_ACCESS, 105, \
"Access to or locking of a resource was denied " \
"by some security mechanism or other.") \
E(CMPP_RC_IO, 106, \
"Indicates an I/O error. Whether it was reading or " \
"writing is context-dependent.") \
E(CMPP_RC_NOT_FOUND, 107, \
"Requested resource not found.") \
E(CMPP_RC_ALREADY_EXISTS, 108, \
"Indicates that a to-be-created resource already exists.") \
E(CMPP_RC_CORRUPT, 109, \
"Data consistency problem.") \
E(CMPP_RC_SYNTAX, 110, \
"Some sort of syntax error.") \
E(CMPP_RC_NOOP, 111, \
"Special sentinel value for some APIs.") \
E(CMPP_RC_UNSUPPORTED, 112, \
"An unsupported operation was request.") \
E(CMPP_RC_DB, 113, \
"Indicates db-level error (e.g. statement prep failed). In such " \
"cases, the error state of the related db handle (cmpp_db) " \
"will be updated to contain more information directly from the " \
"db driver.") \
E(CMPP_RC_NOT_DEFINED, 114, \
"Failed to expand an undefined value.") \
E(CMPP_RC_ASSERT, 116, "An #assert failed.") \
E(CMPP_RC_TYPE, 118, \
"Indicates that some data type or logical type is incorrect.") \
E(CMPP_RC_CANNOT_HAPPEN, 140, \
"This is intended only for internal use, to " \
"report conditions which \"cannot possibly happen\".") \
E(CMPP_RC_HELP, 141, \
"--help was used in the arguments to cmpp_process_argv()") \
E(CMPP_RC_NO_DIRECTIVE, 142, \
"A special case of CMPP_RC_NOT_FOUND needed to disambiguate.") \
E(CMPP_RC_MODULE_VERSION, 143, \
"API version mismatch when loading a module.") \
E(CMPP_RC_end,200, \
"Must be the final entry in the enum. Used for creating " \
"client-side result codes which are guaranteed to live outside " \
"of this one's range.")
/**
Most functions in this library which return an int return result
codes from the cmpp_rc_e enum. None of these entries are
guaranteed to have a specific value across library versions except
for CMPP_RC_OK, which is guaranteed to always be 0 (and the API
guarantees that no other code shall have a value of zero).
The only reasons numbers are hard-coded to the values is to
simplify debugging during development. Clients may use
cmpp_rc_cstr() to get some human-readable (or programmer-readable)
form for any given value in this enum.
*/
enum cmpp_rc_e {
#define E(N,V,H) N = V,
cmpp_rc_e_map(E)
#undef E
};
typedef enum cmpp_rc_e cmpp_rc_e;
/**
Returns the string form of the given cmpp_rc_e value or NULL if
it's not a member of that enum.
*/
char const * cmpp_rc_cstr(int rc);
#define cmpp_rc_ucstr(RC) (unsigned char const*)cmpp_rc_cstr(RC)
/**
CMPP_BITNESS specifies whether the library should use 32- or 64-bit
integer types for its size/length measurements. It's difficult to
envision use cases for a preprocessor which would require counters
or ranges larger than 32 bits provide for, so the default is 32
bits. Builds created with different CMPP_BITNESS values are not
binary-compatible.
*/
#define CMPP_BITNESS 32
#if 32==CMPP_BITNESS
/**
Unsigned integer type for string/stream lengths. 32 bits is
sufficient for all but the weirdest of inputs and outputs.
*/
typedef uint32_t cmpp_size_t;
/**
A signed integer type indicating the maximum length of strings or
byte ranges in a stream. It is most frequently used in API
signatures where "if this value is negative then use
strlen(someOtherArg) to count it".
Maintenance reminder: this type must remain 32-bits or risks
breaking countless printf-style formatting uses, where it frequently
ends up being used with "%.*s".
*/
typedef int32_t cmpp_strlen_t;
/**
The printf-format-compatible format letter (or group of letters)
appropriate for use with cmpp_size_t. Contrary to popular usage,
size_t cannot be portably used with printf(), without careful
casting, because it has neither a fixed size nor a standardized
printf/scanf format specifier (like the stdint.h types do).
*/
#define CMPP_SIZE_T_PFMT PRIu32
#elif 64==CMPP_BITNESS
typedef uint64_t cmpp_size_t;
typedef int64_t cmpp_strlen_t;
#define CMPP_SIZE_T_PFMT PRIu64
#else
#error "Invalid CMPP_BITNESS value. Expecting 32 or 64."
#endif
/**
Semantically like cmpp_strlen_t but guranteed to be 32 bits. This
is primarily provided as a reminder that some sources to which we
pass negative values as a length are limited to 32-bit signed
ranges.
*/
typedef int32_t cmpp_ssize32_t;
/**
Generic interface for streaming in data. Implementations must read
(at most) *n bytes from their input, copy it to dest, assign *n to
the number of bytes actually read, return 0 on success, and return
non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO).
When called, *n is the max length to read. On return, *n must be
set to the amount actually read. Implementations may need to
internally distinguish a short read due to EOF from a short read
due to an I/O error, e.g. using feof() and/or ferror(). A short
read for EOF is not an error but a short read for input failure is.
This library invariably treats a short read as EOF unless the
cmpp_input_f() implementation says otherwise.
The state parameter is the implementation-specified input
file/buffer/whatever channel.
*/
typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n);
/**
Generic interface for streaming out data. Implementations must
write n bytes from src to their destination channel and return 0 on
success, or a value from the cmpp_rc_e enum on error
(e.g. CMPP_RC_IO). The state parameter is the
implementation-specified output channel.
It is implementation-defined whether an n of 0 is legal. This
library refrains from passing 0 to these functions.
In the context of cmpp, the library makes no guarantees that output
will always end at a character boundary. It may send any given
multibyte character as the end resp. start of two calls to this
function. If that is of a concern for implementors of these
functions (e.g. because they're appending the output to a UI
widget), they may need to buffer all of the output before applying
it (see cmpp_b), or otherwise account for partial characters.
That said: the core library, by an accident of design, will always
emit data at character boundaries, assuming that its input is
well-formed UTF-8 text (which cmpp does not validate to be the
case). Custom cmpp_f() implementations are not strictly
required to do so but, because of how cmpp is used, almost
certainly will. But relying on that is ill-advised.
*/
typedef int (*cmpp_output_f)(void * state, void const * src,
cmpp_size_t n);
/**
Generic interface for flushing arbitrary output streams. Must
return 0 on success, a non-0 cmpp_rc_e value on error. When in
doubt, return CMPP_RC_IO on error. The interpretation of the state
parameter is implementation-specific.
*/
typedef int (*cmpp_flush_f)(void * state);
/**
The library's primary class. Each one of these represents a
separate preprocessor instance.
See also: cmpp_dx (the class which client-side extensions interact
with the most).
*/
struct cmpp {
/**
API thunk object to support use via loadable modules. Client code
does not normally need to access this member, but it's exposed
here to give loadable modules more flexibility in how they use
the thunk.
This pointer is _always_ the same singleton object and the
library never exposes a cmpp object with a NULL api member.
*/
cmpp_api_thunk const * const api;
/**
Private internal state. Owned by this object.
*/
cmpp__pimpl * const pimpl;
};
typedef struct cmpp cmpp;
/**
Flags for use with cmpp_ctor_opt::flags.
*/
enum cmpp_ctor_e {
/* Sentinel value. */
cmpp_ctor_F_none = 0,
/* Disables #include. */
cmpp_ctor_F_NO_INCLUDE = 0x01,
/* Disables #pipe. */
cmpp_ctor_F_NO_PIPE = 0x02,
/* Disables #attach, #detach, and #query. */
cmpp_ctor_F_NO_DB = 0x04,
/* Disables #module. */
cmpp_ctor_F_NO_MODULE = 0x08,
/**
Disable all built-in directives which may work with the filesystem
or invoke external processes. Client-defined directives with the
cmpp_f_F_NOT_IN_SAFEMODE flag are also disabled. Directives
disabled via the cmpp_ctor_F_NO_... flags (or equivalent library
built-time options) do not get registered, so will trigger
"unknown directive" errors rather than safe-mode violation errors.
*/
cmpp_ctor_F_SAFEMODE = 0x10,
cmpp_ctor_F_DUMMY_TRAILING_COMMA_OCD
};
/**
A configuration object for cmpp_ctor(). This type may be extended
as new construction-time customization opportunities are
discovered.
*/
struct cmpp_ctor_opt {
/**
Bitmask from the cmpp_ctor_e enum.
*/
cmpp_flag32_t flags;
/**
If not NULL then this must name either an existing SQLite3 db
file or the name of one which can be created on demand. If NULL
then an in-memory or temporary database is used (which one is
unspecified). The library copies these bytes, so they need not be
valid after a call to cmpp_ctor().
*/
char const * dbFile;
};
typedef struct cmpp_ctor_opt cmpp_ctor_opt;
/**
Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return
value must eventually be passed to cmpp_dtor() to free it.
The cfg argument, if not NULL, holds config info for the new
instance. If NULL, an instance with unspecified defaults is
used. These configuration pieces may not be modified after the
instance is created.
It returns 0 if *pp is ready to use and non-0 if either allocation
fails (in which case *pp will be set to 0) or initialization of *pp
failed (in which case cmpp_err_get() can be used to determine why
it failed). In either case, the caller must eventually pass *pp to
cmpp_dtor() to free it.
If the library is built with the preprocessor symbol
CMPP_CTOR_INSTANCE_INIT defined, it must refer to a function with
this signature:
int CMPP_CTOR_INSTANCE_INIT(cmpp *);
The library calls this before returning and arranges to call it
lazily if pp gets reset. The intent is that the init function
installs custom directives using cmpp_d_register(). That
initialization, on error, is expected to set its argument's error
state with cmpp_errf().
*/
CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_opt const * cfg);
/**
If pp is not NULL, it is passed to cmpp_reset() and then freed.
*/
CMPP_EXPORT void cmpp_dtor(cmpp *pp);
/**
realloc(3)-compatible allocator used by the library.
This API very specifically uses sqlite3_realloc() as its basis.
*/
CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n);
/**
malloc(3)-compatible allocator used by the library.
This API very specifically uses sqlite3_malloc() as its basis.
*/
CMPP_EXPORT void * cmpp_malloc(size_t n);
/**
Proxy for cmpp_malloc() which (A) is a no-op returning NULL pp has
error state and (B) sets pp's error state on OOM.
*/
void * cmpp_malloc2(cmpp* pp, cmpp_size_t n);
/**
free(3)-style deallocator. It can also be used as a destructor for
cmpp_d_register() _if_ the memory in question is allocated by
cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of
APIs.
This is not called cmpp_free() to try to avoid any confusion with
cmpp_dtor().
*/
CMPP_EXPORT void cmpp_mfree(void *);
/**
If m is NULL then pp's persistent error code is set to CMPP_RC_OOM,
else this is a no-op. Returns pp's error code.
To simplify certain uses, pp may be NULL, in which case this
function returns CMPP_RC_OOM if m is NULL and 0 if it's not.
*/
CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * m );
/**
Re-initializes all state of pp. This saves some memory for reuse
but resets it all to default states. This closes the database and
will also reset any autoloader, policies, or delimiter
configurations to their compile-time defaults. It retains only a
small amount of state, like any configuration which was passed to
cmpp_ctor().
After calling this, pp is in a cleanly-initialized state and may be
re-used with the cmpp API. Its database will not be initialized
until an API which needs it is called, so pp can be used with
functions which may otherwise be prohibited after the db is
opened. (Do we still have any?)
As of this writing, this is the only way to reliably recover a cmpp
instance from any significant errors. Errors may do things like
leave savepoints out of balance, and this cleanup step resets all
of that state. However, it also loses state like the autoloader.
TODO?: we need(?) a partial-clear operation which keeps some of the
instance's state, most notably custom directives, the db handle,
and any cached prepared statements. See cmpp_errf() for the
distinction between recoverable and non-recoverable errors.
*/
CMPP_EXPORT void cmpp_reset(cmpp *pp);
#if 0
Not yet;
/**
If called before pp has initialized its database, this sets the
file name used for that database. If called afterwards, pp's error
state is updated and CMPP_RC_MISUSE is returned. If called while
pp has error state set, that code is returned without side-effects.
This does not open the database. It is opened on demand when
processing starts.
On success it returns 0 and this function makes a copy of zName.
As a special case, zName may be NULL to use the default name, but
there is little reason to do so unless one changes their mind after
setting it to non-NULL.
*/
//CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName);
#endif
/**
Returns true if the bytes in the range [zName, zName+n) comprise a
legal name for a directive or a define.
It disallows any control characters, spaces, and most punctuation,
but allows alphanumeric (but must not start with a number) as well
as any of: -./:_ (but it may not start with '-'). Any characters
with a high bit set are assumed to be UTF-8 and are permitted as
well.
The name's length is limited, rather arbitrarily, to 64 bytes.
If the key is not legal then false is returned and if zErrPos is
not NULL then *zErrPos is set to the position in zName of the first
offending character. If validation fails because n is too long then
*zErrPos (if zErrPos is not NULL) will be set to 0.
Design note: this takes unsigned characters because it most
commonly takes input from cmpp_arg::z strings.
*/
CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName,
cmpp_size_t n,
unsigned char const **zErrPos);
/**
Adds the given `#define` name to the list of defines, overwriting
any previous value (scoped to the current savepoint, if any).
zKey must be NUL-terminated and legal as a key. The rules are the
same as for cmpp_is_legal_key() except that a '=' is also permitted
if it's not at the start of the string because...
If zVal is NULL then zKey may contain an '=', from which the value
will be extracted. If zVal is not NULL then zKey may _not_ contain
an '='.
The ability for zKey to contain a key=val was initially to
facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was
initially a CLI app (as opposed to a library). It's considered a
"legacy" feature, not recommended for most purposes, but it _is_
convenient for that particular purpose.
Returns 0 on success and updates pp's error state on error.
See: cmpp_define_v2()
See: cmpp_undefine()
See: cmpp_undef_legacy()
*/
CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey,
char const *zVal);
/**
Works like cmpp_define_legacy() except that it does not examine zKey to
see if it contains an '='.
*/
CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal);
/**
Removes the given `#define` name from the list of defines. zKey is,
in this case, treated as a GLOB pattern, and all matching defines
are deleted. (This ugly historical behavior will be removed at some
point, along with that argument!)
If nRemoved is not NULL then, on success, it is set to the number
of entries removed by this call.
Returns 0 on success and updates pp's error state on error. It is not
an error if no value was undefined.
This does _not_ affect defines made using cmpp_define_shadow().
*/
CMPP_EXPORT int cmpp_undef_legacy(cmpp *pp, const char * zKey,
unsigned int *nRemoved);
/**
Undefines the define with the given name (an exact match). Returns
0 on success. If pp
has error state, this is a no-op returning the current error code,
otherwise it returns non-0 only on allocation error or db-related
errors (which, in practice, do not happen for in-memory databases
unless they've run out of memory).
*/
CMPP_EXPORT int cmpp_undefine(cmpp *pp, const char * zKey,
cmpp_strlen_t n);
/**
This works similarly to cmpp_define_v2() except that the new define
"shadows", rather than overwrites, an existing define with the same
name.
All APIs which look up define keys will get the value of the shadow
define. The shadow can be uninstalled with cmpp_define_unshadow(),
effectively restoring its previous value (if any). That function
should be called one time for each call to this one, passing the
same key to each call. A given key may be shadowed any number of
times by this routine. Each one saves the internal ID of the shadow
into *pId (which must not be NULL). That value must be passed to
cmpp_define_unshadow() to ensure that the "shadow stack" stays
balanced.
cmpp_undef() will _not_ undefine an entry added through this
interface.
Returns pp's persistent error code (0 on success).
Design note: this function was added to support adding a define
named __FILE__ to input scripts which works like it does in a C
preprocessor. Alas, supporting __LINE__ would be much more costly,
as it would have to be updated in the db from several places, so
its cost would outweigh its meager benefits.
*/
CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey,
char const *zVal,
int64_t * pId);
/**
Removes the shadow define matching the zKey and id values which
where previously passed to cmpp_define_shadow(). It is not an
error if no match is found, in which case this function has no
visible side-effects.
Unlike cmpp_undef(), zKey is matched precisely, not against a glob.
In order to keep the "shadow stack" properly balanced, this will
delete any shadow entries for the given key which have the same id
or a newer one (i.e. they were left over from a missed call to
cmpp_define_unshadow()).
Returns pp's persistent error code (0 on success).
*/
CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey,
int64_t id);
/**
Adds the given dir to the list of includes. They are checked in the
order they are added.
*/
CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey);
/**
Sets pp's default output channel. If pp already has a channel, it
is closed[^1].
The second argument, if not NULL, is _bitwise copied_, which has
implications for the ownership of out->state (see below). If it is
is NULL, cmpp_outputer_empty is copied in its place, which makes
further output a no-op.
The third argument is a symbolic name for the channel (perhaps its
file name). It is used in debugging and error messages. cmpp does
_not_ copy it, so its bytes must outlive the cmpp instance. (In
practice, the byte names come from main()'s argv or scope-local
strings in the same scope as the cmpp instance.) This argument
should only be NULL if the second argument is.
cmpp_reset(), or opening another channel, will end up calling
out->cleanup() (if it's not NULL) and passing it a pointer to a
_different_ cmpp_outputer object, but with the _same_
cmpp_outputer::state pointer, which may invalidate out->state.
To keep cmpp from doing that, make a copy of the output object, set
the cleanup member of that copy to NULL, then pass that copy to this
function. It is then up to the client to call out->cleanup(out) when
the time is right.
For example:
```
cmpp_outputer my = cmpp_outputer_FILE;
my.state = cmpp_fopen("/some/file", "wb");
cmpp_outputer tmp = my;
tmp.cleanup = NULL;
cmpp_outputer_set( pp, &tmp, "my file");
...
my.cleanup(&my); // will cmpp_fclose(my.state)
```
Potential TODO: internally store the output channel as a pointer.
It's not clear whether that would resolve the above grief or
compound it.
[^1]: depending on the output channel, it might not _actually_ be
closed, but pp is disassociated from it, in any case.
*/
CMPP_EXPORT
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName);
/**
Treats the range [zIn,zIn+nIn) as a complete cmpp input and process
it appropriately. zName is the name of the input for purposes of
error messages. If nIn is negative, strlen() is used to calculate
it.
This is a no-op if pp has any error state set. It returns pp's
persistent error code.
*/
CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName,
unsigned char const * zIn,
cmpp_strlen_t nIn);
/**
A thin proxy for cmpp_process_string() which reads its input from
the given file. Returns 0 on success, else returns pp's persistent
error code.
*/
CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName);
/**
A thin proxy for cmpp_process_string() which reads its input from
the given input source, consuming it all before passing it
on. Returns 0 on success, else returns pp's persistent error code.
*/
CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName,
cmpp_input_f src, void * srcState);
/**
Process the given main()-style arguments list. When calling from
main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0]
(the binary's name).
Each argument is expected to be one of the following:
1) One of --help or -?: causes this function to return CMPP_RC_HELP
without emitting any output.
2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to
Y.
3) -UX: unsets all defines matching glob X.
4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to
the contents of that file.
5) -IX: adds X to the "include path". If _no_ include path is
provided then cmpp assumes a path of ".", but if _any_ paths are
provided then it does not assume that "." is in the path.
6) --chomp-F: specifies whether subsequent -F flags should "chomp"
one trailing newline from their input files.
7) --delimiter|-d=X sets the directive delimiter to X. Its default
is a compile-time constant.
8) --output|-o=filename: sets the output channel to the given file.
A value of "-" means stdout. If no output channel is opened when
this is called, and files are to be processed, stdout is
assumed. (That's a historical artifact from earlier evolutions.)
To override that behavior use cmpp_outputer_set().
9) --file|-f=filename: sets the input channel to the given file.
A value of "-" means stdin.
10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it.
Because it's difficult to pack multiple lines of text into this,
it's really of use for testing #expr and #assert.
11) --@policy=X sets the @token@ parsing policy. X must be
one of (retain, elide, error, off) and defaults to off.
12) -@: shorthand for --@policy=error.
13) --sql-trace: enables tracing of all SQL statements to stderr.
This is useful for seeing how a script interacts with the
database. Use --no-sql-trace to disable it.
14) --sql-trace-x: like --sql-trace but replaces bound parameter
placeholders with their SQL values. Use --no-sql-trace to disable
it.
15) --dump-defines: emit all defines to stdout. They should
arguably go to stderr but that interferes with automated testing.
Any argument which does not match one of the above flags, and does
not start with a "-", is treated as if it were passed to the --file
flag.
Flags may start with either 1 or 2 dashes - they are equivalent.
Flags which take a value may either be in the form X=Y or X Y, i.e.
may be a single argv entry or a pair of them.
It performs two passes on the arguments: the first is for validation
checking for --help/-?. No processing of the input(s) and output(s)
happens unless the first pass completes. Similarly, no validation of
whether any provided filename are actually readable is performed
until the second pass.
Arguments are processed in the order they are given. Thus the following
have completely different meanings:
1) -f foo.in -Dfoo
2) -Dfoo -f foo.in
The former will process foo.in before defining foo.
This behavior makes it possible to process multiple input files in
a single go:
--output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in
It returns 0 on success. cmpp_err_get() can be used to fetch any
error message.
*/
CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv);
/**
Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP.
It emits --help-style text to the given output stream.
As the first argument pass it either argv[0] or NULL. The second
should normally be stdout or stderr.
Reminder to self: this could take a (cmpp_output_f,void*) pair
instead, and should do so for the sake of WASI builds, but its impl
currently relies heavily on fprintf() formatting.
*/
CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName,
cmpp_FILE *os);
/**
Returns pp's current error number (from the cmpp_rc_e enum) and
sets *zMsg (if zMsg is not NULL) to the error string or NULL if no
error is set. The bytes are owned by pp and may be invalidated by
any functions which take pp as an argument.
See cmpp_err_get() for more information.
Naming note: this function predates the cmpp_errinfo class by a
donkey's age, thus this apparent name collision. If it weren't for
this function, that class would be called cmpp_err.
*/
CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg);
/**
Sets or clears (if 0==rc) pp's persistent error state. zFmt may be
NULL or a format string compatible with sqlite3_mprintf().
To simplify certain uses, this is a no-op if pp is NULL, returning
rc without other side effects.
Returns rc with one exception: if allocation of a copy of the error
string fails then CMPP_RC_OOM will be returned (and pp will be
updated appropriately).
If rc is non-0 and pp is currently processing a script, the
resulting error string will be prefixed with error location
information.
It is legal for zFmt to be NULL or an empty string, in which case a
default, vague error message is used (without requiring allocation
of a new string).
Recoverable vs. unrecoverable errors:
Most cmpp APIs become no-ops if their cmpp object has error state
set, treating any error as unrecoverable. That approach simplifies
writing code for it by allowing multiple calls to be chained
without concern for whether the previous one succeeded.
ACHTUNG: simply clearing the error state by passing 0 as the 2nd
argument to this function is _not_ enough to recover from certain
errors. e.g. an error in the middle of a script may leave db
savepoints imbalanced. The only way to _fully_ recover from any
significant failures is to use cmpp_reset(), which resets all of
pp's state.
APIs which may set the error state but are recoverable by simply
clearing that state will document that. Errors from APIs which do
not claim to be recoverable in error cases must be treated as
unrecoverable.
See cmpp_err_get() for more information.
Potential TODO: change the error-reporting interface to support
distinguishing from recoverable and non-recoverable errors. "The
problem" is that no current uses need that - they simply quit and
free up the cmpp instance on error. Maybe that's the way it
_should_ be.
*/
CMPP_EXPORT int cmpp_errf(cmpp *pp, int rc, char const *zFmt, ...);
/** va_list counterpart of cmpp_errf(). */
CMPP_EXPORT int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list vars);
/**
A variant of cmpp_errf() which is not variadic, primarily as a
consolation for WASM builds. zMsg may be NULL. The second, third,
and fourth parameters behave as documented for cmpp_errinfo_set().
If pp is not NULL, this returns either rc or CMPP_RC_OOM, as per
cmpp_errinfo_set().
This permits a NULL pp to simplify certain uses but is a no-op in
that case, returning rc.
*/
CMPP_EXPORT int cmpp_err(cmpp *pp, int rc, char const *zMsg,
cmpp_strlen_t nMsg);
#define cmpp_err_c(PP,RC,MSG) cmpp_err(PP, RC, "" MSG, sizeof(MSG)-1)
static inline void cmpp_err_reset(cmpp *pp){ cmpp_err(pp,0,0,0); }
#if 0
/**
Clears any error state in pp. Most cmpp APIs become no-ops if their
cmpp instance has its error flag set.
See cmpp_err_get() for important details about doing this.
*/
//CMPP_EXPORT void cmpp_err_clear(cmpp *pp);
/**
This works like a combination of cmpp_err_get() and
cmpp_err_clear(), in that it clears pp's error state by transferring
ownership of it to the caller. If pp has any error state, *zMsg is
set to the error string and the error code is returned, else 0 is
returned and *zMsg is set to 0.
The string returned via *zMsg must eventually be passed to
cmpp_mfree() to free it.
This function is provided simply as an optimization to avoid
having to copy the error string in some cases.
ACHTUNG: see the ACHTUNG in cmpp_err_clear().
*/
//CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg);
#endif
/**
Returns pp's current error code, which will be 0 if it currently
has no error state.
To simplify certain uses, this is a no-op if pp is NULL, returning
0.
*/
CMPP_EXPORT int cmpp_err_has(cmpp const * pp);
/**
Returns true if pp was initialized in "safe mode". That is: if the
cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor().
To simplify certain uses, this is a no-op if pp is NULL, returning
false.
*/
CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp);
/**
Starts a new SAVEPOINT in the database. Returns non-0, and updates
pp's persistent error state, on failure.
If this returns 0, the caller is obligated to later pass pp to one
of cmpp_sp_commit() or cmpp_sp_rollback().
*/
CMPP_EXPORT int cmpp_sp_begin(cmpp *pp);
/**
Commits the most recently-opened savepoint UNLESS pp's error state
is set, in which case this behaves like cmpp_sp_rollback().
Returns 0 on success.
A call to cmpp_sp_begin() which returns 0 obligates the caller to
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
for either to be called in any other context.
*/
CMPP_EXPORT int cmpp_sp_commit(cmpp *pp);
/**
Rolls back the most recently-opened savepoint. Returns 0 on
success.
A call to cmpp_sp_begin() which returns 0 obligates the caller to
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
for either to be called in any other context.
*/
CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp);
/**
_Almost_ equivalent to fopen(3) but:
- If name=="-", it returns one of stdin or stdout, depending on the
mode string: stdout is returned if 'w' or '+' appear, otherwise
stdin.
If it returns NULL, the global errno "should" contain a description
of the problem unless the problem was argument validation.
If at all possible, use cmpp_fclose() (as opposed to fclose()) to
close these handles, as it has logic to skip closing the three
standard streams.
*/
CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode);
/**
Passes f to fclose(3) unless f is NULL or one of the C-standard
handles (stdin, stdout, stderr), in which cases it does nothing at
all.
*/
CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f);
/**
A cmpp_output_f() impl which requires state to be a (FILE*), which
this function passes the call on to fwrite(). Returns 0 on
success, CMPP_RC_IO on error.
If state is NULL then stdout is used.
*/
CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n);
/**
A cmpp_output_f() impl which requires state to be a ([const] int*)
referring to a writable file descriptor, which this function
dereferences and passes to write(2).
*/
CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n);
/**
A cmpp_input_f() implementation which requires that state be
a readable (FILE*) handle, which it passes to fread(3).
*/
CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n);
/**
A cmpp_input_f() implementation which requires that state be a
readable file descriptor, in the form of an ([const] int*), which
this function passes to write(2).
*/
CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n);
/**
A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened
for writing, which this function passes the call on to
fflush(). If fflush() returns 0, so does this function, else it
returns non-0.
*/
CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile);
/**
A generic streaming routine which copies data from an
cmpp_input_f() to an cmpp_outpuf_f().
Reads all data from inF(inState,...) in chunks of an unspecified
size and passes them on to outF(outState,...). It reads until inF()
returns fewer bytes than requested or returns non-0. Returns the
result of the last call to outF() or (if reading fails) inF().
Results are undefined if either of inState or outState arguments
are NULL and their callbacks require non-NULL. (This function
cannot know whether a NULL state argument is legal for the given
callbacks.)
Here is an example which basically does the same thing as the
cat(1) command on Unix systems:
```
cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout);
```
Or copy a FILE to a string buffer:
```
cmpp_b os = cmpp_b_empty;
cmpp_FILE * f = cmpp_fopen(...);
rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os);
// On error os might be partially populated.
// Eventually clean up the buffer:
cmpp_b_clear(&os);
```
*/
CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState,
cmpp_output_f outF, void * outState);
/**
Reads the entire contents of the given input stream, allocating it
in a buffer. On success, returns 0, assigns *pOut to the buffer,
and *nOut to the number of bytes read (which will be fewer than are
allocated). It guarantees that on success it NUL-terminates the
buffer at one byte after the returned size, with one exception: if
the stream has no input, both *pOut and *nOut will be set to 0.
On error it returns whatever code xIn() returns.
*/
CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn,
unsigned char **pOut, cmpp_size_t * nOut);
/**
UNTESTED!
Reads the entire contents of the given file, appending it to b.
Returns 0 on success.
If pp is not NULL then:
1) This is a no-op if pp has an error set, returning that code.
2) Any error triggered by this function will be set in its
persistent error state.
If pp is NULL then errors are considered transient.
On error b may be partially populated.
*/
CMPP_EXPORT int cmpp_slurp_file(cmpp *pp, char const *fn,
cmpp_b * b);
/**
A cleanup callback interface for use with cmpp_outputer::cleanup().
Implementations must handle self->state appropriately for its type,
and clear self->state if appropriate, but must not free the self
object. It is implementation-specified whether self->state and/or
self->name are set to NULL by this function. Whether they should be
often depends on how they're used.
*/
typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self);
/**
An interface which encapsulates data for managing a streaming
output destination, primarily intended for use with cmpp_stream()
but also used internally by cmpp for directing output to a buffer.
*/
struct cmpp_outputer {
/**
An optional descriptive name for the channel. The bytes
are owned elsewhere and are typically static or similarly
long-lived.
*/
char const * name;
/**
Output channel.
*/
cmpp_output_f out;
/**
flush() implementation. This may be NULL for most uses of this
class. Cases which specifically require it must document that
requirement so.
*/
cmpp_flush_f flush;
/**
Optional: if not NULL, it must behave appropriately for its state
type, cleaning up any memory it owns.
*/
cmpp_outputer_cleanup_f cleanup;
/**
State to be used when calling this->out() and this->flush(),
namely: this->out(this->state, ... ) and
this->flush(this->state).
Whether or not any given instance of this class owns the memory
pointed to by this member must be documented for their cleanup()
method.
Because cmpp_outputer instances frequently need to be stashed and
unstashed via bitwise copying, it is illegal to replace this
pointer after its initial assignment. The object it points to may
be mutated freely, but this pointer must stay stable for the life
of this object.
*/
void * state;
};
/**
Empty-initialized cmpp_outputer instance, intended for
const-copy initialization.
*/
#define cmpp_outputer_empty_m \
{.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL}
/**
Empty-initialized cmpp_outputer instance, intended for
non-const-copy initialization. These copies can, for purposes of
cmpp's output API, be used as-is to have cmpp process its inputs
but generate no output.
*/
CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty;
/**
If o->out is not NULL, the result of o->out(o->state,p,n) is
returned, else 0 is returned.
*/
CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n);
/**
If o->flush is not NULL, the result of o->flush(o->state) is
returned, else 0 is returned.
*/
CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o);
/**
If o->cleanup is not NULL, it is called, otherwise this is a no-op.
*/
CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o);
/**
A cmpp_outputer initializer which uses cmpp_flush_f_FILE(),
cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its
implementation. After copying this, the state member must be
pointed to an opened-for-writing (FILE*).
*/
CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE;
/**
The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE. If
self->state is not NULL then it is passed to fclose() (_unless_ it
is stdin, stdout, or stderr) and set to NULL. self->name is
also set to NULL.
*/
CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self);
/**
Sets pp's current directive delimiter to a copy of the
NUL-terminated zDelim. The delimiter is the sequence which starts
lines and distinguishes cmpp directives from other input, in the
same way that C preprocessors use '#' as a delimiter.
If zDelim is NULL then the default delimiter is used. The default
delimiter can be set when compiling the library by defining
CMPP_DEFAULT_DELIM to a quoted string value.
zDelim is assumed to be in UTF-8 encoding. If any bytes in the
range (0,32) are found, CMPP_RC_MISUSE is returned and pp's
persistent error state is set.
The delimiter must be short and syntactically unambiguous for the
intended inputs. It has a rather arbitrary maximum length of
32[^1], but it's difficult to envision it being remotely
human-friendly with a delimiter longer than 3 bytes. It's
conceivable, but seemingly far-fetched, that longer delimiters
might be interesting in some machine-generated cases, e.g. using a
random sequence as the delimiter.
Returns 0 on success. Returns non-0 if called when the delimiter
stack is empty, if it cannot copy the string, or zDelim is deemed
unsuitable for use as a delimiter. Calling this when the stack is
empty represents a serious API misuse (possibly indicating that
cmpp_delimiter_pop() was used out of scope) and will trigger an
assert() in debug builds. Except for that last case, errors from
this function are recoverable (see cmpp_errf()).
[^1]: the historical length limit was 12 but we actually hit that
in this very tree when the "//%amalgamation" pieces were added to
replace having to shell out to /usr/bin/sed.
*/
CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim);
/**
Fetches pp's current delimiter string, assigning it to *zDelim.
The string is owned by pp and will be invalidated by any call to
cmpp_delimiter_set() or the #delimiter script directive.
If, by some odd usage constellation, this is called after an
allocation of the delimiter stack has failed, this will set *zDelim
to the compile-time-default delimiter. That "cannot happen" in
normal use because such a failure would have been reacted to and
this would not be called.
*/
CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim);
/**
Pushes zDelim as the current directive delimiter. Returns 0 on
success and non-zero on error (invalid zDelim value or allocation
error). If this returns 0 then the caller is obligated to
eventually call cmpp_delimiter_pop() one time. If it returns non-0
then they must _not_ call that function.
*/
CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim);
/**
Must be called one time for each successful call to
cmpp_delimiter_push(). It restores the directive delimimter to the
value it has when cmpp_delimiter_push() was last called.
Returns pp's current error code, and will set it to non-0 if called
when no cmpp_delimiter_push() is active. Popping an empty stack
represents a serious API misuse and may fail an assert() in debug
builds.
*/
CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp);
/**
If z[*n] ends on a \n or \r\n pair, it/they are stripped,
*z is NUL-terminated there, and *n is adjusted downwards
by 1 or 2. Returns true if it chomped, else false.
*/
CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n);
/**
A basic memory buffer class. This is primarily used with for
creating dynamic strings and as a target for cmpp_outputer_b to
capture arbitrary output for later use.
TODO: add the heuristic that an nAlloc of 0 with a non-NULL z
refers to externally-owned memory. This would change the
buffer-write APIs to automatically copy it before making any
changes. We have code for this in the trees this class derives
from, it just needs to be ported over. It would allow us to avoid
allocating in some cases where we need a buffer but it will always
(or commonly) be a copy of a static string, like a single space.
*/
struct cmpp_b {
/**
This buffer's memory, owned by this object. This library
exclusively uses sqlite3_realloc() and friends for memory
management.
If this pointer is taken away from this object then it must
eventually be passed to cmpp_mfree() or sqlite3_free().
This class's APIs strive to keep valid buffers NUL-terminated at
byte this->z[this->n]. Any deviation from that is (A) unknown and
(B) to be considered a bug.
*/
unsigned char * z;
/**
Number of bytes of this->z which are in use, not counting any
automatic NUL terminator which this class's APIs may add.
*/
cmpp_size_t n;
/**
Number of bytes allocated in this->z.
Potential TODO: use a value of zero here, with a non-zero this->z
or this->n, to mean that this->z is owned elsewhere. This would
cause cmpp_b_append() to copy its original source before
appending. Similarly, cmpp_b_clear() would necessarily _not_ free
this->z. We've used that heuristic in a predecessor of this class
in another tree to good effect for years, but it's not certain
that we'd get the same level of utility out of that capability as
we do in that other project.
*/
cmpp_size_t nAlloc;
/**
cmpp_b APIs which may fail will set this. Similarly, most
of the cmpp_b APIs become no-ops if this is non-0.
*/
int errCode;
};
/**
An empty-initialized cmpp_b struct for use in const-copy
initialization.
*/
#define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0}
/**
An empty-initialized cmpp_b struct for use in non-const copy
initialization.
*/
extern const cmpp_b cmpp_b_empty;
/**
A proxy for sqlite3_str_new() which returns NULL on OOM instead of
returning a dummy object. If it returns NULL then pp's error state
will be update with CMPP_RC_OOM.
*/
CMPP_EXPORT sqlite3_str * cmpp_sqlite3_str_new(cmpp *pp);
/**
Proxy for sqlite3_str_finish() which updates pp's error state if s
has error state. Returns s's string on success and NULL on
error. The returned string must eventualy be passed to
cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so
callers must check pp->err to see if NULL is an error.
If n is not NULL then on success it is set to the byte length of
the returned string, not including its NUL terminator.
If this is called while pp has error state, this does not override
that error state but will return 0.
*/
CMPP_EXPORT char * cmpp_sqlite3_str_finish(cmpp *pp, sqlite3_str *s, int * n);
/**
A class for holding error state.
This class _should_ be called cmpp_err, but by the time it was
added we already had a family of cmpp_err-named APIs and no good
new names for them. Naming it cmpp_e would cause me confusion
because `_e` is the conventional suffix for enums in this library.
*/
struct cmpp_errinfo {
/** Error code. 0 = no error. */
int code;
/**
Buffer memory owned by this object. Updated by many different
cmpp_errinfo APIs and freed by cmpp_errinfo_dtor().
*/
cmpp_b b;
/**
Might point to this->b.z or it might point to a static/const
error strings, in particular for use with OOM.
Except on allocation errors, the cmpp_errinfo APIs which update
this->b point this to this->b.z.
*/
char const *zStatic;
};
typedef struct cmpp_errinfo cmpp_errinfo;
#define cmpp_errinfo_empty_m {.code=0,.b=cmpp_b_empty_m,.zStatic=0}
extern const cmpp_errinfo cmpp_errinfo_empty;
/**
Sets err's state to the given code and string and returns either rc
or, if an allocation error happens while setting the message,
CMPP_RC_OOM.
If rc is 0 then zMsg is ignored, else the first nMsg bytes of zMsg
are copied into err->b, or through zMsg's first NUL byte if nMsg is
negative.
To simplify certain uses, err may be NULL, in which case this simply
returns rc.
*/
CMPP_EXPORT int cmpp_errinfo_set(cmpp_errinfo *err, int rc, char const *zMsg,
cmpp_strlen_t nMsg);
/** va_list form of cmpp_errinfo_set(), supporting sqlite3_str_vappendf()'s
formatting options. */
CMPP_EXPORT int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt,
va_list va);
/** Variadic form of cmpp_errinfo_setf(). */
CMPP_EXPORT int cmpp_errinfo_setf(cmpp_errinfo *err, int rc,
char const *zFmt, ...);
/**
Clears err and frees all memory it owns but does not free err.
*/
CMPP_EXPORT void cmpp_errinfo_dtor(cmpp_errinfo *err);
/**
Resets err's state a non-error state but retains any
memory it has buffered. Returns err.
*/
CMPP_EXPORT cmpp_errinfo * cmpp_errinfo_reuse(cmpp_errinfo *err);
/**
Frees b->z and zeroes out s but does not free s.
*/
CMPP_EXPORT void cmpp_b_clear(cmpp_b *b);
/**
If b has content, b->nUsed is set to 0 and b->z is NUL-terminated
at its first byte, else this is a no-op. b->errCode is set to
0. Returns s.
*/
CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *b);
/**
Swaps all contents of the given buffers, including their persistent
error code.
*/
CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r);
/**
If b->errCode is 0 and b->nAlloc is less than n, b->z is
reallocated to have at least n bytes, else this is a no-op. Returns
0 on success, CMPP_RC_OOM on error.
*/
CMPP_EXPORT int cmpp_b_reserve(cmpp_b *b, cmpp_size_t n);
/**
Works just like cmpp_b_reserve() but on allocation error it updates
pp's error state. If called when pp has error state, that code is
returned and this function has no side-effects.
*/
CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n);
/**
Appends n bytes from src to b, reallocating os as necessary.
Returns 0 on succes, CMPP_RC_OOM on allocation error.
This may modify b even if n is 0, to ensure that it always has a
NUL terminator. src may only be NULL if n is 0.
Errors from this function, and the other cmpp_b_append...()
variants, are recoverable (see cmpp_errf()).
*/
CMPP_EXPORT int cmpp_b_append(cmpp_b * b, void const *src,
cmpp_size_t n);
/**
Works like cmpp_b_append() except that if n is negative,
strlen() is used to calculate it.
*/
CMPP_EXPORT int cmpp_b_append_str(cmpp_b * b, char const * str,
cmpp_strlen_t n);
/**
Convenience form of cmpp_b_append_str() which requires a string
literal.
*/
#define cmpp_b_append_c(B,STR) cmpp_b_append(B, STR "",sizeof(STR)-1)
/**
Works just like cmpp_b_append() but on allocation error it updates
pp's error state. If called when pp has error state, that code is
returned and this function has no side-effects.
*/
CMPP_EXPORT int cmpp_b_append4(cmpp * pp, cmpp_b * b,
void const * src, cmpp_size_t n);
/**
Works like cmpp_b_append4() except that if n is negative,
strlen() is used to calculate it.
*/
CMPP_EXPORT int cmpp_b_append4_str(cmpp * pp, cmpp_b * b,
char const *str, cmpp_strlen_t n);
/**
Convenience form of cmpp_b_append4_str() which requires a string
literal.
*/
#define cmpp_b_append4_c(PP,B,STR) \
cmpp_b_append4(PP, B, STR "", sizeof(STR)-1)
/**
Appends ch to the end of os->z, expanding as necessary, and
NUL-terminates os. Returns os->errCode and is a no-op if that is
non-0 when this is called. This is slightly more efficient than
passing length-1 strings to cmpp_b_append() _if_ os's memory
is pre-allocated with cmpp_b_reserve(), otherwise it may be
less efficient because it may need to allocate frequently if used
repeatedly.
*/
CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch);
/**
Appends a decimal string representation of d to os. Returns
os->errCode and is a no-op if that is non-0 when this is called.
*/
CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d);
/** int64_t counterpart of cmpp_b_append_i32(). */
CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d);
/**
Compares b to [m,m+n) using memcmp() semantics as if b were the
LHS. That is: returns 0 if b and z contain equivalent content,
negative if b's content is "less than" z's, and positive if b's
content is "greater than" z.
If n<0 then m is assumed to be a NUL-terminated string and
strlen() is used to calculate it.
This function will misbehave with inputs having more than 31 bits
of length.
*/
CMPP_EXPORT int cmpp_b_cmp(cmpp_b const * b, void const *m,
cmpp_ssize32_t n);
/**
Unescapes b's contents (if any) inline, adjusting b->n accordingly
(it will never grow but may stay the same size or shrink).
Any sequences of (`\\`, `\b`, `\n`, `\t`, `\r`, `\"`, `\'`, `\``)
are replaced with their traditional single-byte counterparts. Other
bytes are passed through as-is. Other backslash sequences are left
as-is.
Similarly, `\uNNNN` and `\UNNNNNNNN`, where N is runs of hex
digits, unescape to their corresponding Unicode code point, or are
left as-is if they're invalid.
We don't do `\f` or `\v` because they're historically only used in
ugly hacks, like separators for bespoke formats, not "real
inputs". We don't do `\0` because embedded nuls via unescaping are
simply inappropriate in the context of this library.
Potential TODO: add an argument for configuring unescaping or maybe
erroring for unknown escape sequences (pass a cmpp_errinfo object).
Potential TODO: an argument to tell it the byte offset to start at,
to allow us to unescape being-appended-to buffers without
re-unescaping the existing body.
*/
CMPP_EXPORT void cmpp_b_unescape(cmpp_b * b);
/**
A thin wrapper around cmpp_chomp() which chomps b->z.
*/
CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b);
/**
Counts the number of newlines between [zBegin,zPos). zBegin must
be <= zEnd. zPos must be >= zBegin and <= zEnd. This function may
assert() that the preconditions are met.
The range need not be NUL-terminated and embedded NULs are counted
as for other bytes.
Returns the number of newlines and, if pCol is not NULL, sets the
0- and byte-based column number of zPos relative to the most recent
newline or (if no newlines were found) zBegin.
*/
CMPP_EXPORT cmpp_size_t cmpp_count_nl(unsigned char const *zBegin,
unsigned char const * const zEnd,
unsigned char const * const zPos,
cmpp_size_t *pCol);
/**
A cmpp_output_f() impl which requires that its first argument be a
(cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes
of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in
reallocation error. On success it always NUL-terminates buffer->z.
A NULL buffer is treated as success but has no side effects.
Example usage:
```
cmpp_b b = cmpp_b_empty;
int rc = cmpp_stream(cmpp_input_f_FILE, stdin,
cmpp_output_f_b, &b);
...
cmpp_b_clear(&b);
```
*/
CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src,
cmpp_size_t n);
/**
A cmpp_outputer_cleanup_f() implementation which requires that
self->state be either NULL or a cmpp_b pointer. This function
passes it to cmpp_b_clear(). It does _not_ set self->state or
self->name to NULL.
*/
CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self);
/**
A cmpp_outputer prototype which can be copied to use a dynamic
string buffer as an output source. Its state member must be set (by
the client) to a cmpp_b instance. Its out() method is
cmpp_output_f_b(). Its cleanup() method is
cmpp_outputer_cleanup_f_b(). It has no flush() method.
*/
extern const cmpp_outputer cmpp_outputer_b;
/**
Returns a string containing version information in an unspecified
format.
*/
CMPP_EXPORT char const * cmpp_version(void);
/**
Policies for how to handle undefined @tokens@ when performing
content filtering.
*/
enum cmpp_atpol_e {
/** Sentinel value. */
cmpp_atpol_invalid = -1,
/** Turn off @token@ parsing. */
cmpp_atpol_OFF = 0,
/** Retain undefined @token@ - emit it as-is. */
cmpp_atpol_RETAIN,
/** Elide undefined @token@. */
cmpp_atpol_ELIDE,
/** Error for undefined @token@. */
cmpp_atpol_ERROR,
/** A sentinel value for use with cmpp_out_expand(). */
cmpp_atpol_CURRENT,
/**
This isn't _really_ the default. It's the default for the --@ CLI
flag.
*/
cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR,
/**
The compile-time default for all cmpp instances.
*/
cmpp_atpol_DEFAULT = cmpp_atpol_OFF
};
typedef enum cmpp_atpol_e cmpp_atpol_e;
/**
Policies describing how cmpp should react to attempts to use
undefined keys.
*/
enum cmpp_unpol_e {
/* Sentinel. */
cmpp_unpol_invalid,
/** Treat undefined keys as NULL/falsy. This is the default. */
cmpp_unpol_NULL,
/** Trigger an error for undefined keys. This should probably be the
default. */
cmpp_unpol_ERROR,
/**
The compile-time default for all cmpp instances.
*/
cmpp_unpol_DEFAULT = cmpp_unpol_NULL
};
typedef enum cmpp_unpol_e cmpp_unpol_e;
/**
Masks to embed some metadata into cmpp_tt_e token
type IDs.
*/
enum cmpp_tt_mask_e {
/** Mask of the "id" bits for a token ID. */
cmpp_tt_mask_id = 0x00fff,
/** Mask of the "shape" bits for a token ID, describing the general
data type so that we can group certain constructs together under
a single mask for switch/case dispatching purposes. */
cmpp_tt_mask_shape = 0x0f000,
/** Anything which does not have a closer match
is a Word. */
cmpp_tt_shape_word = 0x01000,
/** For numbers, optionally signed. Currently only decimal
integers. */
cmpp_tt_shape_number = 0x02000,
/** For "", '', and `` strings. */
cmpp_tt_shape_string = 0x04000,
/** For {}, [], and () macro-tokens. */
cmpp_tt_shape_group = 0x08000,
/** For expression operators. */
cmpp_tt_shape_op = 0x10000,
/** For EOL, EOC, and EOF. */
cmpp_tt_shape_flow = 0x20000
};
/**
cmpp token types, currently distinct from the public API's cmpp_tt_map
as we consolidate the two.
The first argument to E is the tail of the cmpp_tt_ ## TAIL part
of the enum entry's name. The second is the cmpp_tt_mask_id part
of its enum value (integer). The third is the mask/non-ID part of
its value. The enum's value is the logical OR of arguments 2 and 3.
Maintenance reminder: we need these even in %lite builds for use by
cmpp_tizer.
*/
#define cmpp_tt_map(E) \
E(None, 0, 0) \
E(Error, 1, cmpp_tt_shape_flow) \
/** Anything with no more specific type. */ \
E(Word, 2, cmpp_tt_shape_word) \
E(StringSQ, 3, cmpp_tt_shape_string) \
E(StringDQ, 4, cmpp_tt_shape_string) \
/** Backtick string. */ \
E(StringBT, 5, cmpp_tt_shape_string) \
/** Decimal int, optionally signed but some
contexts will elide a leading + when
rendering then token. */ \
E(IntDec, 6, cmpp_tt_shape_number) \
E(RawLine, 7, 0) \
E(OpPlus, 100, cmpp_tt_shape_op) \
E(OpMinus, 101, cmpp_tt_shape_op) \
E(OpNot, 102, cmpp_tt_shape_op) \
E(OpAnd, 103, cmpp_tt_shape_op) \
E(OpOr, 104, cmpp_tt_shape_op) \
E(OpGlob, 105, cmpp_tt_shape_op) \
E(OpCmpEq, 106, cmpp_tt_shape_op) \
E(OpCmpNeq, 107, cmpp_tt_shape_op) \
E(OpCmpLt, 108, cmpp_tt_shape_op) \
E(OpCmpLe, 109, cmpp_tt_shape_op) \
E(OpCmpGt, 110, cmpp_tt_shape_op) \
E(OpCmpGe, 111, cmpp_tt_shape_op) \
E(OpShiftL, 112, cmpp_tt_shape_op) \
E(OpShiftL3, 113, cmpp_tt_shape_op) \
E(OpShiftR, 114, cmpp_tt_shape_op) \
E(OpBitwiseAnd, 115, cmpp_tt_shape_op) \
E(OpBitwiseOr, 116, cmpp_tt_shape_op) \
E(OpBitwiseNegate,117, cmpp_tt_shape_op) \
/*SQLite does have xor
E(OpXOr, 118, cmpp_tt_shape_op)*/ \
E(OpMultiply, 119, cmpp_tt_shape_op) \
E(OpDivide, 120, cmpp_tt_shape_op) \
E(OpModulo, 121, cmpp_tt_shape_op) \
E(GroupBrace, 200, cmpp_tt_shape_group) \
E(GroupParen, 201, cmpp_tt_shape_group) \
E(GroupSquiggly, 202, cmpp_tt_shape_group) \
E(EOL, 253, cmpp_tt_shape_flow) \
E(EOC, 254, cmpp_tt_shape_flow) \
E(EOF, 255, cmpp_tt_shape_flow) \
/* Arg 2 MUST fit in cmpp_tt_mask_id. */ \
/* Refactoring crutches for legacy
token type compatibility: */ \
E(OpDefined, 300, cmpp_tt_shape_op) \
E(OpNotGlob, 301, cmpp_tt_shape_op) \
E(Noop, 302, cmpp_tt_shape_op) \
E(Null, 303, cmpp_tt_shape_op) \
E(DbString, 304, cmpp_tt_shape_word) \
E(ThinArrowR, 305, cmpp_tt_shape_op) \
E(ThinArrowL, 306, cmpp_tt_shape_op) \
E(AnyType, 307, 0)
/**
Token type IDs and their metadata for use with cmpp_itch, namely
cmpp__token::ttype. No entries have well-defined values except
that cmpp_tt_None is guaranteed to be 0.
cmpp_tt_EOF represents a _logical_ EOF: the end of the tokenizer's
input. For sub-parsing this represent the end of a grouping token.
*/
enum cmpp_tt_e {
#define E(NAME,ID,MASK) cmpp_tt_ ## NAME = ID | MASK,
cmpp_tt_map(E)
#undef E
cmpp_tt__last
};
typedef enum cmpp_tt_e cmpp_tt_e;
/**
If e is a cmpp_tt_e entry then a debugging-friendly string form of
it is returned, else 0 is returned. The returned bytes are static
and NUL-terminated.
*/
CMPP_EXPORT char const * cmpp_tt_cstr(int e);
/**
A snippet from a parent string, usually a parsed token and almost
never NUL-terminated. This is the fundamental atomic unit for
directive and command argument tokenization.
*/
struct cmpp_token {
/** Start of the range. */
unsigned char const *z;
/** Number of bytes in this->z. */
unsigned int n;
/**
Start of the inner range (for group tokens), else the same as
this->z.
Legacy compatibility quirk: see cmpp_args_F_NO_SQUIGGLY_HACK.
*/
unsigned char const *zInner;
/** Number of bytes in this->zInner. */
unsigned int nInner;
/** From enum cmpp_tt_e. */
int ttype;
/*
Potential TODO: add:
cmpp_tizer const * tz;
So that we can derive source location info given only a
token. "The problem" with that is that it indirectly adds
sizeof(void*) to each cmpp_arg object (via each embedding one of
these).
*/
};
typedef struct cmpp_token cmpp_token;
#define cmpp_token_empty_m {.z=0,.n=0,.zInner=0,.nInner=0,.ttype=0}
extern const cmpp_token cmpp_token_empty;
#define cmpp_token_isa(TOKPTR,TTYPE) ((TTYPE)==(TOKPTR)->ttype)
typedef struct cmpp_tizer cmpp_tizer;
/**
Returns a pointer to t's string and assigns *n to its byte length.
If inner is true and t->zInner differs from t->z then the range
[t->zInner,t->nInner) is used, else [t->z,t->n) is used.
Achtung: tokens are rarely NUL-terminated.
*/
char const * cmpp_token_cstr3(cmpp_token const * t, cmpp_size_t * n, bool inner);
int cmpp_token_strcmp(cmpp_token const * t, char const *z, cmpp_strlen_t n);
#define cmpp_token_ustrcmp(T,Z,N) \
cmpp_token_strcmp((T),(unsigned char const *)(Z),(N))
/** Convenience form of cmpp_token_cstr3() which casts the result to
(unsigned char const *). */
#define cmpp_token_ucstr3(T,NP,I) \
(unsigned char const *)cmpp_token_cstr3((T),(NP),(I))
/**
A basic tokenizer for argument/script-like tokens.
*/
struct cmpp_tizer {
/**
Parent of a sub-tokenizer. (To be) Used for error reporting: by
knowing the whole input range we can provide better line/column
info.
*/
cmpp_tizer const *parent;
/** Optional error object through which to report detailed error
info. It is owned by whoever sets it. */
cmpp_errinfo * err;
/** Starting point. This memory must stay valid for the life
of the tokenizer. */
unsigned char const * zBegin;
/** One-after-the-end position. */
unsigned char const * zEnd;
/** Initialize == zBegin. zPos is never > zEnd. */
unsigned char const * zPos;
/** Bytes owned elsewhere. If NULL, inherit from this->parent
(see cmpp_tizer_name()). */
char const * name;
/**
Info for current token. Only valid after a call to
cmpp_tizer_next().
*/
cmpp_token token;
/**
Put-back token. We probably don't need this, as any back-tracking
would be done with call-local copies of tokens. It's overwritten
at the start of cmpp_tizer_next() unless tz is at EOF at the
time it's called (as opposed to hitting EOF in that call).
*/
cmpp_token pbToken;
/**
A place to mark location when an error is expected. If one does
happen, and it channels through the proper magic, the location of
this token will be included in the error message. The intent is
not so much that this be set immediately before each potential
error, but that it be set whenever a token is about to be
processed, so that exceptions through that handling can have a
sensible error position. It's not an exact science.
This token's size is not currently used, only its z member, but
we use a full token here for the sake of future experimentation.
Some cases don't have one, in which case they can set
this->errToken.z to a pointer _if_ it's known to live within
[this->zBegin,this->zEnd).
*/
cmpp_token errToken;
/**
True when we're at the "begin-of-expression". i.e. we're looking
for a command to run. This is true only when looking for the
initial token in a given Itch command line (where a line ends an
EOL or ';'). For subsequent searches in the same command line it
must be false. This is generally managed by cmpp_tizer_next() but
it wouldn't hurt for code which is responsible for finding
command lines to set it at the start of that process.
*/
bool atBOX;
};
/**
An empty-initialized cmpp_tizer struct for use in const copy
initialization.
*/
#define cmpp_tizer_empty_m { \
.parent=0, .err=0, \
.zBegin=0, .zEnd=0, .zPos=0, \
.token=cmpp_token_empty_m, \
.pbToken=cmpp_token_empty_m, \
.errToken=cmpp_token_empty_m, \
.atBOX=true \
}
/**
An empty-initialized cmpp_tizer struct for use in non-const copy
initialization.
*/
extern const cmpp_tizer cmpp_tizer_empty;
/**
Sets up tz to tokenize the given half-open input range (zBegin,zBegin+nBytes),
except that if nBytes is negative then strlen() is used to calculate it.
The err arg may be NULL but it won't be able to report precise
error location info via cmpp_tizer_next().
It is safe to pass the same tz instance multiple times. The
instances own no dynamic state and this function completely
overwrites tz.
Results are specifically undefined if:
- zBegin is NULL.
- zBegin is invalidated during the lifetime of tz.
- The contents of zBegin are modified during the lifetime of tz.
i.e. don't do that.
This function may assert() that its inputs look sane because this
is a column upon which many internals are based and it need not
tolerate bad inputs.
If a call succeeds for any given instance, the caller is obligated
to eventually pass pass tz to cmpp_tizer_dtor(), even if later
calls to this fail. It is safe to pass it to cmpp_tizer_dtor()
regardless of whether this fails, but an initial call is guaranteed
not to leak resources, whereas a follow-up call without an
intervening cmpp_tizer_dtor() call may leak resources if cmpp_tizer
every gains any. The destructor requirement is to leave us the
option of eventually giving cmpp_tizer dynamic state (plus, it
zeroes out the object to help avoid misuse after its usable
lifetime).
*/
CMPP_EXPORT void cmpp_tizer_init(cmpp_tizer * tz, cmpp_errinfo *err,
unsigned char const *zBegin,
cmpp_strlen_t nBytes);
/**
Copies "inherited" info from tzParent to tz, e.g. sets tz->parent
to tzParent. If tz->err is NULL then it sets tz->err to
tzParent->err, else tz->err is retained. Likewise for tz->name
but uses cmpp_tizer_name(tzParent) to get it.
Results are undefined if tz's input range is not fully contained
within tzParent's, and this function may assert, or otherwise fail
fatally, if that is the case (because it represents a serious
footgun for any downstream code which relies on those semantics).
i.e. don't do that.
*/
CMPP_EXPORT void cmpp_tizer_inherit(cmpp_tizer const * tzParent,
cmpp_tizer * tz);
/**
Initializes a sub-tokenizer which is limited to the contents of
tzParent->token. The token's inner range, if any, is used for
the input, so the delimiters for strings and grouping tokens
are not part of the input.
If tok is NULL then tzParent->token is used.
This also sets tzSub->parent to tzParent.
*/
CMPP_EXPORT void cmpp_tizer_init_sub(cmpp_tizer const *tzParent,
cmpp_token const * tok,
cmpp_tizer * tzSub);
/**
Returns the first name found in tz or one of tz->parent
(recursively).
*/
CMPP_EXPORT char const * cmpp_tizer_name(cmpp_tizer const *tz);
/**
If tz holds any state, this relenquishes that state, then tz is
zeroed out, available for discarding or passing it to
cmpp_tizer_init() again. It is illegal to pass an uninitialized
object to this function.
Achtung: tz->err is owned by whoever put it there. This function
zeroes out that member but does not modify that object.
*/
CMPP_EXPORT void cmpp_tizer_dtor(cmpp_tizer * tz);
/**
Calculates the whole input range of tz and its parents (if they
share an encompassing byte range), for purposes of calculating line
counts. Sets *zBegin and *zEnd to the half-open range.
*/
CMPP_EXPORT void cmpp_tizer_full_range(
cmpp_tizer const * tz, unsigned char const **zBegin,
unsigned char const **zEnd
);
/**
Returns true if tz->token.zPos is either at/past tz->zEnd or
cmpp_tt_EOF==tz->token.ttype.
*/
CMPP_EXPORT bool cmpp_tizer_eof(cmpp_tizer const *tz);
/**
Returns true if z lies within, or at the end of, tz's complete
input range, as per cmpp_tizer_full_range().
*/
CMPP_EXPORT bool cmpp_tizer_contains(cmpp_tizer const * tz,
unsigned char const *z);
/**
Rewinds the tokenizer to its initial tokenization position, such
that the next call tp cmpp_tizer_next() will start at that
position. This clears all cmpp_token-type members of tz and sets
tz->atBOX to true.
*/
CMPP_EXPORT void cmpp_tizer_rewind(cmpp_tizer *tz);
/**
Flags for use with cmpp_tizer_next().
*/
enum cmpp_tizer_next_e {
/** Sentinel entry. */
cmpp_tizer_next_NONE = 0,
/**
Tells cmpp_tizer_next() to skip newlines, treating them as
whitespace or comments. By default newlines are end-of-command
distinct tokens reported with the type cmpp_tt_EOL. Sometimes
newlines are EOL tokens, sometimes they're spaces, and it's up to
the caller to specify which is which.
*/
cmpp_tizer_next_SKIP_NL = 0x01,
/**
Tells cmpp_tizer_next() to skip over C-style comments. They may
span lines. For contexts where newlines end tokenization, the
newlines within a comment are not considered newlines.
*/
cmpp_tizer_next_SKIP_C_COMMENTS = 0x02
/**
TODO: a flag which triggers the workaround
described arg.c:cmpp__squiggly_strip_lead().
*/
};
/**
Fetches the next token and updates tz->token.
On success, returns 0, and tz->token contain the relevant
information. On error tz->token.type==cmpp_tt_Error, tz->token.n
will be 0, and tz->token.z will point to the best approximation of
where the error is.
At EOF, 0 is returned, tz->token.z points to the one-after-the-end
position, tz->n==0, and tz->token.type==cmpp_tt_EOF.
*/
CMPP_EXPORT int cmpp_tizer_next(cmpp_tizer *tz, cmpp_flag32_t flags);
/**
Flags for use with cmpp_token_content().
*/
enum cmpp_token_content_e {
/** Sentinel entry. */
cmpp_token_content_NONE = 0,
/**
This returns the whole content of the token, regardless of its
type and with no grouping wrappers or + signs removes.
*/
cmpp_token_content_WHOLE = 0x01,
/**
Do not pass the destination buffer to cmpp_b_reuse().
*/
cmpp_token_content_APPEND = 0x02,
/**
If the token is a string or a group token, trim the content of
leading and trailing spaces.
*/
cmpp_token_content_TRIM = 0x04,
/**
By default it will apply unescaping to string-type tokens unless
this flag is set. It does not unescape squiggly groups, despite
them being a type of string.
*/
cmpp_token_content_NO_UNESCAPE = 0x08
};
/**
Returns the content part of the given token by appending to b or
(depending on the flags) replacing b's content with a copy of tok's
contents, possibly a transformed form of them (depending on the
tok->ttype and flags).
If tok is NULL then tz->token is used.
The target buffer is passed to cmpp_b_reuse() unless flags contains
the cmpp_token_content_APPEND flag.
Returns 0 on success. Errors are propagated via tz->pp.
The following tok->ttype values do not produce any output and cause
0 to be returned:
cmpp_tt_EOF, cmpp_tt_Error, cmpp_tt_None
*/
CMPP_EXPORT int cmpp_token_content(cmpp_tizer const * tz,
cmpp_token const * tok, cmpp_b * b,
cmpp_flag32_t flags);
/**
Calculates the 1-based line and 0-based column of the given token
(or tz->token if tok is NULL) and writes them *l and *c.
*/
CMPP_EXPORT void cmpp_token_lc(cmpp_tizer const * tz,
cmpp_token const * tok,
cmpp_size_t *l, cmpp_size_t *c);
/**
True if (cmpp_token const *)TOKEN's contents match the string literal
STR, else false.
*/
#define cmpp_token_equals_c(TOKEN,STR) \
(sizeof("" STR)-1==(TOKEN)->n && 0==memcmp(STR,(TOKEN)->z,sizeof(STR)-1))
/**
True if (cmpp_token const *)TOKEN's contents match the string literal
STR or ("-" STR), else false. The intent is that "-flag" be passed
here to tolerantly accept either "-flag" or "--flag".
*/
#define cmpp_token_isflag_c(TOKEN,STR) \
(cmpp_token_equals_c(TOKEN,STR) || cmpp_token_equals_c(TOKEN, "-" STR))
/**
Returns true if (t->id & cmpp_tt_mask_shape)
is cmpp_tt_shape_group, else false.
*/
CMPP_EXPORT bool cmpp_token_is_group(cmpp_token const * t);
/**
Returns true if (t->id & cmpp_tt_mask_shape)
is cmpp_tt_shape_string, else false.
*/
CMPP_EXPORT bool cmpp_token_is_string(cmpp_token const * t);
/**
If z is 0 then the error position is cleared. Else if z is within
tz's full input range (including parents), this sets tz's error
position to z. Else this has no side effect.
To be clear: this does not put the tokenizer into an error
state. This records the given position for the sake of any
potential following error. APIs which use cmpp_tizer frequently set
this for each being-processed token, such that that position will
be used for error reporting if something goes wrong in a following
step. The overwhelming majority of the time, this will precisely
pinpoint the location of an error.
*/
CMPP_EXPORT void cmpp_tizer_errpos_set(cmpp_tizer *tz, unsigned char const *z);
/**
Like cmpp_tizer_errpos_set() but requires non-NULL arguments and it
uses the position tok->z.
*/
CMPP_EXPORT void cmpp_tizer_errpos_set_t(cmpp_tizer *tz, cmpp_token const * tok);
/**
If tz is not NULL and has any token set which could potentially be
used for an error position, its address is returned, else 0 is
returned. This prefers tok->errToken, then tz->token, then
tz->zPos, in that order.
This does _not_ imply that there is an error at that position: this
is used for trying to guess a position for a pending error report
when one is otherwise not provided. (The internals which make use
of cmpp_tizer make an honest effort to update the _potential_ error
position as they work so that any errors they trigger will have
accurate location information.)
*/
CMPP_EXPORT unsigned char const * cmpp_tizer_errpos(cmpp_tizer const * tz);
/**
Returns a new sqlite3_str which is prefixed (ideally) with error
location information for tz, specifically for position z. Ownership
of the object is transferred to the caller, who is generally
expected to append to it as if nothing were there and eventually
pass it to sqlite3_str_finish() to finalize it and take over its
C-string result. Returns NULL on OOM[^1].
If z is NULL then cmpp_tizer_errpos() is used.
This function may assert() that z lives in tz's range - any value
out of that range represents a serious misuse of the API. In
non-debug builds it will return a new, but empty, string in that
case.
Very minor caveat: the returned object has no access to an sqlite3
DB handle, which means that it is not subject to the size limits
configured for the DB owned by any given cmpp instance. Thus it is
possible, but only through what may fairly be characterized as
blatant malicious misuse, that this string can grow larger than the
underlying sqlite3 db is configured to accept, leading to
downstream errors if, e.g., the string is used as a the value for a
db column. That said: this function is invariably used for error
output, not db data, so this caveat is largely moot.
[^1]: sqlite3_str_new() _never_ returns NULL. On OOM it returns a
dummy object which is in a perpetual error state. This API,
however, checks for that and will return NULL on OOM.
*/
CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix(cmpp_tizer const *tz,
unsigned char const *z);
/**
A proxy for cmpp_tizer_err_prefix() which takes a token as
input. If it's null then tz->token is used.
*/
CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix_t(cmpp_tizer const *tz,
cmpp_token const *tok);
/**
Convenience helper to loop over a cmpp_tizer until it reaches EOF
on an error. This can be used within Itch commands to iterate over
script-containing strings and token lists.
TZP = a cmpp_tizer_init()'d (cmpp_tizer*).
RC = the name of an int-type var which will be set to the
cmpp_tizer_next() loop on each iteration. It is set to 0 before
starting the loop. If it's 0 after the loop, the tokenizer has
reached its EOF. It it's not 0 then it hit an error and will have
reported it via TZP->err if that's not NULL. The condition (0==RC)
is checked before each step and the loop is aborted if it's not
0. i.e. setting it to non-0 will stop the loop on the next
iteration without fetching a following token.
FLAGS = the second argument to cmpp_tizer_next(). If it's a
variable name instead of a literal, the loop body may change the
flags during the loop to affect the next interation.
POST is the optional Z part of for(X;Y;Z). It may be empty.
Usage:
```
int rc;
cmpp_tizer tz = cmpp_tizer_empty;
cmpp_token const * tok = &tz->token;
cmpp_tizer_init(tz, 0, "a b c", -1);
cmpp_tizer_foreach4(tz, rc, 0,) {
... use tok->ttype and friends ...
}
if( rc ){error;}
else {reached EOF or break'd out of the loop;}
```
Credits: this trick was gleaned from the ParTcl sources.
*/
#define cmpp_tizer_foreach4(TZP,RC,FLAGS,POST) \
RC = 0; \
for( ; (TZP)->zPos<(TZP)->zEnd \
&& 0==RC \
&& (0==(RC=cmpp_tizer_next(TZP,FLAGS))) \
&& cmpp_tt_EOF!=(TZP)->token.ttype; \
POST )
/** Slightly-simplified form of cmpp_size_foreach4() which elides that
one's fourth argument. */
#define cmpp_tizer_foreach3(TZP,RC,FLAGS) \
cmpp_tizer_foreach4(TZP, RC, FLAGS,)
/**
A single argument for a cmpp_f() implementation. When a
cmpp_d::flags does not have the cmpp_f_F_ARGS_RAW bit set then the
part of the input immediately following the directive (and on the
same logical line).
These objects are created by cmpp instances as they parse directive
lines in an input stream, and they pass them, using the cmpp_f()
interface, to the native implementations for each
directive. Similarly, cmpp_itch uses this for command arguments
(which, when it comes down to it, are the same thing except for the
small detail of string interpolation performed by the script
environment).
Lifetime:
For the _most_ part:
Each of these is valid for the duration of the cmpp_f() call into
which the argument was initially passed. Their memory may be freed,
recycled, or otherwise vanish, the moment the cmpp_f() impl
returns.
Caveat:
cmpp_f() impls which do _not_ use the cmpp_f_F_ARGS_V4 flag may,
depending on what type of work they do, be subject to lifetime
invalidation of these objects during the call to
cmpp_f(). Specifically, that happens in impls which themselves
consume from the input stream (e.g. using cmpp_dx_consume()).
Implementations which do not do that have the same lifetime
guarantees as V4-using implementations. The workaround for such
cases is to use cmpp_dx_args_clone() but (A) the V4 argument
handling obviates the need for it and (B) that option will be
removed once all built-in directives are ported to V4.
Historically they have been used together with cmpp_args, a
container for these, but as of 2026-09 the cmpp_args (plural) class
is effectively deprecated and not needed for directives which use
the cmpp_f_F_ARGS_V4 interface (which all new directives "should"
use).
*/
struct cmpp_arg {
/**
Token type from the cmpp_tt_e enum.
*/
int ttype;
/**
The arg's string value, shorn of any opening/closing quotes or ()
or {} or []. The args-parsing process guarantees to NUL-terminate
this. The bytes are typically owned by a cmpp_args object, but
clients may direct them wherever the need to, so long as the
bytes are valid longer than this object is.
The library guarantees that this is NUL-terminated in the
following contexts:
- When this object is passed to a cmpp_f() impl.
- When this object was initialized from a public cmpp_args
API, e.g. cmpp_args_parse() or cmpp_args_clone().
It does not gurantee so in other contexts.
This pointer memory is owned by the API which set it up and it
may be invalidated by any modifications to, e.g., a cmpp_args
object which initialized it.
*/
unsigned char const * z;
/**
The arg's effective length, in bytes, after opening/closing chars
are stripped. That is, its string form is the range
[this->z,this->z+n).
*/
unsigned n;
/**
DEPRECATED: this is part of the pre-V4 args handling and
must not be used in newer versions.
The next argument in the list. It is owned by whatever code set
it up (typically cmpp_args_parse()). The long-term plan is to get
rid of this in favor of argv-style traversal but current
cmpp_args infrastructure requires a linked list.
This will go away or will eventually only be set for directives
which do not opt in to the Newer Ways.
DO NOT use this in cmpp_f() impls tagged with cmpp_f_F_ARGS_V4:
it is unconditionally set to NULL for those.
*/
cmpp_arg const * next;
/**
If this argument came from a cmpp_itch interpreter, this will be
set to a buffer which contains the interpolated form of the
argument. That is, this->z might say "$x" and this->b might say
"3".
In non-cmpp_itch invocations of cmpp_f() this is currently always
0 but The Plan is to eventually offer the ability for directives
to opt-in to interpolations which most directives already
currently use. That would save significant amounts of code for
some directives.
This buffer is owned by this object but the very simple cmpp_arg
lifetime model makes it perfectly legal do one of the following
from cmpp_f() implementations:
- Pass this to cmpp_b_swap(), giving its memory to a buffer
they control.
- Pass the buffer to cmpp_itch_result_take(), which is
basically a proxy for cmpp_b_swap(). In this way a value can
be returned as-is from the command without any allocation.
- Take over ownership of b->z and wipe b's contents by copying
over it with cmpp_b_empty.
- Take over b, assign this->b to 0, and then eventually pass be
to cmpp_b_return(theOwningCmppInstance, b) to relinquish
it. DO NOT pass b to cmpp_free(). This of course requires a
non-const instance, and the ones passed to cmpp_f()
implementations are const.
*/
cmpp_b * interp;
/**
The raw argument token, referring to memory owned by whoever set
this object up. It is intended only for use by low-level
directives where the pointer relationships and memory lifetimes
are well-understood.
Depending on the args-parsing mode, token.z and token.zInner may
point into the containing script's memory, so will only be
NUL-terminated when they're actually at the right-most edge of
their input range (and only if _that_ is the whole input range
rather than from a sub-tokenizer). token.n and token.nInner MUST
be used to constrain the input range when token.z or token.zInner
are emitted or copied.
*/
cmpp_token token;
};
/**
Empty-initialized cmpp_arg instance, intended for const-copy
initialization.
*/
#define cmpp_arg_empty_m { \
.ttype=cmpp_tt_None, \
.z=0,.n=0,.next=0, \
.interp=0, \
.token=cmpp_token_empty_m \
}
/* Forward decls. */
typedef struct cmpp_dx cmpp_dx;
typedef struct cmpp__dx_pimpl cmpp__dx_pimpl;
typedef struct cmpp_d cmpp_d;
/**
Flags for use with cmpp_d::flags.
*/
enum cmpp_f_e {
/** Sentinel value. */
cmpp_f_F_none = 0,
/**
cmpp_dx_next() will not parse the directive's arguments. Instead,
it makes cmpp_dx::arg0 encapsulate the whole line of the
directive (sans the directive's name) as a single argument. The
only transformation which is performed is the removal of
backslashes from backslash-escaped newlines. It is up to the
directive's callback to handle (or not) the arguments.
As of early 2026-08, the memory set up for cmpp_f()
implementations for this argument style is stable. That is, it
remains valid until the cmpp_f() returns, rather than being
invalidated by recursing into the library from the cmpp_f() impl.
Some docs may mention such invalidation because it was real for a
long time, but that is now thankfully resolved.
This flag may be used together with cmpp_f_F_ARGS_V4 but
only exotic use cases would want to do so.
*/
cmpp_f_F_ARGS_RAW = 0x01,
/**
cmpp_dx_next() will parse the directive's arguments.
cmpp_dx::arg0 will point to the first argument in the list, or
NULL if there are no arguments.
If both cmpp_f_F_ARGS_LIST and cmpp_f_F_ARGS_RAW are specified,
it is unspecified which one applies. i.e. don't do that.
See also (and prefer): cmpp_f_F_ARGS_V4.
*/
cmpp_f_F_ARGS_LIST = 0x02,
/**
Specifies that the directive is using "version 4" of the
argument-passing mechanism, in which it populates
cmpp_f_args::argc and cmpp_f_args::argv with the count of and
pointers to the arguments. This mode always includes the
directive or command name being invoked as the first argument, so
argc is always at least 1 and argv is non-NULL unless this flag
is combined with cmpp_f_F_ARGS_RAW (see below).
The advantage to this flag over the other ARGS_... variants is
that the argument-related memory is not invalidated if the
callback recurses into the library. i.e. the memory in
cmpp_f_args::argv and cmpp_f_args::raw are stable for the
duration of the cmpp_f() call. The earlier modes now gurantee
that for cmpp_f_args::raw but not cmpp_dx::args (and the earlier
modes to not set cmpp_f_args::argv).
This is the argument-handling flag which All New Code Should
Use. Lots of old code still doesn't, which is sad, but we're
slowly getting them ported to v4.
Using this flag together with cmpp_f_F_ARGS_LIST is redundant and
harmless but the V4 semantics will be applied.
If cmpp_f_F_ARGS_RAW is used together with this flag then
it works a bit differently:
- cmpp_f_args::argc will be set to the number of arguments
tokenized by the preprocessor (it has to do so to be able to
get to the point of calling the cmpp_f()). Because V4 includes
the name of the being-invoked directive or command as argument
0, argc will always be at least one.
- cmpp_f_args::argv will be NULL. Such cmpp_f() impls are
expected to parse the arguments using (e.g.) cmpp_tizer with
the input range defined by cmpp_f_args::raw.z and
cmpp_f_args::raw.n (noting that it is generally NOT
nul-terminated). That range contains the directive's raw
argument line, up to (but not including) its final newline or
EOF. It is guaranteed to be non-NULL and have a non-0 length
because the directive or command name being invoked is the
first argument in the list. That range is guaranteed to point
back into the original source bytes, backslash-escaped newlines
and all.
*/
cmpp_f_F_ARGS_V4 = 0x40,
/**
Indicates that the cmpp_f() should not be available if the cmpp
instance is configured with the cmpp_ctor_F_SAFEMODE flag. All
directives which do any of the following are obligated to set
this flag:
- Filesystem or network access.
- Invoking external processes.
- Anything else which might be deamed "security-relevant".
When registering a directive which has both opener and closer
implementations, it is sufficient to set this only on the opener.
The library imposes this flag in the following places:
- Registration of a directive (cmpp_d_register()) or cmpp_itch
command (cmpp_itch_cmd_add()) with this flag will fail if
cmpp_is_safemode() is true for that cmpp instance.
- cmpp_dx_process() will refuse to invoke a directive with this
flag when cmpp_is_safemode() is true.
- cmpp_itch refuses to invoke commands with this flag when
cmpp_is_safemode() is true.
The latter two are also protected at registration-time, with the
above behaviors representing "defense in depth" rather than
resolving a known deficiency.
*/
cmpp_f_F_NOT_IN_SAFEMODE = 0x008,
/**
Call-only directives are only usable in [directive ...] "call"
contexts. They are not permitted to have a closing directive.
This flag has no effect on cmpp_itch commands.
*/
cmpp_f_F_CALL_ONLY = 0x010,
/**
Indicates that the directive is incapable of working in a [call]
context and an error should be triggered if it is. _Most_
directives which have a closing directive should have this
flag. The exceptions are directives which only conditionally use
a closing directive, like #query. This flag has no effect on
cmpp_itch commands.
*/
cmpp_f_F_NO_CALL = 0x020,
/**
Tells the framework that the directive's arity should be enforced
by the tokenizer (so that the directive implementation does not
have to deal with it).
A cmpp_d_reg::arity value of 0 or higher causes the framework,
when this flag is set, to enforce precisely that many arguments.
A negative value means to not enforce any argument count. This
capability is generally intended for script bindings (even then,
most of them need a negative arity).
This only really works for very simple cases, as most directives
require variadic flag and arg handling. This also does not
(cannot) enforce argument type restrictions - that has to be done
in the directive.
*/
cmpp_f_F_ENFORCE_ARITY = 0x040,
/**
Indicates that the directive requires an active cmpp_dx in order
to work (e.g. because it reads from the input stream). The
framework will not invoke such directives or cmpp_itch functions
if cmpp_dx_current() returns NULL, generating an error instead.
All commands which consume from a being-preprocessed input stream
require an active cmpp_dx.
Commands which convey all output using cmpp_itch_result_set() and
friends do not require a generally require a cmpp_dx. Those which
consume preprocessor input or emit output to the cmpp output
stream do.
*/
cmpp_f_F_NEEDS_DX = 0x080,
/**
See cmpp_args_F_NO_SQUIGGLY_HACK.
*/
cmpp_f_F_NO_SQUIGGLY_HACK = 0x100,
/**
Mask of the client-usable range for this enum. Values outside of
this mask are reserved for internal use and will be stripped from
registrations made with cmpp_d_register() or cmpp_itch_cmd_add().
*/
cmpp_f_F_MASK = 0x0ffff
};
/**
State related to invocation of a cmpp_f() function.
*/
struct cmpp_f_args {
/**
The preprocessor context.
*/
cmpp * const pp;
/**
The current directive context.
*/
cmpp_dx * const dx;
/**
Number of arguments passed to the directive. This is ONLY SET to
non-0 if this->d->flags contains cmpp_f_F_ARGS_V4. See that
flag's docs for details.
*/
unsigned const argc;
/**
Array of arguments passed to the directive. This is ONLY SET to
non-NULL if this->d->flags contains cmpp_f_F_ARGS_V4. See that
flag's docs for details.
*/
cmpp_arg const * const * const argv;
/**
The calling directive, if any. This is meant to be used in place
of the historical cmpp_dx::d member because this pointer does not
change when recursing into the library (whereas that one does).
We cannot eliminate that one from the API just yet but we are
headed there.
This is currently NULL when the cmpp_f() is called from a
cmpp_itch interpreter instead of the preprocessor. It's not clear
whether we want them to have access to this->dx and this->d, but
they can get either via cmpp_dx_current() anyway, noting that
this->dx->d may change when calling back into the library
to consume input.
*/
cmpp_d const * const d;
/** Brief usage string, intended for use in error messages and
--help functionality. */
char const * usage;
/** Opaque state defined by whoever created the associated
cmpp_f() impl, upon which interpretation depends. */
void * state;
/**
Flags from the cmpp_f_e enum.
*/
cmpp_flag32_t const flags;
/**
The raw arguments memory, for directives which want to use an
alternative tokenizer, emit debug info, or whatever it is what
one wants to do with it. cmpp_itch, for example, uses this to
ensure that its functions can map error location info to the
original source location (which we cannot do if we copy it
unless we add pieces to record the containing script's
position and account for it in error reporting).
Achtungs:
- For cmpp_itch commands (not cmpp_d callbacks, despite having
this same signature), this points directly back into the source
script. That _should_ be the case for non-Itch impls with the
cmpp_f_F_ARGS_V4 flag as well but cannot be without a
considerably compatibility break which is not worth reworking at
this point.
- For all other cases, this pointer lives in dynamic memory which
is edited very slightly from the original script, e.g. to remove
backslash-escaped newlines. (Itch scripts don't need that because
their tokenizer does it.) That memory stays valid for the life of
the cmpp_f() call and is invalidated the moment it
returns. (Directives use the same tokenizer but have backwards
compatibility constraints on how their implementations receive
the arguments until/unless they're ported to the V4 argument
handling.)
As of early 2026-09 the memory pointed to here remains valid
through the duration of the cmpp_f() call even if it recurses
into the library's content-consumption APIs. Before that change,
it would be invalidated if the impl called certain library APIs.
Design note: the ability to get the raw arguments was primarily
added so that directives like #pipe have something they can pass
as-is to external apps or APIs. That said: the arguments must be
able to at least pass through cmpp's argument tokenizer to get to
that point, and there are constructs which will not work well
with that. (We've yet to hit this limitation in any no small
amount of downstream inputs but it can certainly be contrived.)
*/
struct {
/** Start of the args memory. */
unsigned char const * const z;
/** Byte length of this->z. */
cmpp_size_t const n;
} raw;
/**
True if this invocation is a [call...] from preprocessor input,
as distinct from an cmpp_itch interpreter call.
*/
bool const isDxCall;
};
typedef struct cmpp_f_args cmpp_f_args;
/**
A helper for cmpp_f() implementations which use the
cmpp_f_F_ARGS_V4 flag: it uses cmpp_errf() to write a usage-style
error.
This currently sets the result code to CMPP_RC_ERROR, for
lack of a more specific one (we can't use CMPP_RC_ITCH_F_USAGE
because that one is stripped out of %lite builds).
Returns its error code.
This function relies on the fact that args->argc>0 for v4 arguments
and it dereferences args->argv[0] and its contents.
*/
CMPP_EXPORT int cmpp_f_v4_err_usage(cmpp_f_args const *args);
/**
Callback type for cmpp_d::impl::f() and cmpp_itch commands. cmpp
directives and cmpp_itch commands are all implemented as functions
with this signature. Implementations are called by the library as
the preprocessor consumes input and encounters directives, or when
its cmpp_itch interpreter calls one of its registered commands.
The first rule of callback is: to report errors (all of which end
processing of the current input) call one of the cmpp_err() family
of functions, then clean up any local resources, then return. The
library will recognize the error and propagate it. (cmpp_itch
callbacks should prefer cmpp_itch_err() and friends, as that one
distinguishes between script-only and non-script errors and will
not propagate the latter through to the owning cmpp context. We
cannot safely make that distinction in cmpp_err() because there
might, wildly hypothetically, be any number of cmpp_itch instances
and it would have to be told which, which is exactly what
cmpp_itch_err() and friends do.)
See the cmpp_f_args docs regarding memory lifetime of the argument
and its contents. They differ depending on the cmpp_f_e flags used
for registration.
Sidebar: despite cmpp_d and and cmpp_itch both using this
interface, the two things live in separate namespaces. That is,
internally there is no confusion about which is which, and trying
to invoke #someItchCommand as a directive won't work (even if
someItchCommand is a valid Itch command).
*/
typedef void (*cmpp_f)(cmpp_f_args const *args);
/**
A typedef for generic deallocation routines.
*/
typedef void (*cmpp_finalizer_f)(void *);
/**
State specific to concrete cmpp_d implementations.
*/
struct cmpp_d_impl {
/**
Callback func. If any API other than cmpp_dx_process() invokes
this, behavior is undefined.
*/
cmpp_f f;
/**
For custom directives with a non-NULL this->state, this will be
called, and passed that object, when the directive is cleaned
up. For directives with both an opening and a closing tag, this
destructor is only attached to the opening tag.
If any API other othan cmpp's internal cleanup routines invoke
this, behavior is undefined.
*/
cmpp_finalizer_f dtor;
/**
State for the directive's callback. It is accessible in
cmpp_f() impls via theDx->d->impl.state. For custom
directives with both an opening and closing directive, this
same state object gets assigned to both.
*/
void * state;
};
typedef struct cmpp_d_impl cmpp_d_impl;
#define cmpp_d_impl_empty_m {.f=0,.dtor=0}
/**
Each c-pp "directive" is modeled by one of these.
*/
struct cmpp_d {
/**
The directive's name. It is stored along with its length to speed
up searches.
*/
struct {
/**
The directive's name, as it must appear after the directive
delimiter. Its bytes are assumed to be static or otherwise
outlive this object.
*/
const char *z;
/** Byte length of this->z. We record this to speed up searches. */
unsigned n;
} name;
/**
Brief help text. Its memory must outlive the directive.
This member was introduced for the sake of cmpp_itch callbacks,
where it has demonstrable utility. It unfortunately does not
serve the purpose well for directives, which commonly have
multiple call forms, both with and without a closing
tag. Historically (pre-Itch), directives did all of their own
validation error reporting so did not have a use for this member.
As of this writing (2026-09-07), this value is non-NULL but also
not terribly helpful for the library's builtin directives because
the internal setup makes it difficult to wedge it it. Itch
callbacks, on the other hand, tend to set it to something useful.
*/
char const * usage;
/**
The number of arguments this directive requires, or -1
for any number. A value of 0 means it does not accept
arguments.
Enforcement of the arity is opt-in, only enforced if
cmpp_f_F_ENFORCE_ARITY is in this->flags and if this->flags has
cmpp_f_F_ARGS_V4. Before that addition we did not have
any enforcement.
Achtung: v4 arguments add the directive/command name as the first
argument, so the callback always receives an argc of at least 1
and the arguments start at index 1. That "off by one" is _not_
accounted for in this artity value: it counts only the arguments,
not the commeand/directive name.
*/
int arity;
/**
Bitmask of flags from cmpp_f_e plus possibly internal flags.
*/
cmpp_flag32_t flags;
#if 0
/**
TODO: historically directives have all done their own
interpolation, but the internal APIs eventually reached a stage
where the directives were all repeating the same interpolation.
This flag is to provide interpolation options, initially only for
directives, but potentially also for use with (not cmpp_itch
callbacks (same interface).
*/
cmpp_flag32_t interpolationFlags;
#endif
/**
The directive which acts as this directive's closing element
element, or 0 if it has none. Directives may have multiple
closers but they are managed internally, not via this interface.
*/
cmpp_d const * closer;
/**
State specific to concrete implementations.
*/
cmpp_d_impl impl;
};
/**
Each instance of the cmpp_dx class (a.k.a. "directive context")
manages a single input source. It's responsible for the
tokenization of all input, locating directives, and processing
("running") directives. The directive-specific work happens in
cmpp_f() implementations, and this class internally manages the
setup, input traversal, and teardown.
These objects only exist while cmpp is actively processing
input. Client code interacts with them only through cmpp_f()
implementations which the library invokes.
The process of filtering input to look for directives is to call
cmpp_dx_next() until it indicates either an error or that a
directive was found. In the latter case, the cmpp_dx object is
populated with info about the current directive. cmpp_dx_process()
will run that directive, but cmpp_f() implementations sometimes
need to make decisions based on the located directive before doing
so (and sometimes they need to skip running it).
If cmpp_dx_next() finds no directive, the end of the input has been
reached and there is no further output to generate.
Content encountered before a directive is found is passed on to the
output stream via cmpp_out_raw() or cmpp_out_expand().
*/
struct cmpp_dx {
/**
The cmpp object which owns this context.
*/
cmpp * const pp;
/**
The directive on whose behalf this context is active. See
this->args for notes about invalidation of this value.
ACHTUNG: this may be modified by any APIs which recurse into the
library. cmpp_f() impls should generaly use cmpp_f_args::d
instead of this, but impls which have multiple closers are known
to need to be able to see changes to this in order to behave
properly.
*/
cmpp_d const *d;
/**
Name of the input for error reporting. Typically an input script
file name, but it need not refer to a file.
*/
unsigned const char * const sourceName;
/**
State related to arguments passed to the current directive.
DEPRECATED 2026-09: this member not needed for directives which
use cmpp_f_F_ARGS_V4, as that flag uses an approach which (A) is
easier to use in practice and (B) does not invalidate argument
memory when recursing into the library. Once/if older directive
code is updated, this member will go away.
This particular struct is LEGACY and DEPRECATED. It will not be
removed until all delimiter handlers are adapted to the
cmpp_f_F_ARGS_V4 model but it should not be used in any new
code. cmpp_f() impls in that mode should use
cmpp_f_args::argc/argv for a more conventional experience and
none of the inherent memory-invalidation problems which plague
the older model.
It is important to keep in mind that the memory for the members
of this sub-struct may be modified or reallocated
(i.e. invalidated) by any APIs which call in to cmpp_dx_next().
cmpp_f() implementations must take care not to use any of this
memory after calling into that function, cmpp_dx_consume(), or
similar. If needed, it must be copied (e.g. using
cmpp_dx_args_clone() to create a local copy of the parsed
arguments).
*/
struct {
/**
Starting byte of unparsed arguments. This is for cmpp_d_f()
implementations which need custom argument parsing.
The contents differ from the raw, unparsed source file in that
backslash-escaped newlines are replaced with newlines. The API
does not offer access to the raw underlying strings. Maybe it
should.
This member is set regardless of the argument parsing mode
for the directive.
*/
unsigned const char * z;
/**
The byte length of z. It is not called "n", like its
counterpart in other structs, so as to avoid confusion between
dx->args->n and dx->args->argc (see, that was confusing,
right?).
This member is set regardless of the argument parsing mode for
the directive.
*/
cmpp_size_t nz;
/**
The parsed arg count for the this->arg0 list.
ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode.
Use cmpp_f_args::argc instead.
*/
unsigned argc;
/**
The first parsed arg or NULL. How this is set up is affected by
cmpp_d::flags.
ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode.
Use cmpp_f_args::argv instead.
This is specifically _NOT_ defined as a sequential array and
using pointer math to traverse it invokes undefined behavior.
To traverse the list:
for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){
...
}
Directives registered which cmpp_f_F_ARGS_V4 are instead
expected to use cmpp_f_args::argv, and this member may be
NULL for them.
*/
cmpp_arg const * arg0;
} args;
/**
Private impl details.
*/
cmpp__dx_pimpl * const pimpl;
};
/**
Returns pp's current directive context. When processing
directives, this is the context which is active during that
time. When not processing directives, this is NULL.
Lifetime: the returned object is managed by pp and it will be
destroyed as soon as it reaches the end of the dx's input or an
error pre-empts that and propgates back up to that point. Callers
must not hold onto this pointer for longer than the current
function, nor must they expect this function to return the same
value before and after calling into APIs which traverse into new
input scripts (e.g. via the #include directive). In a case like
#include or cmpp_process_string(), this function will return the
same thing immediately before and after that operation, but during
processing of that source it will have a different value which
expires as soon as the source is consumed or errors out.
*/
CMPP_EXPORT cmpp_dx * cmpp_dx_current(cmpp *pp);
/**
Thin proxy for cmpp_errf(), replacing only the first argument.
*/
CMPP_EXPORT int cmpp_dx_errf(cmpp_dx *dx, int rc,
char const *zFmt, ...);
/**
va_list counterpart of cmpp_dx_errf().
*/
CMPP_EXPORT int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt,
va_list vargs);
/**
Scans dx to the next directive line, emitting all input before that
which is _not_ a directive line to dx->pp's output channel unless
it's elided due to being inside a block which elides its content
(e.g. #if).
Returns 0 if no errors were triggered, else a cmpp_rc_e code. This
is a no-op if dx->pp has persistent error state set, and that error
code is returned.
If it returns 0 then it sets *pGotOne to true if a directive was
found and false if not (in which case the end of the input has
been reached and further calls to this function for the same input
source will be no-ops). If it sets *pGotOne to true then it also
sets up dx's state for use with cmpp_dx_process(), which should
(normally) then be called.
ACHTUNG: calling this resets any argument-handling-related state of
dx. That is important for cmpp_f() implemenations, which _must
not_ hold copies of any pointers from dx->args.arg0 or dx->args.z
beyond a call to this function. Any state they need must be
evaluated, potentially copied, before calling this function().
*/
CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne);
/**
This is only legal to call immediately after a successful call to
cmpp_dx_next(). It requires that cmpp_dx_next() has just located
the next directive. This function runs that directive. Returns 0
on success and all that.
Design note: directive-Search and directive-process are two
distinctly separate steps because directives which have both
open/closing tags frequently discard the closing directive without
running it (it exists to tell the directive how far to read). Those
closing directives exist independently, though, and will trigger
errors when encountered outside of the context of their opening
directive tag (e.g. an "#/if" without an "#if").
*/
CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx);
/**
A bitmask of flags for use with cmpp_dx_consume()
*/
enum cmpp_dx_consume_e {
/**
Tells cmpp_dx_consume() to process any directives it encounters
which are not in the specified set of closing directives. Its
default is to fail if another directive is seen.
*/
cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01,
/**
Tells cmpp_dx_consume() that non-directive content encountered
before the designated closing directive(s) must use an at-policy
of cmpp_atpol_OFF. That is: the output target of that function will
get the raw, unfiltered content. This is for cases where the
consumer will later re-emit that content, delaying @token@
parsing until a later step (e.g. #query does this).
This may misinteract in unpredictable ways when used with
cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs.
*/
cmpp_dx_consume_F_ATPOL_OFF = 0x02
};
/**
A helper for cmpp_f() implementations which read in their
blocked-off content instead of passing it through the normal output
channel. e.g. `#define x <<` stores that content in a define named
"x".
This function runs a cmpp_dx_next() loop which does the following:
If the given output channel is not NULL then it first replaces the
output channel with the given one, such that all output which would
normally be produced will be sent there until this function
returns, at which point the output channel is restored. If the
given channel is NULL then output is not captured - it instead goes
dx's current output channel.
dClosers must be a list of nClosers legal closing tags
entries. Typically this is the single closing directive/tag of the
current directive, available to the opening directive's cmpp_f()
impl via dx->d->closer. Some special cases require multiple
candidates, however (examples: #query and the #if family).
The flags argument may be 0 or a bitmask of values from the
cmpp_dx_consume_e enum.
If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set
then this function requires that the next directive in the input be
one specified by dClosers. If the next directive is not one of
those, it will fail with code CMPP_RC_SYNTAX.
If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it
will continue to search for and process directives until the
dCloser directive is found. Calling into other directives will
invalidate certain state that a cmpp_f() has access to - see
below for details. If dCloser is not found before EOF, a
CMPP_RC_SYNTAX error is triggered.
Once one of dCloser is found, this function returns with dx->d
referring to the that directive. In practice, the caller should
_not_ call cmpp_dx_process() at that point - the closing directive
is typically a no-op placeholder which exists only to mark the end
of the block. If the closer has work to do, however, the caller of
this function should call cmpp_dx_process() at that point.
On success it returns 0, the input stream will have been consumed
between the directive dx and its closing tag, and dx->d will point
to the new directive. If os is not NULL then os will have been
sent any content.
On error, processing of the directive must end immediately,
returning from the cmpp_f() impl after cleaning up any local
resources.
ACHTUNG: since this invokes cmpp_dx_next(), it invalidates
dx->args.arg0. Its dx->d is also replaced but the previous value
remains valid until the cmpp instance is cleaned up.
Example from the context of a cmpp_f() implementation
```
// "dx" is the cmpp_dx arg to this function
cmpp_outputer oss = cmpp_outputer_b;
cmpp_b os = cmpp_b_empty;
oss.state = &os;
if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 1, 0) ){
cmpp_b_chomp( &os );
... maybe modify the buffer or decorate the output in some way...
cmpp_out_raw(dx->pp, os.z, os.n);
}
cmpp_b_clear(&os);
```
*/
CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os,
cmpp_d const *const * dClosers,
unsigned nClosers,
cmpp_flag32_t flags);
/**
Output state for use with cmpp_dx_consume_v2().
Memory lifetimes:
- All string pointers in this class point directly back into the
source script so have its lifetime. From this library's
perspective that means until the cmpp_process_string() call which
started the process finishes, at which point those pointers leave
the library's site and are owned by their caller. That caller
might be cmpp_process_stream() or cmpp_process_file(), in which
case such pointers are invalidated before those functions return.
- The cmpp_d pointers are owned by the underlying cmpp instance.
As of this writing the API provides no means to remove
directives, nor are there any plans to introduce any. Thus it is
currently "impossible" that those pointers become invalidated
before the cmpp instance is passed to cmpp_reset() or
cmpp_dtor(). However, users of this API "should" treat them as if
they are invalidated when the function set them up for passing to
cmpp_dx_consume_v2() returns.
*/
struct cmpp_dx_consumed {
/**
Start of the consumed range, starting at the first byte of the
directive's line.
*/
unsigned char const * beginDirective;
/**
Start of the content part of the consumed range, starting at
the byte after the directive's opening line.
*/
unsigned char const * beginContent;
/** One-after-the-end of the content part of the consumed range. */
unsigned char const * endContent;
/** One-after-the-end of the full consumed range, including the
closing directive. */
unsigned char const * endDirective;
/** The opening directive. */
cmpp_d const * dOpen;
/** The closing directive. */
cmpp_d const * dClose;
};
typedef struct cmpp_dx_consumed cmpp_dx_consumed;
#define cmpp_dx_consumed_empty_m {0,0,0,0,0,0}
/**
Works exactly like cmpp_dx_consume() except for the final
argument:
When this is called, con is unconditionally cleared and then
partially re-populated (see cmpp_dx_consumed docs for the exact
semantics of each field):
- con->dOpen is set to dx->d
- con->beginContent is set
- con->beginContent is set
It then calls cmpp_dx_consume(). Regardless of success or error, it
finishes populating con before returning the result of that call:
- con->dClosed = the closing directive (the new dx->d)
- con->endContent
- con->endDirective
The problem this solves:
The #itch directive, in order to be able to provide accurate script
error location information, needs to be able to locate it within
the original source. It uses this function to consume to its #/itch
tag and then access the script from the data stored in con. This
allows it to provide byte-accurate line/column info. If it instead
copied the input, it could provide location info only relative to
the directive's contents (which is not horrible but it simplifies
the internals if they can access the outer directive's memory
directly, as internal buffers, e.g. for string interpolation,
otherwise build walls between individual tokens and their original
source, making them unable to be mapped to an input source
location. Similarly, if #itch were to allow other directives
embedded within a script, it would be forced to make a copy
(preprocessing produces different content than the source,
of course) and it would be literally impossible to map any
code location within that into a value which would make sense
to any user.
Consider the following itch script, which is invalid because
Itch does not permit embedded directives (for the reason given
above):
```
#define foo "this is foo"
#@ policy error
#itch
#if 1
eval {
foo bar baz
bar baz barre
error "@foo@"
}
#/if
#/itch
```
If sub-directives were permitted, the result would come out looking
like:
```
eval {
foo bar baz
bar baz barre
error "this is foo"
}
```
Now, when "error" triggers, it would report line 4, column 3. In
this trivial example that's only off by 1 line, but the distance
grows quickly and eventually spans into evals which run
dyamically-generated code via var- and command-call interpolation,
at which point the line/column values become meaningless and
therefore not only useless but also misleading, causing wasted
developer time as they track numbers which point to either nothing
at all or something completely different.
*/
CMPP_EXPORT int cmpp_dx_consume_v2(cmpp_dx * dx, cmpp_outputer * os,
cmpp_d const *const * dClosers,
unsigned nClosers,
cmpp_flag32_t flags,
cmpp_dx_consumed * con);
/**
Equivalent to cmpp_dx_consume(), capturing to the given buffer
instead of a cmpp_outputer object.
*/
CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b,
cmpp_d const * const * dClosers,
unsigned nClosers,
cmpp_flag32_t flags);
/**
If arg is not NULL, cleans up any resources owned by
arg but does not free arg.
As of this writing, they own none and some code still requires
that. That is Olde Thynking, though, and due to change.
*/
CMPP_EXPORT void cmpp_arg_cleanup(cmpp *pp, cmpp_arg *arg);
/**
If arg is not NULL resets arg to be re-used. arg must have
initially been cleanly initialized by copying cmpp_arg_empty (or
equivalent, i.e. zeroing it out).
*/
CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg);
/**
Returns the C-string representation of arg. If arg->interp is set
then that string is returned, else arg->z is returned.
*/
CMPP_EXPORT char const * cmpp_arg_cstr(cmpp_arg const *arg);
/**
Works like cmpp_arg_cstr() but also writes the byte length
of the resulting string into *n (n may not e NULL).
*/
CMPP_EXPORT char const * cmpp_arg_cstr2(cmpp_arg const *arg, cmpp_size_t *n);
#define cmpp_arg_ucstr(ARG) (unsigned char const*)cmpp_arg_cstr(ARG)
#define cmpp_arg_ucstr2(ARG,N) (unsigned char const*)cmpp_arg_cstr2((ARG),(N))
/**
This is the core argument-parsing function used by the library's
provided directives. Its is available in the public API as a
convenience for custom cmpp_f() implementations, but custom
implementations are not required to make use of it.
Populates a cmpp_arg object by parsing the next token from its
input source.
Expects *pzIn to point to the start of input for parsing arguments
and zInEnd to be the logical EOF of that range. This function
populates pOut with the info of the parse. Returns 0 on success,
non-0 (and updates pp's error state) on error.
Output (the parsed token) is written to *pzOut. zOutEnd must be the
logical EOF of *pzOut. *pzOut needs to be, at most,
(zInEnd-*pzIn)+1 bytes long. This function range checks the output
and will not write to or past zOutEnd, but that will trigger a
CMPP_RC_RANGE error.
On success, *pzIn will be set to 1 byte after the last one parsed
for pOut and *pzOut will be set to one byte after the final output
(NUL-terminated). pOut->z will point to the start of *pzOut and
pOut->n will be set to the byte-length of pOut->z.
When the end of the input is reached, this function returns 0
and sets pOut->ttype to cmpp_tt_EOF.
General tokenization rules:
Tokens come in the following flavors:
- Quoted strings: single- or double-quoted. cmpp_arg::ttype:
cmpp_tt_StringDQ and friends.
- Decimal integers with an optional sign. cmpp_arg::ttype value:
cmpp_tt_Int.
- Groups: (...), {...}, and [...]. cmpp_arg::ttype values:
cmpp_tt_GroupParen, cmpp_tt_GroupSquiggly, and
cmpp_tt_GroupBrace. These types do not automatically get parsed
recursively. To recurse into one of these, pass cmpp_arg_parse()
the grouping argument's bytes as the input range. Their
interpretation is context-dependent but the general conventions
are that {} are used for strings and context-dependent ad hoc
data structures (tcl-style), (...) for subexpressions, and [...]
for either "function calls" or lists of values.
- Word: anything which doesn't look like one of these above. Token
type IDs: cmpp_tt_Word. These are most often interpreted as
#define keys but cmpp_f() implementations sometimes treat
them as literal values.
- A small subset of words and operator-like tokens, e.g. '=' and
'!=', get a very specific ttype, e.g. cmpp_tt_OpNeq, but these
can generally be treated as strings.
- Outside of strings and groups, spaces, tabs, carriage-returns,
and newlines are skipped.
These are explained in more detail in the user's manual
(a.k.a. README.md).
There are many other token types, mostly used internally.
This function supports _no_ backslash escape sequences in
tokens. All backslashes, with the obligatory exception of those
which make up backslash-escaped newlines in the input stream, are
retained as-is in all token types. That means, for example, that
strings may not contain their own quote character.
As an example of where this function is useful: cmpp_f()
implementations which need to selectively parse a subset of the
directive's arguments can use this. As input, their dx argument's
args.z and args.nz members delineate the current directive line's
arguments. See c-pp.c:cmpp_f_pipe() for an example.
*/
CMPP_EXPORT int cmpp_arg_parse(cmpp * pp,
cmpp_arg *pOut,
unsigned char const **pzIn,
unsigned char const *zInEnd,
unsigned char ** pzOut,
unsigned char const * zOutEnd);
CMPP_EXPORT bool cmpp_arg_equals(cmpp_arg const *arg,
char const *z);
/**
True if (cmpp_arg const *)ARG's contents match the string literal
STR, else false.
*/
#define cmpp_arg_equals_c(ARG,STR) \
(sizeof("" STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1))
/**
True if (cmpp_arg const *)ARG's contents match the string literal
STR or ("-" STR), else false. The intent is that "-flag" be passed
here to tolerantly accept either "-flag" or "--flag".
*/
#define cmpp_arg_isflag_c(ARG,STR) \
(cmpp_arg_equals_c(ARG,STR) || cmpp_arg_equals_c(ARG, "-" STR))
#define cmpp_if_arg_isflag_c(ARG,STR) \
if( cmpp_arg_isflag_c(ARG,STR) )
/**
Creates a copy of arg->z. If allocation fails then pp's persistent
error code is set to CMPP_RC_OOM. If pp's error code is not 0 when
this is called then this is a no-op and returns NULL. In other
words, if this function returns NULL, pp's error state was either
already set when this was called or it was set because allocation
failed.
Ownership of the returned memory is transferred to the caller, who
must eventually free it using cmpp_mfree().
*/
CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg);
/**
Flag bitmasks for use with cmpp_arg_interpolate(). With my apologies
for the long names (but consistency calls for them).
TODO (2026-09-07): rename this to cmpp_interpolate() and have it
closely mirror cmpp_itch_interpolate(). The plan is to have
directives opt in to automatic arg interpolation, which would
eliminate a digital ton (==some small fraction of a metric
microgram) of code from the built-in directives.
*/
enum cmpp_arg_interpolate_e {
/**
Specifies that the argument's string value should be used as-is,
rather than expanding it (if the arg's ttype would normally cause
it to be expanded).
*/
cmpp_arg_interpolate_FORCE_STRING = 0x01,
/**
Tells cmpp_arg_interpolate() to not expand arguments with type
cmpp_tt_Word, which it normally treats as define keys. It instead
treats these as strings.
*/
cmpp_arg_interpolate_NO_DEFINES = 0x02,
/**
If set, arguments with a ttype of cmpp_tt_GroupBrace will be
"called" by passing them to cmpp_call_arg(). The space-trimmed
result of the call becomes the output of the cmpp_arg_interpolate()
call.
FIXME? Make this opt-out instead of opt-in. We end up _almost_
always wanting this.
*/
cmpp_arg_interpolate_BRACE_CALL = 0x04,
/**
Explicitly disable [call] expansion even if
cmpp_arg_interpolate_BRACE_CALL is set in the flags.
*/
cmpp_arg_interpolate_NO_BRACE_CALL = 0x08,
#if 0
/**
TODO. Open questions: apply to all types, e.g. results of
[calls...], or just to string-style? This will eventually
backfire if the target buffer has existing content, as we'd end
up multi-unescaping the existing parts.
*/
cmpp_arg_interpolate_UNESCAPE = 0x10,
#endif
#if 0
/* Going to need this for porting the expression
builder from itch to the core. */
cmpp_arg_interpolate_PARENS_EXPR = 0x20,
#endif
/**
TODO? cmpp_arg_interpolate_UNESCAPE to run it through
cmpp_b_unescape().
*/
};
/**
Appends some form of arg to the given buffer.
arg->ttype values of cmpp_tt_Word (define keys) cause the value to
be expanded appropriately. Others get emitted as specified by the
flags argument.
The flags argument influences the expansion decisions, as
documented in the cmpp_arg_interpolate_e enum.
Returns 0 on success and all that.
If b is NULL then: (A) if arg->interp is not NULL then it is reused
as the target else (B) return CMPP_RC_MISUSE and update's pp's
error state with a description of the problem.
See: cmpp_atpol_get(), cmpp_atpol_set()
Reminder to self: though this function may, via script-side
function call resolution, recurse into the library, any such
recursion gets its own cmpp_dx instance. In this context that's
significant because it means this call won't invalidate arg's
memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on
where args came from - typically it's owned by dx but
cmpp_dx_args_clone() exists solely to work around such potential
invalidation).
*/
CMPP_EXPORT int cmpp_arg_interpolate(cmpp *pp, cmpp_arg const *arg,
cmpp_b * b, cmpp_flag32_t flags);
/**
This is a helper routine for cmpp_f() implementations which
process flags like -f filename. This function treats arg as a
filename, consumes that file, and returns 0 on success. The
argument's value is expanded using the preprocessor's conventional
approaches, using cmpp_arg_interpolate().
HUGE WARNING: this routine necessarily requires recursing into dx,
which means that it will INVALIDATE dx->args when it does. It is
CRITICAL that the caller pass a second argument which was populated
by cmpp_dx_args_clone() and eventually, before returning, pass them to
cmpp_args_cleanup().
If name is not NULL then the buffer's contents get replaced with
the resolved name of the file.
content must not be NULL. If filename lookup passes then it is
passed to cmpp_b_reuse() before being streamed the content of the
file. If preprocessIt is true then the content will be piped
through dx->pp and sent to the content buffer, else it will be
consumed as-is into the buffer.
*/
CMPP_EXPORT int cmpp_arg_consume_as_file(
cmpp_dx * dx, cmpp_arg const *arg,
cmpp_b *name, cmpp_b * content, bool preprocessIt
);
/**
Flags for use with cmpp_call_str() and friends.
*/
enum cmpp_call_e {
/** Do not trim a newline from the result. */
cmpp_call_F_NO_TRIM = 0x01,
/** Trim all leading and trailing space and newlines
from the result. */
cmpp_call_F_TRIM_ALL = 0x02
};
/**
This assumes that arg->z holds a "callable" directive
string in the form:
directiveName ...args
This function composes a new cmpp input source from that line,
behaving as if it were prefixed with dx's current directive prefix
if it's not already got one. It processes it with
cmpp_process_string(), appending a (possibly trimmed) copy of that
output to dest.
To simplify common expected usage, by default the output is trimmed
of a single newline. The flags argument, 0 or a bitmask of values
from the cmpp_call_e enum, can be used to modify that behavior.
This is the basis of "function calls" in cmpp.
Returns 0 on success.
Special cases and considerations:
Case 1: dest is not modified if the call produces no output, and
dest is not modified until after the end of the call. That makes
it legal for [z,z+n) to live inside dest, but for that to work one
would need to:
1) Prefix d with a \0 byte.
2) Append what would normally be passed as z to dest.
3) dest->n = 0 (the magic sauce)
4) In place of (z,n) pass (dest->z+1,dest->n-1).
After this call, if dest was not modified then dest->n is still
pointing to a NUL-terminated buffer (part of the cmpp_b contract),
and if it _is_ updated then it will be overwritten from its
starting point, rather than appending after the end of the input
string.
Reminder to self: we cannot avoid copying the call's output here
solely because of the trimming. We can easily trim from dest's RHS
but not so easily from its start. If it were not for trimming we
could pass dest through as the direct output target, rather than
using an intermediary buffer. Bummer. Even so: a potential TODO is
optimize out the copy when we're not trimming. However... doing so
would rule out Case 1 described above, and we currently use that in
at least one place.
*/
int cmpp_call_str(cmpp *pp,
unsigned char const * z,
cmpp_strlen_t n,
cmpp_b * dest,
cmpp_flag32_t flags);
/**
Convert an errno value to a cmpp_rc_e approximation, defaulting to
dflt if no known match is found. This is intended for use by
cmpp_f implementations which use errno-using APIs.
*/
CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt);
/**
Configuration object for use with cmpp_d_register().
*/
struct cmpp_d_reg {
/**
The name of the directive as it will be used in
input scripts, e.g. "mydirective". It will be copied by
cmpp_d_register().
*/
/* Refactoring crutch */
/* Directive name. The library makes a copy of this. */
char const * name;
/**
Enforcement of the arity is opt-in, only enforced if
cmpp_f_F_ENFORCE_ARITY is in this->flags. See the docs for that
flag for more info.
*/
int arity;
struct {
/**
Callback for the directive's opening tag.
*/
cmpp_f f;
/**
Flags from cmpp_f_e. Typically one of cmpp_f_F_ARGS_LIST or
cmpp_f_F_ARGS_RAW.
*/
cmpp_flag32_t flags;
} opener;
struct {
/**
Callback for the directive's closing tag, if any.
This is only relevant for directives which have both an open and
a closing tag (even if that closing tag is only needed in some
contexts, e.g. "#define X <<" (with a closer) vs "#define X Y"
(without)). See cmpp_f_dangling_closer() for a default
implementation which triggers an error if it's seen in the input
and not consumed by its counterpart opening directive. That
implementation has proven useful for #define, #pipe, and friends.
Design notes: it's as yet unclear how to model, in the public
interface, directives which have a whole family of cooperating
directives, namely #if/#elif/#else. OTOH, #query does that.
*/
cmpp_f f;
/**
Flags from cmpp_f_e. For closers this can typically be left at
0, as few do any work beyond ensuring that they match an
opener.
*/
cmpp_flag32_t flags;
} closer;
/**
If not NULL then it is assigned to the directive's opener part
and will be called by the library in either of the following
cases:
- When the custom directive is cleaned up.
- If cmpp_d_register() fails (returns non-0), regardless of how
it fails.
It is passed this->state.
*/
cmpp_finalizer_f dtor;
/**
Implementation state for the callbacks.
*/
void * state;
/**
Brief help text.
*/
char const * usage;
};
typedef struct cmpp_d_reg cmpp_d_reg;
/**
Empty-initialized cmpp_d_reg instance, intended for const-copy
initialization.
*/
#define cmpp_d_reg_empty_m { \
.name=0,.arity=-1, \
.opener={.f=,.flags=0}, \
.closer={.f=0,.flags=0}, \
.dtor=0,.state=0, \
.usage="Missing cmpp_d::usage"\
}
/**
Empty-initialized instance, intended for non-const-copy
initialization.
*/
//extern const cmpp_d_reg cmpp_d_reg_empty;
/**
Registers a new directive, or a pair of opening/closing directives,
with pp.
The semantics of r's members are documented in the cmpp_d_reg
class. r->name and r->opener.f are required. The remainder may be
0/NULL. Its members are copied - r need not live longer than this
call.
When the new directive is seen in a script, r->opener.f() will be
called. If the closing directive (if any) is seen in a script,
r->closer.f() is called. In both cases, the callback
implementation can get access to the r->state object via
cmdd_dx::d::impl::state (a.k.a dx->d->impl.state).
If r->closer.f is not NULL then the closing directive will be named
"/${zName}". (Design note: it is thought that forcing a common
end-directive syntax will lead to fewer issues than allowing
free-form closing tag names, e.g. fewer chances of a name collision
or not quite remembering the spelling of a given closing tag
(#endef vs #enddefine vs #/define).)
Returns 0 on success and updates pp's error state on error. Similarly,
this is a no-op if pp has an error code when this is called, in which
case it returns that result code without other side-effects.
On success, if pOut is not NULL then it is set to the directive
pointer, memory owned by pp until it cleans up its directives. This
is the only place in the API a non-const pointer to a directive can
be found, and it is provided only for very specific use-cases where
a directive needs to be manipulated (carefully) after
registration[^post-reg-manipulation]. If this function also
registered a closing directive, it is available as (*pOut)->closer.
pOut should normally be NULL.
This function, for backwards compatility with directive
implementations, always adds the cmpp_f_F_NEEDS_DX flag to the
opening and closing directives.
Failure modes include:
- Returns CMPP_RC_RANGE if zName is not legal for use as a
directive name. See cmpp_is_legal_key().
- Returns CMPP_RC_OOM on an allocation error.
Errors from this function are recoverable (see cmpp_errf()). A
failed registration, even one which doesn't fail until the
registration of the closing element, will leave pp in a
well-defined state (with neither of r's directives being
registered).
[^post-reg-manipulation]: The one known use case if the #if family
of directives, all of which use the same #/if closing
directive. The public registration API does not account for sharing
of closers that way, and whether it _should_ is still TBD. The
workaround, for this case, is to get the directives as they're
registered and point the cmpp_d::closer of each of #if, #elif, and
#else to #/if.
*/
CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r,
cmpp_d **pOut);
/**
A cmpp_f() impl which is intended to be used as a callback for
directive closing tags for directives in which the opening tag's
implementation consumes the input up to the closing tag. This impl
triggers an error if called, indicating that the directive closing
was seen in the input without its accompanying directive opening.
*/
CMPP_EXPORT void cmpp_f_dangling_closer(cmpp_f_args const *args);
/**
Writes the first n bytes of z to pp's current output channel
without performing any @token@ parsing.
In the following cases this returns current persistent error code
without side-effects:
- cmpp_err_has() is not 0 when this is called. The preprocessor
rejects all output so long as an unhandled error is
pending.
- cmpp_dx_current() is non-NULL and that context is in
skip-mode. (#if does this.)
Returns pp's persistent error code (0 on success) and sets that
code to non-0 on error. This is a no-op if dx->pp has a non-0 error
state, returning that code.
See: cmpp_out_expand()
*/
CMPP_EXPORT int cmpp_out_raw(cmpp* pp, void const *z,
cmpp_size_t n);
/**
Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the
given policy says to (else it passes the content through as-is, as
per cmpp_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's
current policy. A policy of cmpp_atpol_OFF behaves exactly like
cmpp_out_raw().
Returns dx->pp's persistent error code (0 on success) and sets that
code to non-0 on error. This function is a no-op if dx->pp has a
non-0 error state, returning that code instead of doing any work.
If pOut is NULL then dx->pp's default channel is used, with the
caveat that atPolicy's only legal value in that case is
cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be
overridden for that particular output channel, to avoid accidental
filtering when it's not enabled. They do not impose that
restriction for other output channels, which are frequently used
for filtering intermediary results.)
See: cmpp_out_raw()
Notes regarding how this is used internally:
- This function currently specifically does nothing when invoked in
skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode
except when evaluating #elif expressions (a previous #if/#elif
having passed and put us in skip-mode), where it's expanding
expression operands. That part currently (as of 2025-10-21) uses
dx->pp's current policy, and it's not clear whether that is
sufficient or whether we need to force it to expand (and which
policy to use when doing so). We could possibly get away with
always using cmpp_atpol_ERROR for purposes of evaluating at-string
expression operands.
[^1]: Skip-mode is the internal mechanism which keeps directives
from running, and content from being emitted, within a falsy branch
of an #if/#elif block. Only flow-control directives are ever run
when skip-mode is active, and client-provided directives cannot
easily provide flow-control support. Ergo, much of this paragraph
is not relevant for client-level code, but it is for this library's
own use of this function.
*/
CMPP_EXPORT int cmpp_out_expand(cmpp * pp,
cmpp_outputer * pOut,
unsigned char const * zFrom,
cmpp_size_t n,
cmpp_atpol_e policy);
#if 0
#define cmpp_dx_out_expand(DX,OUT,SRC,N,POLICY) \
cmpp_out_expand((DX)->pp, OUT, SRC, N, POLICY)
#define cmpp_dx_out_raw(DX,SRC,N) \
cmpp_out_expand((DX)->pp, SRC, N)
#endif
/**
This creates a formatted string using sqlite3_mprintf() and emits
it using cmpp_out_raw(). Returns CMPP_RC_OOM if allocation of the
string fails, else it returns whatever cmpp_out_raw() returns.
This is a no-op if pp is in an error state, returning that code.
*/
CMPP_EXPORT int cmpp_outf(cmpp *pp, char const *zFmt, ...);
/** va_list counterpart of cmpp_outf(). */
CMPP_EXPORT int cmpp_outfv(cmpp *pp, char const *zFmt, va_list va);
/**
If pp's output channel has a flush method, it is called and its
result returned, else this is a no-op returning 0.
*/
CMPP_EXPORT int cmpp_flush(cmpp *pp);
/**
Convenience form of cmpp_delimiter_get() which returns the
delimiter which was active at the time when the currently-running
cmpp_f() was called. This memory may be invalidated by any calls
into cmpp_dx_process(), cmpp_delimiter_set(), or when the
cmpp_f() impl returns, so a copy of this pointer must not be
retained past such a point.
This function is primarily intended for use in generating debug and
error messages.
If the delimiter stack is empty, this function returns NULL, but
that would indicate library misuse because it always starts with
the build-time-default delimiter at the top of the stack.
*/
CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx);
/**
Borrows a buffer from pp's buffer recycling pool, allocating one if
needed. It returns NULL only on allocation error, in which case it
updates pp's error state.
This transfers ownership of the buffer to the caller, who is
obligated to eventually do ONE of the following:
- Pass it to cmpp_b_return() with the same pp argument.
- Pass it to cmpp_b_clear() then cmpp_mfree().
The purpose of this function is a memory reuse optimization. Most
directives, and many internals, need to use buffers for something
or other and this gives them a way to reuse buffers.
Potential TODO: How this pool optimizes (or not) buffer allotment
is an internal detail. Maybe add an argument which provides a hint
about the buffer usage. e.g. argument-conversion buffers are
normally small but block content buffers can be arbitrarily large.
The pp instance has internally-configured limits on the buffer
pool, both for the number of buffers and their total capacity.
When the count limit is reached, this function starts returning
newly-allocated buffers instead of ones from the pool, which
can increase total allocations of the library. The limits are
not yet configurable.
*/
CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *pp);
/**
Returns a buffer borrowed from cmpp_b_borrow(), transferring
ownership back to pp. Passing a non-NULL b which was not returned
by cmpp_b_borrow() invokes undefined behavior (possibly delayed
until the list is cleaned up). To simplify usage, b may be NULL.
After calling this, b must be considered "freed" - it must not be
used again. This function is free (as it were) to immediately free
the object's memory instead of recycling it.
If recycling b would take pp over its internal limits on buffer
count, b is immediately freed (see cmpp_b_return()). If b->nAlloc
is larger than its internal single-buffer capacity or keeping b->z
would go over the total capacity limit, b's owned memory is freed
(cmpp_b_clear()) but b is retained for further recycling.
*/
CMPP_EXPORT void cmpp_b_return(cmpp *pp, cmpp_b *b);
/**
If NUL-terminated z matches one of the strings listed below, its
corresponding cmpp_atpol_e entry is returned, else
cmpp_atpol_invalid is returned.
If pp is not NULL then (A) this also sets its current at-policy and
(B) it recognizes an additional string (see below). In this case,
if z is not a valid string then pp's persistent error state is set.
Its accepted values each correspond to a like-named policy value:
- "off": no processing of `@` is performed.
- "error": fail if an undefined `X` is referenced in @token@
parsing.
- "retain": emit any unresolved `@X@` tokens as-is to the output
stream. i.e. `@X@` renders as `@X@`.
- "elide": omit unresolved `@X@` from the output, as if their values
were empty. i.e. `@X@` renders as an empty string, i.e. is not
emitted at all.
- "current": if pp!=NULL then it returns the current policy, else
this string resolves to cmpp_atpol_invalid.
*/
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z);
/**
Returns pp's current at-token policy.
*/
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp);
/**
Sets pp's current at-token policy. Returns 0 if pol is valid, else
it updates pp's error state and returns CMPP_RC_RANGE. This is a
no-op if pp has error state, returning that code instead.
The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify
certain client-side usage.
*/
CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol);
/**
Pushes pol as the current at-policy. Returns 0 on success and
non-zero on error (bad pol value or allocation error). If this
returns 0 then the caller is obligated to eventually call
cmpp_atpol_pop() one time. If it returns non-0 then they _must not_
call that function.
*/
CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol);
/**
Must be called one time for each successful call to
cmpp_atpol_push(). It restores the at-policy to the value it
has when cmpp_atpol_push() was last called.
If called when no cmpp_delimiter_push() is active then debug builds
will fail an assert(), else pp's error state is updated if it has
none already.
*/
CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp);
/**
The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It
behaves identically, just for a different policy group with
different names.
Its accepted values are: "null" and "error". The value "current" is
only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid.
*/
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z);
/**
Returns pp's current policy regarding use of undefined define keys.
*/
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp);
/**
Sets pp's current policy regarding use of undefined define keys.
Returns 0 if pol is valid, else it updates pp's error state and
returns CMPP_RC_RANGE.
*/
CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol);
/**
The undefined-policy counterpart of cmpp_atpol_push().
*/
CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol);
/**
The undefined-policy counterpart of cmpp_atpol_pop().
*/
CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp);
/**
The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen
(if zOpen is not NULL) to the opening delimiter and *zClose (if
zClose is not NULL) to the closing delimiter. The memory is owned
by pp and may be invalidated by any calls to cmpp_atdelim_set(),
cmpp_atdelim_push(), or any APIs which consume input. Each string
is NUL-terminated and must be copied by the caller if they need
these strings past a point where they might be invalidated.
If called when the the delimiter stack is empty, debug builds with
fail an assert() and non-debug builds will behave as if the stack
contains the compile-time default delimiters.
*/
CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp,
char const **zOpen,
char const **zClose);
/**
The `@token@`-delimiter counterpart of cmpp_delimeter_set().
This sets the delimiter for `@token@` content to the given opening
and closing strings (which the library makes a copy of). If zOpen
is NULL then the compile-time default is assumed. If zClose is NULL
then zOpen is assumed.
Returns 0 on success. Returns non-0 if called when the delimiter
stack is empty, if it cannot copy the string or zDelim is deemed
unsuitable for use as a delimiter.
In debug builds this will trigger an assert if no `@token@`
delimiter has been set, but pp starts with one level in place, so
it is safe to call without having made an explicit
cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused.
*/
CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp,
char const *zOpen,
char const *zClose);
/**
The `@token@`-delimiter counterpart of cmpp_delimeter_push().
See cmpp_atdelim_set() for the semantics of the arguments.
*/
CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp,
char const *zOpen,
char const *zClose);
/**
The @token@-delimiter counterpart of cmpp_delimiter_pop().
*/
CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp);
/**
Searches the given path (zPath), split on the given path separator
(pathSep), for the given file (zBaseName), optionally with the
given file extension (zExt).
If zBaseName or zBaseName+zExt are found as-is, without any
search path prefix, that will be the result, else the result
is either zBaseName or zBaseName+zExt prefixed by one of the
search directories.
On success, returns a new string, transfering ownership to the
caller (who must eventually pass it to cmpp_mfree() to deallocate).
If no match is found, or on error, returns NULL. On a genuine
error, pp's error state is updated and the error is unlikely to be
recoverable (see cmpp_errf()).
This function is a no-op if called when pp's error state is set,
returning NULL.
Results are undefined (in the sense of whether it will work or not,
as opposed to whether it will crash or not) if pathSep is a control
character.
Design note: this is implemented as a Common Table Expression
query.
*/
CMPP_EXPORT char * cmpp_path_search(cmpp *pp,
char const *zPath,
char pathSep,
char const *zBaseName,
char const *zExt);
/**
Flags and constants related to the cmpp_args type.
*/
enum cmpp_args_e {
/**
cmpp_args_parse() flag which tells cmpp_args_parse() not to
dive into (...) group tokens. It insteads leaves them to be parsed
(or not) by downstream code. The only reason to parse them in
advance is to catch syntax errors sooner rather than later.
*/
cmpp_args_F_NO_PARENS = 0x01,
/**
Tells cmpp_args_parse() to unescape special character sequences,
as per cmpp_b_unescape().
*/
cmpp_args_F_UNESCAPE = 0x02,
/**
Tells the parser to apply the following hack to {...} tokens:
Leading spaces up to the first leading newline are stripped from
the token, along with that one newline.
A sample case where this is relevant:
```
#define x {
blah @foo@
}
#expand x {foo -> 1}
```
If we don't do this (to retain backwards compatibility) then such
constructs may produce a stray newline where they didn't before.
When the library parses directive arguments, directives with the
cmpp_f_F_NO_SQUIGGLY_HACK do not get this and all others do.
The legacy behavior is retained for directives which predate
this addition.
*/
cmpp_args_F_NO_SQUIGGLY_HACK = 0x04
//cmpp_args_F_SQUIGGLY_HACK = 0x04
};
/**
An internal detail of cmpp_args.
*/
typedef struct cmpp_args_pimpl cmpp_args_pimpl;
/**
This type is deprecated. It is retained to support the
pre-2026-09 directive API changes. It is not applicable to
directives whose callbacks use the cmpp_f_F_ARGS_V4 flag.
A container for parsing a line's worth of cmpp_arg objects.
Instances MUST be cleanly initialized by bitwise-copying either
cmpp_args_empty or (depending on the context) cmpp_args_empty_m
before passing them to any APIs.
Instances MUST eventually be passed to cmpp_args_cleanup().
Design notes: this class is provided to the public API as a
convenience, not as a core/required component. It offers one of
many possible solutions for dealing with argument lists and is not
the End All/Be All of solutions. i didn't _really_ want to expose
this class in the public API at all but i also want client-side
directives to have the _option_ to to do some of the things
currently builtin directives can do which are (as of this writing)
unavailable in the public API, e.g. evaluate expressions (in that
limited form which this library supports). A stepping stone to
doing so is making this class public.
*/
struct cmpp_args {
/**
Number of parsed args. In the context of a cmpp_f(), argument
lists do not include their directive's name as an argument because
they have it via cmpp_f_args::d.
*/
unsigned argc;
/**
This is the Olde Waye of doing this. Its state is undefined for
cmpp_args objects passed to cmpp_f() imps on behalf of directives
or commands which have the the cmpp_f_F_ARGS_V4 flag.
The list of args. This is very specifically NOT an array (or at
least not one which client code can rely on to behave
sensibly). Some internal APIs adjust a cmpp_args's arg list,
re-linking the entries via cmpp_arg::next and making array-style
traversal a foot-gun.
To loop over them:
for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...}
For directives with the cmpp_f_F_ARGS_RAW flag, this member will,
after a successful call to cmpp_dx_next(), point to a single
argument which holds the directive's entire argument string,
stripped of leading spaces. That memory IS INVALIDATED by any
APIs which consume input, so copy it if needed or use
cmpp_f_F_ARGS_V4 instead (which makes lifetime guarantees).
*/
cmpp_arg const * arg0;
/**
Internal implementation details. This is initialized via
cmpp_args_parse() and freed via cmpp_args_cleanup().
*/
cmpp_args_pimpl * pimpl;
};
typedef struct cmpp_args cmpp_args;
/**
Empty-initialized cmpp_args instance, intended for const-copy
initialization.
*/
#define cmpp_args_empty_m { \
.argc = 0, \
.arg0 = 0, \
.pimpl = 0 \
}
/**
Empty-initialized instance, intended for non-const-copy
initialization.
*/
extern const cmpp_args cmpp_args_empty;
/**
DEPRECATED. Probably. The need for this goes away with
cmpp_f_F_ARGS_V4-style args processing, and this can be moved into
the internals (where we currently still need it for the older
expression engine).
Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg
objects by iteratively processing that range with cmpp_arg_parse().
If nIn is negative, strlen() is used to calculate it.
Requires that arg be a cleanly-initialized instance (via
bitwise-copying cmpp_args_empty) or that it have been successfully
used with this function before. Behavior is undefined if pArgs was
not properly initialized.
The 3rd argument is an optional bitmask of flags from the
cmpp_args_e enum.
On success it populates arg, noting that an empty list is valid.
The memory pointed to by the arguments made available via
arg->arg0 is all owned by arg and will be invalidated by either a
subsequent call to this function (the memory will be overwritten or
reallocated) or cmpp_args_cleanup() (the memory will be freed).
On error, returns non-0 and updates pp's error state with info
about the problem.
*/
CMPP_EXPORT int cmpp_args_parse(cmpp * pp,
cmpp_args * pOut,
unsigned char const * zInBegin,
cmpp_strlen_t nIn,
cmpp_flag32_t flags);
/**
Frees any resources owned by its argument but does not free the
argument (which is typically stack-allocated). After calling this,
the object may again be used with cmpp_args_parse() (in which case
it eventually needs to be passed to this again).
This is a harmless no-op if `a` is already cleaned up but `a` must
not be NULL.
*/
CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a);
/**
DEPRECATED. Probably. The need for this goes away with
cmpp_f_F_ARGS_V4-style args processing.
A wrapper around cmpp_args_parse() which uses dx->args.z as an
input source. This is sometimes convenient in cmpp_f()
implementations which use cmpp_dx_next(), or similar, to read and
process custom directives, as doing so invalidates dx->arg's
memory.
On success, returns 0 and populates args. On error, returns non-0
and sets dx->pp's error state.
cmpp_dx_args_clone() does essentially the same thing, but is more
efficient when dx->args.arg0 is is already parsed.
*/
CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args);
/**
DEPRECATED 2026-08-25: this is not needed for directives which use
cmpp_f_F_ARGS_V4, as that flag uses an approach which does not
invalidate argument memory when recursing into the library.
Once/if older directive code is updated, this function will go
away.
Populates pOut, replacing any current content, with a copy of each
arg in dx->args.arg0 (traversing arg0->next).
*pOut MUST be cleanly initialized via copying cmpp_args_empty or it
must have previously been used with either cmpp_args_parse() (which
has the same initialization requirement) or this function has
undefined results.
On success, pOut->argc and pOut->arg0 will refer to pOut's copy
of the arguments.
Copying of arguments is necessary in cmpp_f() implementations
which need to hold on to arguments for use _after_ calling
cmpp_dx_next() or any API which calls that (which most directives
don't do). See that function for why.
*/
CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut);
/**
Callback type for use with cmpp_kav_each().
cmpp_kav_each() calls this one time per key/value in such a list,
passing it the relevant key/value strings and lengths, plus the
opaque state pointer which is passed to cmpp_kav_each().
Must return 0 on success or update (or propagate) dx->pp's error
state on error.
*/
typedef int cmpp_kav_each_f(
cmpp *pp,
unsigned char const *zKey, cmpp_size_t nKey,
unsigned char const *zVal, cmpp_size_t nVal,
void* callbackState
);
/**
Flag bitmask for use with cmpp_kav_each().
*/
enum cmpp_kav_each_e {
/**
The key argument should be expanded using cmpp_arg_interpolate()
with a 0 flags value. This flag should normally not be used.
*/
cmpp_kav_each_F_EXPAND_KEY = 0x01,
/**
The key argument should be expanded using cmpp_arg_interpolate()
with a 0 flags value. This flag should normally be used.
*/
cmpp_kav_each_F_EXPAND_VAL = 0x02,
/**
Treat (...) value tokens (ttype=cmpp_tt_GroupParen) as integer
expressions. Keys are never treated this way. Without this flag,
the token expands to the ... part of (...).
*/
cmpp_kav_each_F_PARENS_EXPR = 0x04,
/**
Indicates that an empty input list is an error. If this flag is
not set and the list is empty, the callback will not be called
and no error will be triggered.
*/
cmpp_kav_each_F_NOT_EMPTY = 0x08,
/**
Indicates that the list does not have the '->' part(s). That is,
the list needs to be in pairs of KEY VAL rather than triples of
KEY -> VALUE.
*/
cmpp_kav_each_F_NO_ARROW = 0x10,
/**
If set, keys get the cmpp_arg_interpolate_BRACE_CALL flag added
to them. This implies cmpp_kav_each_F_EXPAND_KEY.
*/
cmpp_kav_each_F_CALL_KEY = 0x20,
/** Value counterpart of cmpp_kav_each_F_CALL_KEY. */
cmpp_kav_each_F_CALL_VAL = 0x40,
/** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */
cmpp_kav_each_F_CALL = 0x60,
//TODO: append to defines which already exist
cmpp_kav_each_F_APPEND = 0,
cmpp_kav_each_F_APPEND_SPACE = 0,
cmpp_kav_each_F_APPEND_NL = 0
};
/**
A helper for cmpp_f() implementations in processing directive
arguments which are lists in this form:
{ a -> b c -> d ... }
("kav" is short for "key arrow value".)
The range [zBegin,zBegin+nIn) contains the raw list (not including
any containing braces, parentheses, quotes, or the like). If nIn is
negative, strlen() is used to calculate it.
The range is parsed using cmpp_args_parse().
For each key/arrow/value triplet in that list, f() is passed the
stringified form of the key and the value, plus the callbackState
pointer.
The flags argument controls whether the keys and values get
expanded or not. (Typically the keys should not be expanded but the
values should.)
Returns 0 on success. If the callback returns non-0, it is expected
to have updated dx's error state. f() will never be called when
dx's error state is non-0.
Error results include:
- CMPP_RC_RANGE: the list is empty does not contain the correct
number of entries (groups of 3, or 2 if flags has
cmpp_kav_each_F_NO_ARROW).
- CMPP_RC_OOM: allocation error.
- Any value returned by cmpp_args_parse().
- Any number of errors can be triggered during expansion of
keys and values.
*/
CMPP_EXPORT int cmpp_kav_each(
cmpp *pp,
unsigned char const *zBegin,
cmpp_strlen_t nIn,
cmpp_kav_each_f callback, void *callbackState,
cmpp_flag32_t flags
);
#if 0
// untested
/**
This works like cmpp_kav_each() except that it treats each token in
the list as a single entry.
When the callback is triggered, the "key" part will be the raw
token and the "value" part will be the expanded form of that
value. Its flags may contain most of the cmpp_kav_each_F_... flags,
with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect
here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the
callback receives the same string for both the key and value.
*/
XCMPP_EXPORT int cmpp_str_each(
cmpp * pp,
unsigned char const *zBegin,
cmpp_strlen_t nIn,
cmpp_kav_each_f callback, void *callbackState,
cmpp_flag32_t flags
);
#endif
/**
An interface for clients to provide directives to the library
on-demand.
This is called when pp has encountered a directive name is does not
know. It is passed the cmpp object, the name of the directive, and
the opaque state pointer which was passed to cmpp_d_autoloader_et().
Implementations should compare dname to any directives they know
about. If they find no match they must return CMPP_RC_NO_DIRECTIVE
_without_ using cmpp_errf() to make the error persistent.
If they find a match, they must use cmpp_d_register() to register
it and (on success) return 0. The library will then look again in
the registered directive list for the directive before giving up.
If they find a match but registration fails then the result of that
failure must be returned.
For implementation-specific errors, e.g. trying to load a directive
from a DLL but the loading of the DLL fails, implementations are
expected to use cmpp_errf() to report the error and to return
that result code after performing any necessary cleanup.
It is legal for an implementation to register multiple directives
in a single invocation (in particular a pair of opening/closing
directives), as well as to register directives other than the one
requested (if necessary). Regardless of which one(s) it registers,
it must return 0 only if it registers one named dname.
*/
typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state);
/**
A c-pp directive "autoloader". See cmpp_d_autoloader_set()
and cmpp_d_autoloader_take().
*/
struct cmpp_d_autoloader {
/** The autoloader callback. */
cmpp_d_autoloader_f f;
/**
Finalizer for this->state. After calling this, if there's any
chance that this object might be later used, then it is important
that this->state be set to 0 (which this finalizer cannot
do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty
over any instances immediately after calling dtor().
*/
cmpp_finalizer_f dtor;
/**
Implementation-specific state, to be passed as the final argument
to this->f and this->dtor.
*/
void * state;
};
typedef struct cmpp_d_autoloader cmpp_d_autoloader;
/**
Empty-initialized cmpp_d_autoloader instance, intended for
const-copy initialization.
*/
#define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0}
/**
Empty-initialized cmpp_d_autoloader instance, intended for
non-const-copy initialization.
*/
extern const cmpp_d_autoloader cmpp_d_autoloader_empty;
/**
Sets pp's "directive autoloader". Each cmpp instance has but a
single autoloader but this API is provided so that several
instances may be chained from client-side code.
This function will call the existing autoloader's destructor (if
any), invalidating any pointers to its state object.
If pNew is not NULL then pp's autoloader is set to a bitwise copy
of *pNew, otherwise it is zeroed out. This transfers ownership of
pNew->state to pp.
See cmpp_d_autoloader_f()'s docs for how pNew must behave.
This function has no error conditions but downstream results are
undefined if if pNew and an existing autoloader refer to the same
dtor/state values (a gateway to double-frees).
*/
CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew);
/**
Moves pp's current autoloader state into pOld, transerring
ownership of it to the caller.
This obligates the caller to eventually either pass that same
pointer to cmpp_d_autoloader_set() (to transfer ownership back to
pp) or to call pOld->dtor() (if it's not NULL), passing it it
pOld->state (even if pOld->state is NULL). In either case, all
contents of pOld are semantically invalidated and perhaps freed.
This would normally be a prelude to cmpp_d_autoloader_set() to
install a custom, perhaps chained, autoloader.
*/
CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld);
/**
True only for ' ' and '\t'.
*/
CMPP_EXPORT bool cmpp_isspace(int ch);
/**
Reassigns *p to the address of the first non-space character at or
after the initial *p value. It stops looking if it reaches
zEnd. Newlines are not consider to be spaces (see cmpp_skip_snl()).
If `*p` does not point to memory before zEnd, or is not a part of
the same logical string, results are undefined.
Achtung: do not pass this the address of a cmpp_b::z,
or similar, as that will effectively corrupt the buffer's
memory. To trim a whole buffer, use something like:
```
cmpp_b ob = cmpp_b_empty;
... populate ob...;
// get the trimmed range:
unsigned char const *zB = ob.z;
unsigned char const *zE = zB + n;
cmpp_skip_snl(&zB, zE);
assert( zB<=zE );
cmpp_skip_snl_trailing(zB, &zE);
assert( zE>=zB );
printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB);
```
Those assert()s are not error handling - they're demonstrating
invariants of the calls made before them.
*/
CMPP_EXPORT void cmpp_skip_space(unsigned char const **p,
unsigned char const *zEnd );
/**
Works just like cmpp_skip_space() but it also
skips newlines.
FIXME (2026-02-21): it does not recognize CRNL pairs as
atomic newlines.
*/
CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p,
unsigned char const *zEnd );
/**
"Trims" trailing cmpp_isspace() characters from the range [zBegin,
*p). *p must initially point to one byte after the end of zBegin
(i.e. its NUL byte or virtual EOF). Upon return *p will be modified
leftwards (if at all) until a non-space is found or *p==zBegin.
*/
CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin,
unsigned char const **p );
/**
Works just like cmpp_skip_space_trailing() but skips
cmpp_skip_snl() characters.
FIXME (2026-02-21): it does not recognize CRNL pairs as
atomic newlines.
*/
CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin,
unsigned char const **p );
/**
Generic array-of-T list memory-reservation routine.
*list is the input array-of-T. nDesired is the number of entries to
reserve (list entry count, not byte length). *nAlloc is the number
of entries allocated in the list. sizeOfEntry is the sizeof(T) for
each entry in *list. T may be either a value type or a pointer
type and sizeofEntry must match, i.e. it must be sizeof(T*) for a
list-of-pointers and sizeof(T) for a list-of-objects.
If pp is not NULL then this function updates pp's error state on
error, else it simply returns CMPP_RC_OOM on error. If pp is not
NULL then this function is a no-op if called when pp's error state
is set, returning that code without other side-effects.
If nDesired > *nAlloc then *list is reallocated to contain at least
nDesired entries, else this function returns without side effects.
On success *list is re-assigned to the reallocated list memory, all
newly-(re)allocated memory is zeroed out, and *nAlloc is updated to
the new allocation size of *list (the number of list entries, not
the number of bytes).
On failure neither *list nor *nAlloc are modified.
Returns 0 on success or CMPP_RC_OOM on error. Errors generated by
this routine are, at least in principle, recoverable (see
cmpp_errf()), though that simply means that the pp object is
left in a well-defined state, not that the app can necessarily
otherwise recover from an OOM.
This seemingly-out-of-API-scope routine is in the public API as a
convenience for client-level cmpp_f() implementations[^1]. This API
internally has an acute need for basic list management and non-core
extensions inherit that as well.
[^1]: this project's own directives are written as if they were
client-side whenever feasible. Some require cmpp-internal state to
do their jobs, though.
*/
CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
cmpp_size_t * nAlloc, unsigned sizeOfEntry);
/**
State for a cmpp__dx_pimpl which we need in order to snapshot the
parse position for purposes of restoring it later. This is
basically to support that #query can contain other #query
directives, but this same capability is required by any directives
which want to both process directives in their content block and
loop over the content block.
See cmpp_dx_pos_save() and cmpp_dx_pos_restore().
*/
struct cmpp_dx_pos {
/** Current parse pos. */
unsigned char const *z;
/** Current line number. */
cmpp_size_t lineNo;
};
typedef struct cmpp_dx_pos cmpp_dx_pos;
#define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}
/**
Stores dx's current input position into pos. pos gets completely
initialized by this routine - it need not (in contrast to many
other functions in this library) be cleanly initialized by the
caller first.
*/
CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos);
/**
Restores dx's input position from pos. Results are undefined if pos
is not populated with the result of having passed the same dx/pos
pointer combination to cmpp_dx_pos_save().
*/
CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos);
/**
Returns, via its output parameters, the current name and input line
number of dx's current directive. This is intended for use with
synthesizing names such as those of embedded scripts. The *zName
bytes are owned by dx and are valid until it is destroyed.
Caveat: this specifically refers to the line holding the current
directive, not any non-directive lines consumed since that
directive was captured.
*/
CMPP_EXPORT void cmpp_dx_src_pos_info(cmpp_dx const *dx, char const **zName,
cmpp_size_t * lineNo);
/**
Expects to be passed a non-NULL pointer and its ostensible length.
If n<0 then this returns strlen(z) else it returns n.
This implements the library's common "if n<0 then use strlen()"
convention.
*/
CMPP_EXPORT cmpp_size_t cmpp_strlen(char const *z, cmpp_strlen_t n);
/**
Unsigned counterpart of cmpp_strlen().
*/
CMPP_EXPORT cmpp_size_t cmpp_strlenu(unsigned char const *z, cmpp_strlen_t n);
/**
Returns true unless:
- !*z
- '0'==*z and !z[1]
z may be NULL only if n is 0. If non-NULL then it must of course be
a valid poiner (even if it's the terminating NUL byte of a string).
*/
CMPP_EXPORT bool cmpp_simple_truth(unsigned char const *z, cmpp_strlen_t n);
/**
If [z,n) is unambiguously a base-10 int, sets *pOut (if not NULL)
to that value and returns true, else returns false.
Warning: because this uses strtol() it may scan more bytes than n.
n is used to validate that strtol()'s ending position matches n,
not to limit the range of z scanned. This is only a warning if z is
shorter than 10 bytes (20 for cmpp_is_int64()) and not
NUL-terminated, otherwise it's irrelevant.
*/
CMPP_EXPORT bool cmpp_is_int(unsigned char const *z, unsigned n,
int *pOut);
/** 64-bit counterpart of cmpp_is_int() which uses strtoll(). See
cmpp_is_int() for a caveat about the scan range.
*/
CMPP_EXPORT bool cmpp_is_int64(unsigned char const *z, unsigned n,
int64_t *pOut);
/**
Returns true if the string [z,z+n) is valid JSON, per SQLite3's
json_valid(theString,flags). If n is negative, strlen() is used to
calculate it.
This function is a no-op, returning false, if pp has pending error
state. That is arguable but it's inherited from lower-level APIs
which this one builds on.
It is wildly hypothetically possible that this call can put pp into
an error state due to db-level issues, but that can be effectively
ruled out for the sake of usage sanity. It will always return false
in that case but may update pp's error state.
Similarly: json_valid() triggers a db error if passed a flags value
which is not in the range [1,15]. To avoid that a client doing so
puts pp into an error state, this function returns false without
side-effects if passed a value out of that range.
*/
CMPP_EXPORT bool cmpp_json_valid(cmpp *pp, unsigned char const *z,
cmpp_strlen_t n, int flags);
/**
Expects [z,z+n) to point to a single JSON value. If
cmpp_json_valid() returns true for the range, it is appended to be
as-is, else it is appended as a JSON-quoted string. Returns 0 on
success, non-0 (OOM or related range overflow) on error, updating
pp's error state if it returns non-0.
For cmpp_json_valid() purposes, a flags value of 3 (JSON/JSON5) is
used.
*/
CMPP_EXPORT int cmpp_b_append_json_value(cmpp *pp, cmpp_b *b,
unsigned char const *z,
cmpp_strlen_t n);
/**
Returns true if [z,z+n) begins with '[', ends with ']', and
cmpp_json_valid() for the same range returns true. i.e. if it's a
JSON-format list. Because this checks for '[' and ']' before
attempting to parse it, this is very fast for most non-list
strings.
If allowJson5 is true then it uses SQLite3 json_valid() flags of 3,
else flags of 1.
*/
CMPP_EXPORT bool cmpp_json_is_list(cmpp *pp, unsigned char const *z,
cmpp_strlen_t n, bool allowJson5);
/**
The JSON-object counterpart of cmpp_json_is_list().
*/
CMPP_EXPORT bool cmpp_json_is_object(cmpp *pp, unsigned char const *z,
cmpp_strlen_t n, bool allowJson5);
/**
Appends some form of [z,z+n) to str, using strlen() to calculate n
if n is negative:
- 0==n: append "" (and empty JSON string).
- cmpp_json_valid(z,n) is true: append the bytes as-is.
- Else append n bytes of z as a JSON-format string using
sqlite3_str_appendf()'s `%J` formatter.
This returns void because that's how sqlite3_str's APIs work. It
will only fail if str gets "too big" (the limits of which varies by
str's source but all of which are arguably out of scope for
cmpp-sized inputs and outputs).
*/
void cmpp_str_json_append(cmpp *pp, sqlite3_str * str,
unsigned char const * z,
cmpp_strlen_t n);
/**
A proxy for sqlite3_prepare_v2() which uses pp's database, supports
sqlite3_str formatting, and updates pp's error state on error.
zSql is the SQL string. nSql is the byte length of zSql, or its
strlen() if nSql is negative.
If pp has error state when this is called, it is a no-op returning
that error code.
For empty inputs sqlite3_prepare() and friends return success but a
NULL *pStmt. This wrapper triggers a CMPP_RC_MISUSE error for that
case.
*/
CMPP_EXPORT int cmpp_stmt_prepare(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, cmpp_strlen_t nSql);
/**
Variadic form of cmpp_stmt_prepare() which supports sqlite3_str
formatting.
*/
CMPP_EXPORT int cmpp_stmt_preparef(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, ...);
/**
va_list counterpart of cmpp_stmt_preparef().
*/
CMPP_EXPORT int cmpp_stmt_preparev(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, va_list);
/**
Steps statement q one time.
On success it returns SQLITE_ROW or SQLITE_DONE, as per
sqlite3_step(). On error it returns another non-0 SQLITE_... code
and updates pp's error state with a corresponding CMPP_RC_... code
and error string.
This is a no-op if called when pp has an error set, returning
SQLITE_ERROR.
If resetIt is true, q is passed to cmpp_stmt_reset(), else the
caller must eventually reset it. Only pass true when no result
columns need to be fetched.
Reminder to self: this must return an SQLITE_... code, not a
CMPP_RC_... code, because it's more useful that way (but also
slightly confusing).
*/
CMPP_EXPORT int cmpp_stmt_step(cmpp * const pp, sqlite3_stmt * const q,
bool resetIt);
/**
Resets and clears bindings from q if q is not NULL. Returns 0 if q
is NULL or if sqlite3_reset() indicates no error (SQLITE_DONE and
SQLITE_ROW are considered to be success codes in this context). If
sqlite3_reset() indicates an error and pp's error state is not
already set then pp's error state is updated and a non-0 value is
returned.
*/
CMPP_EXPORT int cmpp_stmt_reset(cmpp *pp, sqlite3_stmt * const q);
/**
Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or
SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err
is updated with pp->db's current error state. zMsgSuffix is an
optional suffix for the generated error message.
nMsgSuffix is the length of zMsgSuffix, or negative to use strlen()
to figure it out.
*/
CMPP_EXPORT int cmpp_db_rc_v2(cmpp *pp, int dbRc, char const *zMsgSuffix,
cmpp_strlen_t nMsgSuffix);
/** Older signature for cmpp_db_rc_v2(). */
#define cmpp_db_rc(PP,RC,ZMSG) cmpp_db_rc_v2(PP, RC, ZMSG, -1)
/** Proxy for sqlite3_bind_int64() which updates pp's error state on
error. */
CMPP_EXPORT int cmpp_stmt_bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
/**
Proxy for cmpp_stmt_bind_text() which encodes val as a string and
updates pp's error state on error.
For queries which compare values, it's important that they all have
the same type, so some cases where we might want an int needs to be
bound as text instead. As of this writing (2026-08-23), arg.c
depends on this for expression evaluation.
*/
CMPP_EXPORT int cmpp_stmt_bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
/** SQL NULL counterpart of cmpp_stmt_bind_int(). */
CMPP_EXPORT int cmpp_stmt_bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col);
/**
Proxy for sqlite3_bind_text() which updates pp's error state on
error.
*/
CMPP_EXPORT int cmpp_stmt_bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col,
unsigned const char * zStr);
/**
Proxy for sqlite3_bind_text() which updates pp's error state on
error.
*/
CMPP_EXPORT int cmpp_stmt_bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col,
unsigned const char *zStr, cmpp_strlen_t len);
/**
Like cmpp_stmt_bind_textn() but takes a finalizer function as its final
argument. Don't use this without fully understanding why.
Maintenance reminder: if finalizer is declared as void (*dtor)(void
*) then tool/pullthunk.sh is emitting it for the #define cmpp_xyz
part but not the cmpp_api_thunk_map() part. Weird.
*/
CMPP_EXPORT int cmpp_stmt_bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col,
unsigned const char * zStr, cmpp_strlen_t n,
cmpp_finalizer_f finalizer);
/**
The current cmpp_api_thunk::apiVersion value. See
cmpp_api_thunk_map.
*/
#define cmpp_api_thunk_version 20260929
/**
This stub object is provided for cmpp interface compatibility with
loadable-module-capable builds, but that feature is stripped from
this build.
*/
struct cmpp_api_thunk {
uint32_t const thunkVersion;
};
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*NET_WANDERINGHORSE_CMPP_H_INCLUDED_*/
#endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */
#if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED)
#define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED
/**
This file houses declarations and macros for the private/internal
libcmpp APIs.
*/
#include "sqlite3.h"
#include <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 "expecting CMPP_VERSION to have been set up"
#endif
#define CMPP__DB_MAIN_NAME "cmpp"
#if defined(CMPP_AMALGAMATION)
#define CMPP_PRIVATE static
#else
#define CMPP_PRIVATE
#endif
#if CMPP_PLATFORM_IS_WASM
# define CMPP_PLATFORM_IS_WINDOWS 0
# define CMPP_PLATFORM_IS_UNIX 0
# define CMPP_PLATFORM_PLATFORM "wasm"
# define CMPP_PATH_SEPARATOR ':'
# define CMPP__EXPORT_NAMED(X) \
__attribute__((export_name(#X),used,visibility("default")))
// See also:
//__attribute__((export_name("theExportedName"), used, visibility("default")))
# define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */
# if !defined(CMPP_PLATFORM_EXT_DLL)
# define CMPP_PLATFORM_EXT_DLL ""
# endif
#else
//# define CMPP_WASM_EXPORT
# define CMPP__EXPORT_NAMED(X)
# if defined(_WIN32) || defined(WIN32)
# define CMPP_PLATFORM_IS_WINDOWS 1
# define CMPP_PLATFORM_IS_UNIX 0
# define CMPP_PLATFORM_PLATFORM "windows"
# define CMPP_PATH_SEPARATOR ';'
//# include <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
#if !defined(CMPP_SWITCH_FALL_THROUGH)
# if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7)
/*
#define CMPP_USING_GCC
gcc v7+ treats implicit 'switch' fallthrough as a warning
(i.e. error because we always build with -Wall -Werror -Wextra
-pedantic).
Similarly curious, clang #defines __GNUC__.
_Sigh_.
*/
# define CMPP_SWITCH_FALL_THROUGH __attribute__ ((fallthrough))
# else
# define CMPP_SWITCH_FALL_THROUGH
# endif
#endif
/* /CMPP_SWITCH_FALL_THROUGH */
/**
Declares struct T as a container for a list-of-MT. MT may be
pointer-qualified. cmpp__ListType_impl() with the same arguments
implements T_reserve() for basic list allocation. Cleanup, alas, is
MT-dependent.
*/
#define cmpp__ListType_decl(T,MT) \
struct T { \
MT * list; \
cmpp_size_t n; \
cmpp_size_t na; \
}; \
typedef struct T T; \
int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min)
#define cmpp__ListType_impl(T,MT) \
int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \
return cmpp_array_reserve(pp, (void**)&li->list, min, \
&li->na, sizeof(MT)); \
}
#define cmpp__LIST_T_empty_m {.list=0,.n=0,.na=0}
/**
Stack of POD values. Intended for use with cmpp at-token and
undefined key policies.
*/
#define cmpp__PodList_decl(ST,ET) \
struct ST { \
/* current stack index */ \
cmpp_size_t n; \
cmpp_size_t na; \
ET * stack; \
}; typedef struct ST ST; \
void ST ## _wipe(ST * s, ET v); \
int ST ## _push(cmpp *pp, ST * s, ET v); \
void ST ## _set(ST * s, ET v); \
void ST ## _finalize(ST * s); \
void ST ## _pop(ST *s); \
int ST ## _reserve(cmpp *, ST *, cmpp_size_t min)
#define cmpp__PodList_impl(ST,ET) \
void ST ## _wipe(ST * const s, ET v){ \
if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na); \
s->n = 0; \
} \
int ST ## _reserve(cmpp * const pp, ST * const s, \
cmpp_size_t min){ \
return cmpp_array_reserve(pp, (void**)&s->stack, min>0 \
? min : (s->n \
? (s->n==s->na-1 \
? s->na*2 : s->n+1) \
: 8), \
&s->na, sizeof(ET)); \
} \
int ST ## _push(cmpp * const pp, ST * s, ET v){ \
if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v; \
return ppCode; \
} \
void ST ## _set(ST * s, ET v){ \
assert(s->n); \
if( 0== ST ## _reserve(NULL, s, 0) ){ \
s->stack[s->n] = v; \
} \
} \
ET ST ## _get(ST const * const s){ \
assert(s->na && s->na >=s->n); \
return s->stack[s->n]; \
} \
void ST ## _pop(ST *s){ \
assert(s->n); \
if(s->n) --s->n; \
} \
void ST ## _finalize(ST *s){ \
cmpp_mfree(s->stack); \
s->stack = NULL; \
s->n = s->na = 0; \
}
/**
Internal-use-only flags for use with cmpp_d::flags.
These supplement the ones from the public API's cmpp_d_e.
*/
enum cmpp_f_ext_e {
/**
Mask of flag bits from this enum and cmpp_d_e which are for
internal use only and are disallowed in client-side directives.
*/
cmpp_f_F_MASK_INTERNAL = ~cmpp_f_F_MASK,
/**
If true, and if cmpp_f_F_ARGS_LIST is set, then cmpp_args_parse()
will pass its results to cmpp_args__not_simplify(). Only
directives which eval cmpp_arg expressions need this, and the
library does not expose the pieces for evaluating such
expressions. As such, this flag is for internal use only. This
only has an effect if cmpp_f_F_ARGS_LIST is also used.
*/
cmpp_f_F_NOT_SIMPLIFY = 0x10000,
/**
Most directives are inert when they are seen in the "falsy" part
of an if/else. The callbacks for such directives are skipped, as
opposed to requiring each directive's callback to check whether
they should be skipping. This flag indicates that a directive
must always be run, even when skipping content (e.g. inside of an
#if 0 block). Only flow-control directives may have the
FLOW_CONTROL bit set. The library API does not expose enough of
its internals for client-defined directives to make flow-control
decisions.
i really want to get rid of this flag but it seems to be a
necessary evil.
*/
cmpp_f_F_FLOW_CONTROL = 0x20000
};
/**
A single directive line from an input stream.
*/
struct CmppDLine {
/** 1-based line number in the source input. */
cmpp_size_t lineNo;
/** Start of the line within its source input, immediately
after the directive delimiter. */
unsigned char const * zBegin;
/** Start of the line including the directive delimiter. */
unsigned char const * zReallyBegin;
/** One-past-the-end byte of the line. A virtual EOF. It will only
actually be NUL-terminated if it is the last line of the input
and that input has no trailing newline. */
unsigned char const * zEnd;
};
typedef struct CmppDLine CmppDLine;
#define CmppDLine_empty_m \
{.lineNo=0U,.zBegin=0,.zReallyBegin=0,.zEnd=0}
/**
A "stamp" type to turn arbitrary memory blocks into a recycling
list. It can be used to recycle blocks of any size equal to
or greater than its own.
*/
struct CmppRecyclerOverlay {
/* Size */
int32_t size;
void * next;
};
typedef struct CmppRecyclerOverlay CmppRecyclerOverlay;
struct CmppRecyclerList {
/** Head of recycling list. */
CmppRecyclerOverlay *head;
/**
The number of entries in this->head. We store this only so that
we can set some cap on it.
*/
cmpp_size_t n;
};
typedef struct CmppRecyclerList CmppRecyclerList;
#define CmppRecyclerList_empty_m {0,0}
/**
CmppLvl represents one "level" of parsing, pushing one level
for each of `#if` and popping one for each `#/if`.
These pieces are ONLY for use with flow-control directives. It's
not proven that they can be of any use to more than a single
flow-control directive. e.g. if we had a hypothetical #foreach, we
may need to extend this.
*/
struct CmppLvl {
/**
Bitmask of CmppLvl_F_...
*/
cmpp_flag32_t flags;
/**
The directive line number which started this level. This is used for
reporting the starting lines of erroneously unclosed block
constructs.
*/
cmpp_size_t lineNo;
};
typedef struct CmppLvl CmppLvl;
#define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U}
/**
A dynamically-allocated list of CmppLvl objects.
*/
cmpp__ListType_decl(CmppLvlList,CmppLvl*);
#define CmppLvlList_empty_m cmpp__LIST_T_empty_m
CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx);
CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx);
CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl);
CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on);
CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl);
CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx);
/**
A dynamically-allocated list of cmpp_b objects.
*/
cmpp__ListType_decl(cmpp_b_list,cmpp_b*);
#define cmpp_b_list_empty_m cmpp__LIST_T_empty_m
extern const cmpp_b_list cmpp_b_list_empty;
CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li);
//CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li);
//CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li);
/**
cmpp_b_list sorting policies. NULL entries must
always sort last.
*/
enum cmpp_b_list_e {
cmpp_b_list_UNSORTED,
/* Smallest first. */
cmpp_b_list_ASC,
/* Largest first. */
cmpp_b_list_DESC
};
struct cmpp_b_library {
cmpp_b_list list;
/** How/whether this->list is sorted. */
enum cmpp_b_list_e sort;
/** Max number of buffers. 0 disables borrowing,
always allocating. */
uint32_t limitCount;
/** Largest capacity of single buffer to keep.
If exceeded, we keep the cmpp_b instance but
toss its memory. A value of 0 disables this
limit. */
uint32_t limitSingleCapacity;
/** Max total of cmpp_b::nAlloc in the buffers. 0 means no
limit. */
uint64_t limitCapacity;
/** Current cmpp_b::nAlloc in the buffers. */
uint64_t currentCapacity;
};
typedef struct cmpp_b_library cmpp_b_library;
#define cmpp_b_library_empty_m { \
.list=cmpp_b_list_empty_m, \
.sort=cmpp_b_list_UNSORTED, \
.limitCount=50, \
.limitSingleCapacity=512 * 8, \
.limitCapacity=1024 * 1024 * 4, \
.currentCapacity=0 \
}
#if 0
// todo
CMPP_PRIVATE int cmpp_b_library_borrow(cmpp_b_library *li);
CMPP_PRIVATE void cmpp_b_library_return(cmpp_b_library *li);
#endif
/** Cleans up and clears li but retains its various
limit-related members. */
CMPP_PRIVATE void cmpp_b_library_dtor(cmpp_b_library *li);
/**
A dynamically-allocated list of cmpp_arg objects. Used primarily by
cmmp_args.
*/
struct CmppArgList{
/**
The list of arguments.
*/
cmpp_arg * list;
/**
From cmpp_b_borrow(). Holds the argv pointer-array memory.
*/
cmpp_b * avb;
/** Current number of args. */
cmpp_size_t n;
/** Allocated slots for args. */
cmpp_size_t na;
};
typedef struct CmppArgList CmppArgList;
#define CmppArgList_empty_m {.list=0,.avb=0,.n=0,.na=0}
/** Allocate a new arg, owned by li, and return it (cleanly zeroed
out). Returns NULL and updates pp->err on error. */
CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li);
/** Free all memory owned by li but does not free li. */
CMPP_PRIVATE void CmppArgList_dtor(cmpp *pp, CmppArgList *li);
CMPP_PRIVATE int CmppArgList_reserve(cmpp *pp, CmppArgList *li,
cmpp_size_t min);
/**
Returns a list of args suitable for settings as cmpp_f_args::argv,
or NULL if li->n==0 or on OOM (in which case pp's error state is
updated).
If ensureNulTerminated is true then this also redirects each
cmpp_arg::z pointer which does _not_ point to NUL-terminated memory
to to an internal copy of the original which is guaranteed to be
NUL-terminated. (A stepping stone towards removing
cmpp_args_pimpl's internal buffer.)
The memory is owned by this object and is invalidated on any list
operations, so should be set up (with this) immediately before the
callback needs them. It is invalidated by CmppArgList_append() or
CmppArgList_reserve().
*/
CMPP_PRIVATE cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp,
CmppArgList*li,
bool ensureNulTerminated);
/**
The internal part of the cmpp_args interface.
*/
struct cmpp_args_pimpl {
/**
We need(?) a (cmpp*) here for finalization/recycling purposes.
*/
cmpp *pp;
/**
True if these are the arguments for directive [call...] syntax.
This is not set for cmpp_itch calls.
2026-08-25: this is being replaced. We currently need it to keep
some older bits working.
*/
bool isDxCall;
/**
Next entry in the free-list.
TODO (2026-08-31): replace with CmppRecyclerOverlay. 2026-09-19:
we can't do that unless we give up (or reimplement) recycling of
the state reused by cmpp__args_pimpl_reuse().
*/
cmpp_args_pimpl * nextFree;
/** Version 3 and 4 of the real args memory. */
CmppArgList argli;
/**
cmpp_args_parse() copies each argument's bytes into here,
each one NUL-terminated.
*/
cmpp_b argOut;
};
#define cmpp_args_pimpl_empty_m { \
.pp = 0, \
.isDxCall = false, \
.nextFree = 0, \
.argli = CmppArgList_empty_m, \
.argOut = cmpp_b_empty_m \
}
extern const cmpp_args_pimpl cmpp_args_pimpl_empty;
CMPP_PRIVATE void cmpp__args_pimpl_cleanup(cmpp_args_pimpl *p);
/**
The internal part of the cmpp_dx interface.
*/
struct cmpp__dx_pimpl {
/** Start of input. */
unsigned const char * zBegin;
/** One-after-the-end of input. */
unsigned const char * zEnd;
/** For use in pinpointing line/column during error
reporting. */
unsigned const char * zErrPos;
/**
Next context up in the stack. When cmpp_dx's share an input range
in a parent/child manner, We can use this to provide better stack
traces and line/column info by delaying line count until an error
happens, discovering (through this pointer) the whole input
range, and using cmpp_count_nl() to find the line. It's slow,
but (A) we only count lines when generating errors and (B) it's
more accurate than the current approach, which is definitely
off-by-one (or-more).
That said: these don't typically share an input range, but can
for [calls].
*/
cmpp_dx const * up;
/**
Current input position. Generally speaking, only
cmpp_dx_delim_search() should update this, but it turns out that
the ability to rewind the input is necessary for looping
constructs, like #query, when they want to be able to include
other directives in their bodies. Thus we have cmpp_dx_pos_save()
and cmpp_dx_pos_restore() to manipulate this.
Achtung: as of 2026-09-23 this is only updated when we find a
directive line. Places needing more precise line counts are
encouraged to count them. See c.c:cmpp__dx_guess_lineno().
*/
cmpp_dx_pos pos;
/**
Currently input line.
*/
CmppDLine dline;
/** Number of active #savepoints. */
unsigned nSavepoint;
/** Current directive's args. Updated by the parsing and dispatching
steps. */
cmpp_args args;
/**
A stack of state used by #if and friends to inform the innards
that they must not generate output. This is largely historical
and could have been done differently had this code started as a
library instead of a monolithic app.
TODO is to figure out how best to move this state completely into
the #if handler, rather than fiddle with this all throughout the
processing. We could maybe move this stack into CmppIfState?
*/
CmppLvlList dxLvl;
struct {
/**
A copy of this->d's input line which gets translated
slightly from its native form for futher processing.
*/
cmpp_b line;
/**
Holds the semi-raw input line, stripped only of backslash-escaped
newlines and leading spaces. This is primarily for debug output
but also for custom arg parsing for some directives.
*/
cmpp_b argsRaw;
} buf;
/**
Record IDs for/from cmpp_[un]define_shadow().
*/
struct {
/** Shadow-define rowid for __FILE__. */
int64_t sidFile;
/** Shadow-define rowid for #include path entry. */
int64_t ridInclPath;
} shadow;
struct {
/**
True if the next directive is the start of a [call].
*/
bool nextIsCall;
} flags;
};
/**
Calculates the whole input range of dx and its parents, for
purposes of calculating line counts in. Sets *zBegin and *zEnd
to the half-open range.
*/
CMPP_PRIVATE void cmpp__dx_input_range(cmpp_dx const * dx,
unsigned char const **zBegin,
unsigned char const **zEnd);
/**
Initializes or resets a. Returns non-0 on OOM.
*/
CMPP_PRIVATE int cmpp__args_init(cmpp *pp, cmpp_args *a);
/**
If a has state then it's recycled for reuse, else this zeroes out a
except for a->pimpl, which is retained (but may be NULL).
*/
CMPP_PRIVATE void cmpp__args_reuse(cmpp_args *a);
#define cmpp__dx_pimpl_empty_m { \
.zBegin=0, .zEnd=0, .zErrPos=0,\
.pos=cmpp_dx_pos_empty_m, \
.up=0, \
.dline=CmppDLine_empty_m, \
.nSavepoint=0, \
.args = cmpp_args_empty_m, \
.dxLvl = CmppLvlList_empty_m, \
.buf = { \
cmpp_b_empty_m, \
cmpp_b_empty_m \
}, \
.shadow = { \
.sidFile = 0, \
.ridInclPath = 0 \
}, \
.flags = { \
.nextIsCall = false \
} \
}
/**
A level of indirection for CmppDList in order to be able to
manage ownership of their name (string) lifetimes.
*/
struct CmppDList_entry {
/** this->d.name.z points to this, which is owned by the CmppDList
which manages this object. */
char * zName;
cmpp_d d;
};
typedef struct CmppDList_entry CmppDList_entry;
#define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/}
/**
A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs.
We use a list of pointers instead of a flat list so that the
entries are not invalidated by addition of new directives).
*/
cmpp__ListType_decl(CmppDList,CmppDList_entry*);
#define CmppDList_empty_m cmpp__LIST_T_empty_m
/**
State for keeping track of DLL handles, a.k.a. shared-object
handles, a.k.a. "soh".
Instances of this must be either cleanly initialized by bitwise
copying CmppSohList_empty, memset() (or equivalent) them to 0, or
allocating them with CmppSohList_new().
*/
cmpp__ListType_decl(CmppSohList,void*);
#define CmppSohList_empty_m cmpp__LIST_T_empty_m
/**
Operators and operator policies for use with X=Y-format keys. This
is legacy stuff, actually, but some of the #define management still
needs it.
*/
#define CmppKvp_op_map(E) \
E(none,"") \
E(eq1,"=")
enum CmppKvp_op_e {
#define E(N,S) CmppKvp_op_ ## N,
CmppKvp_op_map(E)
#undef E
};
typedef enum CmppKvp_op_e CmppKvp_op_e;
/**
Result type for CmppKvp_parse().
*/
struct CmppKvp {
/* Key part of the kvp. */
cmpp_token k;
/* Key part of the kvp. Might be empty. */
cmpp_token v;
/* Operator part of kvp, if any. */
CmppKvp_op_e op;
};
typedef struct CmppKvp CmppKvp;
extern const CmppKvp CmppKvp_empty;
/**
Parses X or X=Y into p. Sets pp's error state on error.
If nKey is negative then strlen() is used to calculate it.
The third argument specifies whether/how to permit/treat the '='
part of X=Y.
*/
CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p,
unsigned char const *zKey,
cmpp_strlen_t nKey,
CmppKvp_op_e opPolicy);
cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e);
cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e);
#define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH
#define cmpp__policy(PP,WHICH) \
cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n]
/**
A "delimiter" object. That is, the "#" referred to in the libcmpp
docs. It's also outfitted for a second delimiter so that it can be
used for the opening/closing delimiters of @tokens@.
*/
struct cmpp__delim {
/**
Bytes of the directive delimiter/prefix or the @token@ opening
delimiter. Owned elsewhere but often points at this->zOwns.
*/
cmpp_token open;
/**
Closing @token@ delimiter. This has no meaning for the directive
delimiter.
*/
cmpp_token close;
/**
Memory, owned by this object, for this->open and this->close. In
the latter case, it's one string with both delimiters encoded in
it.
*/
unsigned char * zOwns;
};
typedef struct cmpp__delim cmpp__delim;
#define cmpp__delim_empty_m { \
.open={ \
.z=(unsigned char*)CMPP_DEFAULT_DELIM, \
.n=sizeof(CMPP_DEFAULT_DELIM)-1 \
}, \
.close=cmpp_token_empty_m, \
.zOwns=0 \
}
extern const cmpp__delim cmpp__delim_empty;
void cmpp__delim_cleanup(cmpp__delim *d);
/**
A dynamically-allocated list of cmpp__delim objects.
*/
cmpp__ListType_decl(cmpp__delim_list,cmpp__delim);
#define cmpp__delim_list_empty_m {0,0,0}
extern const cmpp__delim_list cmpp__delim_list_empty;
CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li);
static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){
return li->n ? li->list+(li->n-1) : NULL;
}
static inline void cmpp__delim_list_pop(cmpp__delim_list *li){
assert(li->n);
if( li->n ) cmpp__delim_cleanup(li->list + --li->n);
}
static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){
while( li->n ) cmpp__delim_cleanup(li->list + --li->n);
}
/**
The main public-API context type for this library.
*/
struct cmpp__pimpl {
/* Internal workhorse. */
struct {
sqlite3 * dbh;
/**
Optional filename. Memory is owned by this object.
*/
char * zName;
} db;
/**
Current directive context. This was historically const
but that was changed on 2026-08-24 for reasons now stored
in the SCM history from when this line was added.
*/
cmpp_dx * dx;
/** Output channel. */
cmpp_outputer out;
/**
Delimiters version 2.
*/
struct {
/**
Directive delimiter.
*/
cmpp__delim_list d;
/**
@token@ delimiters.
*/
cmpp__delim_list at;
} delim;
/** All frequently-reused queries. */
struct {
#define CMPP__SEL_V_FROM(N) \
"(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \
" ORDER BY source LIMIT 1)"
/** Name of library-generated savepoints. */
#define CMPP__SAVEPOINT_NAME "_cmpp_"
/**
One entry for each distinct query used by cmpp: E(X,SQL), where
X is the member's name and SQL is its SQL.
*/
#define CmppStmt_map(E) \
E(sdefIns, \
"INSERT INTO " \
CMPP__DB_MAIN_NAME ".sdef" \
"(t,k,v) VALUES(?1,?2,?3) RETURNING id") \
E(defIns, \
"INSERT INTO " \
CMPP__DB_MAIN_NAME ".def" \
"(t,k,v) VALUES(?1,?2,?3) " \
"ON CONFLICT(k) DO UPDATE SET v=EXCLUDED.v" \
) \
E(defDelLegacy, \
"DELETE FROM " \
CMPP__DB_MAIN_NAME ".def" \
" WHERE k GLOB ?1") \
E(defDelExact, \
"DELETE FROM " \
CMPP__DB_MAIN_NAME ".def" \
" WHERE k = ?1") \
E(sdefDel, \
"DELETE FROM " \
CMPP__DB_MAIN_NAME ".sdef" \
" WHERE k=?1 AND id>=?2") \
E(defHas, \
"SELECT 1 FROM " \
CMPP__DB_MAIN_NAME ".vdef" \
" WHERE k = ?1") \
E(defGet, \
"SELECT source,t,k,v FROM " \
CMPP__DB_MAIN_NAME ".vdef" \
" WHERE k = ?1 ORDER BY source LIMIT 1") \
E(defGetBool, \
"SELECT cmpp_truthy(v) FROM " \
CMPP__DB_MAIN_NAME ".vdef" \
" WHERE k = ?1" \
" ORDER BY source LIMIT 1") \
E(defGetInt, \
"SELECT CAST(v AS INTEGER)" \
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
" WHERE k = ?1" \
" ORDER BY source LIMIT 1") \
E(defSelAll, "SELECT t,k,v" \
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
" ORDER BY source, k") \
E(inclIns," INSERT OR FAIL INTO " \
CMPP__DB_MAIN_NAME ".incl(" \
" file,srcFile, srcLine" \
") VALUES(?,?,?)") \
E(inclDel, "DELETE FROM " \
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
E(inclHas, "SELECT 1 FROM " \
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
E(inclPathAdd, "INSERT INTO " \
CMPP__DB_MAIN_NAME ".inclpath(priority,dir) " \
"VALUES(coalesce(?1,0),?2) " \
"ON CONFLICT DO NOTHING " \
"RETURNING rowid /*xlates to 0 on conflict*/") \
E(inclPathRmId, "DELETE FROM " \
CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 " \
"RETURNING rowid") \
E(inclSearch, \
"SELECT ?1 fn WHERE cmpp_file_exists(fn) " \
"UNION ALL SELECT fn FROM (" \
" SELECT replace(dir||'/'||?1, '//','/') AS fn " \
" FROM " CMPP__DB_MAIN_NAME ".inclpath" \
" WHERE cmpp_file_exists(fn) " \
" ORDER BY priority DESC, rowid LIMIT 1" \
")") \
/* Compare two values. */ \
E(cmpVV, "SELECT cmpp_compare(?1,?2)") \
/* Compare LHS define to value ?2. */ \
E(cmpDV, \
"SELECT cmpp_compare(" \
CMPP__SEL_V_FROM(1) ", ?2" \
")") \
/* Compare value ?1 to RHS define. */ \
E(cmpVD, \
"SELECT cmpp_compare(" \
"?1," CMPP__SEL_V_FROM(2) \
")") \
/* Compare defines ?1 and ?2. */ \
E(cmpDD, \
"SELECT cmpp_compare(" \
CMPP__SEL_V_FROM(1) \
"," \
CMPP__SEL_V_FROM(2) \
")") \
E(dbAttach, \
"ATTACH ?1 AS ?2") \
E(dbDetach, \
"DETACH ?1") \
E(spBegin, "SAVEPOINT " CMPP__SAVEPOINT_NAME) \
E(spRollback, \
"ROLLBACK TO SAVEPOINT " CMPP__SAVEPOINT_NAME) \
E(spRelease, \
"RELEASE SAVEPOINT " CMPP__SAVEPOINT_NAME) \
/*E(insTtype, \
"INSERT INTO " CMPP__DB_MAIN_NAME ".ttype" \
"(t,n,s) VALUES(?1,?2,?3)")*/ \
E(selPathSearch, \
/*sqlite.org/forum/forumpost/840c98a8e87c2207*/ \
"WITH path(basename, sep, ext, path) AS (\n" \
" select\n" \
" ?1 basename,\n" \
" ?2 sep,\n" \
" ?3 ext,\n" \
" ?4 path\n" \
"),\n" \
"pathsplit(i, l, c, r) AS (\n" \
"-- i = sequential ID\n" \
"-- l = Length remaining\n" \
"-- c = text remaining\n" \
"-- r = current unpacked value\n" \
" SELECT 1,\n" \
" length(p.path)+length(p.sep),\n" \
" p.path||p.sep, ''\n" \
" FROM path p\n" \
" UNION ALL\n" \
" SELECT i+1, instr( c, p.sep ) l,\n" \
" substr( c, instr( c, p.sep ) + 1) c,\n" \
" trim( substr( c, 1,\n" \
" instr( c, p.sep) - 1) ) r\n" \
" FROM pathsplit, path p\n" \
" WHERE l > 0\n" \
"),\n" \
"thefile (f) AS (\n" \
" select basename f FROM path\n" \
" union all\n" \
" select basename||ext\n" \
" from path where ext is not null\n" \
")\n" \
"select 0 i, replace(f,'//','/') AS fn\n" \
"from thefile where cmpp_file_exists(fn)\n" \
"union all\n" \
"select i, replace(r||'/'||f,'//','/') fn\n" \
"from pathsplit, thefile\n" \
"where r<>'' and cmpp_file_exists(fn)\n" \
"order by i\n" \
"limit 1;") \
E(jsonValid, \
/* beware of validation of json_valid() ?2 */ \
"SELECT json_valid(?1,?2)") \
#define E(N,S) sqlite3_stmt * N;
CmppStmt_map(E)
#undef E
} stmt;
cmpp_errinfo err;
/** State for SQL tracing. */
struct {
bool expandSql;
cmpp_size_t counter;
cmpp_outputer out;
} sqlTrace;
struct {
/**
How many dirs we believe are in the #include search list. We
only do this for the sake of the historical "if no path was
added, assume '.'" behavior. This really ought to go away.
*/
unsigned nIncludeDir;
/**
The current depth of cmpp_process_string() calls. We do this so
the directory part of #include'd files can get added to the
#include path and be given a higher priority than previous
include path entries in the stack.
*/
int nDxDepth;
/* Number of active #savepoints. */
unsigned nSavepoint;
/* If >0, enables certain debugging output. */
char doDebug;
/* If true, chomp() files read via -Fx=file. */
unsigned char chompF;
/* Flags passed to cmpp_ctor(). */
cmpp_flag32_t ctorFlags;
/**
An ugly hack for getting cmpp_d_register() to get
syntactically-illegal directive names, like "@policy",
to register.
*/
bool isInternalDirectiveReg;
/**
True if the next directive is the start of a [call]. This is
used for:
1) To set cmpp_dx::isCall, which is useful in certain
directives.
2) So that the cmpp_dx object created for the call can inherit
the line number from its parent context. That's significant
for error reporting.
3) So that #2's cmpp_dx object can communicate that flag to
cmpp_dx_next().
*/
bool nextIsCall;
/**
Set to true by cmpp_call_string() if the being-called string
does not start with a directive delimiter. This tells
cmpp_next_chunk() to assume that it found a delimiter and
immediately treat the rest of the line as a directive line.
*/
bool fakeNextDelim;
/**
True until the cmpp's (sometimes) lazy init has been run. This
is essentially a kludge to work around a wrench cmpp_reset()
throws into cmpp state. Maybe we should just remove
cmpp_reset() from the interface, since error recovery in this
context is not really a thing.
*/
bool needsLazyInit;
} flags;
/** Policies. */
struct {
/** @token@-parsing policy. */
PodList__atpol at;
/** Policy towards referencing undefined symbols. */
PodList__unpol un;
} policy;
/**
Directive state.
*/
struct {
/**
Runtime-installed directives.
*/
CmppDList list;
/**
Directive autoloader/auto-registerer.
*/
cmpp_d_autoloader autoload;
} d;
/**
Module state. Maintenance reminder: filtering this out in
non-module-capable builds is breaking stuff.
*/
struct {
/**
List of DLL handles opened by cmpp_module_extract().
*/
CmppSohList sohList;
/**
Search path for DLLs, delimited by this->pathSep.
*/
cmpp_b path;
/**
File extension for DLLs.
*/
char const * soExt;
/**
Separator char for this->path. Must be a non-control character.
*/
char pathSep;
} mod;
struct {
/**
Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return().
*/
cmpp_b_library buf;
/**
Head of the free-list.
*/
cmpp_args_pimpl * argPimpl;
} recycler;
};
/** Distinct IDs for each cmpp::stmt member. */
enum CmppStmt_e {
CmppStmt_none = 0,
#define E(N,S) CmppStmt_ ## N,
CmppStmt_map(E)
#undef E
};
static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){
return cmpp__delim_list_get(&pp->pimpl->delim.d);
}
static inline char const * cmpp__pp_zdelim(cmpp const *pp){
cmpp__delim const * const d = cmpp__pp_delim(pp);
return d ? (char const *)d->open.z : NULL;
}
#define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp)
#define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp)
/**
Emit [z,(char*)z+n) to the given output channel if
(A) pOut->out is not NULL and (B) pp has no error state and (C)
n>0. On error, pp's error state is updated. Returns pp->err.code.
Skip level is not honored.
*/
CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n);
static inline void cmpp__err_reuse(cmpp *pp){
cmpp_errinfo_reuse(&pp->pimpl->err);
}
static inline bool cmpp__is_safemode(cmpp const * const pp){
return (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags);
}
/**
Initialize pp->db.dbh. If it's already open or ppCode!=0 then
ppCode is returned.
*/
CMPP_PRIVATE int cmpp__db_init(cmpp *pp);
/**
Returns the pp->pimpl->stmt.X corresponding to `which`, initializing
it if needed. If it returns NULL then either this was called when pp
has its error state set or this function will have set the error
state.
These queries MUST NOT be used in contexts where they may be reached
concurrently via recursion. The caller is obligated to
cmpp_stmt_reset() (or equivalent) the statement when they're done
and NOT to sqlite3_finalize() it.
If prepEvenIfErr is true then the ppCode check is bypassed, but it
will still fail if pp->pimpl->db is not opened or if the preparation
itself fails.
*/
CMPP_PRIVATE sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
bool prepEvenIfErr);
/**
Adds zDir to the include path, using the given priority value (use
0 except for the implicit cwd path which #include should (but does
not yet) set). If pRowid is not NULL then *pRowid gets set to
either 0 (if zDir was already in the path) or the row id of the
newly-inserted record, which can later be used to delete just that
entry.
If this returns a non-zero value via pRowid, the caller is
obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(),
even if pp is in an error state.
TODO: normalize zDir (at least remove trailing slashes) before
insertion to avoid that both a/b and a/b/ get be inserted.
*/
CMPP_PRIVATE int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid);
/**
Deletes the include path entry with the given rowid. This will make
make the attempt even if pp is in an error state but also retains
any existing error rather than overwriting it if this operation
somehow fails. Returns pp's error code.
It is not an error for the given entry to not exist.
*/
CMPP_PRIVATE int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid);
/**
Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
the `#include` dirs (cmpp_include_dir_add()). If no file match is
found, NULL is returned. Memory must eventually be passed to
cmpp_mfree() to free it. If nVal is not NULL then it is set to the
byte length of the returned string, or 0 on error or if no result
is found.
*/
CMPP_PRIVATE char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
cmpp_size_t * nVal);
/**
Searches pp's list of directives. If found, return it else return
NULL. See cmpp__d_search3().
*/
CMPP_PRIVATE cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName);
/**
Flags for use with the final argument to
cmpp__d_search3().
*/
enum cmpp__d_search3_e {
/** Internal delayed-registered directives. */
cmpp__d_search3_F_DELAYED = 0x01,
/** Directive autoloader. */
cmpp__d_search3_F_AUTOLOADER = 0x02,
/** As a fallback, search for a DLL if built with that support and
not in safe-mode. */
cmpp__d_search3_F_DLL = 0x04,
/** Options which do not trigger DLL lookup. */
cmpp__d_search3_F_NO_DLL = 0
| cmpp__d_search3_F_DELAYED
| cmpp__d_search3_F_AUTOLOADER,
/** All options. */
cmpp__d_search3_F_ALL = 0
| cmpp__d_search3_F_DELAYED
| cmpp__d_search3_F_AUTOLOADER
| cmpp__d_search3_F_DLL
};
/**
Like cmpp__d_search() but if no match is found then it will search
through its other options and, if found, register it.
The final argument specifies where to search. cmpp__d_search()
always checked first. After that, depending on "what", the search
order is: (1) internal delayed-load modules, (2) autoloader, (3)
DLL.
This may update pp's error state, in which case it will return
NULL.
*/
CMPP_PRIVATE cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
cmpp_flag32_t what);
/**
Sets pp's error state (A) if it's not set already and (B) if
!cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is
legal (to support legacy CLI pieces). Returns ppCode.
*/
CMPP_PRIVATE int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
cmpp_strlen_t nKey,
bool permitEqualSign);
/**
Helper for #query and friends. Binds aVal's value to column bindNdx
of q.
It expands cmpp_TT_Word aVal. cmpp_TT_String* and cmpp_TT_Int* are
bound as strings. A cmpp_TT_GroupParen aVal is eval'ed as an
integer and that int gets bound as a string.
This function strictly binds everything as strings, even if the
value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that
comparison queries will work as expected.
Returns ppCode.
TODO (2026-09-24): add [call] support to this, or a flags arg to
specify how to interpolate it, and move it to the public API.
*/
CMPP_PRIVATE int cmpp__bind_arg(cmpp *pp, sqlite3_stmt * q,
int bindNdx, cmpp_arg const * aVal);
/**
Helper for #query's bind X part, where aGroup is that X.
A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be
either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have
non-empty content. cmpp_TT_GroupBrace treats it as a list of values
to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt
column in one of these forms:
:bindName -> value
$bindName -> value
Each LHS refers to a same-named binding in q's SQL, including the
':' or '$' prefix. (SQLite supports an '@' prefix but we did not
historically allow it here to avoid confusion with the now-defunct
cmpp_TT_StringAt tokens.)
Each bound value is passed to cmpp__bind_arg() for processing.
On success, each aGroup entry is bound to q. On error q's state is
unspecified. Returns ppCode.
See cmpp__bind_arg() for notes about the bind data type.
*/
CMPP_PRIVATE int cmpp__bind_group(cmpp *pp, sqlite3_stmt * const q,
cmpp_arg const * const aGroup);
/**
Returns true if the given key is already in the `#define` list,
else false. Sets pp's error state on db error.
nName is the length of the key part of zName (which might have a
following =y part (TODO 2026-09-21: get rid of that - we no longer
rely on it). If it's negative, strlen() is used to calculate it.
*/
CMPP_PRIVATE int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_t nName);
/**
Returns true if the given key is already in the `#define` list, and
it has a truthy value (is not empty and not equal to '0'), else
false. If zName contains an '=' then only the part preceding that
is used as the key.
nName is the length of zName, or <0 to use strlen() to figure
it out.
Updates ppCode on error.
*/
CMPP_PRIVATE int cmpp__get_bool(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName);
/**
Fetches the given define. If found, sets *pOut to it, else pOut is
unmodified. Returns pp->err.code. If bRequire is true and no entry
is found p->err.code is updated.
*/
CMPP_PRIVATE int cmpp__get_int(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, int *pOut);
#if 0
/**
Searches for a define where (k == zName). If one is found, a copy
of it is assigned to *zVal (the caller must eventually db_free()
it), *nVal (if nVal is not NULL) is assigned its strlen, and
returns non-0. If no match is found, 0 is returned and neither
*zVal nor *nVal are modified. If more than one result matches, a
fatal error is triggered.
It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
non-0. That just means that the key was defined with no value part.
Fixme: return 0 on success and set output *gotOne=0|1.
*/
CMPP_PRIVATE int cmpp__get(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName,
unsigned char **zVal, unsigned int *nVal);
#endif
/**
Searches for a define where (k == zName). If one is found, a copy
of it is appended to b. Returns true if a record is found, else
false.
If enforceUndefPolicy is true, no match is found, and the
undefined-policy specifies to, pp's error state is set to reflect
the unresolved key.
Wonky return semantics.
*/
CMPP_PRIVATE int cmpp__get_b(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, cmpp_b * b,
bool enforceUndefPolicy);
#if 0
/**
Like cmpp__get_b() but with more conventional return semantics.
*gotOne is set to true if a record is found, else false. Returns
pp's current error code and does not run the query if pp has error
state.
*/
CMPP_PRIVATE int cmpp__get_b2(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, cmpp_b * b,
bool enforceUndefPolicy,
bool *gotOne);
#endif
/**
Helper for #query and friends.
It expects that q has just been stepped. For each column in the
row, it sets a define named after the column. If q has row data
then the values come from there. If q has no row then: if
defineIfNoRow is true then it defines each column name to an empty
value else it defines nothing.
*/
CMPP_PRIVATE int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
bool defineIfNoRow,
cmpp_b * optionalCaptureBuffer);
/** Start a new savepoint for dx. */
CMPP_PRIVATE int cmpp__dx_sp_begin(cmpp_dx * const dx);
/** Commit and close dx's current savepoint. */
CMPP_PRIVATE int cmpp__dx_sp_commit(cmpp_dx * const dx);
/** Roll back and close dx's current savepoint. */
CMPP_PRIVATE int cmpp__dx_sp_rollback(cmpp_dx * const dx);
/**
Append's dx's file/line information to sstr. It returns void
because that's how sqlite3_str_appendf() and friends work.
*/
CMPP_PRIVATE void cmpp__dx_append_script_info(cmpp_dx const * dx,
sqlite3_str * sstr);
/**
If zName matches one of the delayed-load directives, that directive
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
no match is found, but pp's error state is not updated in that
case. If a match is found and registration fails, that result code
will propagate via pp.
*/
CMPP_PRIVATE int cmpp__d_delayed_load(cmpp *pp, char const *zName);
CMPP_PRIVATE void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent);
/**
For each character in ('(' '{' '[' '"' '\'' '`') return
a corresponding closing character (which will be the same as
the opener for the string types).
*/
static inline unsigned char cmpp__closer(unsigned char opener){
switch(opener){
case '(': return ')';
case '[': return ']';
case '{': return '}';
//case '<': return '>';
case '"': case '\'': case '`': return opener;
default: return 0;
}
}
/**
Expects **zPos to be value valid for cmpp__closer() and zEnd
to be the logical EOF for *zPos.
This looks for a matching closing token, accounting for nesting. On
success, returns 0 and sets *zPos to the closing character.
It does not honor backslash-escaping of the closing token within
the content, nor does it ensure that other grouping tokens found
between its opening and closing bytes are balanced. Similarly, it
does not recognize quoted strings in the content _as such_ so
inputs like "{ '}'}" will misbehave.
On error it update's pp's error state and returns that code. pp may
be NULL.
If pNl is not NULL then *pNl is incremented for each '\n' character
seen while looking for the closing character.
Potential TODO (2026-08-20): make this routine recursive so that it
can process, at a minimum, quoted strings within non-quoted-string
inputs.
*/
#define CMPP__FIND_CLOSING 3
#if 2 == CMPP__FIND_CLOSING
CMPP_PRIVATE int cmpp__find_closing2(cmpp *pp,
unsigned char const **zPos,
unsigned char const *zEnd,
cmpp_size_t *pNl);
#define cmpp__find_closing(PP,Z0,Z1) \
cmpp__find_closing2(PP, Z0, Z1, NULL)
#define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \
cmpp__find_closing2(PP, Z0, Z1, LCPTR)
#else
/**
Works just like cmpp__find_closing2() but honors backslash
escaping in that it treats all backslash-escaped content as
escaped, not as a closer.
*/
CMPP_PRIVATE int cmpp__find_closing3(cmpp *pp,
unsigned char const **zPos,
unsigned char const *zEnd,
cmpp_size_t *pNl);
#define cmpp__find_closing(PP,Z0,Z1) \
cmpp__find_closing3(PP, Z0, Z1, NULL)
#define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \
cmpp__find_closing3(PP, Z0, Z1, LCPTR)
#endif
static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_strlen_t n){
return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n;
}
static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_strlen_t n){
return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n;
}
/**
"define"s zKey to zVal, recording the value type as tType.
*/
CMPP_PRIVATE int cmpp__define2(cmpp *pp,
unsigned char const * zKey,
cmpp_strlen_t nKey,
unsigned char const *zVal,
cmpp_strlen_t nVal,
cmpp_tt_e tType);
/**
Evals pArgs's arguments as an integer expression. On success, sets
*pResult to the value.
Returns ppCode.
*/
CMPP_PRIVATE int cmpp__args_evalToInt(cmpp *pp, cmpp_args const *pArgs,
int * pResult);
/** Passes the contents of arg through to cmpp__args_evalToInt(). */
CMPP_PRIVATE int cmpp__arg_evalSubToInt(cmpp *pp, cmpp_arg const *arg,
int * pResult);
/**
Evaluated arg as an integer/bool, placing the result in *pResult
and setting *pNext to the first argument to arg's right which this
routine did not consume. Non-0 on error and all that.
*/
CMPP_PRIVATE int cmpp__arg_toBool(cmpp * pp, cmpp_arg const *arg,
int * pResult, cmpp_arg const **pNext);
typedef struct cmpp_argOp cmpp_argOp;
typedef void (*cmpp_argOp_f)(cmpp *pp,
cmpp_argOp const *op,
cmpp_arg const *vLhs,
cmpp_arg const **pvRhs,
int *pResult);
struct cmpp_argOp {
int ttype;
/* 1 or 2 */
unsigned char arity;
/* 0=none/left, 1=right (unary ops only) */
signed char assoc;
cmpp_argOp_f xCall;
};
CMPP_PRIVATE cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt_e tt);
CMPP_PRIVATE char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p);
//CMPP_PRIVATE char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p);
/**
Uncerimoniously bitwise-replaces pp's output channel with oNew. It
does _not_ clean up the previous channel, on the assumption that
the caller is taking any necessary measures.
Apropos necessary measures for cleanup: if oPrev is not NULL,
*oPrev is set to a bitwise copy of the previous channel.
Intended usage:
```
cmpp_outputer oMine = cmpp_outputer_b;
cmpp_b bMine = cmpp_b_empty;
cmpp_outputer oOld = {0};
oMine.state = &bMine;
cmpp__outputer_swap(pp, &oMine, &oOld);
...do some work then ALWAYS do...
cmpp__outputer_swap(pp, &oOld, NULL);
```
Because this involves bitwise copying, care must be taken with
stream state, e.g. bMine.z (above) can be reallocated, so we have
to be sure to swap it back before using bMine again.
*/
CMPP_PRIVATE void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
cmpp_outputer *oPrev);
/**
Init code which is usually run as part of the ctor but may have to
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
because that could leave post-reset in an error state, which is
icky. This call is a no-op after the first.
*/
CMPP_PRIVATE int cmpp__lazy_init(cmpp *pp);
/* Fatally exits the app with the given printf-style message. */
CMPP_NORETURN CMPP_PRIVATE void cmpp__fatalv_base(char const *zFile, int line,
char const *zFmt, va_list);
#define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__)
CMPP_NORETURN CMPP_PRIVATE void cmpp__fatal_base(char const *zFile, int line,
char const *zFmt, ...);
#define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__)
/**
Outputs a printf()-formatted message to stderr.
*/
CMPP_PRIVATE void g_stderr(char const *zFmt, ...);
#define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
#define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg)
#if 0
#define g_debug(PP,lvl,pfexpr) (void)0
#else
#define g_debug(PP,lvl,pfexpr) \
if(lvl<=(PP)->pimpl->flags.doDebug) { \
if( (PP)->pimpl->dx ){ \
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ", \
(PP)->pimpl->dx->sourceName, \
(PP)->pimpl->dx->pimpl->dline.lineNo); \
} \
g_stderr("%s():%d: ", \
__func__,__LINE__); \
g_stderr pfexpr; \
} (void)0
#endif
/** Returns true if zFile is readable, else false. */
CMPP_PRIVATE bool cmpp__file_is_readable(char const *zFile);
#define ustr_c(X) ((unsigned char const *)X)
#define ustr_nc(X) ((unsigned char *)X)
#define ppCode pp->pimpl->err.code
#define dxppCode dx->ppCode
#define itchppCode itch->ppCode
#define cmpp__pi(PP) cmpp__pimpl * const pi = PP->pimpl
#define cmpp__dx_pi(DX) cmpp__dx_pimpl * const dpi = DX->pimpl
#define serr(...) cmpp_errf(pp, CMPP_RC_SYNTAX, __VA_ARGS__)
#define dxserr(...) cmpp_errf(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__)
#define cmpp__li_reserve1_size(li,nInitial) \
(li->n ? (li->n==li->na ? li->na * 3 / 2 : li->n+1) : nInitial)
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
//CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){}
static inline int cmpp__isnl(unsigned char const * z, unsigned char const *zEnd){
//assert(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;
}
}
#if 1
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;
}
#endif
static inline sqlite3_str * cmpp__sqlite3_str_new(cmpp*pp){
sqlite3_str * const s = sqlite3_str_new(pp ? pp->pimpl->db.dbh : 0);
return sqlite3_str_errcode(s)
? (
cmpp_check_oom(pp,NULL), NULL
/* s is a static singleton in this case, not leaked */
)
: s;
}
/**
Returns true if z starts with "::" not immediately followed by a
NUL.
*/
static inline bool cmpp__has_ns_prefix(unsigned char const *z){
return (unsigned char)':'==z[0] && (unsigned char)':'==z[1] && 0!=z[2];
}
CMPP_PRIVATE int cmpp__tt_for_group_char(int ch);
/**
Like cmpp_tt_cstr(), but if bSymbolName is false then it returns
the higher-level token name, which is NULL for most token types.
*/
CMPP_PRIVATE char const * cmpp__tt_cstr2(int tt, bool bSymbolName);
/**
Given UTF8 character value c, this calculates its length, in bytes,
writes the UTF-8 encoding to output, and returns the number of
bytes written. If the calculated size is >length then -1 is
returned (which cannot happen if length>=4). If !output then only
the UTF8 length of c is calculated and returned (and the length
argument is ignored).
Returns -1 if c is not a valid UTF8 character. The most bytes it
will ever write to *output is four, so an output buffer of four
bytes is sufficient for all encoding cases.
It does not NUL-terminate the output unless the character is
incidentally a NUL byte. That is the only case in which the output
will be NUL-terminated.
License note: the above docs and the corresponding code were pulled
into this tree by the same person who wrote them for the upstream
copy.
*/
CMPP_PRIVATE int cmpp__utf8_char_to_cstr(unsigned int c,
unsigned char *output,
cmpp_size_t length);
/**
For unescaping \uNNNN and \UNNNNNNNN sequences. Requires zPos to
point to at least howMany bytes of hex digits and must be a
multiple of 2. On success, sets *rv to the decoded hex value and
returns the number of bytes consumed. On error, returns -1.
*/
CMPP_PRIVATE int cmpp__read_hex_bytes(unsigned char const *zPos,
unsigned int howMany,
unsigned int * rv );
/**
Examines [z,z+n), expected to be a freshly-parsed token, to try to
determine a more precise token type, which it returns.
*/
CMPP_PRIVATE int cmpp__tt_tok_op(unsigned char const * z, uint8_t n);
#define cmpp__dx_current(PP) (PP)->pimpl->dx
/**
Empty-initialized cmpp_outputer instance, intended for
non-const-copy initialization.
*/
extern const cmpp_arg cmpp_arg_empty;
#endif /* include guard */
char const * cmpp_version(void){ return CMPP_VERSION; }
const cmpp_errinfo cmpp_errinfo_empty = cmpp_errinfo_empty_m;
const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m;
const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m;
const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m;
const cmpp_outputer cmpp_outputer_FILE = {
.out = cmpp_output_f_FILE,
.flush = cmpp_flush_f_FILE,
.cleanup = cmpp_outputer_cleanup_f_FILE,
.state = NULL
};
const cmpp_b_list cmpp_b_list_empty =
cmpp_b_list_empty_m;
const cmpp_outputer cmpp_outputer_b = {
.out = cmpp_output_f_b,
.flush = 0,
.cleanup = cmpp_outputer_cleanup_f_b,
.state = NULL
};
/**
Default delimiters for @tokens@.
*/
static const cmpp__delim delimAtDefault = {
.open = { .z = ustr_c("@"), .n = 1 },
.close = { .z = ustr_c("@"), .n = 1 },
.zOwns = NULL
};
static const cmpp_api_thunk cmppApiMethods = {
.thunkVersion = cmpp_api_thunk_version,
};
CMPP__EXPORT(bool, cmpp_isspace)(int ch){
return ' '==ch || '\t'==ch;
}
CMPP__EXPORT(void, cmpp_skip_space)(
unsigned char const **p,
unsigned char const *zEnd
){
assert( *p <= zEnd );
unsigned char const * z = *p;
while( z<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[-1]) ) --z;
*p = z;
}
/**
Sets pp's error state.
*/
CMPP__EXPORT(int, cmpp_errf)(cmpp *pp, int rc, char const *zFmt, ...);
#define cmpp__err cmpp_errf
#define cmpp_dx_err cmpp_dx_errf
/* Open/close pp's output channel. */
static int cmpp__out_fopen(cmpp *pp, const char *zName);
static void cmpp__out_close(cmpp *pp);
#define CmppKvp_empty_m \
{cmpp_token_empty_m,cmpp_token_empty_m,CmppKvp_op_none}
const CmppKvp CmppKvp_empty = CmppKvp_empty_m;
/* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just
supported cmpp_FILE input. */
typedef struct FileWrapper FileWrapper;
struct FileWrapper {
/* File's name. */
char const *zName;
/* cmpp_FILE handle. */
cmpp_FILE * pFile;
/* Where FileWrapper_slurp() stores the file's contents. */
unsigned char * zContent;
/* Size of this->zContent, as set by FileWrapper_slurp(). */
cmpp_size_t nContent;
};
#define FileWrapper_empty_m {0,0,0,0}
static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
/**
Proxy for cmpp_fclose() and frees all memory owned by p. It is not
an error if p is already closed.
*/
static void FileWrapper_close(FileWrapper * p);
/** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */
static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
/* Populates p->zContent and p->nContent from p->pFile. */
//static int FileWrapper_slurp(FileWrapper * p, int bCloseFile );
/**
If p->zContent ends in \n or \r\n, that part is replaced with 0 and
p->nContent is adjusted. Returns true if it chomps, else false.
*/
static bool FileWrapper_chomp(FileWrapper * p);
/*
** Outputs a printf()-formatted message to stderr.
*/
static void g_stderrv(char const *zFmt, va_list);
CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){
switch((cmpp_rc_e)rc){
#define E(N,V,H) case N: return # N;
cmpp_rc_e_map(E)
// These == other entries:
// E(CMPP_RC_ITCH_begin,,)
// E(CMPP_RC_ITCH_end,,)
#undef E
}
return NULL;
}
CMPP__EXPORT(void, cmpp_mfree)(void *p){
/* This MUST be a proxy for sqlite3_free() because allocate memory
exclusively using sqlite3_malloc() and friends. */
sqlite3_free(p);
}
CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){
return sqlite3_realloc64(p, n);
}
CMPP__EXPORT(void *, cmpp_malloc)(size_t n){
#if 1
return sqlite3_malloc64(n);
#else
void * p = sqlite3_malloc64(n);
if( p ) memset(p, 0, n);
return p;
#endif
}
CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){
if( *n && '\n'==z[*n-1] ){
z[--*n] = 0;
if( *n && '\r'==z[*n-1] ){
z[--*n] = 0;
}
return true;
}
return false;
}
CMPP__EXPORT(int, cmpp_slurp)(cmpp_input_f fIn, void *sIn,
unsigned char **pOut, cmpp_size_t * nOut){
unsigned char zBuf[1024 * 16];
unsigned char * pDest = 0;
unsigned nAlloc = 0;
unsigned nOff = 0;
int rc = 0;
cmpp_size_t nr = 0;
while( 0==rc ){
nr = sizeof(zBuf);
if( (rc = fIn(sIn, zBuf, &nr)) ){
break;
}
if(nr>0){
if(nAlloc < nOff + nr + 1){
nAlloc = nOff + nr + 1;
pDest = cmpp_mrealloc(pDest, nAlloc);
}
memcpy(pDest + nOff, zBuf, nr);
nOff += nr;
}else{
break;
}
}
if( 0==rc ){
if(pDest) pDest[nOff] = 0;
*pOut = pDest;
*nOut = nOff;
}else{
cmpp_mfree(pDest);
}
return rc;
}
cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){
cmpp_FILE *f;
if(zName && ('-'==*zName && !zName[1])){
f = (strchr(zMode, 'w') || strchr(zMode,'+'))
? stdout
: stdin
;
}else{
f = fopen(zName, zMode);
}
return f;
}
void cmpp_fclose( cmpp_FILE * f ){
if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
fclose(f);
}
}
void FileWrapper_close(FileWrapper * p){
if(p->pFile) cmpp_fclose(p->pFile);
if(p->zContent) cmpp_mfree(p->zContent);
*p = FileWrapper_empty;
}
int FileWrapper_open(FileWrapper * p, const char * zName,
const char * zMode){
FileWrapper_close(p);
if( (p->pFile = cmpp_fopen(zName, zMode)) ){
p->zName = zName;
return 0;
}else{
return cmpp_errno_rc(errno, CMPP_RC_IO);
}
}
int FileWrapper_slurp(FileWrapper * p, int bCloseFile){
assert(!p->zContent);
assert(p->pFile);
int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile,
&p->zContent, &p->nContent);
if( bCloseFile ){
cmpp_fclose(p->pFile);
p->pFile = 0;
}
return rc;
}
CMPP__EXPORT(int,cmpp_slurp_file)(cmpp *pp, char const *fn,
cmpp_b * b){
if( pp && ppCode ) return ppCode;
int rc;
cmpp_FILE * const fp = cmpp_fopen(fn, "rb");
if( fp ){
/* TODO: get file size (seek() to end, tell(), rewind()) and
cmpp_b_reserve() enough space. */
rc = cmpp_stream(cmpp_input_f_FILE, fp, cmpp_output_f_b, b);
cmpp_fclose(fp);
if( rc && pp ){
rc = cmpp_errf(pp, rc, "Error reading file %s", fn);
}
}else{
rc = cmpp_errf(pp, cmpp_errno_rc(errno, CMPP_RC_IO),
"Error opening file %s", fn);
}
return rc;
}
bool FileWrapper_chomp(FileWrapper * p){
return cmpp_chomp(p->zContent, &p->nContent);
}
/** Flags and constants related to CmppLvl. */
enum CmppLvl_e {
/**
Flag indicating that all ostensible output for a CmpLevel should
be elided. This also suppresses non-flow-control directives from
being processed.
*/
CmppLvl_F_ELIDE = 0x01,
/**
Mask of CmppLvl::flags which are inherited when
CmppLvl_push() is used.
*/
CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE
};
//static const CmppDLine CmppDLine_empty = CmppDLine_empty_m;
/** Free all memory owned by li but does not free li. */
static void CmppLvlList_cleanup(CmppLvlList *li);
/**
Allocate a list entry, owned by li, and return it (cleanly zeroed
out). Returns NULL and updates pp->err on error. It is expected
that the caller will populate the entry's zName using
sqlite3_mprintf() or equivalent.
*/
static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li);
/** Returns the most-recently-appended element of li back to li's
free-list. It expects to receive that value as a sanity-checking
measure and may fail fatally of that's not upheld. */
static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl);
static const cmpp__dx_pimpl cmpp__dx_pimpl_empty =
cmpp__dx_pimpl_empty_m;
#define cmpp_dx_empty_m { \
.pp=0, \
.d=0, \
.sourceName=0, \
.args={ \
.z=0, .nz=0, \
.argc=0, .arg0=0 \
}, \
.pimpl = 0 \
}
const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m;
#define cmpp_d_empty_m {.name={0,0},.arity=-1,.flags=0,.closer=0,.impl=cmpp_d_impl_empty_m}
//static const cmpp_d cmpp_f_empty = cmpp_f_empty_m;
static const CmppDList_entry CmppDList_entry_empty =
CmppDList_entry_empty_m;
/** Free all memory owned by li but does not free li. */
static void CmppDList_cleanup(CmppDList *li);
/**
Allocate a list entry, owned by li, and return it (cleanly zeroed
out). Returns NULL and updates pp->err on error. It is expected
that the caller will populate the entry's zName using
sqlite3_mprintf() or equivalent.
*/
static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li);
/** Returns the most-recently-appended element of li back to li's
free-list. */
static void CmppDList_unappend(CmppDList *li);
/** Resets li's list for re-use but does not free it. Returns li. */
//static CmppDList * CmppDList_reuse(CmppDList *li);
/** Searches li for a matching NUL-terminated name. */
static CmppDList_entry * CmppDList_search(CmppDList const * li,
char const *zName);
/** Reset dx and free any memory it may own. */
static void cmpp_dx_cleanup(cmpp_dx * const dx);
/**
Reset some of dx's parsing-related state in prep for fetching the
next line.
*/
static void cmpp_dx__reset(cmpp_dx * const dx);
/* Returns dx's current directive. */
static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){
return dx->d;
}
static const cmpp__pimpl cmpp__pimpl_empty = {
.db = {
.dbh = 0,
.zName = 0
},
.dx = 0,
.out = cmpp_outputer_empty_m,
.delim = {
.d = cmpp__delim_list_empty_m,
.at = cmpp__delim_list_empty_m
},
.stmt = {
#define E(N,S) .N = 0,
CmppStmt_map(E)
#undef E
},
.err = cmpp_errinfo_empty_m,
.sqlTrace = {
.expandSql = false,
.counter = 0,
.out = cmpp_outputer_empty_m
},
.flags = {
.nIncludeDir = 0,
.nDxDepth = 0,
.nSavepoint = 0,
.doDebug = 0,
.chompF = 0,
.ctorFlags = 0,
.isInternalDirectiveReg = false,
.nextIsCall = false,
.fakeNextDelim = false,
.needsLazyInit = true
},
.policy = {
.at = {0,0,0},
.un = {0,0,0}
},
.d = {
.list = CmppDList_empty_m,
.autoload = cmpp_d_autoloader_empty_m
},
.mod = {
.sohList = CmppSohList_empty_m,
.path = cmpp_b_empty_m,
.soExt = CMPP_PLATFORM_EXT_DLL,
/* Yes, '*'. It makes sense in context. */
.pathSep = '*'
// 0x1e /* "record separator" */ doesn't work. Must be non-ctrl.
},
.recycler = {
.buf = cmpp_b_library_empty_m,
.argPimpl = NULL
}
};
#if 0
static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){
return cmpp__out2(pp, &pp->out, z, n);
}
#endif
/**
Returns an approximate cmpp_tt for the given SQLITE_... value from
sqlite3_column_type() or sqlite3_value_type().
*/
static cmpp_tt_e cmpp__tt_for_sqlite(int sqType);
/**
Init code which is usually run as part of the ctor but may have to
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
because that could leave post-reset in an error state, which is
icky.
*/
int cmpp__lazy_init(cmpp *pp){
if( !ppCode && pp->pimpl->flags.needsLazyInit ){
cmpp__pimpl * const pi = pp->pimpl;
pi->flags.needsLazyInit = false;
cmpp__delim_list * li = &pi->delim.d;
if( !li->n ) cmpp_delimiter_push(pp, NULL);
li = &pi->delim.at;
if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL);
#if defined(CMPP_CTOR_INSTANCE_INIT)
if( !ppCode ){
extern int CMPP_CTOR_INSTANCE_INIT(cmpp*);
int const rc = CMPP_CTOR_INSTANCE_INIT(pp);
if( rc && !ppCode ){
cmpp__err(pp, rc,
"Initialization via CMPP_CTOR_INSTANCE_INIT() failed "
"with code %d/%s.", rc, cmpp_rc_cstr(rc) );
}
}
#endif
}
return ppCode;
}
/**
If pp is not in an OOM state then ensure that the at-policy and
undefined-policy stacks are allocated and then wipe them. Updates
pp's error state on OOM.
*/
static void cmpp__wipe_policies(cmpp *pp){
if( ppCode!=CMPP_RC_OOM /* b/c we're about to allocate */ ){
PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0);
PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0);
if( 0==ppCode ){
PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT);
PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT);
}
}
}
CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_opt const * cfg){
cmpp__pimpl * pi = 0;
cmpp * pp = 0;
void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi));
if( mv ){
if( !cfg ){
static const cmpp_ctor_opt dfltCfg = {0};
cfg = &dfltCfg;
}
cmpp const x = {
.api = &cmppApiMethods,
.pimpl = (cmpp__pimpl*)((unsigned char *)mv + sizeof(cmpp))
/* ^^^ (T const * const) members */
};
memcpy(mv, &x, sizeof(x))
/* FWIW, i'm convinced that this is a legal way to transfer
these const-pointers-to-const. If not, we'll need to change
those cmpp members from (T const * const) to (T const *). */;
pp = mv;
assert(pp->api == &cmppApiMethods);
assert(pp->pimpl);
pi = pp->pimpl;
*pOut = pp;
*pi = cmpp__pimpl_empty;
assert( pi->flags.needsLazyInit );
pi->flags.ctorFlags = cfg->flags;
if( cfg->dbFile ){
pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile);
cmpp_check_oom(pp, pi->db.zName);
}
cmpp__wipe_policies(pp);
cmpp__lazy_init(pp);
}
return pp ? ppCode : CMPP_RC_OOM;
}
CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){
cmpp__pi(pp);
cmpp_outputer_cleanup(&pi->sqlTrace.out);
pi->sqlTrace.out = cmpp_outputer_empty;
if( pi->d.autoload.dtor ){
pi->d.autoload.dtor(pi->d.autoload.state);
}
pi->d.autoload = cmpp__pimpl_empty.d.autoload;
cmpp_b_clear(&pi->mod.path);
if( pi->stmt.spRelease && pi->stmt.spRollback ){
/* Cleanly kill all savepoint levels. This is truly superfluous,
as they'll all be rolled back (if the db is persistent) or
nuked (if using a :memory: db) momentarily. However, we'll
eventually need this for a partial-clear operation which leaves
the db and custom directives intact. For now it lives here but
will eventually move to wherever that ends up being.
2025-11-16: or not. It's fine here, really.
*/
sqlite3_reset(pi->stmt.spRelease);
while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){
sqlite3_reset(pi->stmt.spRollback);
sqlite3_step(pi->stmt.spRollback);
sqlite3_reset(pi->stmt.spRelease);
}
}
cmpp__out_close(pp);
CmppDList_cleanup(&pi->d.list);
#define E(N,S) \
if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;}
CmppStmt_map(E) (void)0;
#undef E
if( pi->db.dbh ){
if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){
sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL)
/* ignoring error */;
}
sqlite3_close(pi->db.dbh);
pi->db.dbh = 0;
}
cmpp__delim_list_reuse(&pi->delim.d);
cmpp__delim_list_reuse(&pi->delim.at);
//why? cmpp_b_list_reuse(&pi->cache.buf);
cmpp__err_reuse(pp);
{/* Zero out pi but save some pieces for later, when pp is
cmpp_dtor()'d */
cmpp__pimpl const tmp = *pi;
*pi = cmpp__pimpl_empty;
pi->err = tmp.err;
pi->db = tmp.db /* restore db.zName */;
pi->recycler = tmp.recycler;
pi->policy = tmp.policy;
pi->delim = tmp.delim;
pi->mod.sohList = tmp.mod.sohList;
cmpp__wipe_policies(pp);
pi->flags.ctorFlags = tmp.flags.ctorFlags;
pi->flags.needsLazyInit = true;
}
}
static void cmpp__delim_list_cleanup(cmpp__delim_list *li);
CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){
if( pp ){
cmpp__pi(pp);
cmpp_reset(pp);
cmpp_mfree(pi->db.zName);
PodList__atpol_finalize(&cmpp__epol(pp,at));
assert(!cmpp__epol(pp,at).na);
PodList__unpol_finalize(&cmpp__epol(pp,un));
assert(!cmpp__epol(pp,un).na);
cmpp_b_library_dtor(&pi->recycler.buf)
/* Reminder to self: that retains the limits */;
cmpp__delim_list_cleanup(&pi->delim.d);
cmpp__delim_list_cleanup(&pi->delim.at);
for( cmpp_args_pimpl * apNext = 0, * ap = pi->recycler.argPimpl;
ap; ap = apNext ){
apNext = ap->nextFree;
ap->nextFree = 0;
cmpp__args_pimpl_cleanup(ap);
cmpp_mfree(ap);
}
cmpp_errinfo_dtor(&pi->err);
*pi = cmpp__pimpl_empty;
cmpp_mfree(pp);
}
}
CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){
return pp && 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags);
}
/** Sets ppCode if m is NULL. Returns ppCode. */
CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){
int rc;
if( pp ){
if( !m ){
//assert(!"oom");
cmpp__err(pp, CMPP_RC_OOM, 0);
}
rc = ppCode;
}else{
rc = m ? 0 : CMPP_RC_OOM;
}
return rc;
}
//CxMPP_WASM_EXPORT
CMPP__EXPORT(void *,cmpp_malloc2)(cmpp *pp, cmpp_size_t n){
void *p = 0;
if( 0==ppCode ){
p = cmpp_malloc(n);
cmpp_check_oom(pp, p);
}
return p;
}
/**
If ppCode is not 0 then it flushes pp's output channel. If that
fails, it sets ppCode. Returns ppCode.
*/
static int cmpp__flush(cmpp *pp){
if( !ppCode && pp->pimpl->out.flush ){
int const rc = pp->pimpl->out.flush(pp->pimpl->out.state);
if( rc && !ppCode ){
cmpp_err(pp, rc, "Flush failed.", -1);
}
}
return ppCode;
}
void cmpp__out_close(cmpp *pp){
cmpp__flush(pp)/*ignoring result*/;
cmpp_outputer_cleanup(&pp->pimpl->out);
pp->pimpl->out = cmpp__pimpl_empty.out;
}
int cmpp__out_fopen(cmpp *pp, const char *zName){
cmpp__out_close(pp);
if( !ppCode ){
cmpp_FILE * const f = cmpp_fopen(zName, "wb");
if( f ){
ppCode = 0;
pp->pimpl->out = cmpp_outputer_FILE;
pp->pimpl->out.state = f;
pp->pimpl->out.name = zName;
}else{
ppCode = cmpp__err(
pp, cmpp_errno_rc(errno, CMPP_RC_IO),
"Error opening file %s", zName
);
}
}
return ppCode;
}
static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw,
const char * zName,
const char * zMode){
int const rc = FileWrapper_open(fw, zName, zMode);
if( rc ){
cmpp__err(pp, rc, "Error %s opening file [%s] "
"with mode [%s]",
cmpp_rc_cstr(rc), zName, zMode);
}
return ppCode;
}
static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){
assert( fw->pFile );
int const rc = FileWrapper_slurp(fw, 1);
if( rc ){
cmpp__err(pp, rc, "Error %s slurping file %s",
cmpp_rc_cstr(rc), fw->zName);
}
return ppCode;
}
void g_stderrv(char const *zFmt, va_list va){
vfprintf(0 ? stdout : stderr, zFmt, va);
}
void g_stderr(char const *zFmt, ...){
va_list va;
va_start(va, zFmt);
g_stderrv(zFmt, va);
va_end(va);
}
CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){
return (char const *)cmpp__dx_zdelim(dx);
}
CMPP__EXPORT(int, cmpp_flush)(cmpp *pp){
cmpp__pi(pp);
if( pi->out.flush ){
return pi->out.flush(pi->out.state);
}
return 0;
}
int cmpp__out2(cmpp *pp, cmpp_outputer *pOut,
void const *z, cmpp_size_t n){
assert( pOut );
if( !ppCode && pOut->out && n ){
int const rc = pOut->out(pOut->state, z, n);
if( rc ){
cmpp__err(pp, rc,
"Write of %" CMPP_SIZE_T_PFMT
" bytes to output stream failed.", n);
}
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_out_raw)(cmpp * pp, void const *z, cmpp_size_t n){
cmpp_dx * const dx = cmpp__dx_current(pp);
if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode;
return cmpp__out2(pp, &pp->pimpl->out, z, n);
}
CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){
assert( out );
cmpp_dx const * const dx = cmpp__dx_current(pp);
if( dx && cmpp_dx_is_eliding(dx) ) return ppCode;
if( !ppCode && zFmt && *zFmt && out->out ){
char * s = sqlite3_vmprintf(zFmt, va);
if( 0==cmpp_check_oom(pp, s) ){
cmpp__out2(pp, out, s, cmpp__strlen(s, -1));
}
cmpp_mfree(s);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){
assert( out );
cmpp_dx const * const dx = cmpp__dx_current(pp);
if( dx && cmpp_dx_is_eliding(dx) ) return ppCode;
if( !ppCode && zFmt && *zFmt && out->out ){
va_list va;
va_start(va, zFmt);
cmpp_outfv2(pp, out, zFmt, va);
va_end(va);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){
return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
}
CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){
if( !ppCode ){
va_list va;
va_start(va, zFmt);
cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
va_end(va);
}
return ppCode;
}
static int cmpp__affirm_undef_policy(cmpp *pp,
unsigned char const *zName,
cmpp_size_t nName){
if( 0==ppCode
&& cmpp_unpol_ERROR==cmpp__policy(pp,un) ){
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
"Key '%.*s' was not found and the undefined-value "
"policy is 'error'.",
(int)nName, zName);
}
return ppCode;
}
static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut,
unsigned char const * zFrom,
cmpp_size_t n, cmpp_atpol_e atPolicy){
enum state_e {
/* looking for @token@ opening @ */
state_opening,
/* looking for @token@ closing @ */
state_closing
};
cmpp__pi(pp);
if( ppCode ) return ppCode;
if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at);
assert( cmpp_atpol_invalid!=atPolicy );
unsigned char const *zLeft = zFrom;
unsigned char const * const zEnd = zFrom + n;
unsigned char const *z =
(cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy)
? zEnd
: zLeft;
unsigned char const chEol = (unsigned char)'\n';
cmpp__delim const * delim =
cmpp__delim_list_get(&pp->pimpl->delim.at);
if( !delim && z<zEnd ){
return cmpp_errf(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_errf(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
for( ; z<zEnd && 0==ppCode; ++z ){
zLeft = z;
for( ;z<zEnd && 0==ppCode; ++z ){
again:
if( chEol==*z ){
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;
/* Handle call of @[directive ...args]@
i'm not a huge fan of this syntax, but that may go away
if we replace the single-char separator with a pair of
opening/closing delimiters.
Minor annoyance: the part of @[...]@ may span lines but
are processed as directives, so they have the same
limitation that newlines must be either backslash-escaped
or be contained within a grouping token.
*/
if( z<zEnd && '['==*z ){
unsigned char const * zb = z;
cmpp_size_t nl = 0;
//g_warn("Scanning: <<%.*s>>", (int)(zEnd-zb), zb);
if( cmpp__find_closing_lc(pp, &zb, zEnd, &nl) ){
break;
}
//g_warn("Found: <<%.*s>>", (int)(zb+1-z), z);
if( zb + delim->close.n >= zEnd
|| 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){
serr("Expecting '%s' after closing ']'.", delim->close.z);
break;
}
//g_warn("Found: <<%.*s>>", (int)(zb - z -1), z+1);
cmpp_call_str(pp, z+1, (zb - z - 1),
cmpp_b_reuse(bCall), 0);
if( 0==ppCode ){
cmpp__out2(pp, pOut, bCall->z, bCall->n);
state = state_opening;
zLeft = z = zb + delim->close.n + 1;
//g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z);
}
}
if( z>=zEnd ) break;
goto again /* avoid adjusting z again */;
}
}else{/*we're looking for delim->closer*/
assert( state_closing==state );
if( z + delim->close.n <= zEnd
&& 0==memcmp(z, delim->close.z, delim->close.n ) ){
/* process the ... part of @...@ */
assert( state_closing==state );
assert( 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_strlen_t const nKey = z - zLeft - delim->open.n;
if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey);
assert( nKey>= 0 );
if( !nKey ){
serr("Empty key is not permitted in %s...%s.",
delim->open.z, delim->close.z);
break;
}
if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){
if(0){
g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n,
(int)bVal->n, bVal->z);
}
if( bVal->n ){
cmpp__out2(pp, pOut, bVal->z, bVal->n);
}else{
/* Elide it */
}
zLeft = z + delim->close.n;
assert( zLeft<=zEnd );
}else if( !ppCode ){
assert( !bVal->n );
/* No matching define . */
switch( atPolicy ){
case cmpp_atpol_ELIDE: zLeft = z + delim->close.n; break;
case cmpp_atpol_RETAIN: tflush; break;
case cmpp_atpol_ERROR:
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
"Undefined %skey%s: %.*s",
delim->open.z, delim->close.z, nKey, zKey);
break;
case cmpp_atpol_invalid:
case cmpp_atpol_CURRENT:
case cmpp_atpol_OFF:
assert(!"this shouldn't be reachable" );
cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d",
atPolicy);
break;
}
}/* process @...@ */
state = state_opening;
assert( z<=zEnd );
}/*matched a closer*/
}/*state_closer==state*/
assert( z<=zEnd );
}/*per-line loop*/
}/*outer loop*/
#if 0
if( 0==ppCode && state_closer==state ){
serr("Opening '%s' found without a closing '%s'.",
delim->open.z, delim->close.z);
}
#endif
tflush;
#undef tflush
cmpp_b_return(pp, bCall);
cmpp_b_return(pp, bVal);
return ppCode;
}
CMPP__EXPORT(int, cmpp_out_expand)(cmpp * const pp,
cmpp_outputer * pOut,
unsigned char const * zFrom,
cmpp_size_t n,
cmpp_atpol_e atPolicy){
cmpp_dx const * const dx = cmpp__dx_current(pp);
if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode;
return cmpp__out_expand(pp, pOut, zFrom, n, atPolicy);
}
CmppLvl * CmppLvl_get(cmpp_dx const *dx){
return dx->pimpl->dxLvl.n
? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1]
: 0;
}
static const CmppLvl CmppLvl_empty = CmppLvl_empty_m;
CmppLvl * CmppLvl_push(cmpp_dx *dx){
CmppLvl * p = 0;
if( !dxppCode ){
CmppLvl * const pPrev = CmppLvl_get(dx);
p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl);
if( p ){
*p = CmppLvl_empty;
p->lineNo = dx->pimpl->dline.lineNo;
//p->d = dx->d;
if( pPrev ){
p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags);
//if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE;
}
}
}
return p;
}
void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){
CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl);
}
void CmppLvl_elide(CmppLvl *lvl, bool on){
if( on ) lvl->flags |= CmppLvl_F_ELIDE;
else lvl->flags &= ~CmppLvl_F_ELIDE;
}
bool CmppLvl_is_eliding(CmppLvl const *lvl){
return lvl && !!(lvl->flags & CmppLvl_F_ELIDE);
}
#if 0
void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){
CmppLvl_elide(CmppLvl_get(dx), on);
}
#endif
bool cmpp_dx_is_eliding(cmpp_dx const *dx){
return CmppLvl_is_eliding(CmppLvl_get(dx));
}
CMPP__EXPORT(sqlite3_str *, cmpp_sqlite3_str_new)(cmpp*pp){
sqlite3_str * const s = sqlite3_str_new(pp ? pp->pimpl->db.dbh : 0);
return sqlite3_str_errcode(s)
? (
cmpp_check_oom(pp,NULL), NULL
/* s is a static singleton in this case, not leaked */
)
: s;
}
CMPP__EXPORT(char *, cmpp_sqlite3_str_finish)(cmpp *pp, sqlite3_str *s, int * n){
char * z = 0;
int const rc = sqlite3_str_errcode(s);
if( !ppCode ){
cmpp_db_rc(pp, rc, "sqlite3_str_errcode()");
}
if(0==rc){
int const nStr = sqlite3_str_length(s);
if(n) *n = nStr;
z = sqlite3_str_finish(s);
if( !z ){
assert( 0==nStr && "else rc!=0" );
}
}else{
cmpp_mfree( sqlite3_str_finish(s) );
}
return z;
}
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out,
char const *zName){
cmpp__pi(pp);
cmpp_outputer_cleanup(&pi->out);
if( out ) pi->out = *out;
else pi->out = cmpp_outputer_empty;
pi->out.name = zName;
}
void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
cmpp_outputer *oPrev){
if( oPrev ){
*oPrev = pp->pimpl->out;
}
pp->pimpl->out = *oNew;
}
#if 0
static void delim__list_dump(cmpp const *pp){
cmpp__delim_list const *li = &pp->pimpl->delim.d;
if( li->n ){
g_warn0("delimiter stack:");
for(cmpp_size_t i = 0; i < li->n; ++i ){
g_warn("#%d: %s", (int)i, li->list[i].z);
}
}
}
#endif
static bool cmpp__valid_delim(cmpp * const pp,
char const *z,
char const *zEnd){
char const * const zB = z;
for( ; z < zEnd; ++z ){
if( *z<33 || 127==*z ){
cmpp_errf(pp, CMPP_RC_SYNTAX,
"Delimiters may not contain "
"control characters.");
return false;
}
}
if( zB==z ){
cmpp_errf(pp, CMPP_RC_SYNTAX,
"Delimiters may not be empty.");
}
return z>zB;
}
CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){
if( ppCode ) return ppCode;
unsigned n = 0;
if( zDelim ){
n = cmpp__strlen(zDelim, -1);
if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){
return ppCode;
}else if( n>32 /* arbitrary but seems sensible enough */ ){
return cmpp__err(pp, CMPP_RC_MISUSE,
"Invalid delimiter (too long): %s", zDelim);
}
}
cmpp__pi(pp);
if( pi->delim.d.n ){
cmpp__delim * const delim = cmpp__pp_delim(pp);
if( !cmpp_check_oom(pp, delim) ){
cmpp__delim_cleanup(delim);
if( zDelim ){
delim->open.n = n;
delim->open.z = delim->zOwns =
(unsigned char*)sqlite3_mprintf("%.*s", n, zDelim);
cmpp_check_oom(pp, delim->zOwns);
}else{
assert( delim->open.z );
assert( !delim->zOwns );
assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 );
}
}
}else{
assert(!"Cannot set delimiter on an empty stack!");
cmpp_errf(pp, CMPP_RC_MISUSE,
"Directive delimter stack is empty.");
}
return ppCode;
}
CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){
cmpp__delim const * d = cmpp__pp_delim(pp);
if( !d ) d = &cmpp__delim_empty;
*zDelim = (char const *)d->open.z;
}
CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){
cmpp__delim * const d =
cmpp__delim_list_push(pp, &pp->pimpl->delim.d);
if( d && cmpp_delimiter_set(pp, zDelim) ){
cmpp__delim_list_pop(&pp->pimpl->delim.d);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){
cmpp__delim_list * const li = &pp->pimpl->delim.d;
if( li->n ){
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
cmpp__delim_list_pop(li);
if( 0 && li->n ){
g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp));
}
}else if( !ppCode ){
assert(!"Attempt to pop an empty delimiter stack.");
cmpp_errf(pp, CMPP_RC_MISUSE,
"Cannot pop an empty delimiter stack.");
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp,
char const *zOpen,
char const *zClose){
if( 0==ppCode ){
cmpp__pi(pp);
cmpp__delim * const d = pi->delim.at.n
? &pi->delim.at.list[pi->delim.at.n-1]
: NULL;
assert( d );
if( !d ){
return cmpp__err(pp, CMPP_RC_MISUSE,
"@token@ delimiter stack is currently empty.");
}
if( 0==zOpen ){
zOpen = (char const *)delimAtDefault.open.z;
zClose = (char const *)delimAtDefault.close.z;
}else if( 0==zClose ){
zClose = zOpen;
}
cmpp_size_t const nO = cmpp__strlen(zOpen, -1);
cmpp_size_t const nC = cmpp__strlen(zClose, -1);
assert( zOpen && zClose );
if( !cmpp__valid_delim(pp, zOpen, zOpen+nO)
|| !cmpp__valid_delim(pp, zClose, zClose+nC) ){
return ppCode;
}
cmpp_b b = cmpp_b_empty
/* Don't use cmpp_b_borrow() here because we'll unconditionally
transfer ownership of b.z to d. */;
if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){
#ifndef NDEBUG
unsigned char const * const zReallocCheck = b.z;
#endif
/* Copy the open/close tokens to a single string to simplify
management. */
cmpp_b_append4(pp, &b, zOpen, nO);
cmpp_b_append_ch(&b, '\0');
cmpp_b_append4(pp, &b, zClose, nC);
assert( zReallocCheck==b.z
&& "Else buffer was not properly pre-sized" );
cmpp__delim_cleanup(d);
d->open.z = b.z;
d->open.n = nO;
d->close.z = d->open.z + nO + 1/*NUL*/;
d->close.n = nC;
d->zOwns = b.z;
b = cmpp_b_empty /* transfer memory ownership */;
}
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen,
char const *zClose){
cmpp__delim * const d =
cmpp__delim_list_push(pp, &pp->pimpl->delim.at);
if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){
cmpp__delim_list_pop(&pp->pimpl->delim.at);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){
cmpp__delim_list * const li = &pp->pimpl->delim.at;
if( li->n ){
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
cmpp__delim_list_pop(li);
}else if( !ppCode ){
assert(!"Attempt to pop an empty @token@ delim stack.");
cmpp_errf(pp, CMPP_RC_MISUSE,
"Cannot pop an empty @token@ delimiter stack.");
}
return ppCode;
}
CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp,
char const **zOpen,
char const **zClose){
cmpp__delim const * d
= cmpp__delim_list_get(&pp->pimpl->delim.at);
assert( d );
if( !d ) d = &delimAtDefault;
if( zClose ) *zClose = (char const *)d->close.z;
if( zOpen ) *zOpen = (char const *)d->open.z;
}
CMPP__EXPORT(bool, cmpp_is_int)(unsigned char const *z, unsigned n,
int *pOut){
if( !n || n > 10 ) return false;
char const * zz = (char *)z;
char /*const sigh*/* zEnd = 0;
int32_t d = strtol(zz, &zEnd, 10);
if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){
if( pOut ) *pOut = d;
return true;
}
return false;
}
CMPP__EXPORT(bool, cmpp_is_int64)(unsigned char const *z, unsigned n,
int64_t *pOut){
if( !n || n > 20 ) return false;
char const * zz = (char *)z;
char /*const sigh*/ * zEnd = 0;
int64_t d = strtoll(zz, &zEnd, 10);
if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){
if( pOut ) *pOut = d;
return true;
}
return false;
}
/**
Impl for the -Fx=filename flag.
TODO?: refactor to take an optional zVal to make it suitable for
the public API. This impl requires that zKey contain
"key=filename".
*/
static int cmpp__set_file(cmpp *pp, unsigned const char * zKey,
cmpp_strlen_t nKey){
if(ppCode) return ppCode;
CmppKvp kvp = CmppKvp_empty;
nKey = cmpp__strlenu(zKey, nKey);
if( CmppKvp_parse(pp, &kvp, zKey, nKey, CmppKvp_op_eq1) ){
return ppCode;
}
sqlite3_stmt * q = 0;
FileWrapper fw = FileWrapper_empty;
if( cmpp__FileWrapper_open(pp, &fw, (char const *)kvp.v.z, "rb") ){
assert(ppCode);
return ppCode;
}
cmpp__FileWrapper_slurp(pp, &fw);
q = cmpp__stmt(pp, CmppStmt_defIns, false);
if( q && 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, (int)kvp.k.n) ){
//g_warn("zKey=%.*s", (int)kvp.k.n, kvp.k.z);
if( pp->pimpl->flags.chompF ){
FileWrapper_chomp(&fw);
}
if( fw.nContent ){
cmpp_stmt_bind_textx(pp, q, 3, fw.zContent,
(cmpp_strlen_t)fw.nContent, SQLITE_STATIC);
fw.zContent = 0 /* transferred ownership */;
fw.nContent = 0;
}else{
cmpp_stmt_bind_null(pp, q, 2);
}
cmpp_stmt_step(pp, q, true);
g_debug(pp,2,("define: %s%s%s\n",
kvp.k.z,
kvp.v.z ? " with value " : "",
kvp.v.z ? (char const *)kvp.v.z : ""));
}
FileWrapper_close(&fw);
return ppCode;
}
int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_t nName){
int rc = 0;
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false);
if( q ){
nName = cmpp__strlen(zName, nName);
cmpp_stmt_bind_textn(pp, q, 1, ustr_c(zName), nName);
if(SQLITE_ROW == cmpp_stmt_step(pp, q, true)){
rc = 1;
}else{
rc = 0;
}
//g_debug(pp,1,("has [%s] ?= %d\n",zName, rc));
}
return rc;
}
int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_strlen_t nName){
int rc = 0;
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false);
if( q ){
nName = cmpp__strlenu(zName, nName);
cmpp_stmt_bind_textn(pp, q, 1, zName, nName);
assert(0==ppCode);
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
rc = sqlite3_column_int(q, 0);
}else{
rc = 0;
cmpp__affirm_undef_policy(pp, zName, nName);
}
cmpp_stmt_reset(pp, q);
}
return rc;
}
int cmpp__get_int(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, int *pOut ){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false);
if( q ){
nName = cmpp__strlenu(zName, nName);
cmpp_stmt_bind_textn(pp, q, 1, zName, nName);
assert(0==ppCode);
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
*pOut = sqlite3_column_int(q,0);
}else{
cmpp__affirm_undef_policy(pp, zName, nName);
}
cmpp_stmt_reset(pp, q);
}
return ppCode;
}
int cmpp__get_b(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, cmpp_b * os, bool enforceUndefPolicy){
int rc = 0;
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
if( q ){
nName = cmpp__strlenu(zName, nName);
cmpp_stmt_bind_textx(pp, q, 1, zName, nName, SQLITE_STATIC);
int n = 0;
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
const unsigned char * z = sqlite3_column_text(q, 3);
n = sqlite3_column_bytes(q, 3);
cmpp_b_append4(pp, os, z, (cmpp_size_t)n);
rc = 1;
}else{
if( enforceUndefPolicy ){
cmpp__affirm_undef_policy(pp, zName, nName);
}
rc = 0;
}
cmpp_stmt_reset(pp, q);
g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
nName, zName, rc, os->n, os->z));
}
return rc;
}
#if 0
int cmpp__get_b2(cmpp *pp, unsigned const char * zName,
cmpp_strlen_t nName, cmpp_b * os, bool enforceUndefPolicy,
bool *gotOne){
static bool nope;
*(gotOne ? gotOne : &nope) = cmpp__get_b(pp, zName, nName, os,
enforceUndefPolicy);
return ppCode;
}
#endif
CMPP__EXPORT(int, cmpp_undef_legacy)(cmpp *pp, const char * zKey,
unsigned int *nRemoved){
sqlite3_stmt * const q =
cmpp__stmt(pp, CmppStmt_defDelLegacy, false);
if( q ){
unsigned int const n = strlen(zKey);
cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), (cmpp_strlen_t)n,
SQLITE_STATIC);
cmpp_stmt_step(pp, q, true);
if( nRemoved ){
*nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh);
}
g_debug(pp,2,("undefine: %.*s\n",n, zKey));
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_undefine)(cmpp *pp, const char * zKey,
cmpp_strlen_t n){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDelExact, false);
if( q ){
n = cmpp__strlen(zKey, n);
cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), n, SQLITE_STATIC);
cmpp_stmt_step(pp, q, true);
g_debug(pp,2,("undefine: %.*s\n",n, zKey));
}
return ppCode;
}
int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority,
int64_t * pRowid){
if( pRowid ) *pRowid = 0;
if( !ppCode && zDir && *zDir ){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false);
if( q ){
/* TODO: normalize zDir before insertion so that a/b and a/b/
are equivalent. The relavent code is in another tree,
awaiting a decision on whether to import it or re-base cmpp
on top of that library (which would, e.g., replace cmpp_b
with that one, which is more mature).
*/
cmpp_stmt_bind_int(pp, q, 1, priority);
cmpp_stmt_bind_textx(pp, q, 2, ustr_c(zDir), -1, SQLITE_STATIC);
int const rc = cmpp_stmt_step(pp, q, false);
if( SQLITE_ROW==rc ){
++pp->pimpl->flags.nIncludeDir;
if( pRowid ){
*pRowid = sqlite3_column_int64(q, 0);
}
}
cmpp_stmt_reset(pp, q);
/*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s",
rc, pRowid ? *pRowid : 0, priority, zDir);*/
g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir));
}
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){
return cmpp__include_dir_add(pp, zDir, 0, NULL);
}
int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
if( q ){
/* Hoop-jumping to allow this to work even if pp's in an error
state. */
int rc = sqlite3_bind_int64(q, 1, rowid);
if( 0==rc ){
rc = sqlite3_step(q);
if( SQLITE_ROW==rc ){
--pp->pimpl->flags.nIncludeDir;
rc = 0;
}else if( SQLITE_DONE==rc ){
rc = 0;
}
}
if( rc && !ppCode ){
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
}
cmpp_stmt_reset(pp, q);
g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid));
}
return ppCode;
}
//%cmpp if not defined strip.module
CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){
#if CMPP_ENABLE_DLLS
if( !ppCode ){
cmpp_b * const ob = &pp->pimpl->mod.path;
if( !zDirs && !ob->n ){
zDirs = getenv("CMPP_MODULE_PATH");
if( !zDirs ){
zDirs = CMPP_MODULE_PATH;
}
}
if( !zDirs || !*zDirs ) return 0;
char const * z = zDirs;
char const * const zEnd = zDirs + strlen(zDirs);
if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){
unsigned char * zo = ob->z + ob->n;
unsigned i = 0;
for( ; z < zEnd && !ppCode; ++z ){
switch( *z ){
case CMPP_PATH_SEPARATOR:
*zo++ = pp->pimpl->mod.pathSep;
break;
default:
if( 1==++i && ob->n ){
cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep);
}
*zo++ = *z;
break;
}
}
*zo = 0;
ob->n = (zo - ob->z);
}
}
return ppCode;
#else
return CMPP_RC_UNSUPPORTED;
#endif
}
//%cmpp /if
CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){
if( 0==ppCode ){
cmpp__pi(pp);
if( pi->db.dbh ){
return cmpp__err(pp, CMPP_RC_MISUSE,
"DB name cannot be set after db initialization.");
}
if( zName ){
char * const z = sqlite3_mprintf("%s", zName);
if( 0==cmpp_check_oom(pp, z) ){
cmpp_mfree(pi->db.zName);
pi->db.zName = z;
}
}else{
cmpp_mfree(pi->db.zName);
pi->db.zName = 0;
}
}
return ppCode;
}
bool cmpp__is_legal_key(unsigned char const *zName,
cmpp_size_t n,
unsigned char const **zErrPos,
bool equalIsLegal){
if( !n || n>64/*arbitrary*/ ){
if( zErrPos ) *zErrPos = 0;
return false;
}
unsigned char const * z = zName;
unsigned char const * const zEnd = zName + n;
for( ; z<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_strlen_t nKey, bool permitEqualSign){
if( !ppCode ){
unsigned char const *zAt = 0;
nKey = cmpp__strlenu(zKey, nKey);
if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){
cmpp__err(pp, CMPP_RC_SYNTAX,
"Illegal character 0x%02x in key [%.*s]",
(int)*zAt, nKey, zKey);
}
}
return ppCode;
}
static cmpp_size_t cmpp__dx_guess_lineno(cmpp_dx const * dx,
unsigned char const * zWhere){
cmpp__dx_pi(dx);
unsigned char const *zB = 0;
unsigned char const *zE = 0;
if( !zWhere ) zWhere = dpi->zErrPos;
cmpp__dx_input_range(dx, &zB, &zE);
//g_warn("Whole script:\n%.*s", (int)(zE-zB), zB);
assert( zB && zE && zB<=zE );
if( !zWhere ){
if( dpi->pos.z<zE && dpi->pos.z>=zB ){
/* This is frequently at zEnd because we read line by line and
it can be at EOF at the end of that line. Similarly,
[call...] contexts have a virtual EOF at the ']'. */
zWhere = dpi->pos.z;
}else{
zWhere = dpi->dline.zBegin;
}
}
return zWhere
? 1 + cmpp_count_nl(zB, zE, zWhere, NULL)
: dpi->dline.lineNo;
}
/**
Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any
non-delimiter output found along the way to dx->pp's output
channel.
This updates dx->pimpl->pos.z as it goes.
If a delimiter is found, it sets *gotOne to true and updates
dx->pimpl->dline to point to the remainder of that line. On no match
*gotOne will be false and EOF will have been reached.
Returns dxppCode. If it returns non-0 then the state of dx's
tokenization pieces are unspecified. i.e. it's illegal to call this
again without a reset.
*/
static int cmpp__dx_delim_search(cmpp_dx * const dx, bool * gotOne){
if( dxppCode ) return dxppCode;
cmpp__dx_pimpl * const dxp = dx->pimpl;
if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
if( dxp->pos.z>=dxp->zEnd ){
*gotOne = false;
return 0;
}
assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
&& "Else we've mismanaged something.");
cmpp__pi(dx->pp);
cmpp__delim const * const delim = cmpp__dx_delim(dx);
if(!delim) {
return cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"The directive delimiter stack is empty.");
}
bool const isCall = dxp->flags.nextIsCall
/* If true then this call is in response to cmpp_call_str(). This
lets us treat the whole input as arguments to the directive,
rather than looking for an EOL. This allows @[call...]@ to span
lines without backslash-escaping them. */;
unsigned char const * const zD = delim->open.z;
unsigned short const nD = delim->open.n;
unsigned char const * const zEnd = dxp->zEnd;
unsigned char const * zLeft = dxp->pos.z;
unsigned char const * z = zLeft;
#if 0
cmpp_size_t const prevLineNo = dxp->dline.lineNo;
unsigned char const * const zPrevDLineBegin =
(dxp->dline.zBegin >= dxp->zBegin
&& dxp->dline.zBegin<=zEnd)
? dxp->dline.zBegin
: 0;
#endif
assert(zD);
assert(nD);
while( 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.
*/
++z;
}
#define tflush \
if( z>zEnd ) z=zEnd; \
if( z>zLeft && cmpp_out_expand(dx->pp, &pi->out, zLeft, \
(cmpp_size_t)(z-zLeft), \
cmpp_atpol_CURRENT) ){ \
return dxppCode; \
} zLeft = z
CmppDLine * const dline = &dxp->dline;
bool atBOL = true /* At the start of a line? Successful calls to
this always end at either BOL or EOF. */;
bool const alreadyGotDelim = pi->flags.fakeNextDelim;
pi->flags.fakeNextDelim = false;
assert( alreadyGotDelim ? isCall : 1 );
/*if( alreadyGotDelim ){
g_warn("alreadyGotDelim: %.*s", (int)(zEnd-z), z);
}*/
if( 0 ){
g_warn("scanning... <<%.*s...>>",
(zEnd-z)>20?20:(zEnd-z), z);
}
while( z<zEnd && !dxppCode ){
if( alreadyGotDelim ){
dline->zReallyBegin = z;
goto got_delim;
}
if( !atBOL ){
/* We're continuing the scan of a line on which the first bytes
didn't match a delimiter. */
while( z<zEnd ){
while((z<zEnd && '\n'==*z)
|| (z+1<zEnd && '\r'==*z && '\n'==z[1]) ){
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. */
unsigned char const * const zBeforeSpace = z;
cmpp_skip_space(&z, zEnd);
int const skip = cmpp__isnl(z, zEnd);
if( skip ){
/* Special case: a line comprised solely of whitespace. If we
don't catch this here, we won't recognize a delimiter which
starts on the next line. */
z += skip;
continue;
}
if( 0 ){
g_warn("at BOL... <<%.*s...>>",
(zEnd-z) > 20 ? 20 : (zEnd-z), z);
}
if( z + nD>zEnd ){
/* Too short for a delimiter. We'll catch the z+nD==zEnd corner
case in a moment. */
z = zEnd;
break;
}
if( memcmp(z, zD, nD) ){
/* Not a delimiter. Keep trying. */
atBOL = false;
++z;
continue;
}
/* z now points to a delimiter which sits at the start of a line
(ignoring leading spaces). */
if( z>zLeft ){
/* Flush up to, but not including, any spaces we skipped. */
unsigned char const * const zx = z;
if( zBeforeSpace<z ) z = zBeforeSpace;
tflush;
z = zLeft = zx;
}
dline->zReallyBegin = z;
z += nD /* skip the delimiter */;
got_delim:
/* Set up dx->pimpl->dline to encompass the whole directive line sans
delimiter and leading spaces. */
#if 0
/* Only count from the previous dline->zBegin.
Nope. This is buggy with:
#query ...
#include ...
#/query
because #query captures the body and re-processes it.
*/
if( zPrevDLineBegin ){
dline->lineNo = prevLineNo
+ cmpp_count_nl(zPrevDLineBegin, z, z, NULL);
}else{
dline->lineNo = cmpp__dx_guess_lineno(dx, z);
}
#else
dline->lineNo = cmpp__dx_guess_lineno(dx, z);
#endif
dxp->pos.lineNo = dline->lineNo;
dline->zBegin = z
/* dx->pimpl->dline starts at the directive name and extends until the
next EOL/EOF. We don't yet know if it's a legal directive
name - cmpp_dx_next() figures that part out. */;
/* Now find the end of the line or EOF, accounting for
backslash-escaped newlines and _not_ requiring backslashes to
escape newlines inside of {...}, (...), or [...]. */
cmpp_tizer tz = cmpp_tizer_empty;
cmpp_tizer_init(&tz, &pi->err, z, (zEnd-z));
int tzrc;
unsigned int nTok = 0;
cmpp_tizer_foreach3(&tz, tzrc, cmpp_tizer_next_SKIP_C_COMMENTS){
#if 0
if( 0 ){
g_warn("tok=%s %.*s", cmpp_tt_cstr(tz.token.ttype),
(int)tz.token.n, tz.token.z);
}
#endif
if( cmpp_tt_EOL==tz.token.ttype ){
if( !isCall ){
tz.zPos = tz.token.z /* we assert this below */;
break;
}
}else{
++nTok;
}
}
if( tzrc ){
assert( dxppCode && "Expecting via tz.err" );
return dxppCode;
}
if( !nTok ){
dxserr("No directive name found after %s.", zD);
/* We could arguably treat this as no match and pass this line
through as-is but that currently sounds like a pothole. cpp
does that and i consider it a serious flaw. In the context of
cpp's early history that made some sense but it does not
here. */
break;
}
z = tz.zPos;
if(0){
g_warn("tokenized: %.*s", (z - dline->zBegin), dline->zBegin);
}
assert( z==zEnd || '\n'==*z );
dline->zEnd = z;
dxp->pos.z = dline->zEnd + 1
/* For the next call to this function, skip the trailing newline
or EOF */;
assert( dline->zBegin < dline->zEnd && "Was checked above" );
if( 0 ){
g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin),
(dline->zEnd-dline->zBegin), dline->zBegin);
}
*gotOne = true;
assert( !dxppCode );
return 0;
}
/* No directives found. We're now at EOL or EOF. Flush any pending
LHS content. */
tflush;
dx->pimpl->pos.z = z;
*gotOne = false;
return dxppCode;
#undef tflush
}
int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey,
cmpp_strlen_t nKey, CmppKvp_op_e opPolicy){
if(ppCode) return ppCode;
char chEq = 0;
char opLen = 0;
*p = CmppKvp_empty;
p->k.z = zKey;
p->k.n = cmpp__strlenu(zKey, nKey);
switch( opPolicy ){
case CmppKvp_op_none:// break;
case CmppKvp_op_eq1:
chEq = '=';
opLen = 1;
break;
default:
assert(!"don't use these");
/* no longer todo: ==, !=, <=, <, >, >= */
chEq = '=';
opLen = 1;
break;
}
assert( chEq );
p->op = CmppKvp_op_none;
unsigned const char * const zEnd = p->k.z + p->k.n;
for(unsigned const char * zPos = p->k.z ; *zPos && 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->na : 0==li->na );
if( 0==ppCode
&& 0==CmppLvlList_reserve(pp, li,
cmpp__li_reserve1_size(li,5)) ){
p = li->list[li->n];
if( !p ){
p = cmpp_malloc2(pp, sizeof(*p));
}
if( p ){
li->list[li->n++] = p;
*p = CmppLvl_empty;
}
}
return p;
}
void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li,
CmppLvl * const lvl){
assert( li->n );
if( li->n ){
if( lvl==li->list[li->n-1] ){
*lvl = CmppLvl_empty;
cmpp_mfree(lvl);
li->list[--li->n] = 0;
}else{
if( pp ){
cmpp_errf(pp, CMPP_RC_ASSERT,
"Misuse of %s(): not passed the top of the stack. "
"The CmppLvl stack is now out of whack.",
__func__);
}else{
cmpp__fatal("Misuse of %s(): not passed the top of the stack",
__func__);
}
/* do not free it - CmppLvlList_cleanup() will catch it. */
}
}
}
void CmppLvlList_cleanup(CmppLvlList *li){
const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m;
while( li->na ){
cmpp_mfree( li->list[--li->na] );
}
cmpp_mfree(li->list);
*li = CmppLvlList_empty;
}
static inline void CmppDList_entry_clean(CmppDList_entry * const e){
assert( e && "Else internal API misuse." );
if( e->d.impl.dtor ){
e->d.impl.dtor( e->d.impl.state );
}
cmpp_mfree(e->zName);
*e = CmppDList_entry_empty;
}
#if 0
CmppDList * CmppDList_reuse(CmppDList *li){
while( li->n ){
CmppDList_entry_clean( li->list[--li->n] );
}
return li;
}
#endif
void CmppDList_cleanup(CmppDList *li){
static const CmppDList CmppDList_empty = CmppDList_empty_m;
while( li->na ){
CmppDList_entry * const dl = li->list[--li->na];
if( dl ){
CmppDList_entry_clean( dl );
cmpp_mfree( dl );
}
}
cmpp_mfree(li->list);
*li = CmppDList_empty;
}
void CmppDList_unappend(CmppDList *li){
assert( li->n );
if( li->n ){
CmppDList_entry_clean(li->list[--li->n]);
}
}
/** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */
static int CmppDList_entry_cmp_pp(const void *p1, const void *p2){
CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1;
CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2;
uint32_t const minN = eL->d.name.n < eR->d.name.n
? eL->d.name.n
: eR->d.name.n;
int const rc = memcmp(eL->d.name.z, eR->d.name.z, minN);
return rc ? rc : (int)eL->d.name.n - (int)eR->d.name.n;
}
static void CmppDList_sort(CmppDList * const li){
if( li->n>1 ){
qsort(li->list, li->n, sizeof(CmppDList_entry*),
CmppDList_entry_cmp_pp);
}
}
CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){
CmppDList_entry * p = 0;
assert( li->list ? li->na : 0==li->na );
if( 0==ppCode
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
cmpp__li_reserve1_size(li, 15),
&li->na, sizeof(p)) ){
p = li->list[li->n];
if( !p ){
li->list[li->n] = p = cmpp_malloc2(pp, sizeof(*p));
}
if( p ){
++li->n;
*p = CmppDList_entry_empty;
}
}
return p;
}
CmppDList_entry * CmppDList_search(CmppDList const * li,
char const *zName){
if( li->n > 32 ){
/* ^^^^^^^^^ See notes itch.c:cmpp_itch_enum_search() */
CmppDList_entry const key = {
.d = {
.name = {
.z = zName,
.n = strlen(zName)
}
}
};
CmppDList_entry const * pKey = &key;
CmppDList_entry ** pRv
= bsearch(&pKey, li->list, li->n, sizeof(li->list[0]),
CmppDList_entry_cmp_pp);
return pRv ? *pRv : 0;
}else{
cmpp_size_t const nName = cmpp__strlen(zName, -1);
for( cmpp_size_t i = 0; i < li->n; ++i ){
CmppDList_entry * const e = li->list[i];
if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){
return e;
}
}
return 0;
}
}
void cmpp__delim_cleanup(cmpp__delim *d){
cmpp__delim const dd = cmpp__delim_empty_m;
cmpp_mfree(d->zOwns);
*d = dd;
assert(!d->zOwns);
assert(d->open.z);
assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM));
assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1);
}
cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){
cmpp__delim * p = 0;
assert( li->list ? li->na : 0==li->na );
if( 0==ppCode
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
cmpp__li_reserve1_size(li,4),
&li->na, sizeof(cmpp__delim)) ){
p = &li->list[li->n++];
*p = cmpp__delim_empty;
}
return p;
}
void cmpp__delim_list_cleanup(cmpp__delim_list *li){
while( li->na ) cmpp__delim_cleanup(li->list + --li->na);
cmpp_mfree(li->list);
*li = cmpp__delim_list_empty;
}
void cmpp__dx_input_range(cmpp_dx const * dx, unsigned char const **zBegin,
unsigned char const **zEnd){
cmpp_dx const * up;
while( (up = dx->pimpl->up) ){
/* If up lives in the same byte range, use it for purposes of
figuring out the source range. */
if( dx->pimpl->zBegin>=up->pimpl->zBegin
&& dx->pimpl->zEnd<=up->pimpl->zEnd ){
dx = up;
}else{
break;
}
}
*zBegin = dx->pimpl->zBegin;
*zEnd = dx->pimpl->zEnd;
}
CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){
if( dxppCode ) return dxppCode;
cmpp__dx_pimpl * const dpi = dx->pimpl;
CmppDLine * const dline = &dpi->dline;
if( !dpi->zBegin ){
*pGotOne = false;
return 0;
}
assert(dpi->zEnd);
assert(dpi->zEnd > dpi->zBegin);
*pGotOne = false;
dpi->zErrPos = dline->zBegin;
cmpp_dx__reset(dx);
bool foundDelim = false;
if( cmpp__dx_delim_search(dx, &foundDelim) || !foundDelim ){
return dxppCode;
}
if( cmpp__args_init(dx->pp, &dpi->args) ){
return dxppCode;
}
cmpp_skip_space( &dline->zBegin, dline->zEnd );
g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n",
dline->lineNo,
(int)(dline->zEnd-dline->zBegin), dline->zBegin));
/* Normalize the directive's line and parse arguments */
const unsigned lineLen = (unsigned)(dline->zEnd - dline->zBegin);
if(!lineLen){
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
"Line #%u has no directive after %s",
dline->lineNo, cmpp_dx_delim(dx));
}
unsigned char const * zi = dline->zBegin /* Start of input */;
unsigned char const * ziEnd = dline->zEnd /* Input EOF */;
cmpp_b * const bufLine = cmpp_b_reuse(&dpi->buf.line)
/* Slightly-transformed copy of the input. */;
if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){
return dxppCode;
}
unsigned char * zo = bufLine->z /* Start of output */;
unsigned char const * const zoEnd =
zo + bufLine->nAlloc /* Output EOF. */;
g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n",
dline->lineNo, lineLen, lineLen, dline->zBegin));
if( 0 ){
g_warn("Directive @ line %u len=%u <<%.*s>>\n",
dline->lineNo, lineLen, lineLen, dline->zBegin);
}
//memset(bufLine->z, 0, bufLine->nAlloc);
//#define out(CH) if(zo==zoEnd) break; (*zo++)=CH
#define out(CH) assert(zo<zoEnd); (*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;
for( ; zi<ziEnd && *zi && zo<zoEnd;
++zi ){
dpi->zErrPos = zi;
/* Write the line to bufLine for the upcoming args parsing to deal
with. Strip backslashes from backslash-escaped newlines. We
leave the newlines intact so that downstream error reporting
can get more precise location info. Backslashes which do not
precede a newline are retained.
2026-09-01: we really don't want this for V4 args, but at this
point we don't know which directive we're looking at, so don't
know if it has v4-style args. It "would be cool" to move the
search for the directive up, "the problem" being that we have
to NUL-terminate it for the search so we need bufLine, anyway.
*/
switch((int)*zi){
case (int)'\\':
if( !zi[1] || zi==ziEnd-1 ){
// special case: ending input with a backslash
out(*zi);
zEsc = 0;
}else if( zEsc ){
assert( zEsc==zi-1 );
/* Put them both back. */
out(*zEsc);
out(*zi);
zEsc = 0;
}else{
zEsc = zi;
}
break;
case (int)'\n':
out(*zi);
zEsc = 0;
break;
default:
if(zEsc){
assert( zEsc==zi-1 );
out(*zEsc);
zEsc = 0;
}
out(*zi);
break;
}
}
if( zo>=zoEnd ){
return cmpp_dx_err(dx, CMPP_RC_RANGE,
"Ran out of argument-processing space.");
}
bufLine->n = (cmpp_size_t)(zo - bufLine->z);
if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
*zo = 0;
#undef out
if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
/* Line has now been normalized into bufLine->z. */
//unsigned char *zNul = 0;
for( zo = bufLine->z; zo<zoEnd && *zo; ++zo ){
/* NULL-terminate the directive so we can search for it. */
if( cmpp_isspace(*zo) ){
//zNul = 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",
cmpp__dx_guess_lineno(dx, 0),
(int)bufLine->n, bufLine->z);
}
bool const v4 = (cmpp_f_F_ARGS_V4 & dx->d->flags);
//if( v4 && zNul ) *zNul = ' '/*undo the NULL terminator*/;
dpi->zErrPos = 0;
assert( zDirective == bufLine->z );
const bool isDxCall
= dpi->args.pimpl->isDxCall
= dpi->flags.nextIsCall;
dpi->flags.nextIsCall = dx->pp->pimpl->flags.nextIsCall = false;
if( isDxCall ){
if( cmpp_f_F_NO_CALL & dx->d->flags ){
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
"%s%s cannot be used in a [call] context.",
cmpp_dx_delim(dx),
dx->d->name.z);
}
}else if( cmpp_f_F_CALL_ONLY & dx->d->flags ){
return cmpp_dx_err(dx, CMPP_RC_TYPE,
"'%s' is a call-only directive, "
"not legal here.", dx->d->name.z);
}
if( v4 ){
/*
For V4 ignore all of that writing to bufLine (except that we
needed it in order to find the directive).
a) Include the directive name in the args.
b) We also want the cmpp_arg::token entries to point back into
dx's memory so that we can us it to calculate error locations
precisely.
*/
dx->args.z = dline->zBegin;
dx->args.nz = dline->zEnd - dline->zBegin;
}else if( bufLine->n > dx->d->name.n ){
dx->args.z = zDirective + dx->d->name.n + 1;
assert( dx->args.z > bufLine->z );
assert( dx->args.z <= bufLine->z+bufLine->n );
dx->args.nz = cmpp__strlenu(dx->args.z, -1);
assert( bufLine->nAlloc > dx->args.nz );
}else{
dx->args.z = ustr_c("\0");
dx->args.nz = 0;
}
if( 0 ){
g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>",
bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z);
}
cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz);
if(0){
g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz,
(int)dx->args.nz, dx->args.z);
}
assert( !dpi->buf.argsRaw.n );
if( dx->args.nz ){
if( 0 ){
g_warn("lineLen=%u zargs len=%u: [%.*s]\n",
(unsigned)lineLen,
(int)dx->args.nz, (int)dx->args.nz,
dx->args.z
);
}
if( cmpp_b_append4(dx->pp, &dpi->buf.argsRaw,
dx->args.z, dx->args.nz) ){
return dxppCode;
}
}
assert( !dpi->args.arg0 );
assert( !dpi->args.argc );
assert( !dpi->args.pimpl->argOut.n );
assert( !dpi->args.pimpl->argli.n );
assert( dx->args.z );
if( !cmpp_dx_is_eliding(dx)
|| 0!=(cmpp_f_F_FLOW_CONTROL & dx->d->flags) ){
if( (cmpp_f_F_ARGS_LIST & dx->d->flags)
|| (v4 && !(cmpp_f_F_ARGS_RAW & dx->d->flags)) ){
cmpp_dx_args_parse(dx, &dpi->args);
//g_warn("parsed arg rc=%d count=%d", dxppCode, dpi->args.argc);
assert( v4 ? (dpi->args.argc>0 || dxppCode) : 1 );
if( v4 ){
assert( dpi->args.argc>0 || dxppCode );
dpi->args.arg0 = 0;
}
}else if( cmpp_f_F_ARGS_RAW & dx->d->flags ){
/* Treat rest of line as one token */
if( !dpi->buf.argsRaw.z
/*ensure it's alloced to simplify directives which use it.
We won't need this if/when we can get all of the
directives ported from ARGS_LIST to ARGS_V4, as we can then
instead set arg->z="" below. .*/
&& cmpp_b_append4(dx->pp, &dpi->buf.argsRaw, "", 0) ){
return dxppCode;
}
cmpp_arg * const arg =
CmppArgList_append(dx->pp, &dpi->args.pimpl->argli);
if( !arg ) return dxppCode;
arg->ttype = cmpp_tt_RawLine;
dpi->args.arg0 = arg;
dpi->args.argc = 1;
assert( dpi->buf.argsRaw.z );
arg->z = dpi->buf.argsRaw.n ? dpi->buf.argsRaw.z : ustr_c("");
arg->n = dpi->buf.argsRaw.n;
//g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z);
}
}
if( 0==dxppCode ){
dx->args.arg0 = dpi->args.arg0;
dx->args.argc = dpi->args.argc;
*pGotOne = true;
}
return dxppCode;
}
CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r,
cmpp_d ** dOut){
CmppDList_entry * e1 = 0, * e2 = 0;
bool const isCallOnly =
(cmpp_f_F_CALL_ONLY & r->opener.flags);
if( ppCode ){
goto end;
}
if( (cmpp_f_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags))
&& (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags) ){
cmpp__err(pp, CMPP_RC_ACCESS,
"Directive %s%s flag cmpp_f_F_NOT_IN_SAFE_MODE is set "
"and the preprocessor is running in safe mode.",
cmpp__pp_zdelim(pp), r->name);
goto end;
}
if( isCallOnly && r->closer.f ){
cmpp__err(pp, CMPP_RC_MISUSE,
"Call-only directives may not have a closing directive.");
goto end;
}
#if 0
if( pp->pimpl->dx ){
cmpp__err(pp, CMPP_RC_MISUSE,
"Directives may not be added while a "
"directive is running."
/* because that might reallocate being-run directives.
2025-10-25: that's since been resolved but we need a
use case before enabling this.
*/);
goto end;
}
#endif
cmpp_size_t const nName = strlen(r->name);
if( !pp->pimpl->flags.isInternalDirectiveReg
&& !cmpp__is_legal_key(ustr_c(r->name), nName, NULL, false) ){
cmpp__err(pp, CMPP_RC_RANGE,
"\"%s\" is not a legal directive name.",
r->name);
goto end;
}
if( cmpp__d_search(pp, r->name) ){
cmpp__err(pp, CMPP_RC_ALREADY_EXISTS,
"Directive name '%s' is already in use.",
r->name);
goto end;
}
e1 = CmppDList_append(pp, &pp->pimpl->d.list);
if( !e1 ) goto end;
e1->d.impl.f = r->opener.f;
e1->d.impl.state = r->state;
e1->d.impl.dtor = r->dtor;
if( pp->pimpl->flags.isInternalDirectiveReg ){
e1->d.flags = r->opener.flags;
}else{
e1->d.flags = r->opener.flags & cmpp_f_F_MASK;
}
e1->d.flags |= cmpp_f_F_NEEDS_DX;
e1->zName = sqlite3_mprintf("%.*s", (int)nName, r->name);
if( 0==cmpp_check_oom(pp, e1->zName) ){
e1->d.name.z = e1->zName;
e1->d.name.n = nName;
if( r->closer.f
&& (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){
e2->d.impl.f = r->closer.f;
e2->d.impl.state = r->state;
if( pp->pimpl->flags.isInternalDirectiveReg ){
e2->d.flags = r->closer.flags;
}else{
e2->d.flags = r->closer.flags & cmpp_f_F_MASK;
}
e2->d.flags |= cmpp_f_F_NEEDS_DX;
e1->d.closer = &e2->d;
e2->zName = sqlite3_mprintf("/%s", r->name);
if( 0==cmpp_check_oom(pp, e2->zName) ){
e2->d.name.z = e2->zName;
e2->d.name.n = e1->d.name.n + 1;
}
}
}
end:
if( ppCode ){
if( e2 ) CmppDList_unappend(&pp->pimpl->d.list);
if( e1 ) CmppDList_unappend(&pp->pimpl->d.list);
else if( r->dtor ){
r->dtor( r->state );
}
}else{
/* TODO (2026-09-22): delay sort under search. Except then
the list is const. */
CmppDList_sort(&pp->pimpl->d.list);
if( dOut ){
*dOut = &e1->d;
}
if( 0 ){
g_warn("Registered: %s%s%s", e1->zName,
e2 ? " and " : "",
e2 ? e2->zName : "");
}
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os,
cmpp_d const * const * const dClosers,
unsigned nClosers,
cmpp_flag32_t flags){
assert( !dxppCode );
bool gotOne = false;
cmpp_outputer const oldOut = dx->pp->pimpl->out;
bool const allowOtherDirectives =
(flags & cmpp_dx_consume_F_PROCESS_OTHER_D);
cmpp_d const * const d = cmpp_dx_d(dx);
cmpp_size_t const lineNo = dx->pimpl->dline.lineNo;
bool const pushAt = (cmpp_dx_consume_F_ATPOL_OFF & flags);
if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){
return dxppCode;
}
if( os ){
dx->pp->pimpl->out = *os;
}
while( 0==dxppCode
&& 0==cmpp_dx_next(dx, &gotOne)
/* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){
if( !gotOne ){
dxserr("No closing directive found for "
"%s%s opened on line %" CMPP_SIZE_T_PFMT ".",
cmpp_dx_delim(dx), d->name.z, lineNo);
}else{
cmpp_d const * const d2 = cmpp_dx_d(dx);
gotOne = false;
for( unsigned i = 0; !gotOne && i < nClosers; ++i ){
gotOne = d2==dClosers[i];
}
//g_warn("gotOne=%d d2=%s", gotOne, d2->name.z);
if( gotOne ) break;
else if( !allowOtherDirectives ){
dxserr("%s%s at line %" CMPP_SIZE_T_PFMT
" may not contain %s%s.",
cmpp_dx_delim(dx), d->name.z, lineNo,
cmpp_dx_delim(dx), d2->name.z);
}else{
cmpp_dx_process(dx);
}
}
}
if( pushAt ){
cmpp_atpol_pop(dx->pp);
}
if( os ){
dx->pp->pimpl->out = oldOut;
}
return dxppCode;
}
CMPP__EXPORT(int, cmpp_dx_consume_v2)(
cmpp_dx * const dx, cmpp_outputer * const os,
cmpp_d const * const * const dClosers,
unsigned nClosers, cmpp_flag32_t flags,
cmpp_dx_consumed * const con){
memset(con, 0, sizeof(*con));
con->beginDirective = dx->pimpl->dline.zBegin;
con->beginContent = dx->pimpl->pos.z;
assert( con->beginContent > con->beginDirective );
con->dOpen = dx->d;
int const rc = cmpp_dx_consume(dx, os, dClosers, nClosers,
flags);
con->endContent = dx->pimpl->dline.zReallyBegin /* of the closer */;
con->endDirective = dx->pimpl->pos.z;
con->dClose = dx->d;
return rc;
}
CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b,
cmpp_d const * const * dClosers,
unsigned nClosers, cmpp_flag32_t flags){
cmpp_outputer oss = cmpp_outputer_b;
oss.state = b;
return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags);
}
char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){
again:
switch(p){
case cmpp_atpol_CURRENT:{
if( pp ){
assert( p!=cmpp__policy(pp, at) );
p = cmpp__policy(pp, at);
pp = 0;
goto again;
}
return NULL;
}
case cmpp_atpol_invalid: return NULL;
case cmpp_atpol_OFF: return "off";
case cmpp_atpol_RETAIN: return "retain";
case cmpp_atpol_ELIDE: return "elide";
case cmpp_atpol_ERROR: return "error";
}
return NULL;
}
cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){
cmpp_atpol_e rv = cmpp_atpol_invalid;
if( 0==strcmp(z, "retain") ) rv = cmpp_atpol_RETAIN;
else if( 0==strcmp(z, "elide") ) rv = cmpp_atpol_ELIDE;
else if( 0==strcmp(z, "error") ) rv = cmpp_atpol_ERROR;
else if( 0==strcmp(z, "off") ) rv = cmpp_atpol_OFF;
if( pp ){
if( cmpp_atpol_invalid==rv
&& 0==strcmp(z, "current") ){
rv = cmpp__policy(pp,at);
}else if( cmpp_atpol_invalid==rv ){
cmpp__err(pp, CMPP_RC_RANGE,
"Invalid @ policy value: %s."
" Try one of retain|elide|error|off|current.", z);
}else{
cmpp__policy(pp,at) = rv;
}
}
return rv;
}
cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e)
cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e)
int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){
if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at);
assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." );
if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol)
&& 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){
PodList__atpol_pop(&cmpp__epol(pp,at));
}
return ppCode;
}
void cmpp_atpol_pop(cmpp * pp){
assert( cmpp__epol(pp,at).n );
if( cmpp__epol(pp,at).n ){
PodList__atpol_pop(&cmpp__epol(pp,at));
}else if( !ppCode ){
cmpp_errf(pp, CMPP_RC_MISUSE,
"%s() called when no cmpp_atpol_push() is active.",
__func__);
}
}
int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){
if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol)
&& cmpp_unpol_set(pp, pol)/*for validation*/ ){
PodList__unpol_pop(&cmpp__epol(pp,un));
}
return ppCode;
}
void cmpp_unpol_pop(cmpp * pp){
assert( cmpp__epol(pp,un).n );
if( cmpp__epol(pp,un).n ){
PodList__unpol_pop(&cmpp__epol(pp,un));
}else if( !ppCode ){
cmpp_errf(pp, CMPP_RC_MISUSE,
"%s() called when no cmpp_unpol_push() is active.",
__func__);
}
}
CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){
return cmpp__epol(pp,at).na
? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT;
}
CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){
if( 0==ppCode ){
switch(pol){
case cmpp_atpol_OFF:
case cmpp_atpol_RETAIN:
case cmpp_atpol_ELIDE:
case cmpp_atpol_ERROR:
assert(cmpp__epol(pp,at).na);
cmpp__policy(pp,at) = pol;
break;
case cmpp_atpol_CURRENT:
break;
default:
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
(int)pol);
}
}
return ppCode;
}
#if 0
char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){
(void)pp;
switch(p){
case cmpp_unpol_NULL: return "null";
case cmpp_unpol_ERROR: return "error";
case cmpp_unpol_invalid: return NULL;
}
return NULL;
}
#endif
cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp,
char const *z){
cmpp_unpol_e rv = cmpp_unpol_invalid;
if( 0==strcmp(z, "null") ) rv = cmpp_unpol_NULL;
else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR;
if( pp ){
if( cmpp_unpol_invalid==rv
&& 0==strcmp(z, "current") ){
rv = cmpp__policy(pp,un);
}else if( cmpp_unpol_invalid==rv ){
cmpp__err(pp, CMPP_RC_RANGE,
"Invalid undefined key policy value: %s."
" Try one of null|error.", z);
}else{
cmpp_unpol_set(pp, rv);
}
}
return rv;
}
CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){
return cmpp__epol(pp,un).na
? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT;
}
CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){
if( 0==ppCode ){
switch(pol){
case cmpp_unpol_NULL:
case cmpp_unpol_ERROR:
cmpp__policy(pp,un) = pol;
break;
default:
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
(int)pol);
}
}
return ppCode;
}
/**
Reminders to self re. savepoint tracking:
cmpp_dx tracks per-input-source savepoints. We always want
savepoints which are created via scripts to be limited to that
script. cmpp instances, on the other hand, don't care about that.
Thus we have two different APIs for starting/ending savepoints.
*/
CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){
if( 0==ppCode ){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true);
assert( q || !"db init would have otherwise failed");
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
++pp->pimpl->flags.nSavepoint;
}
}
return ppCode;
}
int cmpp__dx_sp_begin(cmpp_dx * const dx){
if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){
++dx->pimpl->nSavepoint;
}
return dxppCode;
}
CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){
/* Remember that rollback must (mostly) ignore the
pending error state. */
if( !pp->pimpl->flags.nSavepoint ){
if( 0==ppCode ){
cmpp__err(pp, CMPP_RC_MISUSE,
"Cannot roll back: no active savepoint");
}
}else{
sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true);
assert( q || !"db init would have otherwise failed");
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
q = cmpp__stmt(pp, CmppStmt_spRelease, true);
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
--pp->pimpl->flags.nSavepoint;
}
}
}
return ppCode;
}
int cmpp__dx_sp_rollback(cmpp_dx * const dx){
/* Remember that rollback must (mostly) ignore the pending error state. */
if( !dx->pimpl->nSavepoint ){
if( 0==dxppCode ){
cmpp_dx_err(dx, CMPP_RC_MISUSE,
"Cannot roll back: no active savepoint");
}
}else{
cmpp_sp_rollback(dx->pp);
--dx->pimpl->nSavepoint;
}
return dxppCode;
}
CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){
if( 0==ppCode ){
if( !pp->pimpl->flags.nSavepoint ){
cmpp__err(pp, CMPP_RC_MISUSE,
"Cannot commit: no active savepoint");
}else{
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true);
assert( q || !"db init would have otherwise failed");
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
--pp->pimpl->flags.nSavepoint;
}
}
}else{
cmpp_sp_rollback(pp);
}
return ppCode;
}
int cmpp__dx_sp_commit(cmpp_dx * const dx){
if( 0==dxppCode ){
if( !dx->pimpl->nSavepoint ){
cmpp_dx_err(dx, CMPP_RC_MISUSE,
"Cannot commit: no active savepoint");
}else if( 0==cmpp_sp_commit(dx->pp) ){
--dx->pimpl->nSavepoint;
}
}
return dxppCode;
}
static void cmpp__dx_pimpl_reuse(cmpp__dx_pimpl *p){
#if 0
/* no: we need most of the state to remain
intact. */
cmpp__dx_pimpl const tmp = *p;
*p = cmpp__dx_pimpl_empty;
p->buf = tmp.buf;
p->args = tmp.args;
#endif
cmpp_b_reuse(&p->buf.line);
cmpp_b_reuse(&p->buf.argsRaw);
cmpp__args_reuse(&p->args);
}
void cmpp__dx_pimpl_cleanup(cmpp__dx_pimpl *p){
cmpp_b_clear(&p->buf.line);
cmpp_b_clear(&p->buf.argsRaw);
cmpp_args_cleanup(&p->args);
*p = cmpp__dx_pimpl_empty;
}
void cmpp_dx__reset(cmpp_dx * const dx){
dx->args = cmpp_dx_empty.args;
cmpp__dx_pimpl_reuse(dx->pimpl);
dx->d = 0;
//no: dx->sourceName = 0;
}
void cmpp_dx_cleanup(cmpp_dx * const dx){
unsigned prev = 0;
CmppLvlList_cleanup(&dx->pimpl->dxLvl);
while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){
prev = dx->pimpl->nSavepoint;
cmpp__dx_sp_rollback(dx);
}
cmpp__dx_pimpl_cleanup(dx->pimpl);
memset(dx, 0, sizeof(*dx));
}
#if 2 == CMPP__FIND_CLOSING
int cmpp__find_closing2(cmpp *pp,
unsigned char const **zPos,
unsigned char const *zEnd,
cmpp_size_t * pNl){
unsigned char const * z = *zPos;
unsigned char const opener = *z;
unsigned char const closer = cmpp__closer(opener);
int count = 1;
int rc = 0;
if( !closer ){
return cmpp_errf(pp, CMPP_RC_MISUSE,
"Invalid character (0x%02x) for %s().",
(int)opener, __func__);
}
for( ++z; z < zEnd; ++z ){
if( closer == *z && 0==--count ){
/* Have to check this first for the case of "" and ''. */
break;
}else if( opener == *z ){
++count;
}else if( pNl && '\n'==*z ){
++*pNl;
}
}
if( closer!=*z ){
if( 0 ){
g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
(zEnd - *zPos), *zPos);
}
//assert(!"here");
rc = cmpp__err(pp, CMPP_RC_SYNTAX,
"Unbalanced %c%c: %.*s",
opener, closer,
(int)(z-*zPos), *zPos);
assert( CMPP_RC_SYNTAX==rc );
}else{
if( 0 ){
g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos);
}
*zPos = z;
}
return rc;
}
#else
int cmpp__find_closing3(cmpp *pp,
unsigned char const **zPos,
unsigned char const *zEnd,
cmpp_size_t * pNl){
unsigned char const * z = *zPos;
unsigned char const opener = *z;
unsigned char const closer = cmpp__closer(opener);
bool esc = false;
int count = 1;
int rc = 0;
if( !closer ){
return cmpp_errf(pp, CMPP_RC_MISUSE,
"Invalid character (0x%02x) for %s().",
(int)opener, __func__);
}
for( ++z ; z < zEnd; ++z ){
switch( *z ){
case '\\':
esc = !esc;
continue;
case '\n':
if( pNl && '\n'==*z ) ++*pNl;
CMPP_SWITCH_FALL_THROUGH;
default:
if( esc ){
esc = 0;
}else if( closer == *z && 0==--count ){
/* Have to check this first for the case of "" and ''. */
goto out;
}else if( opener == *z ){
++count;
}
}
}
out:
if( z>=zEnd || count ){
if( 0 ){
g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
(zEnd - *zPos), *zPos);
g_warn("Closer=captured: <<%.*s>>",
(z - *zPos), z);
}
//assert(!"here");
rc = cmpp__err(pp, CMPP_RC_SYNTAX,
"Unbalanced %c%c: %.*s",
opener, closer,
(int)(z-*zPos), *zPos);
assert( CMPP_RC_SYNTAX==rc );
}else{
if( 0 ){
g_warn("group: n=%u <<%.*s>>",
(z + 1 - *zPos), (z +1 - *zPos), *zPos);
}
*zPos = z;
}
return rc;
}
#endif
cmpp_tt_e cmpp__tt_for_sqlite(int sqType){
cmpp_tt_e rv;
switch( sqType ){
case SQLITE_INTEGER: rv = cmpp_tt_IntDec; break;
case SQLITE_NULL: rv = cmpp_tt_Null; break;
default: rv = cmpp_tt_StringDQ; break;
}
return rv;
}
int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
bool defineIfNoRow, cmpp_b * bCol0){
if( 0==ppCode ){
int const nCol = sqlite3_column_count(q);
assert( sqlite3_data_count(q)>0 || defineIfNoRow);
/* Create a #define for each column */
bool const hasRow = sqlite3_data_count(q)>0;
if( bCol0 ) cmpp_b_reuse(bCol0);
for( int i = 0; !ppCode && i < nCol; ++i ){
char const * const zCol = sqlite3_column_name(q, i);
if( hasRow ){
unsigned char const * const zVal = sqlite3_column_text(q, i);
int const nVal = sqlite3_column_bytes(q, i);
cmpp_tt_e const ttype =
cmpp__tt_for_sqlite(sqlite3_column_type(q,i));
cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype);
if( bCol0 ){
cmpp_b_append4(pp, bCol0, zVal, nVal);
}
}else if(defineIfNoRow){
cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_tt_Null);
}else{
break;
}
}
}
return ppCode;
}
cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){
cmpp_d const * d = 0;//cmpp__d_search(zName);
if( !d ){
CmppDList_entry const * e =
CmppDList_search(&pp->pimpl->d.list, zName);
if( e ) d = &e->d;
}
return d;
}
cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
cmpp_flag32_t what){
cmpp_d const * d = cmpp__d_search(pp, zName);
if( !d ){
CmppDList_entry const * e = 0;
if( cmpp__d_search3_F_DELAYED & what ){
int rc = cmpp__d_delayed_load(pp, zName);
if( 0==rc ){
e = CmppDList_search(&pp->pimpl->d.list, zName);
}else if( CMPP_RC_NO_DIRECTIVE!=rc ){
assert( ppCode );
return NULL;
}
}
if( !e
&& (cmpp__d_search3_F_AUTOLOADER & what)
&& pp->pimpl->d.autoload.f
&& 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){
e = CmppDList_search(&pp->pimpl->d.list, zName);
}
#if CMPP_D_MODULE
if( !e
&& !ppCode
&& (cmpp__d_search3_F_DLL & what) ){
char * z = sqlite3_mprintf("libcmpp-d-%s", zName);
cmpp_check_oom(pp, z);
int rc = cmpp_module_load(pp, z, NULL);
sqlite3_free(z);
if( rc ){
if( CMPP_RC_NOT_FOUND==rc ){
cmpp__err_reuse(pp);
}
return NULL;
}
e = CmppDList_search(&pp->pimpl->d.list, zName);
}
#endif
if( e ) d = &e->d;
}
return d;
}
CMPP__EXPORT(int,cmpp_dx_process)(cmpp_dx * const dx){
if( dxppCode ) return dxppCode;
cmpp_d const * const d = cmpp_dx_d(dx);
assert( d );
if( cmpp_dx_is_eliding(dx) && !(d->flags & cmpp_f_F_FLOW_CONTROL) ){
return dxppCode;
}
cmpp_flag32_t const callFlags = 0/*TODO?*/;
bool const isDxCall = dx->pimpl->args.pimpl->isDxCall;
if( (cmpp_f_F_NOT_IN_SAFEMODE & d->flags)
&& (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.ctorFlags) ){
return cmpp_dx_err(dx, CMPP_RC_ACCESS,
"Directive %s%s is disabled by safe mode.",
cmpp_dx_delim(dx), dx->d->name.z);
}
/*
Reminder to self: directives which have modes predating
cmpp_f_F_ARGS_V4 _must_ have their args memory pointing into the
massaged copy of the argument line, not the original source
memory, because that one has backslash-escaped newlines removed
and pre-V4 directive impls rely on the library to have removed
them.
*/
#define argsReallyCommon .pp=dx->pp,.dx=dx,.d=d
# define argsCommon argsReallyCommon
if( cmpp_f_F_ARGS_V4 & d->flags ){
/** ================ V4 ================ */
cmpp_args args = dx->pimpl->args /* transfer ownership. We can't do this
for pre-v4 arg modes. */;
dx->pimpl->args = cmpp_args_empty;
args.arg0 = NULL /*pre-v4 baggage*/;
assert( d->impl.f );
assert( args.pimpl );
assert( args.argc && "V4 gets the directive name as arg[0]" );
if( (d->flags & cmpp_f_F_ENFORCE_ARITY)
&& d->arity>=0
&& (unsigned)d->arity!=dx->pimpl->args.argc - 1
/* -1 for the command name*/ ){
cmpp_errf(
dx->pp, CMPP_RC_MISUSE,
"Invalid argument count (%d) for directive %s. "
"Usage: %s",
(int)dx->pimpl->args.argc, d->name.z,
d->usage ? d->usage : "<missing cmpp_d::usage>"
);
goto bail;
}
CmppArgList * const ali = &args.pimpl->argli;
assert( ali->n==args.argc && "V4 these should not differ in v4"
/* They can differ in pre-v4 because of the old expression
engine's token-snipping */ );
unsigned char const * const zRaw =
/* token source range */ dx->args.z;
assert( zRaw );
cmpp_size_t const nRaw = dx->args.nz;
assert( nRaw && "V4 gets the directive name as argv[0]" );
if( cmpp_f_F_ARGS_RAW & d->flags ){
/** =========== V4/RAW ========== */
cmpp_f_args const fargs = {
argsCommon,
.argc = ali->n, .argv = 0,
.flags = callFlags, .isDxCall = isDxCall,
.raw = {.z = zRaw, .n = nRaw}
};
d->impl.f(&fargs);
}else{
/** ========== V4 cooked ========== */
/*
2026-09-04 checkout [d28f326f116f8]. Bug somewhere. This block
is working around a reproducible legitimate assert if we do
_not_ take ownership of ali->avb. That "should work" (it does
in itch.c) but it's inexplicably leading to a legitimate
assert via:
cmpp_dtor() => cmpp__args_pimpl_cleanup() =>
CmppArgList_dtor() => cmpp_b_return()
for the buffer ali->avb. Reproducer:
gdb --args ./c-pp -I. -I./src -Dsrcdir=./src \
-o libcmpp.c ./tool/libcmpp.c-pp.c
If, below, we leave ali->avb in place and skip the
cmpp_b_return() of it (which is fine, as ali owns that
buffer), then the repro triggers. Under the hood that is
doing literally the exact same thing except that the
lifetimes of the buffers (ba, ali->avb) differ. That suggests
that ali->avb is being used uninitialized or being returned
without setting it to 0 somewhere, but my eyes are not seeing
that anywhere. Maybe we have a lingering misuse somewhere
unrelated which is stomping it. Valgrind is happy, which
implies that such hypothetically misuse would be well within
our rights to do (as opposed to stomping unallocated
memory). Hmm.
*/
cmpp_arg const ** const argv =
CmppArgList_makeArgv(dx->pp, ali, true);
if( !argv ) goto bail;
cmpp_b * const ba = ali->avb;
ali->avb = 0;
assert( ba && "argv lives in here and argv[X]->z might" );
cmpp_f_args const fargs = {
argsCommon,
.argc = ali->n, .argv = argv,
.flags = callFlags, .isDxCall = isDxCall,
.raw = {.z = zRaw, .n = nRaw}
};
d->impl.f(&fargs);
cmpp_b_return(dx->pp, ba);
}
bail:
cmpp_args_cleanup(&args);
}else{
/* V1..3 modes get/handle the args via the legacy dx->args, with
the caveat that directives which consume from the input stream
invalidate that memory (consumed directives each overwrite
it). */
assert(d->impl.f);
cmpp_b * const braw = cmpp_b_borrow(dx->pp)
/* Ensure that the raw args memory remains valid for
the life of the call. */;
if( braw ){
cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw);
cmpp_f_args const fargs = {
argsCommon,
.argc = 0, .argv = 0,
.flags = callFlags, .isDxCall = isDxCall,
.raw = {.z = braw->z ? braw->z : ustr_c(""),
.n = braw->n}
};
d->impl.f(&fargs);
cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw);
cmpp_b_return(dx->pp, braw);
}
}
#undef argsCommon
#undef argsReallyCommon
return dxppCode;
}
static void cmpp_dx__setup_include_path(cmpp_dx * dx){
/* Add the leading dir part of dx->sourceName as the
highest-priority include path. It gets removed
in cmpp_dx__teardown(). */
assert( dx->sourceName );
enum { BufSize = 512 * 4 };
unsigned char buf[BufSize] = {0};
unsigned char *z = &buf[0];
cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1);
if( n > (unsigned)BufSize-1 ) return;
memcpy(z, dx->sourceName, n);
buf[n] = 0;
cmpp_strlen_t i = n - 1;
for( ; i > 0; --i ){
if( '/'==z[i] || '\\'==z[i] ){
z[i] = 0;
n = i;
break;
}
}
if( n>(cmpp_size_t)i ){
/* No path separator found. Assuming '.'. This is intended to
replace the historical behavior of automatically adding '.' if
no -I flags are used. Potential TODO is getcwd() here instead
of using '.' */
n = 1;
buf[0] = '.';
buf[1] = 0;
}
int64_t rowid = 0;
cmpp__include_dir_add(dx->pp, (char const*)buf,
dx->pp->pimpl->flags.nDxDepth,
&rowid);
if( rowid ){
//g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z);
dx->pimpl->shadow.ridInclPath = rowid;
}
}
static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx,
unsigned char const * zIn,
cmpp_strlen_t nIn){
if( 0==ppCode ){
assert( dx->sourceName );
assert( dx->pimpl );
assert( pp==dx->pp );
nIn = cmpp__strlenu(zIn, nIn);
if( !nIn ) return 0;
pp->pimpl->dx = dx;
dx->pimpl->zBegin = zIn;
dx->pimpl->zEnd = zIn + nIn;
cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName,
&dx->pimpl->shadow.sidFile);
++dx->pp->pimpl->flags.nDxDepth;
cmpp_dx__setup_include_path(dx);
}
return ppCode;
}
static void cmpp_dx__teardown(cmpp_dx *dx){
if( dx->pimpl->shadow.ridInclPath>0 ){
cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath);
dx->pimpl->shadow.ridInclPath = 0;
}
if( dx->pimpl->shadow.sidFile ){
cmpp_define_unshadow(dx->pp, "__FILE__",
dx->pimpl->shadow.sidFile);
}
--dx->pp->pimpl->flags.nDxDepth;
cmpp_dx_cleanup(dx);
}
CMPP__EXPORT(int, cmpp_process_string)(
cmpp *pp, const char * zName,
unsigned char const * zIn,
cmpp_strlen_t nIn
){
if( !nIn || (nIn<0 && !*zIn) ) return 0;
if( 0==cmpp__db_init(pp) ){
if( !zName ) zName = "";
cmpp_dx * const oldDx = pp->pimpl->dx;
cmpp__dx_pimpl dxp = cmpp__dx_pimpl_empty;
cmpp_dx dx = {
.pp = pp,
.sourceName = ustr_c(zName),
.args = cmpp_dx_empty.args,
.pimpl = &dxp
};
dxp.up = oldDx;
dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall;
pp->pimpl->flags.nextIsCall = false;
if( dxp.flags.nextIsCall ){
assert( pp->pimpl->dx );
dxp.pos.lineNo = oldDx->pimpl->pos.lineNo;
}
bool gotOne = false;
(void)cmpp__stmt(pp, CmppStmt_sdefIns, true);
(void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true);
(void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
(void)cmpp__stmt(pp, CmppStmt_sdefDel, true)
/* hack: ensure that those queries are allocated now, as an
error in processing may keep them from being created
later. We might want to rethink the
prepare-no-statements-on-error bits, but will have to go back
and fix routines which currently rely on that. */;
cmpp_dx__setup(pp, &dx, zIn, nIn);
assert( !dxp.pos.z );
while(0==ppCode
&& 0==cmpp_dx_next(&dx, &gotOne)
&& gotOne){
cmpp_dx_process(&dx);
}
if(0==ppCode && 0!=dx.pimpl->dxLvl.n){
CmppLvl const * const lv = CmppLvl_get(&dx);
cmpp_dx_err(&dx, CMPP_RC_SYNTAX,
"Input ended inside an unterminated nested construct "
"opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName,
lv ? lv->lineNo : (cmpp_size_t)0);
}
cmpp_dx__teardown(&dx);
pp->pimpl->dx = oldDx;
}
if( !ppCode ){
cmpp_outputer_flush(&pp->pimpl->out)
/* We're going to ignore a result code just this once. */;
}
return ppCode;
}
int cmpp_process_file(cmpp *pp, const char * zName){
if( 0==ppCode ){
FileWrapper fw = FileWrapper_empty;
if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb")
&& 0==cmpp__FileWrapper_slurp(pp, &fw) ){
cmpp_process_string(pp, zName, fw.zContent, fw.nContent);
}
FileWrapper_close(&fw);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName,
cmpp_input_f src, void * srcState){
if( 0==ppCode ){
cmpp_b * const os = cmpp_b_borrow(pp);
int const rc = os
? cmpp_stream(src, srcState, cmpp_output_f_b, os)
: ppCode;
if( 0==rc ){
if( os->n ){
cmpp_process_string(pp, zName, os->z, os->n);
}
}else{
cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName);
}
cmpp_b_return(pp, os);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_call_str)(
cmpp *pp, unsigned char const * z, cmpp_strlen_t n,
cmpp_b * dest, cmpp_flag32_t flags
){
if( ppCode ) return ppCode;
cmpp_b * const bo = cmpp_b_borrow(pp);
cmpp_outputer oB = cmpp_outputer_b;
if( !bo ) return ppCode;
cmpp__pi(pp);
oB.state = bo;
oB.name = pi->out.name;//"[call]";
n = cmpp__strlenu(z, n);
//g_warn("calling: <<%.*s>>", (int)n, z);
unsigned char const * zEnd = z+n;
cmpp_skip_snl(&z, zEnd);
cmpp_skip_snl_trailing(z, &zEnd);
n = (zEnd-z);
if( !n ){
cmpp_errf(pp, CMPP_RC_SYNTAX,
"Empty [call] is not permitted.");
goto end;
}
//g_warn("calling: <<%.*s>>", (int)n, z);
cmpp__delim const * const delim = cmpp__pp_delim(pp);
assert(delim);
pi->flags.fakeNextDelim = (cmpp_size_t)n<=delim->open.n
|| 0!=memcmp(z, delim->open.z, delim->open.n)
/* If it doesn't start with the current delimiter, pretend that it
does by telling the pending cmpp_dx_next() that we have already
parsed the delimiter, causing it to treat the first token as a
directive name. */;
pi->flags.nextIsCall = true
/* Convey (indirectly, via cmpp_process_string()) that the first
cmpp_dx_next() call made via the upcoming cmpp_process_string()
is considered a call context. */;
cmpp_outputer oOld = cmpp_outputer_empty;
cmpp__outputer_swap(pp, &oB, &oOld);
cmpp_process_string(pp, "call"/*if we knew that z was NUL-terminated
then we could use it as the name*/,
z, n);
cmpp__outputer_swap(pp, &oOld, NULL);
assert( !pi->flags.nextIsCall || ppCode );
assert( !pi->flags.fakeNextDelim || ppCode );
pi->flags.nextIsCall = pi->flags.fakeNextDelim = false;
if( !ppCode ){
unsigned char const * zz = bo->z;
unsigned char const * zzEnd = bo->z + bo->n;
if( cmpp_call_F_TRIM_ALL & flags ){
cmpp_skip_snl(&zz, zzEnd);
cmpp_skip_snl_trailing(zz, &zzEnd);
}else if( 0==(cmpp_call_F_NO_TRIM & flags) ){
cmpp_b_chomp(bo);
zzEnd = bo->z + bo->n;
}
if( (zzEnd-zz) ){
cmpp_b_append4(pp, dest, zz, (cmpp_size_t)(zzEnd-zz));
}
}
end:
cmpp_b_return(pp, bo);
return ppCode;
}
CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){
switch(errNo){
/* Please expand on this as tests/use cases call for it... */
case 0:
return 0;
case EINVAL:
return CMPP_RC_MISUSE;
case ENOMEM:
return CMPP_RC_OOM;
case EROFS:
case EACCES:
case EBUSY:
case EPERM:
case EDQUOT:
case EAGAIN:
case ETXTBSY:
return CMPP_RC_ACCESS;
case EISDIR:
case ENOTDIR:
return CMPP_RC_TYPE;
case ENAMETOOLONG:
case ELOOP:
case ERANGE:
return CMPP_RC_RANGE;
case ENOENT:
case ESRCH:
return CMPP_RC_NOT_FOUND;
case EEXIST:
case ENOTEMPTY:
return CMPP_RC_ALREADY_EXISTS;
case EIO:
return CMPP_RC_IO;
default:
return dflt;
}
}
int cmpp_flush_f_FILE(void * _FILE){
return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
}
int cmpp_output_f_FILE( void * state,
void const * src, cmpp_size_t n ){
return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout))
? 0 : CMPP_RC_IO;
}
int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){
int const fd = *((int*)state);
ssize_t const wn = write(fd, src, n);
return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
}
int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){
cmpp_FILE * f = state;
cmpp_size_t const rn = *n;
*n = (cmpp_size_t)fread(dest, 1, rn, f);
return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO);
}
int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){
int const fd = *((int*)state);
ssize_t const rn = read(fd, dest, *n);
if( rn<0 ){
return cmpp_errno_rc(errno, CMPP_RC_IO);
}else{
*n = (cmpp_size_t)rn;
return 0;
}
}
void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){
if( self->state ){
cmpp_fclose( self->state );
self->name = NULL;
self->state = NULL;
}
}
CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){
if( self->state ) cmpp_b_clear(self->state);
}
CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){
return o->out ? o->out(o->state, p, n) : 0;
}
CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){
return o->flush ? o->flush(o->state) : 0;
}
CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){
if( o->cleanup ){
o->cleanup( o );
}
}
CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState,
cmpp_output_f outF, void * outState ){
int rc = 0;
enum { BufSize = 1024 * 4 };
unsigned char buf[BufSize];
cmpp_size_t rn = BufSize;
while( 0==rc
&& (rn==BufSize)
&& (0==(rc=inF(inState, buf, &rn))) ){
if(rn) rc = outF(outState, buf, rn);
}
return rc;
}
CMPP__EXPORT(cmpp_size_t, cmpp_count_nl)(unsigned char const *zBegin,
unsigned char const * const zEnd,
unsigned char const * const zPos,
cmpp_size_t *pCol){
assert( zPos >= zBegin );
assert( zPos <= zEnd );
unsigned char const * z = zBegin;
cmpp_size_t nl = 0, col = 0;
while( z < zPos ){
switch( *z ){
case '\n': ++nl; col = 0; break;
default: ++col; break;
}
++z;
}
if( pCol ) *pCol = col;
return nl;
}
void cmpp__fatalv_base(char const *zFile, int line,
char const *zFmt, va_list va){
cmpp_FILE * const fp = stderr;
fflush(stdout);
fprintf(fp, "\n%s:%d: ", zFile, line);
if(zFmt && *zFmt){
vfprintf(fp, zFmt, va);
fputc('\n', fp);
}
fflush(fp);
exit(1);
}
void cmpp__fatal_base(char const *zFile, int line,
char const *zFmt, ...){
va_list va;
va_start(va, zFmt);
cmpp__fatalv_base(zFile, line, zFmt, va);
va_end(va);
}
CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){
if( zMsg ){
*zMsg = ppCode
? pp->pimpl->err.zStatic
: 0;
}
return ppCode;
}
#if 0
CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){
int const rc = ppCode;
if( rc ){
*zMsg = (char *)pp->pimpl->err.b.z;
pp->pimpl->err = cmpp_errinfo_empty_m;
}else{
*zMsg = 0;
}
return rc;
}
#endif
CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){
return pp ? pp->pimpl->err.code : 0;
}
CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){
*pos = dx->pimpl->pos;
}
CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){
dx->pimpl->pos = *pos;
}
//CMPP_WASM_EXPORT
void cmpp__dx_append_script_info(cmpp_dx const * dx,
sqlite3_str * const sstr){
cmpp__dx_pi(dx);
unsigned char const *zB = 0;
unsigned char const *zE = 0;
unsigned char const *zErr = dpi->zErrPos;
cmpp__dx_input_range(dx, &zB, &zE);
//g_warn("Whole script:\n%.*s", (int)(zE-zB), zB);
assert( zB );
assert( zE );
assert( zB<=zE );
if( !zErr ){
if( dpi->pos.z>=zB && dpi->pos.z<zE ){
/* This is frequently past zEnd */
zErr = dpi->pos.z;
}else{
zErr = dpi->dline.zBegin;
}
}
//g_warn("dpi->zErrPos: %p\n%.3s ...", dpi->zErrPos, dpi->zErrPos);
//g_warn("dpi->pos.z: %p\n %.3s ...", dpi->pos.z, dpi->pos.z);
//g_warn("zErr: %p\n%.3s ...", zErr, zErr);
assert( (!zErr || (zErr>=zB && zErr<=zE))
&& "Else we're pointing to incompatible sources");
cmpp_size_t const nl = zErr
? 1 + cmpp_count_nl(zB, zE, zErr, NULL)
: dpi->dline.lineNo;
if( dx->d ){
sqlite3_str_appendf(sstr, "%s @ %s:%d",
dx->d->name.z,
(dx->sourceName
&& 0==strcmp("-", (char const *)dx->sourceName))
? "<stdin>"
: (char const *)dx->sourceName,
(int)dpi->dline.lineNo);
}
if( nl != dpi->dline.lineNo ){
/* Input position past the directive line. */
sqlite3_str_appendf(sstr, " from line %" CMPP_SIZE_T_PFMT, nl);
}
}
CMPP__EXPORT(void, cmpp_errinfo_dtor)(cmpp_errinfo *err){
cmpp_b_clear(&err->b);
*err = cmpp_errinfo_empty;
}
CMPP__EXPORT(cmpp_errinfo *, cmpp_errinfo_reuse)(cmpp_errinfo *err){
cmpp_b_reuse(&err->b);
err->zStatic = 0;
err->code = 0;
return err;
}
CMPP__EXPORT(int,cmpp_errinfo_set)(cmpp_errinfo *err, int rc,
char const *zMsg,
cmpp_strlen_t nMsg){
if( err ){
cmpp_errinfo_reuse(err);
if( rc && zMsg && *zMsg ){
if( cmpp_b_append_str(&err->b, zMsg, nMsg) ){
rc = err->b.errCode;
}
}
err->code = rc;
}
return rc;
}
int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt, va_list va){
if( err ){
cmpp_errinfo_reuse(err);
err->code = 0 /* some APIs become no-ops if pp->pimpl->err.code
is set, so delay setting this. */;
if( 0==rc ) return rc;
if( CMPP_RC_OOM==rc ){
oom:
err->zStatic ="An allocation failed.";
return err->code = CMPP_RC_OOM;
}
assert( !err->zStatic );
if( zFmt && *zFmt ){
sqlite3_str * sstr = sqlite3_str_new(0);
sqlite3_str_vappendf(sstr, zFmt, va);
int const nz = sqlite3_str_length(sstr);
char * const z = sqlite3_str_finish(sstr);
sstr = 0;
if( !z ){
goto oom;
}
if( err->b.z ){
cmpp_b_append_str(&err->b, z, nz);
sqlite3_free(z);
if( err->b.errCode ) goto oom;
}else{
err->b.z = (unsigned char*)z;
err->b.n = (cmpp_size_t)nz;
err->b.nAlloc = err->b.n + 1/*NUL byte*/;
}
err->zStatic = (char const *)err->b.z;
}else{
err->zStatic = "No error info provided.";
}
err->code = rc;
}
return rc;
}
int cmpp_errinfo_setf(cmpp_errinfo *err, int rc,
char const *zFmt, ...){
if( err ){
va_list va;
va_start(va, zFmt);
rc = cmpp_errinfo_setv(err, rc, zFmt, va);
va_end(va);
}
return rc;
}
CMPP__EXPORT(int, cmpp_err)(cmpp *pp, int rc, char const *zMsg,
cmpp_strlen_t nMsg){
if( pp ){
if( rc ){
nMsg = cmpp__strlen(zMsg, nMsg);
rc = cmpp_errf(pp, rc, "%.*s", (int)nMsg, zMsg)
/* Reminder to self: we have to go through cmpp_errf(),
instead of cmpp_errinfo_set(), so that error location
information gets tacked on. */;
}else{
cmpp_errinfo_reuse(&pp->pimpl->err);
}
}
return rc;
}
int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list va){
if( pp ){
cmpp_errinfo * const err = &pp->pimpl->err;
cmpp_errinfo_reuse(err);
assert( 0==err->code )
/* some APIs become no-ops if pp->pimpl->err.code
is set, so delay setting this. */;
if( 0==rc ) return rc;
if( CMPP_RC_OOM==rc ){
oom:
err->zStatic ="An allocation failed.";
return err->code = CMPP_RC_OOM;
}
assert( !err->zStatic );
if( pp->pimpl->dx || (zFmt && *zFmt) ){
sqlite3_str * const sstr = cmpp__sqlite3_str_new(pp);
if( !sstr ) goto oom;
if( pp->pimpl->dx ){
cmpp__dx_append_script_info(pp->pimpl->dx, sstr);
sqlite3_str_append(sstr, ": ", 2);
}
if( zFmt && *zFmt ){
sqlite3_str_vappendf(sstr, zFmt, va);
}else{
sqlite3_str_appendf(sstr, "No error info provided.");
}
int nz = 0;
char * const z = cmpp_sqlite3_str_finish(pp, sstr, &nz);
if( !z ) goto oom;
#if 0
cmpp_b_append(&err->b, z, nz);
sqlite3_free(z);
#else
if( err->b.z ){
if( cmpp_b_append4_str(pp, &err->b, z, nz) ){
sqlite3_free(z);
goto oom;
}
sqlite3_free(z);
}else{
/* Transfer ownership. */
err->b.z = (unsigned char*)z;
err->b.n = (cmpp_size_t)nz;
err->b.nAlloc = err->b.n + 1 /*NUL terminator*/;
}
#endif
err->zStatic = (char *)err->b.z;
}else{
err->zStatic = "No error info provided.";
}
err->code = rc;
}
return rc;
}
//CMPP_WASM_EXPORT no - variadic
int cmpp_errf(cmpp *pp, int rc,
char const *zFmt, ...){
if( pp ){
va_list va;
va_start(va, zFmt);
//rc = cmpp_errinfo_setv(&pp->pimpl->err, rc, zFmt, va);
rc = cmpp_errfv(pp, rc, zFmt, va);
va_end(va);
}
return rc;
}
const cmpp_d_autoloader cmpp_d_autoloader_empty =
cmpp_d_autoloader_empty_m;
CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){
if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state);
if( pNew ) pp->pimpl->d.autoload = *pNew;
else pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
}
CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){
*pOld = pp->pimpl->d.autoload;
pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
}
#undef cmpp_dx_err
//CMPP_WASM_EXPORT no - variadic
int cmpp_dx_errf(cmpp_dx *dx, int rc,
char const *zFmt, ...){
if( rc ){
if( !dx->pimpl->zErrPos ){
dx->pimpl->zErrPos = dx->pimpl->dline.zBegin;
//g_warn("dx->pimpl->dline.zBegin: %.3s...", dx->pimpl->dline.zBegin);
if( !dx->pimpl->zErrPos ) dx->pimpl->zErrPos = dx->pimpl->zBegin;
//g_warn("dx->pimpl->zBegin: %.3s...", dx->pimpl->zBegin);
}
}else{
dx->pimpl->zErrPos = 0;
}
va_list va;
va_start(va, zFmt);
rc = cmpp_errfv(dx->pp, rc, zFmt, va);
va_end(va);
return rc;
}
int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt, va_list vargs){
return cmpp_errfv(dx->pp, rc, zFmt, vargs);
}
//no: CMPP_WASM_EXPORT
char * cmpp_path_search(cmpp *pp,
char const *zPath,
char pathSep,
char const *zBaseName,
char const *zExt){
char * zrc = 0;
if( !ppCode ){
sqlite3_stmt * const q =
cmpp__stmt(pp, CmppStmt_selPathSearch, false);
if( q ){
unsigned char sep[2] = {pathSep, 0};
cmpp_stmt_bind_text(pp, q, 1, ustr_c(zBaseName));
cmpp_stmt_bind_text(pp, q, 2, sep);
cmpp_stmt_bind_text(pp, q, 3, ustr_c((zExt ? zExt : "")));
cmpp_stmt_bind_text(pp, q, 4, ustr_c((zPath ? zPath: "")));
int const dbrc = cmpp_stmt_step(pp, q, false);
if( SQLITE_ROW==dbrc ){
unsigned char const * s = sqlite3_column_text(q, 1);
int const n = sqlite3_column_bytes(q,1);
if( n ){
zrc = sqlite3_mprintf("%.*s", n, s);
cmpp_check_oom(pp, zrc);
}else{
g_warn("WARNING: empty path result from: %s",
sqlite3_sql(q));
}
}
cmpp_stmt_reset(pp, q);
}
}
return zrc;
}
CMPP__EXPORT(cmpp_dx *,cmpp_dx_current)(cmpp *pp){
return cmpp__dx_current(pp);
}
//cmpp__ListType_impl(cmpp__delim_list,cmpp__delim)
//cmpp__ListType_impl(CmppDList,CmppDList_entry*)
//cmpp__ListType_impl(CmppSohList,void*)
cmpp__ListType_impl(cmpp_b_list,cmpp_b*)
cmpp__ListType_impl(CmppLvlList,CmppLvl*)
/**
Expects that *ndx points to the current argv entry and that it is a
flag which expects a value. This function checks for --flag=val and
(--flag val) forms. If a value is found then *ndx is adjusted (if
needed) to point to the next argument after the value and *zVal is
* pointed to the value. If no value is found then it returns false.
*/
static bool get_flag_val(int argc,
char const * const * argv, int * ndx,
char const **zVal){
char const * zEq = strchr(argv[*ndx], '=');
if( zEq ){
*zVal = zEq+1;
return 1;
}else if(*ndx+1>=argc){
return 0;
}else{
*zVal = argv[++*ndx];
return 1;
}
}
static
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
char const **zValIfEqX );
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
char const **zValIfEqX ){
if( zValIfEqX ) *zValIfEqX = 0;
if( 0==strcmp(zFlag, zArg) ) return true;
char const * z = strchr(zArg,'=');
if( z && z>zArg ){
/* compare the part before the '=' */
if( 0==strncmp(zFlag, zArg, z-zArg) ){
if( !zFlag[z-zArg] ){
if( zValIfEqX ) *zValIfEqX = z+1;
return true;
}
/* Else it was a prefix match. */
}
}
return false;
}
CMPP__EXPORT(cmpp_size_t, cmpp_strlen)(char const *z, cmpp_strlen_t n){
return cmpp__strlen(z, n);
}
CMPP__EXPORT(cmpp_size_t, cmpp_strlenu)(unsigned char const *z, cmpp_strlen_t n){
return cmpp__strlenu(z, n);
}
CMPP__EXPORT(void,cmpp_dx_src_pos_info)(
cmpp_dx const *dx, char const **zName,
cmpp_size_t * lineNo
){
if( zName ) *zName = (char const *)dx->sourceName;
if( lineNo ) *lineNo = dx->pimpl->pos.lineNo;
}
CMPP__EXPORT(int, cmpp_f_v4_err_usage)(cmpp_f_args const *args){
return cmpp_errf(args->pp, CMPP_RC_ERROR,
"Usage: %s %s",
args->argv[0]->z, args->usage);
}
CMPP__EXPORT(bool, cmpp_simple_truth)(unsigned char const *z,
cmpp_strlen_t n){
switch(n){
case 0: return false;
case 1: return *z && '0'!=*z;
//case 4: return 0==memcmp(z,"true",4+1);
//case 5: return 0!=memcmp(z,"false",5+1);
default:
return true;
}
}
CMPP__EXPORT(bool, cmpp_json_valid)(cmpp *pp, unsigned char const *z,
cmpp_strlen_t n, int flags){
bool rv = false;
n = cmpp__strlenu(z, n);
if( n && flags>0 && flags<16 ){
/* ^^^^ json_valid() is picky about its flags argument. We skip
processing for that case because it can put pp into an error
state which the caller is not likely to check for because who
would after such an innocuous function? */
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_jsonValid, false);
if( q ){
cmpp_stmt_bind_textx(pp, q, 1, z, n, SQLITE_STATIC);
cmpp_stmt_bind_int(pp, q, 2, flags);
if( SQLITE_ROW==cmpp_stmt_step(pp, q, false) ){
rv = 0!=sqlite3_column_int(q, 0);
}
cmpp_stmt_reset(pp, q);
}
}
return rv;
}
static inline bool cmpp__json_is_container(cmpp * const pp,
char opener,
char closer,
unsigned char const * const z,
cmpp_strlen_t n, bool allowJson5){
n = cmpp__strlenu(z, n);
return (
n>1
&& z[0]==(unsigned char)opener
&& z[n-1]==(unsigned char)closer
) ? (
n==2
? true
: cmpp_json_valid(pp, z, n, allowJson5 ? 3 : 1)
) : false;
}
CMPP__EXPORT(bool,cmpp_json_is_list)(cmpp *pp,
unsigned char const * const z,
cmpp_strlen_t n, bool allowJson5){
return cmpp__json_is_container(pp, '[', ']', z, n, allowJson5);
}
CMPP__EXPORT(bool,cmpp_json_is_object)(cmpp *pp,
unsigned char const * const z,
cmpp_strlen_t n, bool allowJson5){
return cmpp__json_is_container(pp, '{', '}', z, n, allowJson5);
}
CMPP__EXPORT(void,cmpp_str_json_append)(cmpp *pp, sqlite3_str * str,
unsigned char const * z,
cmpp_strlen_t n){
n = cmpp__strlenu(z, n);
if( !n ){
sqlite3_str_append(str, "\"\"", 2);
return;
}
int64_t j;
if( cmpp_is_int64(z, n, &j) ){
sqlite3_str_appendf(str, "%lld", j);
}else{
/* If it's JSON then append it as-is, don't escape it. We
"could" handle ints here but it's much faster to do them
above. */
//g_warn("arg %d: %.*s", (int)i, (int)n, z);
if( cmpp_json_valid(pp, z, n, 1) ){
/* Using any json_valid() flags which allow json5 will end
up generating JSON5 output, which we don't want here
because it would probably give us headaches everywhere
else. e.g. [list new a b c] would result in [a, b, c]
instead of ["a", "b", "c"]. */
//g_warn("is-valid 1: %.*s", (int)n, z);
sqlite3_str_append(str, (char *)z, (int)n);
}else{
//g_warn("is-valid 0: %.*s", (int)n, z);
sqlite3_str_appendf(str, "%.*J", (int)n, z);
}
}
}
void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false);
if( q ){
while( SQLITE_ROW==sqlite3_step(q) ){
int const tt = sqlite3_column_int(q, 0);
unsigned char const * zK = sqlite3_column_text(q, 1);
unsigned char const * zV = sqlite3_column_text(q, 2);
int const nK = sqlite3_column_bytes(q, 1);
int const nV = sqlite3_column_bytes(q, 2);
char const * zTt = cmpp__tt_cstr2(tt, true);
if( tt && zTt ) zTt += 3;
else zTt = "String";
fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "",
nK, zK, zTt, nV, zV);
}
cmpp_stmt_reset(pp, q);
}
}
/**
This is what was originally the main() of cmpp v1, back when it was
a monolithic app. It still serves as the driver for main() but is
otherwise unused.
*/
CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc,
char const * const * argv){
if( ppCode ) return ppCode;
int nFile = 0 /* number of files/-e scripts seen */;
#define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \
cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \
argv[i]); \
break; \
}
#define M(X) cmpp__arg_is_flag(X, zArg, &zVal)
#define ISFLAG(X) else if(M(X))
#define ISFLAG2(X,Y) else if(M(X) || M(Y))
#define NOVAL if( zVal ){ \
cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \
break; \
} (void)0
#define open_output_if_needed \
if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break
cmpp__staticAssert(TT_None,0==(int)cmpp_tt_None);
cmpp__staticAssert(Mask1, cmpp_f_F_MASK_INTERNAL & cmpp_f_F_FLOW_CONTROL);
cmpp__staticAssert(Mask2, cmpp_f_F_MASK_INTERNAL & cmpp_f_F_NOT_SIMPLIFY);
cmpp__staticAssert(Mask3, 0==(cmpp_f_F_MASK_INTERNAL & cmpp_f_F_MASK));
for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){
/**
Loop through the flags twice. The first time we just validate
and look for --help/-?. The second time we process the flags.
This approach allows us to easily chain multiple files and
flags:
./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y
Which, it turns out, is a surprisingly useful way to work.
*/
#define DOIT if(1==doIt)
for(int i = 0; i < argc && 0==ppCode; ++i){
char const * zVal = 0;
int isNoFlag = 0;
char const * zArg = argv[i];
//g_stderr("i=%d zArg=%s\n", i, zArg);
zVal = 0;
while('-'==*zArg) ++zArg;
if(zArg==argv[i]/*not a flag*/){
zVal = zArg;
goto do_infile;
}
//g_warn("zArg=%s", zArg);
if( 0==strncmp(zArg,"no-",3) ){
zArg += 3;
isNoFlag = 1;
}
if( M("?") || M("help") ){
NOVAL;
cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]);
break;
}else if('D'==*zArg){
++zArg;
if(!*zArg){
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D");
}else DOIT {
cmpp_define_legacy(pp, zArg, 0);
}
}else if('F'==*zArg){
++zArg;
if(!*zArg){
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F");
}else DOIT {
cmpp__set_file(pp, ustr_c(zArg), -1);
}
}
ISFLAG("e"){
ARGVAL;
DOIT {
++nFile;
open_output_if_needed;
cmpp_process_string(pp, "-e script",
(unsigned char const *)zVal, -1);
}
}else if('U'==*zArg){
++zArg;
if(!*zArg){
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U");
}else DOIT {
cmpp_undef_legacy(pp, zArg, NULL);
}
}else if('I'==*zArg){
++zArg;
if(!*zArg){
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I");
}else DOIT {
cmpp_include_dir_add(pp, zArg);
}
}else if('L'==*zArg){
++zArg;
if(!*zArg){
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L");
}else DOIT {
cmpp_module_dir_add(pp, zArg);
}
}
ISFLAG2("o","outfile"){
ARGVAL;
DOIT {
cmpp__out_fopen(pp, zVal);
}
}
ISFLAG2("f","file"){
ARGVAL;
do_infile:
DOIT {
if( !pp->pimpl->mod.path.z ){
cmpp_module_dir_add(pp, NULL);
}
++nFile;
if( 0
&& !pp->pimpl->flags.nIncludeDir
&& cmpp_include_dir_add(pp, ".") ){
break;
}
open_output_if_needed;
cmpp_process_file(pp, zVal);
}
}
ISFLAG("@"){
NOVAL;
DOIT {
assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF );
cmpp_atpol_set(pp, isNoFlag
? cmpp_atpol_OFF
: cmpp_atpol_DEFAULT_FOR_FLAG);
}
}
ISFLAG("@policy"){
ARGVAL;
cmpp_atpol_from_str(pp, zVal);
}
ISFLAG("debug"){
NOVAL;
DOIT {
pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1;
}
}
ISFLAG2("u","undefined-policy"){
ARGVAL;
cmpp_unpol_from_str(pp, zVal);
}
ISFLAG("sql-trace"){
NOVAL;
/* Needs to be set before the start of the second pass, when
the db is inited. */
DOIT {
pp->pimpl->sqlTrace.expandSql = false;
do_trace_flag:
cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out);
if( isNoFlag ){
pp->pimpl->sqlTrace.out = cmpp_outputer_empty;
}else{
pp->pimpl->sqlTrace.out = cmpp_outputer_FILE;
pp->pimpl->sqlTrace.out.state = stderr;
}
}
}
ISFLAG("sql-trace-x"){
NOVAL;
DOIT {
pp->pimpl->sqlTrace.expandSql = true;
goto do_trace_flag;
}
}
ISFLAG("chomp-F"){
NOVAL;
DOIT pp->pimpl->flags.chompF = !isNoFlag;
}
ISFLAG2("d","delimiter"){
ARGVAL;
DOIT {
cmpp_delimiter_set(pp, zVal);
}
}
ISFLAG2("dd", "dump-defines"){
DOIT {
cmpp_FILE * const fp =
/* tcl's exec treats output to stderr as failure.
If we use [exec -ignorestderr] then it instead replaces
stderr's output with its own message, invalidating
test expectations. */
1 ? stdout : stderr;
fprintf(fp, "All %sdefine entries:\n",
cmpp__pp_zdelim(pp));
cmpp__dump_defines(pp, fp, 1);
}
}
#if !defined(CMPP_OMIT_D_DB)
ISFLAG2("db", "db-file"){
/* Undocumented flag used for testing purposes. */
ARGVAL;
DOIT {
cmpp_db_name_set(pp, zVal);
}
}
#endif
ISFLAG("version"){
NOVAL;
#if !defined(CMPP_OMIT_FILE_IO)
fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n",
cmpp_version(),
sqlite3_libversion(),
sqlite3_sourceid());
#endif
doIt = 100;
break;
}
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
ISFLAG("safe-mode"){
if( i>0 ){
cmpp_errf(pp, CMPP_RC_MISUSE,
"--%s, if used, must be the first argument.",
zArg);
break;
}
}
#endif
else{
cmpp__err(pp,CMPP_RC_MISUSE,
"Unhandled flag: %s", argv[i]);
}
}
DOIT {
if(!nFile){
/* We got no file arguments, so read from stdin. */
if(0
&& !pp->pimpl->flags.nIncludeDir
&& cmpp_include_dir_add(pp, ".") ){
break;
}
open_output_if_needed;
cmpp_process_file(pp, "-");
}
}
#undef DOIT
}
return ppCode;
#undef ARGVAL
#undef M
#undef ISFLAG
#undef ISFLAG2
#undef NOVAL
#undef open_output_if_needed
}
void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){
#if defined(CMPP_OMIT_FILE_IO)
(void)zAppName; (void)fOut;
#else
fprintf(fOut, "%s version %s\nwith SQLite %s %s\n",
zAppName ? zAppName : "c-pp",
cmpp_version(),
sqlite3_libversion(),
sqlite3_sourceid());
fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName);
fprintf(fOut,
"Flags and filenames may be in any order and "
"they are processed in that order.\n"
"\nFlags:\n");
#define GAP " "
#define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D)
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
arg("--safe-mode",
"Disables preprocessing directives which use the filesystem "
"or invoke external processes. If used, it must be the first "
"argument.");
#endif
arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
GAP "Because arguments are processed in order, this should\n"
GAP "normally be given before -f.");
arg("-f|--file FILE","Process FILE (default=- (stdin)).\n"
GAP "All non-flag arguments are assumed to be the input files.");
arg("-e SCRIPT",
"Treat SCRIPT as a complete c-pp input and process it.\n"
GAP "Doing anything marginally useful with this requires\n"
GAP "using it several times, once per directive. It will not\n"
GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for "
CMPP_DEFAULT_DELIM "expr, "
CMPP_DEFAULT_DELIM "assert, and "
CMPP_DEFAULT_DELIM "define.");
arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
arg("-UXYZ","Undefine all defines matching glob XYZ.");
arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
arg("-LXYZ","Add dir XYZ to the loadable module search path.");
arg("-FXYZ=filename",
"Define XYZ to the raw contents of the given file.\n"
GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n"
GAP "Maybe it should be. Or maybe we need a new flag for that.");
arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE "
"(default=" CMPP_DEFAULT_DELIM ").");
arg("--@policy retain|elide|error|off",
"Specifies how to handle @tokens@ (default=off).\n"
GAP "off = do not look for @tokens@\n"
GAP "retain = parse @tokens@ and retain any undefined ones\n"
GAP "elide = parse @tokens@ and elide any undefined ones\n"
GAP "error = parse @tokens@ and error out for any undefined ones"
);
arg("-u|--undefined-policy NAME",
"Sets the policy for how to handle references to undefined key:\n"
GAP "null = treat them as empty/falsy. This is the default.\n"
GAP "error = trigger an error. This should probably be "
"the default."
);
arg("-@", "Equivalent to --@policy=error.");
arg("-no-@", "Equivalent to --@policy=off (the default).");
arg("--sql-trace", "Send a trace of all SQL to stderr.");
arg("--sql-trace-x",
"Like --sql-trace but expand all bound values in the SQL.");
arg("--no-sql-trace", "Disable SQL tracing (default).");
arg("--chomp-F", "One trailing newline is trimmed from files "
"read via -FXYZ=filename.");
arg("--no-chomp-F", "Disable --chomp-F (default).");
#undef arg
#undef GAP
fputs("\nFlags which require a value accept either "
"--flag=value or --flag value. "
"The exceptions are that the -D... and -F... flags "
"require their '=' to be part of the flag (because they "
"are parsed elsewhere).\n\n",fOut);
#endif /*CMPP_OMIT_FILE_IO*/
}
const cmpp_b cmpp_b_empty = cmpp_b_empty_m;
CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp,
cmpp_b * const os,
void const * src,
cmpp_size_t n){
if( !ppCode && cmpp_b_append(os, src, n) ){
cmpp_check_oom(pp, 0);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_b_append_str)(cmpp_b * b, char const * str,
cmpp_strlen_t n){
return cmpp_b_append(b, str, cmpp__strlen(str, n));
}
CMPP__EXPORT(int, cmpp_b_append4_str)(cmpp * pp, cmpp_b * b,
char const * str,
cmpp_strlen_t n){
return cmpp_b_append4(pp, b, str, cmpp__strlen(str, n));
}
CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp,
cmpp_b * const os,
cmpp_size_t n){
if( !ppCode && cmpp_b_reserve(os, n) ){
cmpp_check_oom(pp, 0);
}
return ppCode;
}
CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){
if( s->z ) cmpp_mfree(s->z);
*s = cmpp_b_empty;
}
CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){
if( s->z ){
#if 1
memset(s->z, 0, s->nAlloc)
/* valgrind pushes for this, which is curious because
cmpp_b_reserve[3]() memset()s new space to 0.
Try the following without this block using one commit after
[5f9c31d1da1d] (that'll be the commit that this comment and #if
block were added):
##define foo
##if not defined a
##/if
##query define {select ?1 a} bind [1]
There's a misuse complaint about a jump depending on
uninitialized memory deep under cmpp__is_int(), in strlen(), on
the "define" argument of the ##query. It does not appear if
the lines above it are removed, which indicates that it's at
least semi-genuine. gcc v13.3.0, if it matters.
*/;
#else
s->z[0] = 0;
#endif
s->n = 0;
}
s->errCode = 0;
return s;
}
CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){
if( l!=r ){
cmpp_b const x = *l;
*l = *r;
*r = x;
}
}
CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){
if( 0==s->errCode && s->nAlloc < n ){
void * const m = cmpp_mrealloc(s->z, s->nAlloc + n);
if( m ){
memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc))
/* valgrind convincingly recommends this. */;
s->z = m;
s->nAlloc += n;
}else{
s->errCode = CMPP_RC_OOM;
}
}
return s->errCode;
}
CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src,
cmpp_size_t n){
if(0==os->errCode){
cmpp_size_t const nNeeded = os->n + n + 1;
if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){
assert( CMPP_RC_OOM==os->errCode );
return os->errCode;
}
if( n ){
memcpy(os->z + os->n, src, n);
os->n += n;
}
os->z[os->n] = 0;
if( 0 ) {
g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n,
(char const*) src, (int)os->n);
}
}
return os->errCode;
}
CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){
if( 0==os->errCode
&& (os->n+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->na ){
cmpp_b * const b = li->list[--li->na];
if(b){
cmpp_b_clear(b);
cmpp_mfree(b);
}
}
cmpp_mfree(li->list);
*li = cmpp_b_list_empty;
}
CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){
while( li->n ){
cmpp_b * const b = li->list[--li->n];
if(b) cmpp_b_reuse(b);
}
}
static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){
cmpp_b * p = 0;
assert( li->list ? li->na : 0==li->na );
if( !cmpp_b_list_reserve(NULL, li,
cmpp__li_reserve1_size(li, 20)) ){
p = li->list[li->n];
if( p ){
cmpp_b_reuse(p);
}else{
p = cmpp_malloc(sizeof(*p));
if( p ){
li->list[li->n++] = p;
*p = cmpp_b_empty;
}
}
}
return p;
}
/**
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
largest first and empty slots last.
*/
static int cmpp_b__cmp_desc(const void *p1, const void *p2){
cmpp_b const * const eL = *(cmpp_b const **)p1;
cmpp_b const * const eR = *(cmpp_b const **)p2;
if( eL==eR ) return 0;
else if( !eL ) return 1;
else if (!eR ) return -1;
return (int)(/*largest first*/eL->nAlloc - eR->nAlloc);
}
/**
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
smallest first and empty slots last.
*/
static int cmpp_b__cmp_asc(const void *p1, const void *p2){
cmpp_b const * const eL = *(cmpp_b const **)p1;
cmpp_b const * const eR = *(cmpp_b const **)p2;
if( eL==eR ) return 0;
else if( !eL ) return 1;
else if (!eR ) return -1;
return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc);
}
/**
Sort li's buffer list using the given policy. NULL entries always
sort last. This is a no-op of how == cmpp_b_list_UNSORTED or
li->n<2.
*/
static void cmpp_b_list__sort(cmpp_b_list * const li,
enum cmpp_b_list_e how){
switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){
case cmpp_b_list_UNSORTED:
break;
case cmpp_b_list_DESC:
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc);
break;
case cmpp_b_list_ASC:
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc);
break;
}
}
CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){
cmpp__pi(pp);
#define re pi->recycler.buf
cmpp_b_list * const li = &re.list;
cmpp_b * b = 0;
if( cmpp_b_list_UNSORTED==re.sort ){
re.sort = cmpp_b_list_DESC;
cmpp_b_list__sort(li, re.sort);
assert( cmpp_b_list_UNSORTED!=re.sort
|| re.list.n<2 );
}
for( cmpp_size_t i = 0; i < li->n; ++i ){
b = li->list[i];
if( b ){
assert( b->nAlloc <= re.currentCapacity );
re.currentCapacity -= b->nAlloc;
li->list[i] = 0;
assert( !b->n &&
"Someone wrote to a buffer after giving it back" );
if( i < li->n-1 ){
re.sort = cmpp_b_list_UNSORTED;
}
return cmpp_b_reuse(b);
}
}
/**
Allocate the list entry now and then remove the buffer from it to
"borrow" it. We allocate now, instead of in cmpp_b_return(), so
that that function has no OOM condition (handling it properly in
higher-level code would be a mess).
*/
b = cmpp_b_list_push(li);
if( 0==cmpp_check_oom(pp, b) ) {
assert( b==li->list[li->n-1] );
li->list[li->n-1] = 0;
}
#undef re
return b;
}
CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){
if( !b ) return;
cmpp__pi(pp);
#define re pi->recycler.buf
if( !re.limitCount || (re.list.n+1>re.limitCount) ){
goto free_it;
}
if( (re.limitCapacity
&& (b->nAlloc + re.currentCapacity > re.limitCapacity))
|| (re.limitSingleCapacity
&& b->nAlloc>re.limitSingleCapacity) ){
/* keep b but not its memory. */
cmpp_b_clear(b);
}
cmpp_b_list * const li = &re.list;
/* TODO (2026-09-05): we should go from the back of the list, as
these are always returned in reverse order of borrow, sometimes
out of order by 2-3 for a batch of buffers. */
cmpp_size_t i = li->n;
while( i ){
--i;
if( !li->list[i] ){
re.currentCapacity += b->nAlloc;
li->list[i] = cmpp_b_reuse(b);
if( i+1 < li->n ) re.sort = cmpp_b_list_UNSORTED;
return;
}
}
#undef re
assert( !"This shouldn't be possible - no available "
"slot in buffer recycler. "
"This suggests that cmpp_b_return() was used without a "
"corresponding cmpp_b_borrow() or that a given buffer "
"was returned multiple times." );
free_it:
cmpp_b_clear(b);
cmpp_mfree(b);
}
void cmpp_b_library_dtor(cmpp_b_library *li){
static const cmpp_b_library empty = cmpp_b_library_empty_m;
cmpp_b_list_cleanup(&li->list);
cmpp_b_library const old = *li;
*li = empty;
li->limitCount = old.limitCount;
li->limitSingleCapacity = old.limitSingleCapacity;
li->limitCapacity = old.limitCapacity;
}
CMPP__EXPORT(int, cmpp_output_f_b)(
void * state, void const * src, cmpp_size_t n
){
if( state ){
return cmpp_b_append(state, src, n);
}
return 0;
}
#if CMPP__OBUF
int cmpp__obuf_flush(cmpp__obuf * b);
int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n);
void cmpp__obuf_cleanup(cmpp__obuf * b);
int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n);
int cmpp_flush_f_obuf(void * state);
void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o);
const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m;
const cmpp_outputer cmpp_outputer_obuf = {
.out = cmpp_output_f_obuf,
.flush = cmpp_flush_f_obuf,
.cleanup = cmpp_outputer_cleanup_f_obuf
};
#endif
/* base64 pieces copied from sqlite3.org/src/file/ext/misc/base64.c on
2026-07-19. */
#define PC 0x80 /* pad character */
#define WS 0x81 /* whitespace */
#define ND 0x82 /* Not above or digit-value */
#define PAD_CHAR '='
static const char b64Numerals[64+1]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#define BX_NUMERAL(dv) (b64Numerals[(uint8_t)(dv)])
/* Width of base64 lines. Should be an integer multiple of 4. */
#define B64_DARK_MAX 72
/* Encode a byte buffer into base64 text with linefeeds appended to limit
** encoded group lengths to B64_DARK_MAX or to terminate the last group.
*/
static char* toBase64( uint8_t const *pIn, cmpp_size_t nbIn, char *pOut ){
int nCol = 0;
while( nbIn >= 3 ){
/* Do the bit-shuffle, exploiting unsigned input to avoid masking. */
pOut[0] = BX_NUMERAL(pIn[0]>>2);
pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f);
pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6));
pOut[3] = BX_NUMERAL(pIn[2]&0x3f);
pOut += 4;
nbIn -= 3;
pIn += 3;
if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){
*pOut++ = '\n';
nCol = 0;
}
}
if( nbIn > 0 ){
signed char nco = nbIn+1;
cmpp_strlen_t nbe;
unsigned long qv = *pIn++;
for( nbe=1; nbe<3; ++nbe ){
qv <<= 8;
if( (cmpp_size_t)nbe<nbIn ) qv |= *pIn++;
}
for( nbe=3; nbe>=0; --nbe ){
char ce = (nbe<nco)? BX_NUMERAL((uint8_t)(qv & 0x3f)) : PAD_CHAR;
qv >>= 6;
pOut[nbe] = ce;
}
pOut += 4;
*pOut++ = '\n';
}
*pOut = 0;
return pOut;
}
#if 0 /* from-base-64. We don't yet have a #base64 -d[ecode] */
/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */
static const uint8_t b64DigitValues[128] = {
/* HT LF VT FF CR */
ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
/* US */
ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND,
/*sp + / */
WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63,
/* 0 1 5 9 = */
52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND,
/* A O */
ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
/* P Z */
15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND,
/* a o */
ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
/* p z */
41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND
};
#define BX_DV_PROTO(c) \
((((uint8_t)(c))<0x80)? (uint8_t)(b64DigitValues[(uint8_t)(c)]) : 0x80)
#define IS_BX_DIGIT(bdp) (((uint8_t)(bdp))<0x80)
#define IS_BX_WS(bdp) ((bdp)==WS)
#define IS_BX_PAD(bdp) ((bdp)==PC)
/* Skip over text which is not base64 numeral(s). */
static char * skipNonB64( char *s, int nc ){
char c;
while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
return s;
}
/* Decode base64 text into a byte buffer. */
static uint8_t* fromBase64( char *pIn, int ncIn, uint8_t *pOut ){
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
while( ncIn>0 && *pIn!=PAD_CHAR ){
static signed char nboi[] = { 0, 0, 1, 2, 3 };
char *pUse = skipNonB64(pIn, ncIn);
unsigned long qv = 0L;
int nti, nbo, nac;
ncIn -= (pUse - pIn);
pIn = pUse;
nti = (ncIn>4)? 4 : ncIn;
ncIn -= nti;
nbo = nboi[nti];
if( nbo==0 ) break;
for( nac=0; nac<4; ++nac ){
char c = (nac<nti)? *pIn++ : b64Numerals[0];
uint8_t bdp = BX_DV_PROTO(c);
switch( bdp ){
case ND:
/* Treat dark non-digits as pad, but they terminate decode too. */
ncIn = 0;
deliberate_fall_through; /* FALLTHRU */
case WS:
/* Treat whitespace as pad and terminate this group.*/
nti = nac;
deliberate_fall_through; /* FALLTHRU */
case PC:
bdp = 0;
--nbo;
deliberate_fall_through; /* FALLTHRU */
default: /* bdp is the digit value. */
qv = qv<<6 | bdp;
break;
}
}
switch( nbo ){
case 3:
pOut[2] = (qv) & 0xff;
deliberate_fall_through; /* FALLTHRU */
case 2:
pOut[1] = (qv>>8) & 0xff;
deliberate_fall_through; /* FALLTHRU */
case 1:
pOut[0] = (qv>>16) & 0xff;
break;
}
pOut += nbo;
}
return pOut;
}
#endif /* base64 decode */
/**
Base64-encodes the contents of bIn and replaces bIn's contents
(if any) with these.
Potential TODO is have it append to bOut, but it's simpler to
replace it and that's all our use cases currently need.
*/
int cmpp__b_base64_encode(cmpp *pp, cmpp_b const * bIn, cmpp_b * bOut){
cmpp_size_t nc;
cmpp_size_t const nv = bIn->n;
char *cBuf;
cmpp_size_t const nvMax = 1000 * 1000 * 1000
/* 1B == default SQLITE_MAX_LENGTH, but that symbol is not exposed
in its public API. We "could" extract that limit from pp's db
here, but... no. We're only using it as a guideline: bOut is
not not part of the db state so we're not beholden to the db's
limits. In this context the limit is arbitrary, anyway - we
inherit it from this code's origins and retain it for
simplicity. */;
if( ppCode ) return ppCode;
nc = 4*((nv+2)/3); /* quads needed */
nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */
if( nvMax < nc ){
return cmpp_errf(pp, CMPP_RC_RANGE,
"Blob expanded to base64 is too big.");
}
cmpp_b_reuse(bOut);
if( !bIn->z || !bIn->n ){
/* empty is okay */
return 0;
}
if( 0==cmpp_b_reserve3(pp, bOut, nc) ){
cBuf = (char*)bOut->z;
bOut->n = (cmpp_size_t)(toBase64(bIn->z, bIn->n, cBuf) - cBuf);
assert( bOut->n < bOut->nAlloc );
cBuf[bOut->n] = 0;
}
return ppCode;
}
static int cmpp__hexbyte( int ch ){
if(ch>='0' && ch<='9') return ch - '0';
else if(ch>='a' && ch<='f') return ch - 'a' + 10;
else if(ch>='A' && ch<='F') return ch - 'A' + 10;
else return -1;
}
int cmpp__utf8_char_to_cstr(unsigned int c, unsigned char *output,
cmpp_size_t length){
/*
Stolen from the internet many years ago, adapted from several
variations which all _seem_ to have derived from librdf.
*/
cmpp_size_t size=0;
/* check for illegal code positions:
* U+D800 to U+DFFF (UTF-16 surrogates)
* U+FFFE and U+FFFF
*/
if((c > 0xD7FF && c < 0xE000)
|| c == 0xFFFE || c == 0xFFFF) return -1;
/* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */
if(c > 0x10ffff) return -1;
if(c < 0x00000080) size = 1;
else if(c < 0x00000800) size = 2;
else if(c < 0x00010000) size = 3;
else size = 4;
if(!output) return (int)size;
else if(size > length) return -1;
else switch(size) {
case 0:
assert(!"can't happen anymore");
output[0] = 0;
return 0;
case 4:
output[3] = 0x80 | (c & 0x3F);
c = c >> 6;
c |= 0x10000;
CMPP_SWITCH_FALL_THROUGH;
case 3:
output[2] = 0x80 | (c & 0x3F);
c = c >> 6;
c |= 0x800;
CMPP_SWITCH_FALL_THROUGH;
case 2:
output[1] = 0x80 | (c & 0x3F);
c = c >> 6;
c |= 0xc0;
CMPP_SWITCH_FALL_THROUGH;
case 1:
output[0] = (unsigned char)c;
CMPP_SWITCH_FALL_THROUGH;
default:
return (int)size;
}
}
int cmpp__read_hex_bytes(unsigned char const *zPos,
unsigned int howMany,
unsigned int * rv ){
unsigned int rc = 0;
int ch1, ch2;
unsigned int n = 0;
assert(!(howMany%2) && "howMany must be an even number!");
assert(zPos && rv);
while(n<howMany){
ch1 = *zPos ? cmpp__hexbyte(*zPos++) : -1;
ch2 = (ch1>=0) ? cmpp__hexbyte(*zPos++) : -1;
if(ch2<0) return -1;
rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2);
n += 2;
}
*rv = rc;
return (int)n;
}
CMPP__EXPORT(void,cmpp_b_unescape)(cmpp_b * b){
unsigned char const * z = b->z;
unsigned char * oz = b->z;
unsigned char const * const zEnd = z ? z + b->n : 0;
unsigned char const *zEsc = 0;
while( z < zEnd ){
switch( *z ){
case '\\':
if( zEsc ){
*oz++ = '\\';
zEsc = 0;
}else{
zEsc = z;
}
++z;
continue;
case 'b': case 'n': case 'r': case 't':
case '"': case '`': case '\'':
if( zEsc ){
switch( *z ){
case 'b': *oz++ = '\b'; break;
case 'n': *oz++ = '\n'; break;
case 'r': *oz++ = '\r'; break;
case 't': *oz++ = '\t'; break;
/* No \f or \v. Nobody uses those. And no \0 because
it's inappropriate in this context. */
case '"': case '`': case '\'': *oz++ = *z; break;
case 'u': case 'U':{
/* \uXXXX and \UXXXXXXXX */
unsigned int uChar = 0;
unsigned const ulen = 'u'==*z ? (unsigned)4 : (unsigned)8;
unsigned char const uCount = z+ulen<zEnd
? cmpp__read_hex_bytes(z+1, ulen, &uChar)
: 0;
zEsc = 0;
if( uCount != ulen ){
no_such_u:
/* We don't have a way of reporting an error here
beyond b->errCode BUT the tokenizer we feed this
from is supposed to have validated these already.
We'll just emit invalid ones as-is. */
*oz++ = *zEsc;
*oz++ = *z++;
continue;
}
assert(0 == (uCount % 2));
int const check = cmpp__utf8_char_to_cstr(uChar, oz,
uCount);
if(check<0){
goto no_such_u;
}
oz += check;
z += uCount;
continue;
}
}
++z;
break;
}
CMPP_SWITCH_FALL_THROUGH;
default:
if( zEsc ) *oz++ = '\\' /* put it back */;
*oz++ = *z++;
}
zEsc = 0;
}
if( oz ){
assert( oz<=zEnd );
if( zEsc ){
assert( oz<zEnd );
/* Put trailing backslash back. */
*oz++ = '\\';
}
*oz = 0;
b->n = (cmpp_size_t)(oz - b->z);
}
}
CMPP__EXPORT(int,cmpp_b_cmp)(cmpp_b const * b, void const *p,
cmpp_ssize32_t n){
unsigned char const * z = p;
cmpp_size_t const nn = cmpp__strlenu(z, n);
int const rc = memcmp(z, b->z, nn<b->n ? nn : b->n);
return (rc || b->n==nn)
? rc
: (b->n<nn ? -1 : 1/* prefix match: shortest first */);
}
CMPP__EXPORT(int,cmpp_b_append_json_value)(cmpp *pp, cmpp_b *b,
unsigned char const *z,
cmpp_strlen_t n){
int64_t j = 0;
int rc;
n = cmpp__strlenu(z, n);
if( cmpp_is_int64(z, n, &j) ){
rc = cmpp_b_append_i64(b, j);
if( rc ) cmpp_err(pp, rc, 0, 0);
}else if( cmpp_json_valid(pp, z, n, 3) ){
rc = cmpp_b_append4(pp, b, z, n);
}else {
sqlite3_str * const s = cmpp__sqlite3_str_new(pp);
if( s ){
sqlite3_str_appendf(s, "%J", z);
int nn = 0;
char * const zz = cmpp_sqlite3_str_finish(pp, s, &nn);
if( zz ){
rc = cmpp_b_append4(pp, b, zz, n);
}else{
rc = cmpp_err_has(pp);
assert( rc );
}
cmpp_mfree(zz);
}else{
rc = cmpp_err_has(pp);
assert( rc );
}
}
return rc;
}
#undef PC
#undef WS
#undef ND
#undef PAD_CHAR
#undef UB_TYPEDEF
#undef BX_DV_PROTO
#undef IS_BX_DIGIT
#undef IS_BX_WS
#undef IS_BX_PAD
#undef BX_NUMERAL
#undef B64_DARK_MAX
static int cmpp__stmt_prepare(cmpp *pp, bool honorError,
sqlite3_stmt **pStmt,
const char * zSql, cmpp_strlen_t nSql){
if( honorError && ppCode ) return ppCode;
nSql = cmpp__strlen(zSql, nSql);
int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, zSql, nSql, pStmt, 0);
if( 0==rc && !*pStmt ){
cmpp_err_c(pp, CMPP_RC_MISUSE,
"Empty queries are not permitted here.");
}else if( cmpp_db_rc_v2(pp, rc, zSql, nSql) ){
g_warn("SQLite rc=%d Offending SQL: %.*s", rc, nSql, zSql);
}
return ppCode;
}
static int cmpp__stmt_preparev(cmpp *pp, bool honorError,
sqlite3_stmt **pStmt,
const char * zSql, va_list va){
if( honorError && ppCode ) return ppCode;
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
char * z = 0;
int n = 0;
assert( pp->pimpl->db.dbh );
sqlite3_str_vappendf(str, zSql, va);
z = cmpp_sqlite3_str_finish(pp, str, &n);
if( z ){
(void)cmpp__stmt_prepare(pp, honorError, pStmt, z, n);
sqlite3_free(z);
}
return ppCode;
}
CMPP__EXPORT(int,cmpp_stmt_prepare)(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, cmpp_strlen_t nSql){
return cmpp__stmt_prepare(pp, true, pStmt, zSql, nSql);
}
CMPP__EXPORT(int,cmpp_stmt_preparev)(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, va_list va){
return cmpp__stmt_preparev(pp, true, pStmt, zSql, va);
}
CMPP__EXPORT(int,cmpp_stmt_preparef)(cmpp *pp, sqlite3_stmt **pStmt,
const char * zSql, ...){
if( ppCode ) return ppCode;
int rc;
va_list va;
va_start(va, zSql);
rc = cmpp__stmt_preparev(pp, true, pStmt, zSql, va);
va_end(va);
return rc;
}
sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
bool prepEvenIfErr){
if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL;
sqlite3_stmt ** q = 0;
char const * zSql = 0;
switch(which){
default:
cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which);
return NULL /* not reached */;
#define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break;
CmppStmt_map(E)
#undef E
}
assert( q );
assert( zSql && *zSql );
if( !*q && (!ppCode || prepEvenIfErr) ){
cmpp__stmt_prepare(pp, false, q, zSql, -1);
}
return *q;
}
CMPP__EXPORT(int,cmpp_stmt_reset)(cmpp *pp, sqlite3_stmt * const q){
int rc = 0;
if( q ){
sqlite3_clear_bindings(q);
rc = sqlite3_reset(q);
switch( rc ){
case 0:
case SQLITE_DONE:
case SQLITE_ROW:
rc = 0;
break;
default:
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
}
}
return rc;
}
static inline int cmpp__stmt_is_sp(cmpp const * const pp,
sqlite3_stmt const * const q){
return q==pp->pimpl->stmt.spBegin
|| q==pp->pimpl->stmt.spRelease
|| q==pp->pimpl->stmt.spRollback;
}
CMPP__EXPORT(int,cmpp_stmt_step)(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){
int rc = SQLITE_ERROR;
assert( q );
if( !ppCode || cmpp__stmt_is_sp(pp,q) ){
rc = sqlite3_step(q);
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
}
if( resetIt /* even if ppCode!=0 */ ) cmpp_stmt_reset(pp, q);
assert( 0!=rc );
return rc;
}
CMPP__EXPORT(int, cmpp_stmt_bind_int)(cmpp *pp, sqlite3_stmt *pStmt,
int col, int64_t val){
return ppCode
? ppCode
: cmpp_db_rc(pp, sqlite3_bind_int64(pStmt, col, val),
"from cmpp_stmt_bind_int()");
}
CMPP__EXPORT(int, cmpp_stmt_bind_int_text)(cmpp *pp, sqlite3_stmt *pStmt,
int col, int64_t val){
unsigned char buf[32];
snprintf((char *)buf, sizeof(buf), "%" PRIi64, val);
return cmpp_stmt_bind_textn(pp, pStmt, col, buf, -1);
}
CMPP__EXPORT(int, cmpp_stmt_bind_null)(cmpp *pp, sqlite3_stmt *pStmt,
int col){
return ppCode
? ppCode
: cmpp_db_rc(pp, sqlite3_bind_null(pStmt, col),
"from cmpp_stmt_bind_null()");
}
CMPP__EXPORT(int, cmpp_stmt_bind_textx)(cmpp *pp, sqlite3_stmt *pStmt,
int col, unsigned const char * zStr,
cmpp_ssize32_t n, void (*dtor)(void *)){
if( 0==ppCode ){
cmpp_db_rc_v2(
pp, (zStr && n)
? sqlite3_bind_text(pStmt, col,
(char const *)zStr,
(int)n, dtor)
: sqlite3_bind_null(pStmt, col),
sqlite3_sql(pStmt),
-1
);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_stmt_bind_textn)(cmpp *pp, sqlite3_stmt *pStmt,
int col,
unsigned const char * zStr,
cmpp_ssize32_t n){
return cmpp_stmt_bind_textx(pp, pStmt, col, zStr, (int)n,
SQLITE_TRANSIENT);
}
CMPP__EXPORT(int, cmpp_stmt_bind_text)(cmpp *pp, sqlite3_stmt *pStmt,
int col,
unsigned const char * zStr){
return cmpp_stmt_bind_textn(pp, pStmt, col, zStr, -1);
}
#if 0
int cmpp_stmt_bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
const char * zFmt, ...){
if( 0==p->err.code ){
int rc;
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
int n = 0;
char * z;
va_list va;
va_start(va,zFmt);
sqlite3_str_vappendf(str, zFmt, va);
va_end(va);
z = cmpp_str_finish(str, &n);
cmpp_db_rc(
pp, z
? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free)
: sqlite3_bind_null(pStmt, col),
sqlite3_sql(pStmt)
);
cmpp_mfree(z);
}
return p->err.code;
}
#endif
/**
Expects an SQLITE_... result code and returns an approximate match
from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE
as non-errors, returning 0 for those.
*/
static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){
(void)db;
int rc = 0;
switch(sqliteCode & 0xff){
case SQLITE_ROW:
case SQLITE_DONE:
case SQLITE_OK: rc = 0; break;
case SQLITE_NOMEM: rc = CMPP_RC_OOM; break;
case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break;
case SQLITE_TOOBIG:
case SQLITE_FULL:
case SQLITE_RANGE: rc = CMPP_RC_RANGE; break;
case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break;
case SQLITE_PERM:
case SQLITE_AUTH:
case SQLITE_BUSY:
case SQLITE_LOCKED:
case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break;
case SQLITE_CANTOPEN:
case SQLITE_IOERR: rc = CMPP_RC_IO; break;
case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break;
default:
//MARKER(("sqlite3_errcode()=0x%04x\n", rc));
rc = CMPP_RC_DB; break;
}
return rc;
}
CMPP__EXPORT(int,cmpp_db_rc_v2)(cmpp *pp, int dbRc, char const *zMsg,
cmpp_strlen_t nMsg){
switch(dbRc){
case 0:
case SQLITE_DONE:
case SQLITE_ROW:
return 0;
case SQLITE_NOMEM:
return CMPP_RC_OOM;
default:
if( zMsg && (nMsg = cmpp__strlen(zMsg, nMsg)) ){
return cmpp_errf(
pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
"SQLite error #%d: %s: %.*s",
dbRc,
pp->pimpl->db.dbh
? sqlite3_errmsg(pp->pimpl->db.dbh)
: "<no db handle>",
nMsg, zMsg
);
}else{
return cmpp_errf(
pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
"SQLite error #%d: %s",
dbRc, pp->pimpl->db.dbh
? sqlite3_errmsg(pp->pimpl->db.dbh)
: "<no db handle>"
);
}
}
}
/**
The base "define" impl. Requires q to be an INSERT for one of the
define tables and have the (t,k,v) columns set up to bind to ?1,
?2, and ?3. nVal must be the number of bytes of zVal to bind. If
zVal is NULL then (zVal="1", nVal=1) is assumed, else if nVal is 0
then zVal is bound as SQL NULL. That's wonky, but it's what we
currently have.
*/
static
int cmpp__define_impl(cmpp * const pp,
sqlite3_stmt * const q,
unsigned char const * zKey,
cmpp_strlen_t nKey,
unsigned char const *zVal,
cmpp_strlen_t nVal,
int tType,
bool resetStmt){
if( 0==ppCode){
assert( q );
nKey = cmpp__strlenu(zKey, nKey);
nVal = cmpp__strlenu(zVal, nVal);
if( 0==cmpp_stmt_bind_textn(pp, q, 2, zKey, (int)nKey)
&& 0==cmpp_stmt_bind_int(pp, q, 1, tType) ){
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
/* TODO? if tType==cmpp_TT_Blob, bind it as a blob */
if( zVal ){
if( nVal ){
cmpp_stmt_bind_textn(pp, q, 3, zVal, (int)nVal);
}else{
/* Arguable */
cmpp_stmt_bind_null(pp, q, 3);
}
}else{
cmpp_stmt_bind_int(pp, q, 3, 1);
}
cmpp_stmt_step(pp, q, resetStmt);
g_debug(pp,2,("define: %s [%s]=[%.*s]\n",
cmpp_tt_cstr(tType), zKey, (int)nVal, zVal));
}
}
return ppCode;
}
int cmpp__define2(cmpp *pp,
unsigned char const * zKey,
cmpp_strlen_t nKey,
unsigned char const *zVal,
cmpp_strlen_t nVal,
cmpp_tt_e tType){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
if( q ){
cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true);
}
return ppCode;
}
/**
The legacy variant of define() which accepts X=Y in zKey. This
continues to exist because it's convenient for passing args from
main().
*/
static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal,
cmpp_tt_e ttype ){
if(ppCode) return ppCode;
CmppKvp kvp = CmppKvp_empty;
if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1,
zVal
? CmppKvp_op_none
: CmppKvp_op_eq1) ) {
return ppCode;
}
if( kvp.v.z ){
if( zVal ){
assert(!"cannot happen - CmppKvp_op_none will prevent it");
return cmpp_errf(pp, CMPP_RC_MISUSE,
"Cannot assign two values to [%.*s] [%.*s] [%s]",
kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal);
}
}else{
kvp.v.z = (unsigned char const *)zVal;
kvp.v.n = zVal ? (int)strlen(zVal) : 0;
}
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
if( !q ) return ppCode;
int64_t intCheck = 0;
switch( ttype ){
case cmpp_tt_None:
if(kvp.v.n){
if( cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
ttype = cmpp_tt_IntDec;
if( '+'==*kvp.v.z ){
++kvp.v.z;
--kvp.v.n;
}
}else{
ttype = cmpp_tt_DbString;
}
}else if( kvp.v.z ){
ttype = cmpp_tt_DbString;
}else{
ttype = cmpp_tt_IntDec;
intCheck = 1 /* No value ==> value of 1. */;
}
break;
case cmpp_tt_IntDec:
if( !cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
ttype = cmpp_tt_DbString;
}
break;
default:
break;
}
if( 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, kvp.k.n)
&& 0==cmpp_stmt_bind_int(pp, q, 1, ttype) ){
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
switch( ttype ){
case cmpp_tt_IntDec:
cmpp_stmt_bind_int(pp, q, 3, intCheck);
break;
case cmpp_tt_Null:
cmpp_stmt_bind_null(pp, q, 3);
break;
default:
cmpp_stmt_bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n);
break;
}
cmpp_stmt_step(pp, q, true);
g_debug(pp,2,("define: [%.*s]=[%.*s]\n",
kvp.k.n, kvp.k.z,
kvp.v.n, kvp.v.z));
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){
return cmpp__define_legacy(pp, zKey, zVal, cmpp_tt_None);
}
CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){
return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1,
cmpp_tt_DbString);
}
static
int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey,
cmpp_strlen_t nKey,
unsigned char const *zVal,
cmpp_strlen_t nVal,
int ttype,
int64_t * pId){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false);
if( q ){
if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false)
&& pId ){
*pId = sqlite3_column_int64(q, 0);
assert( *pId );
}
cmpp_stmt_reset(pp, q);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey,
char const *zVal, int64_t *pId){
assert( pId );
return cmpp__define_shadow(pp, ustr_c(zKey), -1,
ustr_c(zVal), -1, cmpp_tt_DbString, pId);
}
static
int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey,
cmpp_strlen_t nKey, int64_t id){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false);
if( q ){
cmpp_stmt_bind_textn(pp, q, 1, zKey, (int)nKey);
cmpp_stmt_bind_int(pp, q, 2, id);
cmpp_stmt_step(pp, q, true);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){
return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id);
}
/*
** This sqlite3_trace_v2() callback outputs tracing info using
** ((cmpp*)c)->sqlTrace.pFile.
*/
static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){
switch(dx){
case SQLITE_TRACE_STMT:{
char const * const zSql = x;
cmpp * const pp = c;
cmpp__pi(pp);
if(pi->sqlTrace.out.out){
char * const zExp = pi->sqlTrace.expandSql
? sqlite3_expanded_sql((sqlite3_stmt*)p)
: 0;
sqlite3_str * const s = sqlite3_str_new(pi->db.dbh);
if( pi->dx ){
cmpp__dx_append_script_info(pi->dx, s);
sqlite3_str_appendchar(s, 1, ':');
sqlite3_str_appendchar(s, 1, ' ');
}
sqlite3_str_appendall(s, zExp ? zExp : zSql);
sqlite3_str_appendchar(s, 1, '\n');
int const n = sqlite3_str_length(s);
if( n ){
char * const z = sqlite3_str_finish(s);
if( z ){
cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n);
sqlite3_free(z);
}
}
sqlite3_free(zExp);
}
break;
}
}
return 0;
}
#include <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));
}
}
/*
** sqlite3 UDF which returns the size of a file, or NULL if
** stat(2) fails.
*/
static void cmpp__udf_file_size(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zName;
(void)(argc);
zName = (const char*)sqlite3_value_text(argv[0]);
if( 0!=zName && 0!=zName[0] ){
struct stat sb;
if( 0==stat(zName, &sb) ){
sqlite3_result_int64(context, (int64_t)sb.st_size);
}
}
}
static void cmpp__udf_truthy(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
(void)(argc);
assert(1==argc);
int buul = 0;
sqlite3_value * const sv = argv[0];
switch( sqlite3_value_type(sv) ){
case SQLITE_NULL:
break;
case SQLITE_FLOAT:
buul = 0.0!=sqlite3_value_double(sv);
break;
case SQLITE_INTEGER:
buul = 0!=sqlite3_value_int(sv);
break;
case SQLITE_TEXT:
case SQLITE_BLOB:{
int const n = sqlite3_value_bytes(sv);
if( n>1 ) buul = 1;
else if( 1==n ){
const char *z =
(const char*)sqlite3_value_text(sv);
buul = z
? 0!=strcmp(z,"0")
: 0;
}
}
}
sqlite3_result_int(context, buul);
}
/**
SQLite3 UDF which compares its two arguments using memcmp()
semantics. NULL will compare equal to NULL, but less than anything
else.
*/
static void cmpp__udf_compare(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
(void)(argc); /* Unused parameter */
assert(2==argc);
sqlite3_value * const v1 = argv[0];
sqlite3_value * const v2 = argv[1];
unsigned char const * const z1 = sqlite3_value_text(v1);
unsigned char const * const z2 = sqlite3_value_text(v2);
int const n1 = sqlite3_value_bytes(v1);
int const n2 = sqlite3_value_bytes(v2);
int rv;
if( !z1 ){
rv = z2 ? -1 : 0;
}else if( !z2 ){
rv = 1;
}else{
rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2);
}
if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv);
sqlite3_result_int(context, rv);
}
int cmpp__db_init(cmpp *pp){
cmpp__pi(pp);
if( pi->db.dbh || ppCode ) return ppCode;
int rc;
char * zErr = 0;
const char * zDrops =
"BEGIN EXCLUSIVE;"
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;"
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;"
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;"
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;"
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;"
"DROP VIEW IF EXISTS " CMPP__DB_MAIN_NAME ".vdef;"
"COMMIT;"
;
const char * zSchema =
"BEGIN EXCLUSIVE;"
"CREATE TABLE " CMPP__DB_MAIN_NAME ".def("
/* ^^^ defines */
"t INTEGER DEFAULT NULL,"
/*^^ type: cmpp_tt or NULL */
"k TEXT PRIMARY KEY NOT NULL,"
"v TEXT DEFAULT NULL"
") WITHOUT ROWID;"
"CREATE TABLE " CMPP__DB_MAIN_NAME ".incl("
/* ^^^ files currently being included */
"file TEXT PRIMARY KEY NOT NULL,"
"srcFile TEXT DEFAULT NULL,"
"srcLine INTEGER DEFAULT 0"
") WITHOUT ROWID;"
"CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath("
/* ^^^ include path. We use (ORDER BY priority DESC, rowid) to
make their priority correct. priority should only be set by the
#include directive for its cwd entry. */
"priority INTEGER DEFAULT 0," /* higher sorts first */
"dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE"
");"
"CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath("
/* ^^^ module path. We use ORDER BY ROWID to make their
priority correct. */
"dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
");"
"CREATE TABLE " CMPP__DB_MAIN_NAME ".predef("
/* ^^^ pre-defines */
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
"k TEXT PRIMARY KEY NOT NULL,"
"v TEXT DEFAULT NULL"
") WITHOUT ROWID;"
"INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)"
" VALUES(NULL,'cmpp::version','" CMPP_VERSION "')"
";"
/**
sdefs - "scoped defines" or "shadow defines". The problem these
solve is the one of supporting a __FILE__ define in cmpp input
sources, such that it remains valid both before and after an
#include, but has a new name in the scope of an #include. We
can't use savepoints for that because they're a nuclear option
affecting _all_ #defines in the #include'd file, whereas we
normally want #defines to stick around across files.
See cmpp_define_shadow() and cmpp_define_unshadow().
*/
"CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
"k TEXT NOT NULL,"
"v TEXT DEFAULT NULL"
");"
/**
vdef is a view consolidating the various #define stores. It's
intended to be used for all general-purpose fetching of defines
and it orders the results such that the library's defines
supercede all others, then scoped keys, then client-level
defines.
To push a new sdef we simply insert into sdef. Then vdef will
order the newest sdef before any entry from the def table.
*/
"CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS"
" SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef"
/* ------^^^^ sorts before numbers */
" UNION ALL"
" SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef"
/* ^^^^ sorts newest of matching keys first */
" UNION ALL"
" SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def"
" ORDER BY 1, 3"
";"
#if 0
"CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype("
/* ^^^ token types */
"t INTEGER PRIMARY KEY NOT NULL,"
/*^^ type: cmpp_tt */
"n TEXT NOT NULL,"
/*^^ cmpp_tt_... name. */
"s TEXT DEFAULT NULL"
/* Symbolic or directive name, if any. */
");"
#endif
"COMMIT;"
"BEGIN EXCLUSIVE;"
;
cmpp__err_reuse(pp);
int openFlags = SQLITE_OPEN_READWRITE;
if( pi->db.zName ){
openFlags |= SQLITE_OPEN_CREATE;
}
rc = sqlite3_open_v2(
pi->db.zName ? pi->db.zName : ":memory:",
&pi->db.dbh, openFlags, 0);
if(rc){
cmpp_db_rc_v2(pp, rc, pi->db.zName
? pi->db.zName
: ":memory:", -1);
sqlite3_close(pi->db.dbh);
pi->db.dbh = 0;
assert(ppCode);
return rc;
}
sqlite3_busy_timeout(pi->db.dbh, 5000);
sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME,
CMPP__DB_MAIN_NAME);
rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT,
cmpp__db_sq3TraceV2, pp);
if( cmpp_db_rc(pp, rc, "Installing tracer failed") ){
goto end;
}
//g_warn("Schema:\n%s\n",zSchema);
struct {
/* SQL UDFs */
char const * const zName;
void (*xUdf)(sqlite3_context *,int,sqlite3_value **);
int arity;
int flags;
} aFunc[] = {
{
.zName = "cmpp_file_exists",
.xUdf = cmpp__udf_file_exists,
.arity = 1,
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
},
{
.zName = "cmpp_file_size",
.xUdf = cmpp__udf_file_size,
.arity = 1,
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
},
{
.zName = "cmpp_truthy",
.xUdf = cmpp__udf_truthy,
.arity = 1,
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
},
{
.zName = "cmpp_compare",
.xUdf = cmpp__udf_compare,
.arity = 2,
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
}
};
assert( 0==rc );
for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){
rc = sqlite3_create_function(
pi->db.dbh, aFunc[i].zName, aFunc[i].arity,
aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0
);
}
if( cmpp_db_rc(pp, rc, "UDF registration failed.") ){
return ppCode;
}
if( pi->db.zName ){
/* Drop all cmpp tables when using a persistent db so that we are
not beholden to a given structure. TODO: a config flag to
toggle this. */
rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr);
}
if( !rc ){
rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr);
}
if( !rc ){
extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *);
rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL);
}
if(rc){
if( zErr ){
cmpp_errf(pp, cmpp__db_errcode(pi->db.dbh, rc),
"SQLite error #%d initializing DB: %s", rc, zErr);
sqlite3_free(zErr);
}else{
cmpp_errf(pp, cmpp__db_errcode(pi->db.dbh, rc),
"SQLite error #%d initializing DB", rc);
}
goto end;
}
#if 0
while(0){
/* Insert the ttype mappings. We don't yet make use of this but
only for lack of a use case ;). */
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false);
if( !q ) goto end;
#define E(N,STR) \
cmpp_stmt_bind_int(pp, q, 1, cmpp_tt_ ## N); \
cmpp_stmt_bind_textn(pp, q, 2, \
ustr_c("cmpp_tt_ " # N), sizeof("cmpp_tt_" # N)-1); \
if( STR ) cmpp_stmt_bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \
else cmpp_stmt_bind_null(pp, q, 3); \
if( SQLITE_DONE!=cmpp_stmt_step(pp, q, true) ) return ppCode;
cmpp_tt_map(E)
#undef E
sqlite3_finalize(q);
pi->stmt.insTtype = 0;
break;
}
#endif
end:
if( !ppCode ){
/*
** Keep us from getting in the situation later that delayed
** preparation if one of the savepoint statements fails (e.g. due
** to OOM or memory corruption). We need to be able to run these
** even in OOM cases (while admitting that they may fail to run in
** OOM cases).
*/
cmpp__stmt(pp, CmppStmt_spBegin, false);
cmpp__stmt(pp, CmppStmt_spRelease, false);
cmpp__stmt(pp, CmppStmt_spRollback, false);
cmpp__lazy_init(pp);
}
return ppCode;
}
static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){
return cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' may only be used once.",
arg->z);
}
/* No-op cmpp_f() impl. */
static void cmpp_f_noop(cmpp_f_args const * args){
(void)args;
}
/**
cmpp_kav_each_f() impl for use by #define {k->v}.
*/
static int cmpp_kav_each_f_define__group(
cmpp *pp,
unsigned char const *zKey, cmpp_size_t nKey,
unsigned char const *zVal, cmpp_size_t nVal,
void* callbackState
){
if( (callbackState==pp)
&& cmpp__has(pp, (char const*)zKey, nKey) ){
return ppCode;
}
return cmpp__define2(pp, zKey, nKey, zVal, nVal,
cmpp_tt_DbString);
}
/* #error impl. */
static void cmpp_f_error(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
const char *zBegin = (char const *)dx->args.z;
unsigned n = (unsigned)dx->args.nz;
if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){
++zBegin;
n -= 2;
}
if( n ){
cmpp_dx_errf(dx, CMPP_RC_ERROR, "%.*s", n, zBegin);
}else{
cmpp_dx_errf(dx, CMPP_RC_ERROR, "(no additional info)");
}
}
/* Impl. for #define. */
static void cmpp_f_define(cmpp_f_args const * args){
assert(args->argc>0 && "V4 arg semantics.");
assert(args->argv && "V4|!RAW arg semantics.");
assert( cmpp_arg_equals(args->argv[0],
(char const*)args->dx->d->name.z) );
cmpp_dx * const dx = args->dx;
if( args->argc<2 ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting one or more arguments");
return;
}
cmpp_arg const * aKey = 0;
int nChomp = 0;
unsigned nHeredoc = 0;
unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */;
bool ifNotDefined = false /* true if '?' arg */;
cmpp_arg const *aAppend = 0;
bool raw = false;
#define checkIsDefined(ARG) \
if(ifNotDefined && (cmpp__has(dx->pp, (char const*)ARG->z, ARG->n) \
|| dxppCode)) break
for( unsigned i = 1; 0==dxppCode && i < args->argc; ++i){
cmpp_arg const * arg = args->argv[i];
//g_warn("arg=%s", arg->z);
if( 1==i && cmpp_arg_equals_c(arg, "?") ){
/* Only set the key if it's not already defined. */
ifNotDefined = true;
continue;
}
switch( arg->ttype ){
case cmpp_tt_OpShiftL3:
++nChomp;
/* fall through */
case cmpp_tt_OpShiftL:
if( i<1 || i+1<args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Ill-placed '%s'.", arg->z);
}else if( arg->n >= sizeof(acHeredoc)-1 ){
cmpp_dx_errf(dx, CMPP_RC_RANGE,
"Heredoc name is too large.");
}else if( !aKey ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Missing key before %s.",
cmpp__tt_cstr2(cmpp_tt_OpShiftL, false));
}else{
assert( aKey );
nHeredoc = aKey->n;
memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/);
}
break;
case cmpp_tt_OpCmpEq:
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Ill-placed '%s'.", arg->z);
break;
case cmpp_tt_IntDec:
case cmpp_tt_StringBT:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:
case cmpp_tt_Word:
if( cmpp_arg_isflag_c(arg,"-chomp") ){
++nChomp;
break;
}
if( cmpp_arg_isflag_c(arg,"-raw") ){
raw = true;
break;
}
if( cmpp_arg_isflag_c(arg,"-append")
|| cmpp_arg_isflag_c(arg,"-a") ){
aAppend = args->argv[++i];
if( !aAppend ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting argument for %s",
arg->z);
}
arg = aAppend;
break;
}
handle_value:
if( aKey ){
/* This is the second arg - the value */
#if 1
if( i+1<args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Unexpected token after %s",
arg->z);
break;
}
#endif
checkIsDefined(aKey);
cmpp_b * const os = cmpp_b_borrow(dx->pp);
cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0;
while( os ){
if( ba ){
cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false);
if( dxppCode ) break;
if( 0 ){
g_warn("key=%s\n", aKey->z);
g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z);
}
}
if( cmpp_arg_interpolate(args->pp, arg, os,
cmpp_arg_interpolate_BRACE_CALL) ) break;
cmpp_b * const which = (ba && ba->n) ? ba : os;
if( which==ba && os->n ){
if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n);
cmpp_b_append4(args->pp, ba, os->z, os->n);
}
int n = nChomp;
while( n-- && cmpp_b_chomp(which) ){}
cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n,
arg->ttype);
if( 0 ){
g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z);
g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z);
}
break;
}
cmpp_b_return(dx->pp, os);
cmpp_b_return(dx->pp, ba);
aKey = 0;
}else if( cmpp_tt_Word!=arg->ttype ){
cmpp_dx_errf(dx, CMPP_RC_TYPE,
"Expecting a define-name token here.");
}else if( i+1<args->argc ){
aKey = arg;
}else{
/* No value = a value of 1. */
checkIsDefined(arg);
cmpp__define2(dx->pp, arg->z, arg->n,
ustr_c("1"), 1, cmpp_tt_IntDec);
}
break;
case cmpp_tt_GroupSquiggly:
assert( !acHeredoc[0] );
if( aKey ){
/* Treat this as a value */
goto handle_value;
}else if( (ifNotDefined ? i>2 : i>1) || i+1<args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"{...} must be the only argument.")
/* This is for simplicity's sake. */;
}else{
cmpp_kav_each(args->pp, arg->z, arg->n,
cmpp_kav_each_f_define__group,
ifNotDefined ? args->pp : NULL,
cmpp_kav_each_F_NOT_EMPTY
| cmpp_kav_each_F_CALL_VAL
| cmpp_kav_each_F_PARENS_EXPR
//TODO cmpp_kav_each_F_IF_UNDEF
);
}
aKey = 0;
break;
case cmpp_tt_GroupParen:{
if( !aKey ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"(...) is not permitted as a key.");
break;
}
checkIsDefined(aKey);
int d = 0;
if( 0==cmpp__arg_evalSubToInt(args->pp, arg, &d) ){
char exprBuf[32] = {0};
cmpp_size_t nVal =
(cmpp_size_t)snprintf(&exprBuf[0],
sizeof(exprBuf), "%d", d);
assert(nVal>0);
cmpp__define2(dx->pp, aKey->z, aKey->n,
ustr_c(&exprBuf[0]), nVal,
cmpp_tt_IntDec);
}
break;
}
case cmpp_tt_GroupBrace:{
if( !aKey ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"[...] is not permitted as a key.");
break;
}
checkIsDefined(aKey);
cmpp_b * const b = cmpp_b_borrow(dx->pp);
if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){
int n = nChomp;
while( n-- && cmpp_b_chomp(b) ){}
cmpp__define2(dx->pp, aKey->z, aKey->n,
b->z, b->n, cmpp_tt_AnyType);
}
cmpp_b_return(dx->pp, b);
break;
}
default:
// TODO: treat (...) as an expression
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Unhandled arg type %s: %s",
cmpp__tt_cstr2(arg->ttype, true), arg->z);
break;
}
}
if( 0==dxppCode && nHeredoc ){
// Process (#define KEY <<)
cmpp_b * const os = cmpp_b_borrow(dx->pp);
assert( args->d->closer );
if( os &&
0==cmpp_dx_consume_b(dx, os, &args->d->closer, 1,
raw
? cmpp_dx_consume_F_ATPOL_OFF
: cmpp_dx_consume_F_PROCESS_OTHER_D) ){
while( nChomp-- && cmpp_b_chomp(os) ){}
g_debug(dx->pp,2,("define heredoc: [%s]=[%.*s]\n",
acHeredoc, (int)os->n, os->z));
if( !ifNotDefined
|| !cmpp__has(dx->pp, (char const*)acHeredoc, nHeredoc) ){
cmpp__define2(
dx->pp, acHeredoc, nHeredoc, os->z, os->n,
cmpp_tt_DbString
);
}
}
cmpp_b_return(dx->pp, os);
}
#undef checkIsDefined
return;
}
/* Impl. for #undef */
static void cmpp_f_undef(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
if( args->argc<2 ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting one or more arguments");
return;
}
cmpp_size_t n = 0;
char const * z;
for( unsigned i = 1; i < args->argc && 0==dxppCode; ++i ){
cmpp_arg const * arg = args->argv[i];
z = cmpp_arg_cstr2(arg, &n);
if( 0 ){
g_stderr(" %s: %s %p n=%d %.*s\n", args->d->name.z,
cmpp__tt_cstr2(arg->ttype, true), arg->z,
(int)arg->n, (int)arg->n, arg->z);
}
if( cmpp_tt_Word==arg->ttype ){
#if 0
/* Too strict? */
if( 0==cmpp__legal_key_check(dx->pp, z, n, false) ) {
cmpp_undefine(dx->pp, (char const *)z);
}
#else
cmpp_undefine(dx->pp, z, n);
#endif
}else{
cmpp_errf(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s",
args->d->name.z, arg->z);
}
}
}
/* Impl. for #eval. */
static void cmpp_f_eval(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
if( 2!=args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"#eval requires one argument");
return;
}
cmpp_arg const * const arg = args->argv[1];
cmpp_b * const b = arg->n ? cmpp_b_borrow(dx->pp) : 0;
if( b ){
/* We need to collect the line info before processing the arg so
that it points to the start of the script. */
sqlite3_str * const sstr = cmpp__sqlite3_str_new(args->pp);
if( sstr ){
cmpp__dx_append_script_info(dx, sstr);
char * const z = sqlite3_str_finish(sstr);
if(0==cmpp_check_oom(args->pp, z)
&& 0==cmpp_arg_interpolate(args->pp, arg, b,
cmpp_arg_interpolate_BRACE_CALL)
&& b->n){
cmpp_process_string(args->pp, z ? z : args->d->name.z,
b->z, b->n);
}
sqlite3_free(z);
}
cmpp_b_return(args->pp, b);
}
return;
}
/* Impl. for #expand. */
static void cmpp_f_expand(cmpp_f_args const * args){
if( args->argc<2 || args->argc>3 ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"%sexpand requires one or two arguments",
cmpp__dx_zdelim(args->dx));
return;
}
cmpp_arg const * argKav = 0 /* {key -> value ...} */;
cmpp_arg const * argToB = 0 /* one of X or Y from: #expand X Y */;
cmpp_arg const * aLhs = 0;
cmpp_arg const * aRhs = 0;
cmpp_arg const * aErr = 0; /* Current arg for error reportin */
cmpp_b * const osIn = cmpp_b_borrow(args->pp);
int nChomp = 0 /* Strip nChomp newlines from result */;
int unChomp = 0 /* Add unChomp newlines to result */;
bool raw = false /* interpret block content as raw or
preprocessor-ready? */;
bool inSp = false /* started a local savepoint? */;
if( !osIn ) goto end;
for( unsigned i = 1; i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
aErr = arg;
switch( arg->ttype ){
case cmpp_tt_Word:
if( cmpp_arg_isflag_c(arg,"-chomp") ){
++nChomp;
continue;
}
if( cmpp_arg_isflag_c(arg,"-unchomp") ){
++unChomp;
continue;
}
if( cmpp_arg_isflag_c(arg,"-raw") ){
raw = true;
continue;
}
if( !aLhs ){
aLhs = arg;
continue;
}
goto unexpected_arg;
default:
assert( !aRhs );
if( aLhs ){
if( i+1<args->argc ){
assert( arg!=aLhs );
assert( !aRhs );
goto unexpected_arg;
}
aRhs = arg;
}else{
aLhs = arg;
}
continue;
}
}
if( !aLhs ){
cmpp_err(args->pp, CMPP_RC_MISUSE,
"Expecting one or two arguments.", -1);
goto end;
}
if( aRhs ){
if( cmpp_tt_GroupSquiggly!=aRhs->ttype ){
aErr = aRhs;
goto unexpected_arg;
}
argKav = aRhs;
argToB = aLhs;
}else{
if( cmpp_tt_GroupSquiggly==aLhs->ttype ){
argKav = aLhs;
}else{
argToB = aLhs;
}
}
assert( argKav || argToB );
if( argToB ){
if( cmpp_arg_interpolate(args->pp, argToB, osIn,
cmpp_arg_interpolate_BRACE_CALL) ){
goto end;
}
}
if( argKav ){
assert(!argKav->next);
if( (aErr = argKav->next) ){
goto unexpected_arg;
}
if( cmpp_sp_begin(args->pp) ) goto end;
inSp = true;
if( cmpp_kav_each(args->pp, argKav->z, argKav->n,
cmpp_kav_each_f_define__group,
0,
cmpp_kav_each_F_EXPAND_VAL
| cmpp_kav_each_F_CALL_VAL
| cmpp_kav_each_F_PARENS_EXPR) ){
goto end;
}
}
if( argToB && cmpp_tt_GroupSquiggly==argToB->ttype ){
cmpp_outputer xout = cmpp_outputer_b;
cmpp_outputer old = {0};
cmpp_b * const b = cmpp_b_borrow(args->pp);
if( !(xout.state = b) ) goto end;
cmpp__outputer_swap(args->pp, &xout, &old);
cmpp_process_string(args->pp, "#expand buffer", osIn->z, osIn->n);
cmpp__outputer_swap(args->pp, &old, &xout);
cmpp_b_swap(b, osIn);
cmpp_b_return(args->pp, b);
if( cmpp_err_has(args->pp) ) goto end;
}
if( !argToB ){
assert(!osIn->n);
if( args->isDxCall ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"#expand.../#expand is not legal for [call].");
goto end;
}
if( cmpp_dx_consume_b(args->dx, osIn, &args->d->closer, 1,
raw
? cmpp_dx_consume_F_ATPOL_OFF
: cmpp_dx_consume_F_PROCESS_OTHER_D) ){
goto end;
}
}
while( nChomp-- && cmpp_b_chomp(osIn) ){}
while( unChomp-- && cmpp_b_append_ch(osIn, '\n') ){}
//MARKER(("Emitting %d bytes: %.*s\n", (int)osIn->n, (int)osIn->n, osIn->z));
cmpp_out_expand(args->pp, NULL, osIn->z, osIn->n, cmpp_atpol_CURRENT);
end:
if( inSp ){
cmpp_sp_rollback(args->pp);
}
cmpp_b_return(args->pp, osIn);
return;
unexpected_arg:
assert( aErr );
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unexpected argument: %.*s",
(int)aErr->n, aErr->z);
goto end;
}
/* Impl. for #once. */
static void cmpp_f_once(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_d const * const d = args->d;
assert(d);
assert(d->closer);
if( dx->args.arg0 ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting no arguments");
return;
}
cmpp_b * const b = cmpp_b_borrow(args->pp);
if( !b ) return;
else{
/* Synthesize an ID from the source code position. Typically
we'll hit any position only once in a given run, but #include
and #query loops can cause re-visits. */
cmpp__delim const * const dlim = cmpp__dx_delim(args->dx);
cmpp_b_append4(args->pp, b, dlim->open.z, dlim->open.n);
cmpp_b_append4(args->pp, b, d->name.z, d->name.n);
cmpp_b_append_ch(b, ':');
char const * zFile = 0;
cmpp_size_t lnNo = 0;
cmpp_dx_src_pos_info(dx, &zFile, &lnNo);
cmpp_b_append4_str(args->pp, b, zFile, -1);
cmpp_b_append_ch(b, ':');
cmpp_b_append_i32(b, (int)lnNo);
if( b->errCode ) goto end;
}
if( 0 ){
g_warn("#once dline=%d pos.lineNo=%d, key: %s",
(int)dx->pimpl->dline.lineNo,
(int)dx->pimpl->pos.lineNo,
b->z);
}
int const had = cmpp__has(args->pp, (char const *)b->z, b->n);
if( dxppCode ) goto end;
else if( had ){
/* Consume until #/once in elide mode. */
CmppLvl * const lvl = CmppLvl_push(dx);
if( lvl ){
CmppLvl_elide(lvl, true);
cmpp_outputer devNull = cmpp_outputer_empty;
cmpp_dx_consume(dx, &devNull, &d->closer, 1,
cmpp_dx_consume_F_PROCESS_OTHER_D);
CmppLvl_pop(dx, lvl);
}
}else if( 0==cmpp_define_v2(args->pp, (char const*)b->z, "1") ){
cmpp_dx_consume(dx, NULL, &d->closer, 1,
cmpp_dx_consume_F_PROCESS_OTHER_D);
}
end:
cmpp_b_return(args->pp, b);
return;
}
/* Impl. for #/define, /#query, /#pipe. */
CMPP__EXPORT(void, cmpp_f_dangling_closer)(
cmpp_f_args const * args
){
cmpp_dx * const dx = args->dx;
char const * const zD = cmpp_dx_delim(dx);
dxserr("%s%s used without its opening directive.",
zD, args->d->name.z);
}
#ifndef CMPP_OMIT_D_INCLUDE
static int cmpp__including_has(cmpp *pp, unsigned const char * zName){
int rc = 0;
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false);
if( q && 0==cmpp_stmt_bind_text(pp, q, 1, zName) ){
if(SQLITE_ROW == cmpp_stmt_step(pp, q, true)){
rc = 1;
}else{
rc = 0;
}
g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc));
}
return rc;
}
char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
cmpp_size_t * nVal){
char * zName = 0;
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false);
if( nVal ) *nVal = 0;
if( q && 0==cmpp_stmt_bind_text(pp, q, 1, zKey) ){
int const rc = cmpp_stmt_step(pp, q, false);
if(SQLITE_ROW==rc){
const unsigned char * z = sqlite3_column_text(q, 0);
int const n = sqlite3_column_bytes(q,0);
zName = n ? sqlite3_mprintf("%.*s", n, z) : 0;
if( n ) cmpp_check_oom(pp, zName);
if( nVal ) *nVal = n;
}
cmpp_stmt_reset(pp, q);
}
return zName;
}
/**
Removes zKey from the currently-being-`#include`d list
list.
*/
static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false);
if( q ){
cmpp_stmt_bind_text(pp, q, 1, ustr_c(zKey));
cmpp_stmt_step(pp, q, true);
g_debug(pp,2,("incl rm [%s]\n", zKey));
}
return ppCode;
}
/**
Adds the given filename to the list of being-`#include`d files,
using the given source file name and line number of error reporting
purposes. If recursion is later detected.
*/
static int cmpp__including_add(cmpp *pp, unsigned const char * zKey,
unsigned const char * zSrc, cmpp_size_t srcLine){
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false);
if( q ){
cmpp_stmt_bind_text(pp, q, 1, zKey);
cmpp_stmt_bind_text(pp, q, 2, zSrc);
cmpp_stmt_bind_int(pp, q, 3, srcLine);
cmpp_stmt_step(pp, q, true);
g_debug(pp,2,("is-including-file add [%s] from [%s]:%"
CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine));
}
return ppCode;
}
/* Impl. for #include. */
static void cmpp_f_include(cmpp_f_args const * args){
char * zResolved = 0;
cmpp_size_t nResolved;
cmpp_b * const ob = cmpp_b_borrow(args->pp);
bool raw = false;
bool resolve = false;
cmpp_arg const * argDelim = 0;
if( !ob ){
goto end;
}
unsigned i = 1;
for( ; i < args->argc; ++i){
cmpp_arg const * arg = args->argv[i];
if( cmpp_arg_isflag_c(arg, "-raw") ){
if( resolve ) goto no_raw_resolve;
raw = true;
continue;
}
if( cmpp_arg_isflag_c(arg, "-delimiter") ){
if( ++i==args->argc ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"-delimiter requires an argument");
goto end;
}
argDelim = args->argv[i];
continue;
}
if( cmpp_arg_isflag_c(arg, "-resolve") ){
if( raw ){
no_raw_resolve:
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"-raw and -resolve may not be used together.");
goto end;
}else{
resolve = true;
continue;
}
}
break;
#undef FLAG
}
if( i>=args->argc ){
cmpp_errf(args->pp, CMPP_RC_SYNTAX,
"Expecting at least one filename argument.");
}
if( argDelim && (raw || resolve) ) argDelim = 0;
if( argDelim ){
if( cmpp_arg_interpolate(args->pp, argDelim, cmpp_b_reuse(ob),
cmpp_arg_interpolate_BRACE_CALL)
|| cmpp_delimiter_push(args->pp, (char*)ob->z) ){
argDelim = 0/*our signal to not pop the delimiter*/;
goto end;
}
}
for( ; i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(ob),
cmpp_arg_interpolate_BRACE_CALL) ){
break;
}
//g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved);
if(!raw && !resolve && cmpp__including_has(args->pp, ob->z)){
/* Note that different spellings of the same filename
** will elude this check, but that seems okay, as different
** spellings means that we're not re-running the exact same
** invocation. We might want some other form of multi-include
** protection, rather than this, however. There may well be
** sensible uses for recursion. */
cmpp_errf(args->pp, CMPP_RC_RANGE,
"Recursive include of file: %s",
ob->z);
break;
}
cmpp_mfree(zResolved);
nResolved = 0;
zResolved = cmpp__include_search(args->pp, ob->z, &nResolved);
if( !zResolved ){
if( !cmpp_err_has(args->pp) ){
cmpp_errf(args->pp, CMPP_RC_NOT_FOUND,
"file not found: %s", ob->z);
}
break;
}
if( resolve ){
cmpp_out_raw(args->pp, zResolved, nResolved);
}else if( raw ){
if( !args->pp->pimpl->out.out ) break;
FILE * const fp = cmpp_fopen(zResolved, "r");
if( fp ){
int const rc = cmpp_stream(cmpp_input_f_FILE, fp,
args->pp->pimpl->out.out,
args->pp->pimpl->out.state);
if( rc ){
cmpp_errf(args->pp, rc, "Unknown error streaming file %s.",
arg->z);
}
cmpp_fclose(fp);
}else{
cmpp_errf(args->pp, cmpp_errno_rc(errno, CMPP_RC_IO),
"Unknown error opening file %s.", arg->z);
}
}else{
cmpp__including_add(args->pp, ob->z,
ustr_c(args->dx->sourceName),
args->dx->pimpl->dline.lineNo);
cmpp_process_file(args->pp, zResolved);
cmpp__include_rm(args->pp, ob->z);
}
}
end:
if( argDelim ){
cmpp_delimiter_pop(args->pp);
}
cmpp_mfree(zResolved);
cmpp_b_return(args->pp, ob);
}
#endif /* #ifndef CMPP_OMIT_D_INCLUDE */
/**
cmpp_f() callback state for cmpp_f_if(): pointers to the
various directives of that family.
*/
struct CmppIfState {
cmpp_d * dIf;
cmpp_d * dElif;
cmpp_d * dElse;
cmpp_d const * dEndif;
};
typedef struct CmppIfState CmppIfState;
/* Version 2 of #if. */
static void cmpp_f_if(cmpp_f_args const * xargs){
cmpp_dx * const dx = xargs->dx;
/* Reminder to self:
We need to be able to recurse, even in skip mode, for #if nesting
to work. That's not great because it means we are evaluating
stuff we ideally should be skipping over, but it's keeping the
current tests working as-is. We can/do, however, avoid evaluating
expressions and such when recursing via skip mode. If we can
eliminate that here, by keeping track of the #if stack depth,
then we can possibly eliminate the whole CmppLvl_F_ELIDE
flag stuff.
The more convoluted version 1 #if (which this replaced not hours
ago) kept track of the skip state across a separate directive
function for #if and #/if. That was more complex but did avoid
having to recurse into #if in order to straighten out #elif and
#else. Update: tried a non-recursive variant involving moving
this function's gotTruth into the CmppLvl object() and
managing the CmppLvl stack here, but it just didn't want to
work for me and i was too tired to figure out why.
*/
int gotTruth = 0 /*expr result*/;
CmppIfState const * const cis = xargs->d->impl.state;
cmpp_d const * dClosers[] = {
cis->dElif, cis->dElse, cis->dEndif
};
CmppLvl * lvl = 0;
CmppDLine const dline = dx->pimpl->dline;
cmpp_args args = cmpp_args_empty;
char delim[20] = {0};
#define skipOn CmppLvl_elide((lvl), true)
#define skipOff CmppLvl_elide((lvl), false)
assert( xargs->d==cis->dIf );
if( !dx->args.arg0 ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting an expression.");
return;
}
snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx));
delim[sizeof(delim)-1] = 0;
lvl = CmppLvl_push(dx);
if( !lvl ) goto end;
if( cmpp_dx_is_eliding(dx) ){
gotTruth = 1;
}else if( cmpp__args_evalToInt(xargs->pp, &dx->pimpl->args,
&gotTruth) ){
goto end;
}else if( !gotTruth ){
skipOn;
}
cmpp_d const * dPrev = xargs->d;
cmpp_outputer devNull = cmpp_outputer_empty;
while( !dxppCode ){
dPrev = dx->d;
bool const isFinal = dPrev==cis->dElse
/* true if expecting an #/if. */;
if( cmpp_dx_consume(dx,
CmppLvl_is_eliding(lvl) ? &devNull : NULL,
isFinal ? &cis->dEndif : dClosers,
isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]),
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
break;
}
cmpp_d const * const d2 = dx->d;
if( !d2 ){
dxserr("Reached end of input in an untermined %s%s opened "
"at line %" CMPP_SIZE_T_PFMT ".",
delim, cis->dIf->name.z, dline.lineNo);
}
if( d2==cis->dEndif ){
break;
}else if( isFinal ){
assert(!"cannot happen - caught by consume()");
dxserr("Expecting %s%s to close %s%s.",
delim, cis->dEndif->name.z,
delim, dPrev->name.z);
break;
}else if( gotTruth ){
skipOn;
continue;
}else if( d2==cis->dElif ){
if( 0==cmpp_dx_args_parse(dx, &args)
&& 0==cmpp__args_evalToInt(xargs->pp, &args, &gotTruth) ){
if( gotTruth ) skipOff;
else skipOn;
}
continue;
}else{
assert( d2==cis->dElse
&& "Else (haha!) we cannot have gotten here" );
skipOff;
continue;
}
assert(!"unreachable");
}
#undef skipOff
#undef skipOn
end:
cmpp_args_cleanup(&args);
if( lvl ){
bool const lvlIsOk = CmppLvl_get(dx)==lvl;
CmppLvl_pop(dx, lvl);
if( !lvlIsOk && !dxppCode ){
assert(!"i naively believe that this is not possible");
cmpp_dx_errf(dx, CMPP_RC_SYNTAX,
"Mis-terminated %s%s opened at line "
"%" CMPP_SIZE_T_PFMT ".",
delim, cis->dIf->name.z, dline.lineNo);
}
}
return;
}
/* Version 2 of #elif, #else, and #/if. */
static void cmpp_f_if_dangler(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
CmppIfState const * const cis = args->d->impl.state;
char const *zDelim = cmpp_dx_delim(dx);
cmpp_dx_errf(dx, CMPP_RC_SYNTAX,
"%s%s with no matching %s%s",
zDelim, args->d->name.z,
zDelim, cis->dIf->name.z);
}
static void cmpp__dump_sizeofs(void){
#define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X))
SO(cmpp);
SO(cmpp_api_thunk);
SO(cmpp_arg);
SO(cmpp_args);
SO(cmpp_args_pimpl);
SO(cmpp_b);
SO(cmpp_d);
SO(cmpp_d_reg);
SO(cmpp__delim);
SO(cmpp__delim_list);
SO(cmpp_dx);
SO(cmpp__dx_pimpl);
SO(cmpp__dx_pimpl);
SO(cmpp_outputer);
SO(cmpp__pimpl);
SO(((cmpp__pimpl*)0)->stmt);
SO(((cmpp__pimpl*)0)->policy);
SO(cmpp_tizer);
SO(cmpp_token);
SO(CmppArgList);
SO(CmppDLine);
SO(CmppDList);
SO(CmppDList_entry);
SO(CmppLvl);
SO(PodList__atpol);
printf("cmpp_tt__last = %d\n",
cmpp_tt__last);
#undef SO
}
/* Impl. for #pragma. */
static void cmpp_f_pragma(cmpp_f_args const * args){
if(args->argc<2){
cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Expecting an argument");
return;
}else if(args->argc>2){
cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Too many arguments");
return;
}
cmpp_arg const * arg = args->argv[1];
const char * const zArg = (char const *)arg->z;
#define M(X) 0==strcmp(zArg,X)
if(M("defines")){
cmpp__dump_defines(args->pp, stderr, 1);
return;
}
if(M("sizeof")){
cmpp__dump_sizeofs();
return;
}
if(M("chomp-F")){
args->pp->pimpl->flags.chompF = 1;
return;
}
if(M("no-chomp-F")){
args->pp->pimpl->flags.chompF = 0;
return;
}
if(M("api-thunk")){
return;
}/*API thunk*/
#undef M
cmpp_errf(args->pp, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg);
}
/* Impl. for #savepoint. */
static void cmpp_f_savepoint(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
if( 2 != args->argc ){
cmpp_dx_errf(dx, CMPP_RC_SYNTAX, "Expecting one argument");
return;
}
cmpp_arg const * const arg = args->argv[1];
if( cmpp_arg_isflag_c(arg, "begin") ){
cmpp__dx_sp_begin(dx);
}else if( cmpp_arg_isflag_c(arg, "rollback") ){
cmpp__dx_sp_rollback(dx);
}else if( cmpp_arg_isflag_c(arg, "commit") ){
cmpp__dx_sp_commit(dx);
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unknown savepoint option: %s",
arg->z);
}
}
/* #stderr impl. */
static void cmpp_f_stderr(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
if(dx->args.z){
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName,
dx->pimpl->dline.lineNo,
(int)dx->args.nz, dx->args.z);
}else{
cmpp_d const * d = args->d;
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n",
dx->sourceName, dx->pimpl->dline.lineNo,
cmpp_dx_delim(dx), d->name.z);
}
}
/**
Manages both the @token@ policy and the delimiters.
#@ ?push? policy NAME ?<<?
#@ ?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_f_at(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_arg const * arg = dx->args.arg0;
if( !arg ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting arguments.");
return;
}
enum ops { op_none, op_set, op_push, op_pop, op_heredoc };
enum popWhichE { pop_policy = 0x01, pop_delim = 0x02,
pop_both = pop_policy | pop_delim };
enum ops op = op_none /* what to do */;
int popWhich = 0 /* what to pop */;
bool gotPolicy = false;
bool checkedCallForm = !args->isDxCall;
cmpp_arg const * argDelimO = 0 /* @token@ opener */;
cmpp_arg const * argDelimC = 0 /* @token@ closer */;
cmpp__pi(args->pp);
cmpp_atpol_e polNew = cmpp_atpol_get(args->pp);
for( ; arg; arg = arg ? arg->next : NULL ){
//g_warn("arg=%s", arg->z);
if( !checkedCallForm ){
assert( args->isDxCall );
checkedCallForm = true;
if( cmpp_arg_equals_c(arg, "policy") ){
char const * z =
cmpp__atpol_name(args->pp, cmpp__policy(args->pp,at));
if( z ){
cmpp_out_raw(args->pp, z, strlen(z));
}
}else if( cmpp_arg_equals_c(arg, "delimiter") ){
char const * zO = 0;
char const * zC = 0;
cmpp_atdelim_get(args->pp, &zO, &zC);
if( zC ){
cmpp_out_raw(args->pp, zO, strlen(zO));
cmpp_out_raw(args->pp, " ", 1);
cmpp_out_raw(args->pp, zC, strlen(zC));
}
goto end;
}else{
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"In call form, '%s' expects one of "
"'policy' or delimiter'.");
}
goto end;
}/* checkedCallForm */
if( !argDelimC && op_none==op ){
/* Look for push|pop. */
if( cmpp_arg_equals_c(arg, "pop") ){
arg = arg->next;
if( !arg ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'pop' expects arguments of 'policy' "
"and/or 'delimiter' and/or 'both'.");
goto end;
}
for( ; arg; arg = arg->next ){
if( 0==(pop_policy & popWhich)
&& cmpp_arg_equals_c(arg, "policy") ){
popWhich |= pop_policy;
}else if( 0==(pop_delim & popWhich)
&& cmpp_arg_equals_c(arg, "delimiter") ){
popWhich |= pop_delim;
}else if( 0==(pop_both & popWhich)
&& cmpp_arg_equals_c(arg, "both") ){
popWhich |= pop_both;
}else{
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Invalid argument to 'pop': ", arg->z);
goto end;
}
}
assert( !arg );
op = op_pop;
break;
}/* pop */
if( cmpp_arg_equals_c(arg, "push") ){
op = op_push;
continue;
}
if( cmpp_arg_equals_c(arg, "set") ){
/* set is implied if neither of push/pop are and we get
a policy name. */
op = op_set;
continue;
}
/* Fall through */
}/* !argDelimC && op_none==op */
if( !gotPolicy && cmpp_arg_equals_c(arg, "policy") ){
arg = arg->next;
if( !arg ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'policy' requires a policy name argument.");
goto end;
}
polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z);
if( cmpp_atpol_invalid==polNew ){
cmpp_atpol_from_str(args->pp, (char const*)arg->z)
/* Will set the error state to something informative. */;
goto end;
}
if( op_none==op ) op = op_set;
gotPolicy = true;
continue;
}
if( !argDelimC && cmpp_arg_equals_c(arg, "delimiter") ){
assert( !argDelimO && !argDelimC );
argDelimO = arg->next;
argDelimC = argDelimO ? argDelimO->next : NULL;
if( !argDelimC ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'delimiter' requires two arguments.");
goto end;
}
arg = argDelimC->next;
continue;
}
if( op_pop!=op ){
if( cmpp_arg_equals_c(arg,"<<") ){
if( arg->next ) {
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'%s' must be the final argument.",
arg->z);
goto end;
}
op = op_heredoc;
break;
}
}
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z);
return;
}/*arg collection*/
assert( !dxppCode );
assert( cmpp_atpol_invalid!=polNew );
#define popcheck(LIST) \
if(dxppCode) goto end; \
if(!LIST.n) goto bad_pop
if( op_pop==op ){
assert( popWhich>0 && popWhich<=3 );
if( pop_policy & popWhich ){
popcheck(pi->policy.at);
cmpp_atpol_pop(args->pp);
}
if( pop_delim & popWhich ){
popcheck(pi->delim.at);
cmpp_atdelim_pop(args->pp);
}
goto end;
}
assert( op_set==op || op_push==op || op_heredoc==op );
if( argDelimC ){
/* Push or set the @token@ delimiters */
if( 0 ){
g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push",
argDelimO->z, argDelimC->z);
}
if( op_push==op || op_heredoc==op ){
if( cmpp_atdelim_push(args->pp, (char const*)argDelimO->z,
(char const*)argDelimC->z) ){
goto end;
}
argDelimO = 0 /* Re-use argDelimC as a flag in case we need to
roll this back on an error below. */;
}else{
assert( op_set==op );
if( cmpp_atdelim_set(args->pp, (char const*)argDelimO->z,
(char const*)argDelimC->z) ){
goto end;
}
argDelimO = argDelimC = 0;
}
}
assert( !dxppCode );
assert( !argDelimO );
if( op_heredoc==op ){
if( cmpp_atpol_push(args->pp, polNew) ){
if( argDelimC ){
popcheck(pi->delim.at);
cmpp_atdelim_pop(args->pp);
}
}else{
bool const pushedDelim = NULL!=argDelimC;
assert( args->d->closer );
cmpp_dx_consume(dx, NULL, &args->d->closer, 1,
cmpp_dx_consume_F_PROCESS_OTHER_D)
/* !Invalidates argDelimO and argDelimC! */;
popcheck(pi->policy.at);
cmpp_atpol_pop(args->pp);
if( pushedDelim ) cmpp_atdelim_pop(args->pp);
}
}else if( op_push==op ){
if( cmpp_atpol_push(args->pp, polNew) && argDelimC ){
/* Roll back delimiter push */
cmpp_atdelim_pop(args->pp);
}
}else{
assert( op_set==op );
if( cmpp__policy(args->pp,at)!=polNew ){
cmpp_atpol_set(args->pp, polNew);
}
}
end:
return;
bad_pop:
cmpp_dx_errf(dx, CMPP_RC_RANGE,
"Cannot pop an empty stack.");
#undef popcheck
}
static void cmpp_f_expr(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
int rv = 0;
assert( dx->args.z );
if( 0 ){
g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc,
dx->args.arg0->n, dx->args.arg0->z);
g_stderr("%s() dx->args.z [%.*s]\n", __func__,
(int)dx->args.nz, dx->args.z);
}
if( !dx->args.argc ){
dxserr("An empty expression is not permitted.");
return;
}
#if 0
for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
g_stderr("got type=%s n=%u z=%.*s\n",
cmpp__tt_cstr2(a->ttype, true),
(unsigned)a->n, (int)a->n, a->z);
}
#endif
if( 0==cmpp__args_evalToInt(args->pp, &dx->pimpl->args, &rv) ){
if( 'a'==args->d->name.z[0] ){
if( !rv ){
cmpp_errf(args->pp, CMPP_RC_ASSERT,
"Assertion failed: %s",
args->raw.z);
}
}else{
char buf[60];
snprintf(buf, sizeof(buf), "%d\n", rv);
cmpp_out_raw(args->pp, buf, strlen(buf));
}
}
}
static void cmpp_f_undef_policy(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_unpol_e up = cmpp_unpol_invalid;
int nSeen = 0;
assert( args->argc>0 && "v4 args" );
enum ops { op_invalid, op_set, op_push, op_pop };
enum ops op = op_invalid;
if( 1==args->argc ){
cmpp_f_v4_err_usage(args);
return;
}
unsigned i = 1;
cmpp_arg const * arg;
cmpp_arg const * argPol = 0;
again:
arg = args->argv[i];
++nSeen;
if( 1==nSeen ){
if( cmpp_arg_equals_c(arg, "push") ){
if( ++i==args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting argument to 'push'.");
return;
}
op = op_push;
goto again;
}else if( cmpp_arg_equals_c(arg, "pop") ){
if( ++i < args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Extra argument after 'pop': %s",
cmpp_arg_cstr(args->argv[i]));
return;
}
op = op_pop;
}
}
if( op_pop!=op ){
if( cmpp_arg_equals_c(arg,"error") ){
if( op_invalid==op ) op = op_set;
argPol = arg;
up = cmpp_unpol_ERROR;
}else if( cmpp_arg_equals_c(arg,"null") ){
if( op_invalid==op ) op = op_set;
argPol = arg;
up = cmpp_unpol_NULL;
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unhandled argument: %s",
cmpp_arg_cstr(arg));
return;
}
}
assert( op_invalid!=op );
if( op_pop!=op && cmpp_unpol_invalid==up ){
if( argPol ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unhandled undefined-policy '%s'."
" Try one of: error, null",
cmpp_arg_cstr(argPol));
}else{
cmpp_f_v4_err_usage(args);
}
return;
}
switch( op ){
case op_set: cmpp_unpol_set(args->pp, up); break;
case op_push: cmpp_unpol_push(args->pp, up); break;
case op_pop:
if( !cmpp__epol(args->pp,un).n ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"No %s%s push is active.",
cmpp_dx_delim(dx), args->d->name.z);
}else{
cmpp_unpol_pop(args->pp);
}
break;
default:
cmpp__fatal("Not possible - unhandled switch case");
}
}
#ifndef CMPP_OMIT_D_DB
/* Impl. for #attach. */
static void cmpp_f_attach(cmpp_f_args const * args){
if( 4!=args->argc ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"%s expects: STRING as NAME",
args->d->name.z);
return;
}
cmpp_b * bDbFile = 0;
cmpp_b * bSchema = 0;
for( unsigned i = 1; i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
if( !bDbFile ){
bDbFile = cmpp_b_borrow(args->pp);
if( !bDbFile ) goto end;
if( 0==cmpp_arg_interpolate(args->pp, arg, bDbFile,
cmpp_arg_interpolate_BRACE_CALL)
&& !bDbFile->n ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Empty db file name is not permitted. "
"If '%s' is intended as a value, "
"it should be quoted.", arg->z);
goto end;
}
if( ++i == args->argc
|| !cmpp_arg_equals_c(args->argv[i], "as") ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Expecting 'as' after db file name.");
goto end;
}
continue;
}
if( !bSchema ){
bSchema = cmpp_b_borrow(args->pp);
if( !bSchema ) goto end;
if( 0==cmpp_arg_interpolate(args->pp, arg, bSchema,
cmpp_arg_interpolate_BRACE_CALL)
&& !bSchema->n ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Empty db schema name is not permitted."
"If '%s' is intended as a value, "
"it should be quoted.",
arg->z);
goto end;
}
continue;
}
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unhandled argument: %s", arg->z);
goto end;
}
if( !bSchema ){
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Missing schema name.");
goto end;
}
sqlite3_stmt * const q =
cmpp__stmt(args->pp, CmppStmt_dbAttach, false);
if( q ){
cmpp_stmt_bind_textn(args->pp, q, 1, bDbFile->z, bDbFile->n);
cmpp_stmt_bind_textn(args->pp, q, 2, bSchema->z, bSchema->n);
cmpp_stmt_step(args->pp, q, true);
}
end:
cmpp_b_return(args->pp, bDbFile);
cmpp_b_return(args->pp, bSchema);
}
/* Impl. for #detach. */
static void cmpp_f_detach(cmpp_f_args const * args){
if( 2!=args->argc ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"%s expects: NAME", args->d->name.z);
return;
}
cmpp_b * const b = cmpp_b_borrow(args->pp);
if( !b ) return;
if( cmpp_arg_interpolate(args->pp, args->argv[1], b,
cmpp_arg_interpolate_BRACE_CALL) ){
goto end;
}
if( b->n ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Empty db schema name is not permitted.");
goto end;
}
sqlite3_stmt * const q =
cmpp__stmt(args->pp, CmppStmt_dbDetach, false);
if( q ){
cmpp_stmt_bind_textn(args->pp, q, 1, b->z, b->n);
cmpp_stmt_step(args->pp, q, true);
}
end:
cmpp_b_return(args->pp, b);
}
#endif /* #ifndef CMPP_OMIT_D_DB */
static void cmpp_f_delimiter(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_arg const * arg = dx->args.arg0;
enum ops { op_none, op_set, op_push, op_pop };
enum ops op = op_none;
cmpp_arg const * argD = 0;
bool doHeredoc = false;
for( ; arg; arg = arg->next ){
if( op_none==op ){
/* Look for push|pop. */
if( cmpp_arg_equals_c(arg, "push") ){
op = op_push;
continue;
}else if( cmpp_arg_equals_c(arg, "pop") ){
if( arg->next ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'pop' expects no arguments.");
return;
}
op = op_pop;
break;
}
/* Fall through */
}
if( !argD ){
if( op_none==op ) op = op_set;
argD = arg;
continue;
}else if( !doHeredoc && cmpp_arg_equals_c(arg,"<<") ){
if( args->isDxCall ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'%s' is not legal in [call] form.", arg->z);
return;
}else if( arg->next ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"'%s' must be the final argument.", arg->z);
return;
}
op = op_push;
doHeredoc = true;
continue;
}
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
return;
}
if( op_pop==op ){
cmpp_delimiter_pop(args->pp);
}else if( !argD ){
if( args->isDxCall ){
cmpp__delim const * const del = cmpp__dx_delim(dx);
if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n);
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE, "No delimiter specified.");
}
return;
}else{
char const * const z =
(0==strcmp("default",(char*)argD->z))
? NULL
: (char const*)argD->z;
if( op_push==op ){
cmpp_delimiter_push(args->pp, (char const*)argD->z);
}else{
assert( op_set==op );
if( doHeredoc ) cmpp_delimiter_push(args->pp, z);
else cmpp_delimiter_set(args->pp, z);
}
}
if( !cmpp_err_has(dx->pp) ){
if( args->isDxCall ){
cmpp__delim const * const del = cmpp__dx_delim(dx);
if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n);
}else if( doHeredoc ){
assert( op_push==op );
cmpp_dx_consume(dx, NULL, &args->d->closer, 1,
cmpp_dx_consume_F_PROCESS_OTHER_D);
cmpp_delimiter_pop(args->pp);
}
}
}
#ifndef NDEBUG
/* Experimenting grounds. */
static void cmpp_f_experiment(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
void * st = args->d->impl.state;
(void)st;
g_warn("raw args: %s", args->raw.z);
g_warn("argc=%u", args->argc);
g_warn("isCall=%d\n", args->isDxCall);
if( 1 ){
g_warn("argc=%u", args->argc);
for(unsigned i = 0; i < args->argc; ++i ){
cmpp_arg const * const a = args->argv[i];
g_stderr("got type=%s, n=%u z=%.*s\n",
cmpp__tt_cstr2(a->ttype, true),
(unsigned)a->n, (int)a->n, a->z);
}
}
if( 0 ){
cmpp__dump_sizeofs();
}
if( 0 ){
cmpp_tizer tz = cmpp_tizer_empty;
cmpp_token const * const tok = &tz.token;
int rc = 0;
cmpp_b * const b = cmpp_b_borrow(args->pp);
assert( b );
cmpp_b_append4(args->pp, b, args->raw.z, args->raw.n);
cmpp_tizer_init(&tz, &args->pp->pimpl->err, b->z, b->n);
g_warn0("Begin tizer test");
cmpp_b * const bc = cmpp_b_borrow(args->pp);
const cmpp_flag32_t cflags = 0;//cmpp_token_content_WHOLE;
while( 0==(rc = cmpp_tizer_next(&tz, 0))
&& cmpp_tt_EOF!=tok->ttype ){
cmpp_token_content(&tz, 0, bc, cflags);
g_warn("token type=%d: %.*s ==> %.*s",
tok->ttype, (int)tok->n, tok->z, (int)bc->n, bc->z);
}
cmpp_b_return(args->pp, bc);
g_warn("End tizer test. rc=%d", dxppCode);
cmpp_dx_errf(dx, 0, 0);
cmpp_tizer_dtor(&tz);
cmpp_b_return(args->pp, b);
if(rc) return;
}
}
#endif /* #ifndef NDEBUG */
#ifndef CMPP_OMIT_D_DB
/**
Helper for #query and friends. Expects arg to be an SQL value. If
arg->next is "bind" then this consumes the following two arguments(
"bind" BIND_ARG), where BIND_ARG must be one of either
cmpp_tt_GroupSquiggly or cmpp_tt_GroupBrace.
If it returns 0 then:
- If "bind" was found then *pBind is set to the BIND_ARG argument
and *pNext is set to the one after that.
- Else *pBind is set to NULL and and *pNext is set to
arg->next.
In either case, *pNext may be set to NULL.
*/
static
int cmpp__consume_sql_args(cmpp *pp, cmpp_f_args const *args,
unsigned int * i,
cmpp_arg const **pBind){
if( 0==ppCode ){
#define nextArg (++*i<args->argc ? args->argv[*i] : 0)
*pBind = 0;
cmpp_arg const *pN = nextArg;
if( pN && cmpp_arg_equals_c(pN, "bind") ){
pN = nextArg;
if( !pN || (
cmpp_tt_GroupSquiggly!=pN->ttype
&& cmpp_tt_GroupBrace!=pN->ttype
) ){
return serr("Expecting {...} or [...] after 'bind'.");
}
*pBind = pN;
}else{
*pBind = 0;
}
#undef nextArg
}
return ppCode;
}
/**
cmpp_kav_each_f() impl for used by #query's `bind {...}` argument.
*/
static int cmpp_kav_each_f_query__bind(
cmpp * const pp,
unsigned char const * const zKey, cmpp_size_t nKey,
unsigned char const * const zVal, cmpp_size_t nVal,
void * const callbackState
){
/* Expecting: :bindName -> bindValue */
if( ':'!=zKey[0] && '$'!=zKey[0] && '@'!=zKey[0] ){
cmpp_errf(pp, CMPP_RC_MISUSE,
"Bind keys must start with ':' or '$' or '@'.");
}else{
sqlite3_stmt * const q = callbackState;
assert( q );
int const bindNdx =
sqlite3_bind_parameter_index(q, (char const*)zKey);
if( bindNdx ){
cmpp_stmt_bind_textn(pp, q, bindNdx, zVal, nVal);
}else{
cmpp_errf(pp, CMPP_RC_RANGE, "Invalid bind name: %.*s",
(int)nKey, zKey);
}
}
return ppCode;
}
int cmpp__bind_group(cmpp * const pp, sqlite3_stmt * const q,
cmpp_arg const * const aGroup){
if( ppCode ) return ppCode;
if( cmpp_tt_GroupSquiggly==aGroup->ttype ){
return cmpp_kav_each(
pp, aGroup->z, aGroup->n,
cmpp_kav_each_f_query__bind, q,
cmpp_kav_each_F_NOT_EMPTY
| cmpp_kav_each_F_CALL_VAL
| cmpp_kav_each_F_PARENS_EXPR
);
}
if( cmpp_tt_GroupBrace!=aGroup->ttype ){
return cmpp_errf(pp, CMPP_RC_MISUSE,
"Expecting {...} or [...] "
"for SQL binding list.");
}
int bindNdx = 0;
cmpp_args args = cmpp_args_empty;
cmpp_args_parse(pp, &args, aGroup->z, aGroup->n, 0);
if( !args.argc && !ppCode ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Empty SQL bind list is not permitted.");
/* Keep going so we can clean up a partially-parsed args. */
}
for( cmpp_arg const * aVal = args.arg0;
!ppCode && aVal;
aVal = aVal->next ){
++bindNdx;
if( 0 ){
g_warn("bind #%d %s <<%s>>", bindNdx,
cmpp__tt_cstr2(aVal->ttype, true), aVal->z);
}
cmpp__bind_arg(pp, q, bindNdx, aVal);
}
cmpp_args_cleanup(&args);
return ppCode;
}
/** #query impl */
static void cmpp_f_query(cmpp_f_args const * args){
if( !args->argc ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Expecting one or more arguments");
return;
}
cmpp * const pp = args->pp;
cmpp_dx * const dx = args->dx;
sqlite3_stmt * q = 0;
cmpp_b * const obBody = cmpp_b_borrow(args->pp);
cmpp_b * const sql = cmpp_b_borrow(args->pp);
cmpp_outputer obNull = cmpp_outputer_empty;
int nChomp = 0;
int nUnchomp = 0;
bool spStarted = false;
bool seenDefine = false;
bool seenEmit = false;
bool batchMode = false;
cmpp_arg const * aBind = 0;
cmpp_d const * const dNoRows = args->d->impl.state;
cmpp_d const * const dClosers[2] = {args->d->closer, dNoRows};
if( !obBody || !sql ) goto cleanup;
assert( dNoRows );
//g_warn("args.argc=%d", args.argc);
#define nextArg (++i<args->argc ? args->argv[i] : 0)
for( unsigned i = 1; i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
//g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z);
if( cmpp_arg_equals_c(arg, "define") ){
if( seenDefine ){
cmpp__dx_err_just_once(dx, arg);
goto cleanup;
}
seenDefine = true;
continue;
}
if( cmpp_arg_equals_c(arg, "emit") ){
if( seenEmit ){
cmpp__dx_err_just_once(dx, arg);
goto cleanup;
}
seenEmit = true;
continue;
}
if( cmpp_arg_equals_c(arg, "-chomp") ){
++nChomp;
continue;
}
if( cmpp_arg_equals_c(arg, "-unchomp") ){
++nUnchomp;
continue;
}
if( cmpp_arg_equals_c(arg, "-batch") ){
if( batchMode ){
cmpp__dx_err_just_once(dx, arg);
goto cleanup;
}
batchMode = true;
continue;
}
if( !sql->n ){
if( !arg->n ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Expecting a non-empty SQL argument.");
goto cleanup;
}
if( cmpp__consume_sql_args(pp, args, &i, &aBind) ){
goto cleanup;
}
if( cmpp_arg_interpolate(pp, arg, sql, cmpp_arg_interpolate_BRACE_CALL) ){
goto cleanup;
}
//g_warn("SQL: <<%s>>", sql->z);
continue;
}
cmpp_errf(pp, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
goto cleanup;
}
#undef nextArg
if( ppCode ) goto cleanup;
seenEmit |= args->isDxCall;
if( seenDefine || seenEmit ){
if( seenDefine && nChomp ){
/* fixme (2026-07-30): support chomp for define */
serr("-chomp and define may not be used together.");
goto cleanup;
}else if( batchMode ){
serr("-batch and define/emit may not be used together.");
goto cleanup;
}
}
if( !sql->n ){
serr("Expecting an SQL-string argument.");
goto cleanup;
}
if( batchMode ){
if( aBind ){
serr("Bindable emits may not be used with -batch.");
goto cleanup;
}
char *zErr = 0;
cmpp__pi(args->pp);
int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr);
rc = cmpp_db_rc(args->pp, rc, zErr);
sqlite3_free(zErr);
goto cleanup;
}
if( cmpp_db_rc_v2(pp, sqlite3_prepare_v2(
pp->pimpl->db.dbh, (char const *)sql->z,
(int)sql->n, &q, 0), 0, 0) ){
goto cleanup;
}else if( !q ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Empty SQL is not permitted.");
goto cleanup;
}
//g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q));
int const nCol = sqlite3_column_count(q);
if( !nCol ){
cmpp_errf(pp, CMPP_RC_RANGE,
"SQL does not have any result columns.");
goto cleanup;
}
if( !seenDefine ){
if( cmpp_sp_begin(pp) ) goto cleanup;
spStarted = true;
}
if( aBind && cmpp__bind_group(pp, q, aBind) ){
goto cleanup;
}
bool gotARow = false;
cmpp_dx_pos dxPosStart;
cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D;
cmpp_dx_pos_save(dx, &dxPosStart);
int const nChompOrig = nChomp;
while( 0==ppCode ){
int const dbrc = cmpp_stmt_step(pp, q, false);
if( SQLITE_ROW==dbrc ){
gotARow = true;
nChomp = nChompOrig;
cmpp_b_reuse(obBody);
if( cmpp__define_from_row(pp, q, false,
seenEmit ? obBody : 0) ){
break;
}
if( seenEmit ){
while( nChomp-- && cmpp_b_chomp(obBody) ){}
while( nUnchomp-- ) cmpp_b_append_ch(obBody, '\n');
if( seenEmit && !obBody->errCode && obBody->n ){
cmpp_out_raw(args->pp, obBody->z, obBody->n);
}
break;
}else if( seenDefine ){
break;
}
cmpp_dx_pos_restore(dx, &dxPosStart);
/* If it weren't for -chomp, we wouldn't need to
buffer this. */
if( cmpp_dx_consume_b(dx, obBody, dClosers,
sizeof(dClosers)/sizeof(dClosers[0]),
consumeFlags) ){
goto cleanup;
}
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
while( nChomp-- && cmpp_b_chomp(obBody) ){}
if( obBody->n && cmpp_out_raw(args->pp, obBody->z, obBody->n) ){
break;
}
if( dx->d == dNoRows ){
if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/,
consumeFlags) ){
goto cleanup;
}
assert( dx->d == dClosers[0] );
/* TODO? chomp? */
}
continue;
}
if( 0==ppCode && (seenDefine || seenEmit) ){
/* If we got here, there was no result row. */
cmpp__define_from_row(pp, q, true, 0);
}
break;
}/*result row loop*/
cmpp_stmt_reset(pp, q);
if( ppCode ) goto cleanup;
while( !seenDefine && !seenEmit && !gotARow ){
/* No result rows. Skip past the body, emitting the #query:no-rows
content, if any. We disable @token processing for that first
step because (A) the output is not going anywhere, so no need
to expand it (noting that expanding may have side effects via
@[call...]@) and (B) the @tokens@ referring to this query's
results will not have been set because there was no row to set
them from, so @expanding@ them would fail. */
cmpp_atpol_e const atpol = cmpp_atpol_get(args->pp);
if( cmpp_atpol_set(args->pp, cmpp_atpol_OFF) ) break;
cmpp_dx_consume(dx, &obNull, dClosers,
sizeof(dClosers)/sizeof(dClosers[0]),
consumeFlags);
cmpp_atpol_set(args->pp, atpol);
if( dxppCode ) break;
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
if( dx->d == dNoRows ){
if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/,
consumeFlags) ){
break;
}
assert( dx->d == dClosers[0] );
/* TODO? chomp? */
}
break;
}
cleanup:
cmpp_b_return(args->pp, obBody);
cmpp_b_return(args->pp, sql);
sqlite3_finalize(q);
if( spStarted ) cmpp_sp_rollback(pp);
}
#endif /* #ifndef CMPP_OMIT_D_DB */
/**
#sum ...args
Emits the sum of its arguments, treating each as an integer.
Non-integer arguments are silently skipped, i.e. treated as zeroes.
*/
static void cmpp_f_sum(cmpp_f_args const * args){
int64_t nTotal = 0, nn = 0;
cmpp_b * const b = cmpp_b_borrow(args->pp);
if( !b ) return;
int rc = b ? 0 : CMPP_RC_OOM;
for( unsigned i = 1; 0==rc && i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
rc = cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b),
cmpp_arg_interpolate_BRACE_CALL);
if( 0==rc && cmpp_is_int64(b->z, b->n, &nn) ){
nTotal += nn;
}
}
if( 0==rc ){
cmpp_b_append_i64(cmpp_b_reuse(b), nTotal);
cmpp_out_raw(args->pp, b->z, b->n);
}
cmpp_b_return(args->pp, b);
}
/**
#arg ?flags? the-arg
-trim-left
-trim-right
-trim: trim both sides
It sends its arg to cmpp_arg_interpolate() to expand it, optionally
trims the result, and emits that value.
This directive is not expected to be useful except, perhaps in
testing cmpp itself. Its trim flags, in particular, aren't commonly
useful because #arg is only useful in a function call context and
those unconditionally trim their output.
*/
static void cmpp_f_arg(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
bool trimL = false, trimR = false;
cmpp_arg const * arg = dx->args.arg0;
if( !dx->args.argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting an argument.");
return;
}
//g_warn("argc=%d", dx->args.argc);
cmpp_b * const b = cmpp_b_borrow(args->pp);
for( ; arg && !cmpp_err_has(args->pp); arg = arg->next ){
#define FLAG(X)if( cmpp_arg_isflag_c(arg, X) )
FLAG("-raw") {
a2bFlags = cmpp_arg_interpolate_FORCE_STRING;
continue;
}
FLAG("-trim-left") { trimL=true; continue; }
FLAG("-trim-right") { trimR=true; continue; }
FLAG("-trim") { trimL=trimR=true; continue; }
#undef FLAG
if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), a2bFlags) ){
unsigned char const * zz = b->z;
unsigned char const * zzEnd = b->z + b->n;
if( trimL ) cmpp_skip_snl(&zz, zzEnd);
if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd);
if( 0 ){
g_warn("arg n=%d bn=%d %s %.*s",
(int)arg->n, (int)b->n, cmpp_tt_cstr(arg->ttype),
(int)(zzEnd-zz), zz);
}
//assert(!cmpp_dx_is_eliding(args->dx));
if( zzEnd-zz>0 ){
cmpp_out_raw(args->pp, zz, zzEnd-zz);
//g_warn0("here");
}
}
}
cmpp_b_return(args->pp, b);
}
/**
#join ?flags? ...args
-s SEPARATOR: sets the separator for its RHS arguments. Default=space.
-nl: append a newline (will be stripped by [call]s!). This is the default
when !(flags & cmpp_f_F_IS_CALL).
-nonl: do not append a newline. Default when dx->isDxCall.
*/
static void cmpp_f_join(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
cmpp_b * const b = cmpp_b_borrow(args->pp);
cmpp_b * const bSep = cmpp_b_borrow(args->pp);
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
bool addNl = !args->isDxCall;
int n = 0;
if( !b || !bSep ) goto end;
if( !dx->args.argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"%s%s expects ?flags? ...args",
cmpp_dx_delim(dx), args->d->name.z);
goto end;
}
cmpp_b_append_ch(bSep, ' ');
cmpp_check_oom(args->pp, bSep->z);
for( cmpp_arg const * arg = dx->args.arg0; arg
&& !b->errCode
&& !bSep->errCode
&& !cmpp_err_has(args->pp);
arg = arg->next ){
#define FLAG(X)if( cmpp_arg_isflag_c(arg, X) )
FLAG("-s"){
if( !arg->next ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Missing SEPARATOR argument to -s.");
break;
}
cmpp_arg_interpolate(args->pp, arg->next,
cmpp_b_reuse(bSep),
cmpp_arg_interpolate_BRACE_CALL);
arg = arg->next;
/* FIXME: unescape bSep */
continue;
}
//FLAG("-nl"){ addNl=true; continue; }
FLAG("-nonl"){ addNl=false; continue; }
#undef FLAG
if( n++ && cmpp_out_raw(args->pp, bSep->z, bSep->n) ){
break;
}
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), a2bFlags) ){
break;
}
cmpp_out_raw(args->pp, b->z, b->n);
}
if( !cmpp_err_has(args->pp) ){
if( !n ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting at least one argument.");
}else if( addNl ){
cmpp_out_raw(args->pp, "\n", 1);
}
}
end:
cmpp_b_return(args->pp, b);
cmpp_b_return(args->pp, bSep);
}
/* Impl. for #file */
static void cmpp_f_file(cmpp_f_args const * args){
cmpp_dx * const dx = args->dx;
enum e_op {
op_none, op_exists, op_join
};
cmpp_b * const b0 = cmpp_b_borrow(args->pp);
if( !b0 ) goto end;
enum e_op op = op_none;
cmpp_arg const * arg = 0;
#define nextArg if( ++i==args->argc ) goto missing_arg; arg = args->argv[i]
unsigned i = 1;
for(; i < args->argc; ++i ){
arg = args->argv[i];
if( op_none==op ){
if( cmpp_arg_equals_c(arg, "exists") ){
nextArg;
op = op_exists;
if( i+1<args->argc ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"%s%s exists: too many arguments",
cmpp_dx_delim(dx), args->d->name.z);
goto end;
}
break;
}else if( cmpp_arg_equals_c(arg, "join") ){
nextArg;
op = op_join;
break;
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unknown %s%s command: %s",
cmpp_dx_delim(dx), args->d->name.z, arg->z);
goto end;
}
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"%s%s unhandled argument: %s",
cmpp_dx_delim(dx), args->d->name.z, arg->z);
goto end;
}
}
cmpp_flag32_t const bFlags = cmpp_arg_interpolate_BRACE_CALL;
switch( op ){
case op_none: goto missing_arg;
case op_join: {
assert( arg );
for( int j = 0; arg; ++j ){
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags)
|| (j && cmpp_out_raw(args->pp, "/", 1))
|| (b0->n && cmpp_out_raw(args->pp, b0->z, b0->n)) ){
break;
}
arg = ++i==args->argc ? 0 : args->argv[i]
/* The internals actually NULL-pad argv, so we don't _need_
to do that ternary, but that detail is not part of the
public API and i'm not yet sure whether it should be. */;
}
cmpp_out_raw(args->pp, "\n", 1);
break;
}
case op_exists: {
assert( arg );
if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags) ){
bool const b = cmpp__file_is_readable((char const *)b0->z);
cmpp_out_raw(args->pp, b ? "1\n" : "0\n", 2);
}
break;
}
}
#undef nextArg
end:
cmpp_b_return(args->pp, b0);
return;
missing_arg:
if( arg ){
cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s %s: missing argument",
cmpp_dx_delim(dx), args->d->name.z, arg->z );
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s: missing subcommand",
cmpp_dx_delim(dx), args->d->name.z);
}
goto end;
}
/**
#cmp LHS op RHS
*/
static void cmpp_f_cmp(cmpp_f_args const * args){
cmpp_b * const bL = cmpp_b_borrow(args->pp);
cmpp_b * const bR = cmpp_b_borrow(args->pp);
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
if( !bL || !!bR ) goto end;
for( cmpp_arg const * arg = args->dx->args.arg0; arg
&& !cmpp_err_has(args->pp);
arg = arg->next ){
if( !bL->z ){
cmpp_arg_interpolate(args->pp, arg, bL, a2bFlags);
continue;
}
if( !bR->z ){
cmpp_arg_interpolate(args->pp, arg, bR, a2bFlags);
continue;
}
break;
}
if( cmpp_err_has(args->pp) ) goto end;
if( !bL->z || !bR->z ){
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Usage: LHS RHS");
goto end;
}
assert( bL->z );
assert( bR->z );
char cbuf[20];
int const cmp = strcmp((char*)bL->z, (char*)bR->z);
int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp);
assert(n>0);
cmpp_out_raw(args->pp, cbuf, (cmpp_size_t)n);
end:
cmpp_b_return(args->pp, bL);
cmpp_b_return(args->pp, bR);
}
/** Impl for #base64. */
static void cmpp_f_base64(cmpp_f_args const * args){
bool comma = false;
bool quote = false;
cmpp_arg const * argFile = 0;
bool preprocessFileArg = false;
cmpp_b * const ob = cmpp_b_borrow(args->pp);
if( !ob ){
goto end;
}
for( unsigned i = 1; i < args->argc; ++i ){
cmpp_arg const * arg = args->argv[i];
#define M(STR) cmpp_arg_equals_c(arg, STR)
if( M("-comma") ) comma = true;
else if( M("-quote") ) quote = true;
else if( M("-f") || M("-file")
|| (preprocessFileArg=M("-F")) || (preprocessFileArg=M("-FILE"))
){
if( ++i==args->argc ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Expecting an argument after %s.", arg->z);
goto end;
}
argFile = arg = args->argv[i];
continue;
}else{
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unhandled argument: %s", arg->z);
goto end;
}
#undef M
}
if( argFile ){
if(cmpp_arg_consume_as_file(args->dx, argFile, NULL, ob,
preprocessFileArg) ){
goto end;
}
}else{
if( args->isDxCall ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"#%s...#/%s is not legal for a [call]. "
"Pass the -f FILENAME flag instead.",
args->d->name.z, args->d->name.z);
goto end;
}else{
assert( !ob->n );
if( cmpp_dx_consume_b(args->dx, ob, &args->d->closer, 1,
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
goto end;
}
}
}
if( ob->n ){
cmpp_b * const b64 = cmpp_b_borrow(args->pp);
if( !b64 ) goto end;
int cmpp__b_base64_encode(cmpp *, cmpp_b const *, cmpp_b *)/* in b.c */;
cmpp__b_base64_encode(args->pp, ob, b64);
cmpp_b_swap(ob, b64);
cmpp_b_return(args->pp, b64);
if( cmpp_err_has(args->pp) ) goto end;
}
unsigned char const * zEnd = ob->z + ob->n;
#define out(P,N) if( cmpp_out_raw(args->pp, P, N) ) goto end
if( !quote ){
cmpp_out_raw(args->pp, ob->z, ob->n);
goto end;
}
for( unsigned char const * z = ob->z; z < zEnd; ++z ){
if( quote ){
out("\"", 1);
}
bool closed = false;
for( ;z<zEnd;++z ){
if( '\n'==*z ){
if( quote ){
if( comma && z+1<zEnd ){
out("\\n\",\n",5);
}else{
out("\\n\"\n",4);
}
}else{
out(z, 1);
}
closed = true;
break;
}else{
out(z, 1);
}
}
if( quote && !closed ){
out("\\n\"\n",4);
}
}
#undef out
end:
cmpp_b_return(args->pp, ob);
return;
}
/**
If zName matches one of the delayed-load directives, that directive
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
no match is found, but pp's error state is not updated in that
case. If a match is found and registration fails, that result code
will propagate via pp.
*/
int cmpp__d_delayed_load(cmpp *pp, char const *zName){
if( ppCode ) return ppCode;
int rc = CMPP_RC_NO_DIRECTIVE;
unsigned const nName = strlen(zName);
pp->pimpl->flags.isInternalDirectiveReg = true;
#define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME))
#define M_OC(NAME) (M(NAME) || M("/" NAME))
#define M_IF(NAME) if( M(NAME) )
#define CF(X) cmpp_f_F_ ## X
#define F_A_RAW CF(ARGS_RAW)
#define F_A_V4 CF(ARGS_V4)
#define F_A_LIST CF(ARGS_LIST)
#define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY)
#define F_UNSAFE cmpp_f_F_NOT_IN_SAFEMODE
#define F_NC cmpp_f_F_NO_CALL
#define F_CALL cmpp_f_F_CALL_ONLY
#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \
cmpp_d_reg SYMNAME = { \
.name = NAME, \
.opener = { \
.f = OPENER, \
.flags = OFLAGS \
}, \
.closer = { \
.f = CLOSER, \
.flags = CFLAGS \
}, \
.dtor = 0, \
.state = 0, \
.arity = -1, \
.usage = "#directive .usage is TODO. See README.md for docs." \
}
#define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS ) \
DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \
rc = cmpp_d_register(pp, &rReg, NULL); \
goto end
/* The #if family requires some hand-holding... */
if( M_OC("if") || M("elif") || M("else") ) {
DREG0(rIf, "if",
cmpp_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL),
cmpp_f_if_dangler, 0);
DREG0(rElif, "elif",
cmpp_f_if_dangler, F_NC,
0, 0);
DREG0(rElse, "else",
cmpp_f_if_dangler, F_NC,
0, 0);
CmppIfState * const cis = cmpp_malloc2(pp, sizeof(*cis));
if( !cis ) goto end;
memset(cis, 0, sizeof(*cis));
rIf.state = cis;
rIf.dtor = cmpp_mfree;
if( cmpp_d_register(pp, &rIf, &cis->dIf)
/* rIf must be first to avoid leaking cis on error */
|| cmpp_d_register(pp, &rElif, &cis->dElif)
|| cmpp_d_register(pp, &rElse, &cis->dElse) ){
rc = ppCode;
}else{
assert( cis->dIf && cis->dElif && cis->dElse );
assert( !cis->dEndif );
assert( cis == cis->dIf->impl.state );
assert( cmpp_mfree==cis->dIf->impl.dtor );
cis->dElif->impl.state
= cis->dElse->impl.state
= cis;
cis->dElif->closer
= cis->dElse->closer
= cis->dEndif
= cis->dIf->closer;
rc = 0;
}
goto end;
}/* #if and friends */
/* Basic core directives... */
#define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS) \
if( M_OC(N) ){ \
DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS); \
} (void)0
M_IF_CORE("@", cmpp_f_at, F_A_LIST,
cmpp_f_dangling_closer, 0);
M_IF_CORE("arg", cmpp_f_arg, F_A_LIST, 0, 0);
M_IF_CORE("assert", cmpp_f_expr, F_EXPR, 0, 0);
M_IF_CORE("base64", cmpp_f_base64, F_A_V4,
cmpp_f_dangling_closer, 0);
M_IF_CORE("cmp", cmpp_f_cmp, F_A_LIST, 0, 0);
M_IF_CORE("define", cmpp_f_define, F_A_V4,
cmpp_f_dangling_closer, 0);
M_IF_CORE("delimiter", cmpp_f_delimiter, F_A_LIST,
cmpp_f_dangling_closer, 0);
M_IF_CORE("error", cmpp_f_error, F_A_RAW, 0, 0);
M_IF_CORE("eval", cmpp_f_eval, F_A_V4,
cmpp_f_dangling_closer, 0);
M_IF_CORE("expand", cmpp_f_expand, F_A_V4,
cmpp_f_dangling_closer, 0);
M_IF_CORE("expr", cmpp_f_expr, F_EXPR, 0, 0);
M_IF_CORE("join", cmpp_f_join, F_A_LIST, 0, 0);
M_IF_CORE("once", cmpp_f_once, F_A_LIST | F_NC,
cmpp_f_dangling_closer, 0);
M_IF_CORE("pragma", cmpp_f_pragma, F_A_V4, 0, 0);
M_IF_CORE("savepoint", cmpp_f_savepoint, F_A_V4, 0, 0);
M_IF_CORE("stderr", cmpp_f_stderr, F_A_RAW, 0, 0);
M_IF_CORE("sum", cmpp_f_sum, F_A_V4, 0, 0);
M_IF_CORE("undef", cmpp_f_undef, F_A_V4, 0, 0);
M_IF_CORE("undefined-policy", cmpp_f_undef_policy, F_A_V4, 0, 0);
M_IF_CORE("//", cmpp_f_noop, F_A_RAW, 0, 0);
M_IF_CORE("file", cmpp_f_file,
F_A_V4 | F_UNSAFE, 0, 0);
#undef M_IF_CORE
/* Directives which can be disabled via build flags or
flags to cmpp_ctor()... */
#define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \
M_IF(NAME) { \
if( 0==(FLAG & pp->pimpl->flags.ctorFlags) ) { \
DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \
} \
goto end; \
}
#ifndef CMPP_OMIT_D_INCLUDE
M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE,
cmpp_f_include, F_A_V4 | F_UNSAFE,
0, 0);
#endif
#ifndef CMPP_OMIT_D_DB
M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB,
cmpp_f_attach, F_A_V4 | F_UNSAFE,
0, 0);
M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB,
cmpp_f_detach, F_A_V4 | F_UNSAFE,
0, 0);
if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.ctorFlags)
&& (M_OC("query") || M("query:no-rows")) ){
DREG0(rQ, "query", cmpp_f_query, F_A_V4 | F_UNSAFE,
cmpp_f_dangling_closer, 0);
cmpp_d * dQ = 0;
rc = cmpp_d_register(pp, &rQ, &dQ);
if( rc ) goto end;
/*
It would be preferable to delay registration of query:no-rows
until we need it, but doing so causes an error when:
|#if 0
|#query
|...
|#query:no-rows HERE
|...
|#/query
|#/if
Because query:no-rows won't have been registered, and unknown
directives are an error even in skip mode. Maybe they shouldn't
be. Maybe we should just skip them in skip mode. That's only
been an issue since doing delayed registration of directives, so
it's not come up until recently (as of 2025-10-27). i was so
hoping to be able to get _rid_ of skip mode at some point.
*/
cmpp_d * dNoRows = 0;
cmpp_d_reg const rNR = {
.name = "query:no-rows",
.arity = -1,
.opener = {
.f = cmpp_f_dangling_closer,
.flags = F_NC
}
};
rc = cmpp_d_register(pp, &rNR, &dNoRows);
if( rc ) goto end;
dNoRows->closer = dQ->closer;
assert( !dQ->impl.state );
dQ->impl.state = dNoRows;
goto end;
}
#endif /*CMPP_OMIT_D_DB*/
#undef M_IF_FLAGGED
#ifndef NDEBUG
M_IF("experiment"){
DREG("experiment", cmpp_f_experiment,
F_A_V4 | F_UNSAFE, 0, 0);
}
#endif
end:
#undef DREG
#undef DREG0
#undef F_EXPR
#undef F_A_RAW
#undef F_A_LIST
#undef F_A_V4
#undef F_UNSAFE
#undef F_NC
#undef F_CALL
#undef CF
#undef M
#undef M_OC
#undef M_IF
pp->pimpl->flags.isInternalDirectiveReg = false;
return ppCode ? ppCode : rc;
}
#include <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
#include <assert.h>
#include <errno.h>
const cmpp_args_pimpl cmpp_args_pimpl_empty =
cmpp_args_pimpl_empty_m;
const cmpp_args cmpp_args_empty = cmpp_args_empty_m;
const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m;
CMPP__EXPORT(bool, cmpp_arg_equals)(cmpp_arg const *arg,
char const *z){
return 0==memcmp(arg->z, z, arg->n)
? arg->n==strlen(z)
: 0;
}
CMPP__EXPORT(char const *,cmpp_arg_cstr)(cmpp_arg const *arg){
return (char const *)arg->z;
}
CMPP__EXPORT(char const *,cmpp_arg_cstr2)(cmpp_arg const *arg,
cmpp_size_t *n){
*n = arg->n;
return (char const *)arg->z;
}
//just in case these ever get dynamic state
void cmpp_arg_cleanup(cmpp *pp, cmpp_arg *arg){
if( arg ){
(void)pp;
*arg = cmpp_arg_empty;
}
}
void cmpp_arg_reuse(cmpp_arg *arg){
if( arg ){
*arg = cmpp_arg_empty;
}
}
void CmppArgList_reuse(cmpp*pp, CmppArgList *li){
for(cmpp_size_t n = li->na; n; ){
cmpp_arg * const arg = &li->list[--n];
if( arg ){
(void)pp;
cmpp_arg_reuse( arg );
}
assert( !li->list[n].next );
}
if( li->avb ) cmpp_b_reuse(li->avb);
li->n = 0;
}
void CmppArgList_dtor(cmpp *pp, CmppArgList *li){
static const CmppArgList CmppArgList_empty = CmppArgList_empty_m;
assert(pp);
while( li->na ){
cmpp_arg * const arg = &li->list[--li->na];
if( arg ){
cmpp_arg_cleanup(pp, arg);
}
}
cmpp_mfree(li->list);
cmpp_b_return(pp, li->avb);
*li = CmppArgList_empty;
}
cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp, CmppArgList*li,
bool ensureNulTerminated){
if( li->n ){
if( !li->avb ){
li->avb = cmpp_b_borrow(pp);
if( !li->avb ) return 0;
}
cmpp_size_t sz = (
/* argv */
sizeof(cmpp_arg*) * (li->n+1/*NULL-pad it*/)
);
cmpp_size_t const strOffset =
/* Offset into li->abv->z of the string table */ sz;
if( ensureNulTerminated ){
for(cmpp_size_t i = 0; i < li->n; ++i ){
cmpp_arg const * const a = &li->list[i];
if( a->n && a->z[a->n]/* not NUL-terminated*/ ){
sz += a->n + 1/*NUL*/;
}
}
}
if( cmpp_b_reserve(li->avb, sz) ){
cmpp_b_return(pp, li->avb);
li->avb = 0;
return 0;
}
li->avb->n = sz;
/* Direct argv[X] to a suitable pointer and argv[X]->z
to a NUL-terminated copy of itself. */
cmpp_arg const ** const av = (cmpp_arg const **)li->avb->z;
unsigned char * oz = li->avb->z + strOffset;
for(cmpp_size_t i = 0; i < li->n; ++i){
cmpp_arg * const a = &li->list[i];
av[i] = a;
if( ensureNulTerminated
&& a->n && a->z[a->n]/* not NUL-terminated*/ ){
//g_warn("arg: %.*s", (int)a->n, a->z);
assert( oz < (li->avb->z + li->avb->n - a->n) );
memcpy(oz, a->z, a->n);
a->z = oz;
oz += a->n;
*oz++ = 0;
}
#if 0
/* Legacy cruft we don't want at this level */
if( i && av[i] && av[i-1] ){
av[i-1]->next = av[i];
}
#endif
}
av[li->n] = 0;
return av;
}
return 0;
}
cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){
cmpp_arg * p = 0;
assert( li->list ? li->na : 0==li->na );
if( 0==ppCode
&& 0==CmppArgList_reserve(pp, li,
cmpp__li_reserve1_size(li,10)) ){
p = &li->list[li->n++];
cmpp_arg_reuse( p );
if( li->avb ){
cmpp_b_return(pp, li->avb);
li->avb = 0;
}
}
return p;
}
#if 0
/** Returns the most-recently-appended arg of li back to li's
free-list. */
static void CmppArgList_unappend(CmppArgList *li){
assert( li->n );
if( li->n ){
cmpp_arg_reuse( &li->list[--li->n] );
}
}
#endif
void cmpp__args_pimpl_cleanup(cmpp_args_pimpl *p){
assert( p->pp );
assert( !p->nextFree );
cmpp_b_clear(&p->argOut);
CmppArgList_dtor(p->pp, &p->argli);
*p = cmpp_args_pimpl_empty;
}
static void cmpp__args_pimpl_reuse(cmpp_args_pimpl *p){
assert( p->pp );
assert( !p->nextFree );
cmpp_b_reuse(&p->argOut);
CmppArgList_reuse(p->pp, &p->argli);
assert( !p->argOut.n );
assert( !p->argli.n );
}
static void cmpp__args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){
if( p ){
assert( p->pp );
cmpp__pi(pp);
assert( !p->nextFree );
cmpp__args_pimpl_reuse(p);
p->nextFree = pi->recycler.argPimpl;
pi->recycler.argPimpl = p;
}
}
static cmpp_args_pimpl * cmpp__args_pimpl_borrow(cmpp *pp){
cmpp__pi(pp);
cmpp_args_pimpl * p = 0;
if( pi->recycler.argPimpl ){
p = pi->recycler.argPimpl;
pi->recycler.argPimpl = p->nextFree;
p->nextFree = 0;
p->pp = pp;
assert( !p->argOut.n && "Buffer was used when not borrowed" );
}else{
p = cmpp_malloc2(pp, sizeof(*p));
if( 0==cmpp_check_oom(pp, p) ) {
*p = cmpp_args_pimpl_empty;
p->pp = pp;
}
}
return p;
}
CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){
if( a ){
if( a->pimpl ){
cmpp * const pp = a->pimpl->pp;
assert( pp );
if( pp ){
cmpp__args_pimpl_return(pp, a->pimpl);
}else{
cmpp__args_pimpl_cleanup(a->pimpl);
cmpp_mfree(a->pimpl);
}
}
*a = cmpp_args_empty;
}
}
//CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a)
void cmpp__args_reuse(cmpp_args *a){
cmpp_args_pimpl * const p = a->pimpl;
if( p ) cmpp__args_pimpl_reuse(p);
*a = cmpp_args_empty;
a->pimpl = p;
}
int cmpp__args_init(cmpp * pp, cmpp_args * a){
if( 0==ppCode ){
if( a->pimpl ){
assert( a->pimpl->pp == pp );
cmpp__args_reuse(a);
assert(! a->pimpl->argOut.n );
assert( a->pimpl->pp == pp );
}else{
a->pimpl = cmpp__args_pimpl_borrow(pp);
assert( !a->pimpl || a->pimpl->pp==pp );
}
}
return ppCode;
}
/**
Declare cmpp_argOp_f_NAME().
*/
#define cmpp_argOp_decl(NAME) \
static void cmpp_argOp_f_ ## NAME (cmpp *pp, \
cmpp_argOp const *op, \
cmpp_arg const *vLhs, \
cmpp_arg const **pvRhs, \
int *pResult)
cmpp_argOp_decl(compare);
#if 0
cmpp_argOp_decl(logical1);
cmpp_argOp_decl(logical2);
cmpp_argOp_decl(defined);
#endif
static const struct {
const cmpp_argOp opAnd;
const cmpp_argOp opOr;
const cmpp_argOp opGlob;
const cmpp_argOp opNotGlob;
const cmpp_argOp opNot;
const cmpp_argOp opDefined;
#define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge)
#define E(NAME) const cmpp_argOp op ## NAME;
cmpp_argOps_cmp_map(E)
#undef E
} cmpp_argOps = {
.opAnd = {
.ttype = cmpp_tt_OpAnd,
.arity = 2,
.assoc = 0,
.xCall = 0//cmpp_argOp_f_logical2
},
.opOr = {
.ttype = cmpp_tt_OpOr,
.arity = 2,
.assoc = 0,
.xCall = 0//cmpp_argOp_f_logical2
},
.opGlob = {
.ttype = cmpp_tt_OpGlob,
.arity = 2,
.assoc = 0,
.xCall = 0//cmpp_argOp_f_glob
},
.opNotGlob = {
.ttype = cmpp_tt_OpNotGlob,
.arity = 2,
.assoc = 0,
.xCall = 0//cmpp_argOp_f_glob
},
.opNot = {
.ttype = cmpp_tt_OpNot,
.arity = 1,
.assoc = 1,
.xCall = 0//cmpp_argOp_f_logical1
},
.opDefined = {
.ttype = cmpp_tt_OpDefined,
.arity = 1,
.assoc = 1,
.xCall = 0//cmpp_argOp_f_defined
},
/* Comparison ops... */
#define E(NAME) .op ## NAME = { \
.ttype = cmpp_tt_OpCmp ## NAME, .arity = 2, .assoc = 0, \
.xCall = cmpp_argOp_f_compare },
cmpp_argOps_cmp_map(E)
#undef E
};
cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt_e tt){
switch(tt){
case cmpp_tt_OpAnd: return &cmpp_argOps.opAnd;
case cmpp_tt_OpOr: return &cmpp_argOps.opOr;
case cmpp_tt_OpGlob: return &cmpp_argOps.opGlob;
case cmpp_tt_OpNot: return &cmpp_argOps.opNot;
case cmpp_tt_OpDefined: return &cmpp_argOps.opDefined;
#define E(NAME) case cmpp_tt_OpCmp ## NAME: return &cmpp_argOps.op ## NAME;
cmpp_argOps_cmp_map(E)
#undef E
default: return NULL;
}
}
#define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype)
/**
An internal helper for cmpp_argOp_...(). It binds some value of
*paArg to column bindNdx of query q and sets *paArg to the next
argument to be consumed. This function expects that q is set up to
do the right thing when *paArg is a Word-type value (see
cmpp_argOp_f_compare()).
*/
static void cmpp_argOp__cmp_bind(cmpp * const pp,
sqlite3_stmt * const q,
int bindNdx,
cmpp_arg const ** paArg){
cmpp_arg const * const arg = *paArg;
assert(arg);
switch( ppCode ? 0 : arg->ttype ){
case 0: break;
case cmpp_tt_Word:
/* In this case, q is supposed to be set up to use
CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word
and performs the expansion to its value in the query. */
cmpp_stmt_bind_textn(pp, q, bindNdx, arg->z, arg->n);
*paArg = arg->next;
break;
case cmpp_tt_StringBT:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:
case cmpp_tt_IntDec:{
cmpp__bind_arg(pp, q, bindNdx, arg);
*paArg = arg->next;
break;
}
case cmpp_tt_OpNot:
case cmpp_tt_OpDefined:
case cmpp_tt_GroupParen:{
int rv = 0;
if( 0==cmpp__arg_toBool(pp, arg, &rv, paArg) ){
cmpp_stmt_bind_int(pp, q, bindNdx, rv);
}
*paArg = arg->next;
break;
}
/* TODO? cmpp_tt_GroupParen */
default:
cmpp_errf(pp, CMPP_RC_TYPE,
"Invalid argument type (%s) for the comparison "
"queries: %s",
cmpp_tt_cstr(arg->ttype), arg->z);
}
}
/**
Internal helper for cmp_argOp_...().
Expects q to be a query with an integer in result column 0. This
steps/resets the query and applies the given comparison operator's
logic to column 0's value, placing the result of the operator in
*pResult.
If q has no result row, a default value of 0 is assumed.
*/
static void cmpp_argOp__cmp_apply(cmpp * const pp,
cmpp_argOp const * const op,
sqlite3_stmt * const q,
int * const pResult){
if( 0==ppCode ){
int rc = cmpp_stmt_step(pp, q, false);
assert( SQLITE_ROW==rc || ppCode );
if( SQLITE_ROW==rc ){
rc = sqlite3_column_int(q, 0);
}else{
rc = 0;
}
switch( op->ttype ){
case 0: break;
case cmpp_tt_OpCmpEq: *pResult = 0==rc; break;
case cmpp_tt_OpCmpNeq: *pResult = 0!=rc; break;
case cmpp_tt_OpCmpLt: *pResult = rc<0; break;
case cmpp_tt_OpCmpLe: *pResult = rc<=0; break;
case cmpp_tt_OpCmpGt: *pResult = rc>0; break;
case cmpp_tt_OpCmpGe: *pResult = rc>=0; break;
default:
cmpp__fatal("Cannot happen: invalid arg mapping");
}
}
cmpp_stmt_reset(pp, q);
}
/**
Applies *paRhs as the RHS of an integer binary operator, the LHS of
which is the lhs argument. The result is put in *pResult. On
success *paRhs is set to the next argument for the expression to
parse.
*/
static void cmpp_argOp_applyTo(cmpp *pp,
cmpp_argOp const * const op,
int lhs,
cmpp_arg const ** paRhs,
int * pResult){
sqlite3_stmt * q = 0;
cmpp_arg const * aRhs = *paRhs;
assert(aRhs);
q = cmpp_tt_Word==aRhs->ttype
? cmpp__stmt(pp, CmppStmt_cmpVD, false)
: cmpp__stmt(pp, CmppStmt_cmpVV, false);
if( q ){
char numbuf[32];
int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs);
cmpp_stmt_bind_textn(pp, q, 1, ustr_c(numbuf), nNum);
cmpp_argOp__cmp_bind(pp, q, 2, paRhs);
cmpp_argOp__cmp_apply(pp, op, q, pResult);
}
}
cmpp_argOp_decl(compare){
cmpp_arg const * const vRhs = *pvRhs;
sqlite3_stmt * q = 0;
/* Select which query to use, depending on whether each
of the LHS/RHS are Word tokens. For Word tokens
the corresponding query columns get bound to
a subquery which resolves the word. Non-word
tokens get bound as-is. */
if( cmpp_tt_Word==vLhs->ttype ){
q = cmpp_tt_Word==vRhs->ttype
? cmpp__stmt(pp, CmppStmt_cmpDD, false)
: cmpp__stmt(pp, CmppStmt_cmpDV, false);
if(0){
g_warn("\nvLhs=%s %s\nvRhs=%s %s\n",
cmpp_tt_cstr(vLhs->ttype), vLhs->z,
cmpp_tt_cstr(vRhs->ttype), vRhs->z);
}
}else if( cmpp_tt_Word==vRhs->ttype ){
q = cmpp__stmt(pp, CmppStmt_cmpVD, false);
}else{
q = cmpp__stmt(pp, CmppStmt_cmpVV, false);
}
if( q ){
//cmpp_stmt_bind_textn(pp, q, 1, vLhs->z, vLhs->n);
cmpp_argOp__cmp_bind(pp, q, 1, &vLhs);
cmpp_argOp__cmp_bind(pp, q, 2, pvRhs);
cmpp_argOp__cmp_apply(pp, op, q, pResult);
}
}
#undef cmpp_argOp_decl
#if 0
static inline int cmpp_dxt_isBinOp(cmpp_tt tt){
cmpp_argOp const * const a = cmpp_argOp_for_tt(tt);
return a ? 2==a->arity : 0;
}
static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){
return tt==cmpp_tt_OpNot || cmpp_tt_OpDefined;
}
static inline int cmpp_dxt_isGroup(cmpp_tt tt){
return tt==cmpp_tt_GroupParen || tt==cmpp_tt_GroupBrace || cmpp_tt_GroupSquiggly;
}
#endif
int cmpp__arg_evalSubToInt(cmpp *pp,
cmpp_arg const *arg,
int * pResult){
cmpp_args sub = cmpp_args_empty;
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
cmpp__args_evalToInt(pp, &sub, pResult);
}
cmpp_args_cleanup(&sub);
return ppCode;
}
int cmpp__args_evalToInt(cmpp * const pp,
cmpp_args const *pArgs,
int * pResult){
if( ppCode ) return ppCode;
cmpp_arg const * pNext = 0;
cmpp_arg const * pPrev = 0;
int result = *pResult;
cmpp_b osL = cmpp_b_empty;
cmpp_b osR = cmpp_b_empty;
static int level = 0;
++level;
#define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__)
//lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z);
for( cmpp_arg const *arg = pArgs->arg0;
arg && 0==ppCode;
pPrev = arg, arg = pNext ){
pNext = arg->next;
if( cmpp_tt_Noop==arg->ttype ){
arg = pPrev /* help the following arg to DTRT */;
continue;
}
cmpp_argOp const * const thisOp = argOp(arg);
cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0;
if( 0 ){
lout("arg: %s @%p %s\n",
cmpp__tt_cstr2(arg->ttype, true), arg, arg->z);
if(1){
if( pPrev ) lout(" prev arg: %s %s\n",
cmpp__tt_cstr2(pPrev->ttype, true), pPrev->z);
if( pNext ) lout(" next arg: %s %s\n",
cmpp__tt_cstr2(pNext->ttype, true), pNext->z);
}
}
if( thisOp ){ /* Basic validation */
if( !pNext ){
serr("Missing '%s' RHS.",
cmpp__tt_cstr2(thisOp->ttype, false));
break;
}else if( !pPrev && 2==thisOp->arity ){
serr("Missing %s LHS.",
cmpp__tt_cstr2(thisOp->ttype, false));
break;
}
if( nextOp && nextOp->arity>1 ){
serr("Invalid '%s' RHS: %s", arg->z, pNext->z);
break;
}
}
switch( arg->ttype ){
case cmpp_tt_OpNot:
case cmpp_tt_OpDefined:
if( pPrev && !argOp(pPrev) ){
cmpp_errf(pp, CMPP_RC_CANNOT_HAPPEN,
"We expected to have consumed '%s' by "
"this point.",
pPrev->z);
}else{
cmpp__arg_toBool(pp, arg, &result, &pNext);
}
break;
case cmpp_tt_OpAnd:
case cmpp_tt_OpOr:{
assert( pNext );
assert( pPrev );
/* Reminder to self: we can't add short-circuiting of the RHS
right now because the handling of chained unary ops on the
RHS is handled via cmpp__arg_toBool(). */
int rv = 0;
if( 0==cmpp__arg_toBool(pp, pNext, &rv, &pNext) ){
if( cmpp_tt_OpAnd==arg->ttype ) result = result && rv;
else result = result || rv;
}
//g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0);
break;
}
case cmpp_tt_OpNotGlob:
case cmpp_tt_OpGlob:{
assert( pNext );
assert( pPrev );
assert( pNext!=arg );
assert( pPrev!=arg );
if( cmpp_arg_interpolate(pp, pNext, &osL, 0) ){
break;
}
unsigned char const * const zGlob = osL.z;
if( 0==cmpp_arg_interpolate(pp, pPrev, &osR, 0) ){
if( 0 ){
g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z);
}
result = 0==sqlite3_strglob((char const *)zGlob,
(char const *)osR.z);
if( cmpp_tt_OpNotGlob==arg->ttype ){
result = !result;
}
//g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result);
}
pNext = pNext->next;
break;
}
#define E(NAME) case cmpp_tt_OpCmp ## NAME:
cmpp_argOps_cmp_map(E) {
cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0;
if( prevOp ){
/* Chained operators */
cmpp_argOp_applyTo(pp, thisOp, result, &pNext, &result);
}else{
assert( pNext );
assert( pPrev );
assert( thisOp );
assert( thisOp->xCall );
thisOp->xCall(pp, thisOp, pPrev, &pNext, &result);
}
break;
}
#undef E
#define checkConsecutiveNonOps \
if( pPrev && !argOp(pPrev) ){ \
serr("Illegal consecutive non-operators: %s %s", \
pPrev->z, arg->z); \
break; \
}(void)0
case cmpp_tt_IntDec:
case cmpp_tt_StringBT:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:
checkConsecutiveNonOps;
if( !cmpp_is_int(arg->z, arg->n, &result) ){
/* This is mostly for and/or ops. glob will reach back and
grab arg->z. */
result = 0;
}
break;
case cmpp_tt_Word:
checkConsecutiveNonOps;
cmpp__get_int(pp, arg->z, arg->n, &result);
break;
case cmpp_tt_GroupParen:{
checkConsecutiveNonOps;
cmpp_args sub = cmpp_args_empty;
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
cmpp__args_evalToInt(pp, &sub, &result);
}
cmpp_args_cleanup(&sub);
break;
}
case cmpp_tt_GroupBrace:{
checkConsecutiveNonOps;
cmpp_b b = cmpp_b_empty;
if( 0==cmpp_call_str(pp, arg->z, arg->n, &b, 0) ){
cmpp_is_int(b.z, b.n, &result);
}
cmpp_b_clear(&b);
break;
}
#undef checkConsecutiveNonOps
default:
assert( arg->z );
serr("Illegal expression token %s: %s",
cmpp__tt_cstr2(arg->ttype, true), arg->z);
}/*switch(arg->ttype)*/
}/* foreach arg */
if( 0 ){
lout("END %s() result=%d\n", __func__, result);
}
--level;
if( !ppCode ){
*pResult = result;
}
cmpp_b_clear(&osL);
cmpp_b_clear(&osR);
return ppCode;
#undef lout
}
#undef argOp
#undef cmpp_argOp_decl
/**
Workaround for legacy (pre-2026-08-30) behaviour: trim
leading spaces and one leading newline on groups. We do not
really want cmpp_tizer_next() to do that so we'll account
for it here to avoid a regression causing extra output
lines for directive arguments. This was only ever
relevant for squiggly groups, as neither (...) nor
[...] were ever used in ways which affected output.
A sample case:
#define x {
blah @foo@
}
#expand x {foo -> 1}
If we don't retain the legacy behavior then such constructs start
producing a stray newline where they didn't before.
*/
void cmpp__squiggly_strip_lead(cmpp_token *tok){
if( cmpp_tt_GroupSquiggly!=tok->ttype
|| !tok->nInner ) return;
if( tok->nInner ){
unsigned char const * inz = tok->zInner;
unsigned char const * enz = tok->zInner + tok->nInner;
cmpp_skip_space(&inz, tok->z+tok->nInner);
if( inz < tok->zInner+tok->nInner ){
if( '\n'==*inz ) ++inz;
else if(inz+1<enz && '\r'==*inz && '\n'==inz[1]) inz+=2;
//g_warn("trimmed to %.*s", (int)(tok->zInner+tok->nInner - inz), inz);
}
tok->n = tok->nInner = (tok->zInner+tok->nInner - inz);
tok->z = tok->zInner = inz;
}
}
static void cmpp__t2a_ttype_set(cmpp_token const *tok,
cmpp_arg * arg){
arg->ttype = tok->ttype;
arg->token = *tok;
if( cmpp_tt_Word==arg->ttype ){
int const ttop = cmpp__tt_tok_op(tok->z, tok->n);
if( ttop ){
//g_warn("ttop %s", cmpp_tt_cstr(ttop));
arg->ttype = ttop;
}
/*else if( cmpp_is_int64(arg->z, arg->n, 0) ){
arg->ttype = cmpp_tt_IntDec;
}*/
}
}
CMPP__EXPORT(int, cmpp_args_parse)(cmpp * const pp,
cmpp_args * const pArgs,
unsigned char const * const zInBegin,
cmpp_strlen_t nIn,
cmpp_flag32_t flags){
assert( zInBegin );
unsigned char const * const zInEnd =
zInBegin + cmpp__strlenu(zInBegin, nIn);
nIn = cmpp__strlenu(zInBegin, nIn);
cmpp_tizer tz = cmpp_tizer_empty;
cmpp_token * const tok = &tz.token;
int rc = 0;
cmpp_b * bUnesc = 0;
unsigned char * zOut = 0;
unsigned char const * zOutEnd = 0;
#if !defined(NDEBUG)
unsigned char const * zReallocCheck = 0;
#endif
cmpp_tizer_init(&tz, &pp->pimpl->err,
zInBegin, (cmpp_size_t)nIn);
if( cmpp__args_init(pp, pArgs) ) return ppCode;
if( 0 ){
g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin),
zInBegin);
}
cmpp_size_t const nBuffer =
/* Buffer size for our copy of the args. We need to know the
size before we start so that we can have each arg reliably
point back into this without it being reallocated during
parsing. */
(cmpp_size_t)(zInEnd - zInBegin)
/* Plus we need one final NUL and one NUL byte per argument, but
we don't yet know how many arguments we will have, so let's
estimate... */
+ ((cmpp_size_t)(zInEnd - zInBegin))/2
+ 5/*fudge room*/;
cmpp_b * const buffer = &pArgs->pimpl->argOut;
assert( !buffer->n );
if( cmpp_b_reserve3(pp, buffer, nBuffer) ){
goto end;
}
if( cmpp_args_F_UNESCAPE & flags ){
bUnesc = cmpp_b_borrow(pp);
if( !bUnesc ) goto end;
}
zOut = buffer->z;
zOutEnd = zOut + buffer->nAlloc - 1;
#if !defined(NDEBUG)
zReallocCheck = buffer->z;
#endif
if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin),
zInBegin);
pArgs->arg0 = NULL;
pArgs->argc = 0;
cmpp_flag32_t const tFlags = cmpp_tizer_next_SKIP_NL
| cmpp_tizer_next_SKIP_C_COMMENTS;
for( int i = 0; (0==(rc=cmpp_tizer_next(&tz,tFlags)))
&& cmpp_tt_EOF!=tok->ttype
; ++i){
(void)i;
#if 0
if( cmpp_tt_EOC==tok->ttype
&& ';'!=*tok->z ){
//EOL
continue;
}
#endif
if( !(cmpp_args_F_NO_SQUIGGLY_HACK & flags) ){
cmpp__squiggly_strip_lead(tok);
}
cmpp_arg * const arg =
CmppArgList_append(pp, &pArgs->pimpl->argli);
if( !arg ) break;
assert( pArgs->pimpl->argli.n );
assert(zReallocCheck==buffer->z
&& "Else buffer was reallocated, invalidating other arg->z");
if( 0 ){
g_warn("tok %s=%.*s", cmpp_tt_cstr(tok->ttype), (int)(tok->n), tok->z);
if( tok->z!=tok->zInner ){
g_warn("tok => %.*s", (int)(tok->nInner), tok->zInner);
}
}
unsigned char const * zX = 0;
unsigned char const * zXEnd = 0;
if( bUnesc ){
if( cmpp_b_append4(pp, cmpp_b_reuse(bUnesc),
tok->zInner, tok->nInner) ){
break;
}
cmpp_b_unescape(bUnesc);
zX = bUnesc->z;
zXEnd = zX + bUnesc->n;
}else{
zX = tok->zInner;
zXEnd = zX + tok->nInner;
}
assert( zOut + (zXEnd-zX) < zOutEnd );
if( 0 ){
g_warn("arg %s: %.*s",
cmpp_tt_cstr(tok->ttype),
(int)(zXEnd-zX), zX);
}
memcpy(zOut, zX, (zXEnd-zX));
arg->z = zOut;
arg->n = (zXEnd-zX);
zOut += arg->n;
*zOut++ = 0;
cmpp__t2a_ttype_set(tok, arg);
assert( arg->token.z==tok->z );
if( 0 ){
g_warn("tok.ttype=%s arg ttype %s: %.*s",
cmpp_tt_cstr(tok->ttype),
cmpp_tt_cstr(arg->ttype),
(int)arg->n, arg->z);
}
assert( arg->ttype );
if( 0==(flags & cmpp_args_F_NO_PARENS)
&& cmpp_tt_GroupParen==tok->ttype ){
/* Sub-expression. We tokenize it here just to ensure that we
can, so we can fail earlier rather than later. This is why
we need a recycler for the cmpp_args buffer memory. */
cmpp_args sub = cmpp_args_empty;
cmpp_args_parse(pp, &sub, arg->z, arg->n, flags);
//g_stderr("Parsed sub-expr: %s\n", sub.buffer.z);
cmpp_args_cleanup(&sub);
if( ppCode ) break;
}
}/*foreach token*/
//g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(ppCode), pArgs->args.n);
end:
cmpp_b_return(pp, bUnesc);
cmpp_tizer_dtor(&tz);
if( 0==ppCode ){
pArgs->argc = pArgs->pimpl->argli.n;
assert( !pArgs->arg0 );
for( unsigned int i = 1; i < pArgs->argc; ++i ){
/* We have to link arg->next after the loop because
CmppArgList_append() can realloc, invalidating
the links.
2026-09-04: this is legacy stuff but many directives rely on
it.
*/
cmpp_arg * const a = &pArgs->pimpl->argli.list[i];
assert( ! a->next );
pArgs->pimpl->argli.list[i-1].next = a;
assert( a->token.z );
}
if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list;
if( zOut<zOutEnd ) *zOut = 0;
if( 0 ){
for( cmpp_arg const * a = pArgs->arg0; a; a = a->next ){
g_stderr(" got: %s %.*s\n", cmpp__tt_cstr2(a->ttype, true),
a->n, a->z);
}
}
}
assert(zReallocCheck==buffer->z
&& "Else buffer was reallocated, invalidating argN->z");
return ppCode;
}
CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0,
cmpp_args * const dest){
if( cmpp__args_init(pp, dest) || !a0 ) return ppCode;
cmpp_b * const ob = &dest->pimpl->argOut;
CmppArgList * const argli = &dest->pimpl->argli;
unsigned int i = 0;
cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */;
assert( !ob->n );
assert( !dest->arg0 );
assert( !dest->argc );
assert( !argli->n );
/* Preallocate ob->z to fit a copy of a0's args. */
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
nReserve += a->n + 1/*NUL byte*/;
}
if( cmpp_b_reserve3(pp, ob, nReserve+1)
|| CmppArgList_reserve(pp, argli, i) ){
goto end;
}
assert( argli->na>=i );
i = 0;
#ifndef NDEBUG
unsigned char const * const zReallocCheck = ob->z;
#endif
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
cmpp_arg * const aNew = &argli->list[i];
aNew->n = a->n;
aNew->z = ob->z + ob->n;
aNew->ttype = a->ttype;
aNew->token = a->token;
if( i ) argli->list[i-1].next = aNew;
assert( !a->z[a->n] && "Expecting a NUL byte there" );
cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/);
if( 0 ){
g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype),
(int)a->n, a->z, a->z);
}
assert( zReallocCheck==ob->z
&& "This cannot fail: ob->z was pre-allocated" );
}
dest->argc = i;
dest->arg0 = i ? &argli->list[0] : 0;
end:
if( ppCode ){
cmpp__args_reuse(dest);
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){
return cmpp_args_clone(dx->pp, dx->args.arg0, pOut);
}
CMPP__EXPORT(char *,cmpp_arg_strdup)(cmpp *pp, cmpp_arg const *arg){
char * z = 0;
if( 0==ppCode ){
z = sqlite3_mprintf("%s",arg->z);
cmpp_check_oom(pp, z);
}
return z;
}
CMPP__EXPORT(int,cmpp_arg_parse)(
cmpp * const pp, cmpp_arg *pOut,
unsigned char const **pzIn,
unsigned char const *zInEnd,
unsigned char ** pzOut,
unsigned char const * zOutEnd
){
cmpp_tizer tz = cmpp_tizer_empty;
cmpp_token * const tok = &tz.token;
cmpp_tizer_init(&tz, &pp->pimpl->err, *pzIn,
(zInEnd-*pzIn));
unsigned char * zo = *pzOut;
int rc = 0;
cmpp_flag32_t const tnFlags = cmpp_tizer_next_SKIP_NL
| cmpp_tizer_next_SKIP_C_COMMENTS;
cmpp_arg_reuse( pOut );
pOut->z = zo;
rc = cmpp_tizer_next(&tz, tnFlags);
if( rc ) return rc/*pp's err already set*/;
if( cmpp_tt_EOF==tok->ttype ){
pOut->z = zInEnd;
pOut->n = 0;
pOut->ttype = cmpp_tt_EOF;
return 0;
}
if( zo+tok->nInner>=zOutEnd-1 ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Ran out of output space (%u bytes) while "
"parsing an argument",
(unsigned)(zOutEnd-*pzOut));
}else{
cmpp__squiggly_strip_lead(tok);
pOut->n = tok->n;
memcpy(zo, tok->zInner, tok->nInner);
zo += tok->nInner;
*zo++ = 0;
*pzOut = zo;
*pzIn = tz.zPos;
cmpp__t2a_ttype_set(tok, pOut);
if(0){
g_stderr("parse1: %s n=%u <<%.*s>>",
cmpp__tt_cstr2(pOut->ttype, true), pOut->n,
pOut->n, pOut->z);
}
}
#undef out
#undef eot_break
return ppCode;
}
int cmpp__arg_toBool(cmpp * const pp, cmpp_arg const *arg,
int * pResult, cmpp_arg const **pNext){
switch( ppCode ? 0 : arg->ttype ){
case 0: break;
case cmpp_tt_Word:
*pNext = arg->next;
*pResult = cmpp__get_bool(pp, arg->z, arg->n);
break;
case cmpp_tt_IntDec:
*pNext = arg->next;
cmpp_is_int(arg->z, arg->n, pResult)/*was already validated*/;
break;
case cmpp_tt_StringBT:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:{
int rc = 0;
*pNext = arg->next;
if( cmpp_is_int(arg->z, arg->n, &rc) ){
*pResult = rc;
}else{
*pResult = arg->n>0 && 0!=memcmp("0\0", arg->z, 2);
}
break;
}
case cmpp_tt_GroupParen:{
*pNext = arg->next;
cmpp_args sub = cmpp_args_empty;
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
cmpp__args_evalToInt(pp, &sub, pResult);
}
cmpp_args_cleanup(&sub);
break;
}
case cmpp_tt_OpDefined:
if( !arg->next ){
serr("Missing '%s' RHS.", arg->z);
}else if( cmpp_tt_Word!=arg->next->ttype ){
serr( "Invalid '%s' RHS: %s", arg->z, arg->next->z);
}else{
cmpp_arg const * aOperand = arg->next;
*pNext = aOperand->next;
if( aOperand->n>1
&& '#'==aOperand->z[0]
&& !!cmpp__d_search3(pp, (char const*)aOperand->z+1,
cmpp__d_search3_F_NO_DLL) ){
*pResult = 1;
}else{
*pResult = cmpp__has(pp, (char const *)aOperand->z,
aOperand->n);
}
}
break;
case cmpp_tt_OpNot:{
assert( arg->next && "See cmpp_args__not_simplify()");
assert( cmpp_tt_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()");
if( 0==cmpp__arg_toBool(pp, arg->next, pResult, pNext) ){
*pResult = !*pResult;
}
break;
}
default:
serr("Invalid token type %s for %s(): %s",
cmpp__tt_cstr2(arg->ttype, true), __func__, arg->z);
break;
}
return ppCode;
}
CMPP__EXPORT(int, cmpp_arg_interpolate)(cmpp * const pp, cmpp_arg const *arg,
cmpp_b * ob, cmpp_flag32_t flags){
/**
Reminder to self: this function specifically does not do any
expression evaluation of its arguments. Please avoid the
temptation to make it do so. Unless it proves necessary. Or
useful. Even then, though, consider the implications deeply
before doing so.
2026-09-21: The above still currently applies but we're going to
want expressions if/when the v1 expression cruft is replaced with
v2.
*/
if( !ob ){
if( arg->interp ){
ob = cmpp_b_reuse(arg->interp);
}else{
return cmpp_errf(pp, CMPP_RC_MISUSE,
"%s() only accepts a NULL cmpp_b if arg->interp "
"is not NULL.", __func__);
}
}
switch( ppCode
? 0
: ((cmpp_arg_interpolate_FORCE_STRING & flags)
? cmpp_tt_shape_string : arg->ttype) ){
case 0:
break;
case cmpp_tt_Word:
if( 0==(flags & cmpp_arg_interpolate_NO_DEFINES) ){
cmpp__get_b(pp, arg->z, arg->n, ob, true);
break;
}
goto theDefault;
case cmpp_tt_GroupBrace:
if( !(cmpp_arg_interpolate_NO_BRACE_CALL & flags)
&& (cmpp_arg_interpolate_BRACE_CALL & flags) ){
cmpp_call_str(pp, arg->z, arg->n, ob, 0);
break;
}
/* fall through */
default: {
theDefault: ;
cmpp_outputer oss = cmpp_outputer_b;
oss.state = ob;//no: cmpp_b_reuse(ob); Append instead.
cmpp__out2(pp, &oss, arg->z, arg->n);
break;
}
}
return ppCode;
}
int cmpp__bind_arg(cmpp * const pp, sqlite3_stmt * const q,
int bindNdx, cmpp_arg const * const arg){
if( 0 ){
g_warn("bind #%d %s <<%.*s>>", bindNdx,
cmpp__tt_cstr2(arg->ttype, true),
(int)arg->n, arg->z);
}
switch( arg->ttype ){
default:
case cmpp_tt_IntDec:
case cmpp_tt_StringBT:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:
cmpp_stmt_bind_textn(pp, q, bindNdx, arg->z, (int)arg->n);
break;
case cmpp_tt_Word:{
cmpp_b os = cmpp_b_empty;
if( 0==cmpp_arg_interpolate(pp, arg, &os, 0) ){
if( 0 ){
g_warn("bind #%d <<%s>> => <<%.*s>>",
bindNdx, arg->z, (int)os.n, os.z);
}
cmpp_stmt_bind_textn(pp, q, bindNdx, os.z, (int)os.n);
}
cmpp_b_clear(&os);
break;
}
case cmpp_tt_GroupParen:{
cmpp_args sub = cmpp_args_empty;
int i = 0;
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0)
&& 0==cmpp__args_evalToInt(pp, &sub, &i) ){
/* See comment above about cmpp_tt_Int. */
cmpp_stmt_bind_int_text(pp, q, bindNdx, i);
}
cmpp_args_cleanup(&sub);
break;
}
case cmpp_tt_GroupBrace:{
cmpp_b b = cmpp_b_empty;
cmpp_call_str(pp, arg->z, arg->n, &b, 0);
cmpp_stmt_bind_textn(pp, q, bindNdx, b.z, b.n);
cmpp_b_clear(&b);
break;
}
}
return ppCode;
}
/**
If a is in li->list, return its non-const pointer from li->list
(O(1)), else return NULL.
*/
static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){
if( li->na && a>=li->list && a<(li->list + li->na) ){
return li->list + (a - li->list);
}
return NULL;
}
/**
To be called only by cmpp_dx_args_parse() and only if the current
directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY.
Filter chains of "not" operators from pArgs, removing unnecessary
ones. Also collapse "not glob" into a single cmpp_tt_OpNotGlob argument.
Performs some basic validation as well to simplify downstream
operations. Returns p->err.code and is a no-op if that's set
before this is called.
*/
static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){
cmpp_arg * pPrev = 0;
cmpp_arg * pNext = 0;
CmppArgList * const ali = &pArgs->pimpl->argli;
pArgs->argc = 0;
for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL;
arg && !ppCode;
pPrev=arg, arg = pNext ){
pNext = CmppArgList_arg_nc(ali, arg->next);
assert( pNext || !arg->next );
if( cmpp_tt_OpNot==arg->ttype ){
if( !pNext ){
serr("Missing '%s' RHS", arg->z);
break;
}
cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype);
if( nop && nop->arity>1 && cmpp_tt_OpGlob!=nop->ttype ){
serr("Illegal '%s' RHS: binary '%s' operator",
arg->z, pNext->z);
break;
}
int bNeg = 1;
if( '!'==*arg->z ){
/* odd number of ! == negate */
bNeg = arg->n & 1;
}
while( pNext && cmpp_tt_OpNot==pNext->ttype ){
bNeg = !bNeg;
arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next);
}
if( pNext && cmpp_tt_OpGlob==pNext->ttype ){
/* Transform it to a cmpp_tt_OpNotGlob or cmpp_tt_OpGlob. */
assert( pNext->z > arg->z + arg->n );
arg->n = pNext->z + pNext->n - arg->z;
arg->next = pNext->next;
arg->ttype = bNeg
? cmpp_tt_OpNotGlob
: pNext->ttype;
++pArgs->argc;
}else if( pPrev ){
if( bNeg ){
++pArgs->argc;
}else{
/* Snip this node out. */
pPrev->next = pNext;
}
}else{
assert( 0==pArgs->argc );
++pArgs->argc;
if( !bNeg ){
arg->ttype = cmpp_tt_Noop;
}
}
/* Potential bug in waiting/fixme: by eliding all nots we are
** changing the behavior from forced coercion to bool to
** coercion to whatever the LHS wants. */
}else{
++pArgs->argc;
}
}
pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL;
return ppCode;
}
CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx, cmpp_args *args){
if( !dxppCode ){
cmpp_flag32_t const flags = cmpp_args_F_NO_PARENS
| ((cmpp_f_F_NO_SQUIGGLY_HACK & dx->d->flags)
? cmpp_args_F_NO_SQUIGGLY_HACK
: 0/* legacy compatibility */);
if( 0==cmpp_args_parse(dx->pp, args, dx->args.z,
dx->args.nz, flags)
&& (cmpp_f_F_NOT_SIMPLIFY & dx->d->flags) ){
cmpp_args__not_simplify(dx->pp, args);
}
}
return dxppCode;
}
/* Helper for cmpp_kav_each() and friends. */
static
int cmpp__each_parse_args(cmpp *pp,
cmpp_args *args,
unsigned char const *zBegin,
cmpp_strlen_t nz,
cmpp_flag32_t flags){
if( 0==cmpp_args_parse(pp, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){
if( !args->argc
&& (cmpp_kav_each_F_NOT_EMPTY & flags) ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Empty list is not permitted here.");
}
}
return ppCode;
}
/* Helper for cmpp_kav_each() and friends. */
static
int cmpp__each_paren_expr(cmpp *pp, cmpp_arg const * arg,
unsigned char * pOut, size_t nOut){
cmpp_args sub = cmpp_args_empty;
int rc = cmpp_args_parse(pp, &sub, arg->z, arg->n, 0);
if( 0==rc ){
int d = 0;
rc = cmpp__args_evalToInt(pp, &sub, &d);
if( 0==rc ){
snprintf((char *)pOut, nOut, "%d", d);
}
}
cmpp_args_cleanup(&sub);
return rc;
}
CMPP__EXPORT(int, cmpp_kav_each)(cmpp *pp,
unsigned char const *zBegin,
cmpp_strlen_t nIn,
cmpp_kav_each_f callback,
void *callbackState,
cmpp_flag32_t flags){
if( ppCode ) return ppCode;
/* Reminder to self: we cannot reuse internal buffers here because a
callback could recurse into this or otherwise use APIs which use
those same buffers. */
cmpp_args args = cmpp_args_empty;
cmpp_b * const bKey = cmpp_b_borrow(pp);
cmpp_b * const bVal = bKey ? cmpp_b_borrow(pp) : 0;
if( !bVal ) goto end;
bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags);
unsigned char exprBuf[32] = {0};
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_interpolate() flags*/;
assert( zBegin );
if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){
goto end;
}else if( reqArrow && 0!=args.argc%3 ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Expecting a list of 3 tokens per entry: "
"KEY -> VALUE");
goto end;
}else if( !reqArrow && 0!=args.argc%2 ){
cmpp_errf(pp, CMPP_RC_RANGE,
"Expecting a list of 2 tokens per entry: "
"KEY VALUE");
goto end;
}
if( cmpp_kav_each_F_CALL_KEY & flags ){
a2bK |= cmpp_arg_interpolate_BRACE_CALL;
flags |= cmpp_kav_each_F_EXPAND_KEY;
}
if( cmpp_kav_each_F_CALL_VAL & flags ){
a2bV |= cmpp_arg_interpolate_BRACE_CALL;
flags |= cmpp_kav_each_F_EXPAND_VAL;
}
cmpp_arg const * aNext = 0;
for( cmpp_arg const * aKey = args.arg0;
!ppCode && aKey;
aKey = aNext ){
aNext = aKey->next;
cmpp_arg const * aVal = aKey->next;
if( !aVal ){
serr("Expecting %s after key '%s'.",
(reqArrow ? "->" : "a value"),
aKey->z);
break;
}
if( reqArrow ){
if( cmpp_tt_ThinArrowR!=aVal->ttype ){
serr("Expecting -> after key '%s'.", aKey->z);
break;
}
aVal = aVal->next;
if( !aVal ){
serr("Expecting a value after '%s' ->.", aKey->z);
break;
}
}
//g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z);
/* Expand the key/value parts if needed... */
unsigned char const *zKey;
unsigned char const *zVal;
cmpp_size_t nKey, nVal;
if( cmpp_kav_each_F_EXPAND_KEY & flags ){
if( cmpp_arg_interpolate(pp, aKey, cmpp_b_reuse(bKey), a2bK) ){
break;
}
zKey = bKey->z;
nKey = bKey->n;
}else{
zKey = aKey->z;
nKey = aKey->n;
}
if( cmpp_tt_GroupParen==aVal->ttype
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
if( cmpp__each_paren_expr(pp, aVal, &exprBuf[0],
sizeof(exprBuf)-1) ){
break;
}
zVal = &exprBuf[0];
nVal = cmpp__strlenu(zVal, -1);
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
if( cmpp_arg_interpolate(pp, aVal, cmpp_b_reuse(bVal),
a2bV) ){
break;
}
zVal = bVal->z;
nVal = bVal->n;
}else{
zVal = aVal->z;
nVal = aVal->n;
}
aNext = aVal->next;
if( 0!=callback(pp, zKey, nKey, zVal, nVal, callbackState) ){
break;
}
}
end:
cmpp_b_return(pp, bKey);
cmpp_b_return(pp, bVal);
cmpp_args_cleanup(&args);
return ppCode;
}
#if 0
CMPP__EXPORT(int, cmpp_str_each)(cmpp *pp,
unsigned char const *zBegin,
cmpp_strlen_t nIn,
cmpp_kav_each_f callback,
void *callbackState,
cmpp_flag32_t flags){
g_warn0("UNTESTED!");
if( ppCode ) return ppCode;
/* Reminder to self: we cannot reuse internal buffers here because a
callback could recurse into this or otherwise use APIs which use
those same buffers. */
cmpp_b ob = cmpp_b_empty;
cmpp_args args = cmpp_args_empty;
unsigned char exprBuf[32] = {0};
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
assert( zBegin );
if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){
goto cleanup;
}
cmpp_arg const * aNext = 0;
for( cmpp_arg const * arg = args.arg0;
!ppCode && arg;
arg = aNext ){
aNext = arg->next;
//g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z);
/* Expand the key/value parts if needed... */
unsigned char const *zVal;
cmpp_size_t nVal;
if( cmpp_tt_GroupParen==arg->ttype
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
if( cmpp__each_paren_expr(pp, arg, &exprBuf[0],
sizeof(exprBuf)-1) ){
break;
}
zVal = &exprBuf[0];
nVal = cmpp__strlenu(zVal, -1);
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
if( cmpp_arg_interpolate(pp, arg, cmpp_b_reuse(&ob), 0) ){
break;
}
zVal = ob.z;
nVal = ob.n;
}else{
zVal = arg->z;
nVal = arg->n;
}
if( 0!=callback(pp, arg->z, arg->n, zVal, nVal, callbackState) ){
break;
}
}
cleanup:
cmpp_b_clear(&ob);
cmpp_args_cleanup(&args);
return ppCode;
}
#endif
int cmpp_arg_consume_as_file(
cmpp_dx * dx, cmpp_arg const *arg,
cmpp_b * bNameOut, cmpp_b * content, bool preprocessIt
){
if( !arg ){
return cmpp_dx_errf(dx, CMPP_RC_MISUSE,
"Expecting filename argument.");
}
cmpp_b * const bFile = cmpp_b_borrow(dx->pp);
if( !bFile ) return dxppCode;
else if( cmpp_arg_interpolate(dx->pp, arg, bFile,
cmpp_arg_interpolate_BRACE_CALL) ){
goto end;
}
if( !bFile->n || !bFile->z[0] ){
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Empty filename is not permitted.");
goto end;
}
cmpp_FILE * const fp =
cmpp_fopen((char const*)bFile->z, "r")
/* Resolving as -Idir does would arguably be a bug. */;
if( fp ){
cmpp_outputer out = cmpp_outputer_b;
int rc;
out.state = cmpp_b_reuse(content);
if( preprocessIt ){
cmpp_outputer oOld = {0};
cmpp__outputer_swap(dx->pp, &out, &oOld);
rc = cmpp_process_stream(dx->pp, (char const *)bFile->z,
cmpp_input_f_FILE, fp);
cmpp__outputer_swap(dx->pp, &oOld, NULL);
}else{
rc = cmpp_stream(cmpp_input_f_FILE, fp, out.out, out.state);
}
if( rc && !cmpp_err_has(dx->pp) ){
/* Propagate error. */
cmpp_dx_errf(dx, rc, "Unknown error streaming file [%s].",
bFile->z);
}
cmpp_fclose(fp);
}else{
cmpp_dx_errf(dx, cmpp_errno_rc(errno, CMPP_RC_IO),
"Unknown error opening file %s.", bFile->z);
}
end:
if( bNameOut && bFile && !dxppCode ){
cmpp_b_swap(bFile, bNameOut);
}
cmpp_b_return(dx->pp, bFile);
return dxppCode;
}
cmpp__ListType_impl(CmppArgList,cmpp_arg)
#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
# define CMPP_D_MODULE 1
#else
# define CMPP_D_MODULE 0
#endif
#if CMPP_D_MODULE
/**
The #module directive:
#module dll ?moduleName?
Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a
directive module.
*/
CMPP_PRIVATE void cmpp_f_module(cmpp_f_args const * args){
cmpp_arg const * aName = 0;
cmpp_arg const * aDll = 0;
cmpp_b * const bDll = cmpp_b_borrow(args->pp);
cmpp_b * bName = 0;
if( !bDll ) goto end;
for( unsigned i = 1; i < args->argc; ++i ){
cmpp_arg const * const arg = args->argv[i];
//MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z));
if( !aDll ){
aDll = arg;
continue;
}
if( !aName ){
aName = arg;
bName = cmpp_b_borrow(args->pp);
if( !bName ) goto end;
continue;
}
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Unhandled argument: %s", arg->z);
goto end;
}
if( !aDll ){
cmpp_errf(args->pp, CMPP_RC_MISUSE,
"Expecting (dllName ?directiveName?) arguments.");
goto end;
}
if( cmpp_arg_interpolate(args->pp, aDll, bDll,
cmpp_arg_interpolate_BRACE_CALL) ){
goto end;
}
if( bName && cmpp_arg_interpolate(args->pp, aName, bName,
cmpp_arg_interpolate_BRACE_CALL) ){
goto end;
}
if( !bDll->n ){
cmpp_errf(args->pp, CMPP_RC_RANGE,
"Empty DLL name is not permitted.");
goto end;
}
cmpp_module_load(args->pp, (char const *)bDll->z,
(bName && bName->n)
? (char const *)bName->z
: NULL);
end:
cmpp_b_return(args->pp, bDll);
cmpp_b_return(args->pp, bName);
return;
}
#endif /* #module */
/**
Module loader pedantic licensing note: Most of cmpp's
module-loading code was copied verbatim from another project[^1],
but was written by the same author who relicenses it in
cmpp.
[^1]: https://fossil.wanderinghorse.net/r/cwal
*/
#if CMPP_ENABLE_DLLS
#if CMPP_HAVE_DLOPEN
typedef void * cmpp_soh;
# include <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)
#if CmppSohList_works
int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){
int const rc = cmpp_array_reserve(pp, (void**)&soli->list,
soli->n
? (soli->n==soli->na
? soli->na*3/2
: soli->n+1)
: 8,
&soli->na, sizeof(void*));
if( 0==rc ){
soli->list[soli->n++] = soh;
}
return rc;
}
#endif
#if 0
/**
Passes soli to CmppSohList_close() then frees soli. Results are
undefined if soli is not NULL but was not returned from
CmppSohList_new().
Special case: if built without DLL-closing support, this is a no-op.
*/
//static void CmppSohList_free(CmppSohList *soli);
void CmppSohList_free(CmppSohList *s){
if( s ){
}
}
/**
Returns a new, cleanly-initialized CmppSohList or NULL
on allocation error. The returned instance must eventually be
passed to CmppSohList_free().
Special case: if built without DLL-closing support, this returns a
no-op singleton instance.
*/
//static CmppSohList * CmppSohList_new(void);
CmppSohList * CmppSohList_new(void){
#if CmppSohList_works
CmppSohList * s = cmpp_malloc(sizeof(*s));
if( s ) *s = CmppSohList_empty;
return s;
#else
static CmppSohList soli = CmppSohList_empty;
return &soli;
#endif
}
#endif
#undef CmppSohList_works
#if !CMPP_ENABLE_DLLS
static int cmpp__err_no_dlls(cmpp * const pp){
return cmpp_errf(pp, CMPP_RC_UNSUPPORTED,
"No dlopen() equivalent is installed "
"for this build configuration.");
}
#endif
//no: CMPP_WASM_EXPORT
int cmpp_module_load(cmpp * pp, char const * fname,
char const * symName){
(void)fname; (void)symName;
return cmpp__err_no_dlls(pp);
}
/*
** 2015-08-18, 2023-04-28
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
**
** This file demonstrates how to create a table-valued-function using
** a virtual table. This demo implements the generate_series() function
** which gives the same results as the eponymous function in PostgreSQL,
** within the limitation that its arguments are signed 64-bit integers.
**
** Considering its equivalents to generate_series(start,stop,step): A
** value V[n] sequence is produced for integer n ascending from 0 where
** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
** for each produced value (independent of production time ordering.)
**
** All parameters must be either integer or convertable to integer.
** The start parameter is required.
** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
** The step parameter defaults to 1 and 0 is treated as 1.
**
** Examples:
**
** SELECT * FROM generate_series(0,100,5);
**
** The query above returns integers from 0 through 100 counting by steps
** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total
** of 21 rows.
**
** SELECT * FROM generate_series(0,100);
**
** Integers from 0 through 100 with a step size of 1. 101 rows.
**
** SELECT * FROM generate_series(20) LIMIT 10;
**
** Integers 20 through 29. 10 rows.
**
** SELECT * FROM generate_series(0,-100,-5);
**
** Integers 0 -5 -10 ... -100. 21 rows.
**
** SELECT * FROM generate_series(0,-1);
**
** Empty sequence.
**
** HOW IT WORKS
**
** The generate_series "function" is really a virtual table with the
** following schema:
**
** CREATE TABLE generate_series(
** value,
** start HIDDEN,
** stop HIDDEN,
** step HIDDEN
** );
**
** The virtual table also has a rowid which is an alias for the value.
**
** Function arguments in queries against this virtual table are translated
** into equality constraints against successive hidden columns. In other
** words, the following pairs of queries are equivalent to each other:
**
** SELECT * FROM generate_series(0,100,5);
** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5;
**
** SELECT * FROM generate_series(0,100);
** SELECT * FROM generate_series WHERE start=0 AND stop=100;
**
** SELECT * FROM generate_series(20) LIMIT 10;
** SELECT * FROM generate_series WHERE start=20 LIMIT 10;
**
** The generate_series virtual table implementation leaves the xCreate method
** set to NULL. This means that it is not possible to do a CREATE VIRTUAL
** TABLE command with "generate_series" as the USING argument. Instead, there
** is a single generate_series virtual table that is always available without
** having to be created first.
**
** The xBestIndex method looks for equality constraints against the hidden
** start, stop, and step columns, and if present, it uses those constraints
** to bound the sequence of generated values. If the equality constraints
** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step.
** xBestIndex returns a small cost when both start and stop are available,
** and a very large cost if either start or stop are unavailable. This
** encourages the query planner to order joins such that the bounds of the
** series are well-defined.
**
** Update on 2024-08-22:
** xBestIndex now also looks for equality and inequality constraints against
** the value column and uses those constraints as additional bounds against
** the sequence range. Thus, a query like this:
**
** SELECT value FROM generate_series($SA,$EA)
** WHERE value BETWEEN $SB AND $EB;
**
** Is logically the same as:
**
** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB));
**
** Constraints on the value column can server as substitutes for constraints
** on the hidden start and stop columns. So, the following two queries
** are equivalent:
**
** SELECT value FROM generate_series($S,$E);
** SELECT value FROM generate_series WHERE value BETWEEN $S and $E;
**
*/
#if 0
#include "sqlite3ext.h"
#else
#include "sqlite3.h"
#endif
#include <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;
}
/*
** 2026-08-22:
**
** 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 low-level tokenizer used by cmpp directives
** and cmpp_itch callbacks.
*/
#include <ctype.h>
const cmpp_tizer cmpp_tizer_empty = cmpp_tizer_empty_m;
const cmpp_token cmpp_token_empty = cmpp_token_empty_m;
CMPP__EXPORT(char const *, cmpp_tt_cstr)(int e){
switch(e){
#define E(NAME,ID,MASK) case ID | MASK: return # NAME;
cmpp_tt_map(E)
#undef E
default: return 0;
}
}
char const * cmpp__tt_cstr2(int tt, bool bSymbolName){
switch(tt){
#define E(N,V,M) case cmpp_tt_ ## N: \
return bSymbolName ? "cmpp_tt_" # N : # N;
cmpp_tt_map(E)
#undef E
}
return NULL;
}
CMPP__EXPORT(void, cmpp_token_lc)(cmpp_tizer const * tz,
cmpp_token const * tok,
cmpp_size_t *l, cmpp_size_t *c){
if( !tok ) tok = &tz->token;
assert(tok->z);
unsigned char const * zb = 0;
unsigned char const * ze = 0;
cmpp_tizer_full_range(tz, &zb, &ze);
*l = 1 + cmpp_count_nl(zb, tok->z+1, tok->z, c);
}
CMPP__EXPORT(bool, cmpp_tizer_eof)(cmpp_tizer const *tz){
return tz->zPos>=tz->zEnd || cmpp_tt_EOF==tz->token.ttype;
}
CMPP__EXPORT(bool, cmpp_token_is_group)(cmpp_token const * t){
return cmpp_tt_shape_group==(t->ttype & cmpp_tt_mask_shape);
}
CMPP__EXPORT(bool, cmpp_token_is_string)(cmpp_token const * t){
return cmpp_tt_shape_string==(t->ttype & cmpp_tt_mask_shape);
}
int cmpp__tt_for_group_char(int ch){
switch(ch){
case '{': return cmpp_tt_GroupSquiggly;
case '[': return cmpp_tt_GroupBrace;
case '(': return cmpp_tt_GroupParen;
case '"': return cmpp_tt_StringDQ;
case '\'': return cmpp_tt_StringSQ;
case '`': return cmpp_tt_StringBT;
default:
return 0;
}
}
/**
Proxy for cmpp_errf(tx->err,...).
*/
static int cmpp_tizer_err_setf(cmpp_tizer * tz, int code,
char const *zFmt, ...);
/** va_list counterpart of cmpp_tizer_err_set(). */
static int cmpp_tizer_err_setv(cmpp_tizer * tz, int code,
char const *zFmt, va_list vargs);
/** True only for bytes which may legally immediately follow a group
token. */
static bool cmpp_tizer_terminates_group(int groupType, int ch){
switch(ch){
case 0:
case ' ': case '\n': case '\r': case '\t': case ';': return true;
case ']': case ')': case '}':
/* experiment 2026-08-31 to get {a b !!} to work */
//return cmpp_tt_shape_string==(cmpp_tt_mask_shape & groupType);
(void)groupType;
return true;
default: return false;
}
}
CMPP__EXPORT(void, cmpp_tizer_full_range)(cmpp_tizer const * tz,
unsigned char const **zBegin,
unsigned char const **zEnd){
cmpp_tizer const * up;
while( (up = tz->parent) ){
/* If tz lives in the same byte range, use it for purposes of
figuring out the source range. */
if( tz->zBegin>=up->zBegin
&& tz->zEnd<=up->zEnd ){
tz = up;
}else{
break;
}
}
*zBegin = tz->zBegin;
*zEnd = tz->zEnd;
}
CMPP__EXPORT(bool, cmpp_tizer_contains)(cmpp_tizer const * tz,unsigned char const *z){
unsigned char const * zB = 0;
unsigned char const * zE = 0;
cmpp_tizer_full_range(tz, &zB, &zE);
return z>=zB && z<=zE;
}
#if 0
CMPP__EXPORT(cmpp_size_t, cmpp_tizer_count_nl)(cmpp_tizer const * tz, unsigned char const *z){
unsigned char const * zB = 0;
unsigned char const * zE = 0;
cmpp_tizer_full_range(tz, &zB, &zE);
return (z>=zB && z<=zE)
? cmpp_count_nl(zB, zE, z, 0)
: 0;
}
#endif
CMPP__EXPORT(char const *, cmpp_tizer_name)(cmpp_tizer const *tz){
char const * z = tz->name;
cmpp_tizer const * up = tz;
while( !z && (up = up->parent) ){
z = up->name;
}
return z;
}
CMPP__EXPORT(sqlite3_str *, cmpp_tizer_err_prefix)(cmpp_tizer const *tz,
unsigned char const *z){
if( !z ) z = cmpp_tizer_errpos(tz);
unsigned char const *zB = 0;
unsigned char const *zE = 0;
cmpp_tizer_full_range(tz, &zB, &zE);
assert( z>=zB && z<=zE && "Else internal misuse of this API" );
sqlite3_str * str = cmpp__sqlite3_str_new(0);
if( str && z>=zB && z<=zE ){
cmpp_size_t col = 0;
cmpp_size_t const ln = 1+cmpp_count_nl(zB, zE, z, &col);
char const * zName = cmpp_tizer_name(tz);
if( !zName ) zName = "<unnamed script>";
sqlite3_str_appendf(str, "Error in [%s], possibly near %u:%u: ",
zName, (unsigned)ln, (unsigned)col);
if( sqlite3_str_errcode(str) ){
sqlite3_str_free(str);
str = 0;
}
}
return str;
}
CMPP__EXPORT(sqlite3_str *, cmpp_tizer_err_prefix_t)(cmpp_tizer const *tz,
cmpp_token const * tok){
return cmpp_tizer_err_prefix(tz, tok ? tok->z : 0);
}
CMPP__EXPORT(void, cmpp_tizer_errpos_set)(cmpp_tizer *tz,
unsigned char const *z){
if( z ){
if( cmpp_tizer_contains(tz, z) ){
tz->errToken = cmpp_token_empty;
tz->errToken.z = tz->errToken.zInner = z;
}/*else{
// This mis-identifies positions of interpolation errors.
tz->errToken = tz->token;
}*/
}else{
tz->errToken = cmpp_token_empty;
}
}
CMPP__EXPORT(void, cmpp_tizer_errpos_set_t)(cmpp_tizer *tz,
cmpp_token const * tok){
assert( tok );
cmpp_tizer_errpos_set(tz, tok->z);
}
CMPP__EXPORT(unsigned char const *, cmpp_tizer_errpos)(cmpp_tizer const * tz){
if( tz ){
unsigned char const * const z = tz->errToken.z
? tz->errToken.z
: (tz->token.z
? tz->token.z
: tz->zPos);
return cmpp_tizer_contains(tz, z) ? z : 0;
}
return 0;
}
CMPP__EXPORT(int, cmpp_tizer_err_setv)(cmpp_tizer * tz, int code, char const *zFmt, va_list vargs){
if( tz->err ){
cmpp_token * const tok = &tz->token;
tok->ttype = cmpp_tt_Error;
if( !tok->z ){
tok->z = (tz->zPos>=tz->zBegin && tz->zPos<tz->zEnd)
? tz->zPos
: tz->zEnd;
}
code = cmpp_errinfo_setv(tz->err, code, zFmt, vargs);
}
return code;
}
CMPP__EXPORT(int, cmpp_tizer_err_setf)(cmpp_tizer * tz, int code, char const *zFmt, ...){
va_list va;
va_start(va, zFmt);
code = cmpp_tizer_err_setv(tz, code, zFmt, va);
va_end(va);
return code;
}
CMPP__EXPORT(void, cmpp_tizer_init)(cmpp_tizer * tz, cmpp_errinfo *err,
unsigned char const *zBegin,
cmpp_strlen_t nBytes){
nBytes = cmpp__strlenu(zBegin, nBytes);
unsigned char const * const zEnd = zBegin + nBytes;
assert( zEnd );
assert( zBegin && zBegin<=zEnd );
tz->err = err;
tz->zBegin = zBegin;
tz->zEnd = zEnd;
cmpp_tizer_rewind(tz);
}
CMPP__EXPORT(void, cmpp_tizer_rewind)(cmpp_tizer *tz){
tz->zPos = tz->zBegin;
tz->token = tz->pbToken = tz->errToken = cmpp_token_empty;
tz->atBOX = true;
}
CMPP__EXPORT(void, cmpp_tizer_inherit)(cmpp_tizer const * tzParent,
cmpp_tizer * tz){
assert( tz->zBegin );
assert( tz->zBegin>=tzParent->zBegin );
assert( tz->zEnd<=tzParent->zEnd );
tz->parent = tzParent;
if( !tz->err ) tz->err = tzParent->err;
if( !tz->name ) tz->name = cmpp_tizer_name(tzParent);
}
CMPP__EXPORT(void, cmpp_tizer_init_sub)(cmpp_tizer const *tzParent,
cmpp_token const * tok,
cmpp_tizer * tzSub){
if( !tok ) tok = &tzParent->token;
cmpp_tizer_inherit(tzParent, tzSub);
//tzSub->name = cmpp_tizer_name(tzParent);
cmpp_tizer_init(tzSub, tzParent->err, tok->zInner, tok->nInner);
}
CMPP__EXPORT(void, cmpp_tizer_dtor)(cmpp_tizer * tz){
*tz = cmpp_tizer_empty;
}
static int cmpp_tizer_next_impl(cmpp_tizer * const tz, cmpp_flag32_t flags){
cmpp_token * const tok = &tz->token;
unsigned char const * z = tz->zPos;
unsigned char const * const zEnd = tz->zEnd;
unsigned char const * const zOrigin = z;
unsigned char const * zEsc = 0;
bool keepGoing = true;
int rc = 0;
bool const skipNL = cmpp_tizer_next_SKIP_NL & flags;
bool const skipCComments = cmpp_tizer_next_SKIP_C_COMMENTS & flags;
tz->errToken = cmpp_token_empty;
assert( cmpp_tt_None==0 && "API requirement" );
assert( tz->zPos < tz->zEnd && "Expected from cmpp_tizer_next()" );
assert( 0==tok->n && "Expected from cmpp_tizer_next()" );
assert( tz->zPos == tok->z && "Expected from cmpp_tizer_next()" );
if( skipNL ){
assert( !cmpp_isspace(*z) && '\n'!=*z
&& tz->zPos==tok->z && "Expected from cmpp_tizer_next()" );
}
tok->z = z;
tok->zInner = 0;
tok->n = tok->nInner = 0;
tok->ttype = cmpp_tt_None;
#define maybeSetTtype(T) if(!tok->ttype) tok->ttype= cmpp_tt_ ## T
while( z<zEnd && 0==rc && keepGoing ){
switch( *z ){
case '/':
if( tok->ttype || !skipCComments || zEsc
|| z+1==zEnd || '*'!=z[1] ){
goto do_word;
}
/* Skip C-style comments */
z += 2;
while( z+1<zEnd && '*'!=*z && '/'!=z[1] ){
++z;
}
if( z==zEnd || '*'!=*z ){
cmpp_tizer_errpos_set(tz, tok->z);
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
"Did not find closing */ for ",
"C comment.");
break;
}
assert( z+1<zEnd && '*'==*z && '/'==z[1] );
z += 2;
//g_warn("C comment: %.*s", (int)(z-tok->z), tok->z);
tok->z = z;
break;
case '!':
/* This is a consolation for older cmpp_arg code: a run of '!'
is tagged as OpNot and the expression engine is responsible
for dealing with it by reducing it to a single NOT or NOT
NOT operation. */
if( zEsc ){
maybeSetTtype(Word);
zEsc = 0;
++z;
}else if( tok->ttype ){
goto do_word;
}else{
while( '!'==*z ) ++z;
//g_warn("not(s): <<%.*s>>", (z+1-tok->z), tok->z);
if( z<zEnd && !cmpp_tizer_terminates_group(0,*z) ){
tok->ttype = cmpp_tt_Word;
goto do_word;
}
//g_warn("not(s): <<%.*s>>", (z+1-tok->z), tok->z);
tok->ttype = cmpp_tt_OpNot;
keepGoing = false;
}
break;
case '\\':
if( zEsc ){
maybeSetTtype(Word);
zEsc = 0;
}else{
zEsc = z;
}
++z;
tz->atBOX = false;
continue;
case ';':
if( zEsc ){
++z;
maybeSetTtype(Word);
break;
}else if( tok->ttype ){
/* Don't consume this - keep the content before it and we'll
pick this up as an EOC on the next trip. */
keepGoing = false;
break;
}else{
tok->ttype = cmpp_tt_EOC;
++z;
keepGoing = false;
tz->atBOX = true;
break;
}
case '-':
case '+':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':{
if( zEsc ){
tz->errToken = *tok;
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
"Invalid escape sequence \\%c.",
*z);
break;
}
if( tok->ttype ){
goto do_word;
}
assert( z==tok->z );
//unsigned char const * const zLeft = z;
if( *z < '0' /*signed*/ ) ++z;
while( z<zEnd && isdigit(*z) ) ++z;
if( z==zEnd || ';'==*z || '\n'==*z || cmpp_isspace(*z) ){
/* ^^^ reminder to self: we don't need to catch ')', '}', or
end-of-string here for "{a + 1}" or similar because
sub-tokenization of grouping constructs is limited to the
token's inner part. */
#if 0
/* FIXME: we want this but it's inexplicably breaking
a test. */
if( z==zLeft+1 ){
/* Standlone + or start of a non-numeric word */
tok->ttype = cmpp_tt_Word;
break;
}
#endif
tok->ttype = cmpp_tt_IntDec;
keepGoing = false;
break;
}else{
z = tok->z /* rewind and do this as a word */;
goto do_word;
}
case '#':
if( zEsc ){
assert( !tz->atBOX );
maybeSetTtype(Word);
++z;
break;
}else if( tz->atBOX ){
/* Skip shell/tcl/perl-style comments. */
continue_comment:
while( z<zEnd && '\n'!=*++z );
/* There's no(?) world in which newlines which immediately
follow a single-line comment are relevant to command
execution. OTOH, a one-line comment may end a command... so
let's treat the comment as an EOL. */;
if( z<zEnd ){
assert( '\n'==*z );
if( '\\'==z[-1] ){
++z;
goto continue_comment;
}
++z /* consume \n */;
if( skipNL ){
tok->z = z;
tok->ttype = 0;
}else{
tok->ttype = cmpp_tt_EOL;
keepGoing = false;
}
tz->atBOX = true;
break;
}
break;
}
goto do_word;
}
case '\r':
if( z<zEnd && '\n'==z[1] ){
++z;
goto do_nl;
}else{
g_warn0("What do we do this this abomination?");
goto do_word;
break;
}
case '\n':
do_nl:
if( zEsc ){
/* Treat a backslash-escaped newline like space */
if( tok->ttype ){
z = zEsc /* go back to the backslash */;
keepGoing = false;
break;
}
++z;
cmpp_skip_space(&z, zEnd);
tok->z = z;
tz->atBOX = false;
}else if( tok->ttype ){
/* Don't consume - there's a pending word */
keepGoing = false;
tz->atBOX = true;
break;
}else if( skipNL ){
tok->z = ++z;
tok->ttype = 0;
tz->atBOX = true;
}else{
++z;
keepGoing = false;
tz->atBOX = true;
tok->ttype = cmpp_tt_EOL;
}
break;
case ' ': case '\t':
if( zEsc ){
++z;
maybeSetTtype(Word);
}else if( tok->ttype ){
/* Terminate pending token here */
keepGoing = false;
}else{
//assert( tz->atBOX );
tok->z = ++z;
}
//assert( !tz->atBOX );
break;
case '{': case '[': case '(':
case '"': case '`': case '\'':{
if( tok->ttype || zEsc ){
/* Scratch that - this is a continuation */
goto do_word;
}
//g_warn("*z=%c isEsc=%d", *z, (int)!!zEsc);
int const tty = cmpp__tt_for_group_char(*z);
unsigned char const chOpen = *z;
assert( tty );
tok->zInner = z+1;
if( 0==cmpp__find_closing(0, &z, zEnd) ){
tok->ttype = tty;
tok->nInner = (cmpp_size_t)(z - tok->zInner);
++z/*consume closer*/;
if( 0 ){
g_warn("<<%.*s>> => <<%.*s>>",
(int)(z - tok->z), tok->z,
(int)tok->nInner, tok->zInner);
}
if( z<zEnd ){
if( !cmpp_tizer_terminates_group(tty, *z) ){
/* We're eventually going to want to support a Tcl-like
bellybutton operation, in which case this has to be
patched. Or maybe we'll use JS syntax for that
instead: ...$x vs. {*}$x. */
tz->errToken = *tok;
cmpp_size_t col = 0, ln = 0;
cmpp_token_lc(tz, tok, &ln, &col);
if( 1 ){
g_warn("<<%.*s>> => <<%.*s>>",
(int)(z+1 - tok->z), tok->z);
}
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
"Trailing junk after group "
"token (char=%c) opened at script line "
"%d column %d.",
*z, (int)ln, (int)col);
}
}
}else{
cmpp_size_t col = 0, ln = 0;
cmpp_token_lc(tz, 0, &ln, &col);
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
"Did not find closing character "
"for '%c' opened at script line %d "
"column %d.",
(char)chOpen, (int)ln, (int)col);
}
tz->atBOX = false;
break;
}
case 'u': case 'U':
if( zEsc ){
/* \uXXXX and \UXXXXXXXX */
unsigned char uCount = 0;
unsigned int uChar = 0;
const unsigned ulen = 'u'==*z ? (unsigned)4 : (unsigned)8;
uCount = z+ulen<zEnd
? cmpp__read_hex_bytes(z+1, ulen, &uChar)
: 0;
if(uCount != ulen){
tz->errToken = *tok;
rc = cmpp_tizer_err_setf(
tz, CMPP_RC_SYNTAX,
"Invalid \\%c value. "
"Expecting \\uNNNN or \\UNNNNNNNN", *z
);
break;
}
assert(0 == (uCount % 2));
unsigned char ignored[12];
int const check = cmpp__utf8_char_to_cstr(uChar, ignored,
uCount);
if(check<0){
tz->errToken = *tok;
rc = cmpp_tizer_err_setf(
tz, CMPP_RC_SYNTAX,
"Invalid UTF: \\%c sequence. Expecting %u hex digits.\n",
*z, (int)ulen
);
break;
}
z += uCount;
maybeSetTtype(Word);
break;
}/* \u... and \U... */
CMPP_SWITCH_FALL_THROUGH;
default:
do_word:
++z;
maybeSetTtype(Word);
tz->atBOX = false;
break;
}
zEsc = NULL;
}
#undef maybeSetTtype
if( 0==rc ){
if( z==tok->z ){
tok->ttype = cmpp_tt_EOF;
tok->n = tok->nInner = 0;
tok->zInner = z;
}else if( zEsc ){
cmpp_size_t col = 0;
cmpp_size_t const ln =
cmpp_count_nl(tz->zBegin, tz->zEnd, zEsc, &col);
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX, "Stray escape sequence "
"near line %d column %d.", (int)ln, (int)col);
}else{
assert( z>tok->z );
tok->n = (cmpp_size_t)(z - tok->z);
assert( tok->ttype > 0 );
if( !tok->zInner ){
tok->zInner = tok->z;
tok->nInner = tok->n;
}
if( 0 ){
g_warn("token ttype=%d = %.*s", tok->ttype, (int)tok->n, tok->z);
}
}
tz->zPos = z;
}else{
tok->ttype = cmpp_tt_Error;
tok->n = tok->nInner = 0;
if( z<zEnd ) tok->z = z;
tok->zInner = z;
if( !tz->errToken.z ){
tz->errToken.z = zOrigin;
}
}
return rc;
}
CMPP__EXPORT(int, cmpp_tizer_next)(cmpp_tizer * const tz,
cmpp_flag32_t flags){
cmpp_token * const tok = &tz->token;
if( tz->zPos<tz->zEnd ){
tz->pbToken = tz->token;
tok->n = 0;
if( tz->zPos==tz->zBegin || (cmpp_tizer_next_SKIP_NL & flags) ){
cmpp_skip_snl( &tz->zPos, tz->zEnd );
}else{
cmpp_skip_space( &tz->zPos, tz->zEnd );
}
if( tz->zPos < tz->zEnd ){
tok->z = tz->zPos;
return cmpp_tizer_next_impl(tz, flags);
}
}
tok->ttype = cmpp_tt_EOF;
tok->n = tok->nInner = 0;
tok->z = tok->zInner = tz->zEnd;
return 0;
}
CMPP__EXPORT(int,cmpp_token_content)(cmpp_tizer const *tz,
cmpp_token const * tok,
cmpp_b * b, cmpp_flag32_t flags){
if( !tok ) tok = &tz->token;
if( 0==(cmpp_token_content_APPEND & flags) ){
cmpp_b_reuse(b);
}
if( cmpp_token_content_WHOLE & flags ){
return cmpp_b_append(b, tok->z, tok->n);
}
switch(tok->ttype){
case cmpp_tt_None:
case cmpp_tt_EOF:
case cmpp_tt_Error:
return 0;
case cmpp_tt_IntDec:{
assert(tok->n);
const bool plus = '+'==*tok->z;
assert(plus==0 || plus==1);
return cmpp_b_append(b, tok->z+plus, tok->n-plus);
}
case cmpp_tt_GroupBrace:
case cmpp_tt_GroupParen:
case cmpp_tt_GroupSquiggly:
case cmpp_tt_StringDQ:
case cmpp_tt_StringSQ:
case cmpp_tt_StringBT:{
assert( tok->n > 1 );
assert( tok->nInner == tok->n-2 );
assert( tok->zInner >= tok->z );
unsigned char const * z = tok->zInner;
unsigned char const * zEnd = z + tok->nInner;
//g_warn("group inner: %.*s", (int)(zEnd-z), z);
if( cmpp_token_content_TRIM & flags ){
cmpp_skip_snl(&z, zEnd);
cmpp_skip_snl_trailing(z, &zEnd);
}
assert( z<=zEnd );
if( zEnd==z ) return 0;
int const rc = cmpp_b_append(b, z, (cmpp_size_t)(zEnd-z));
if( 0==rc
&& cmpp_tt_shape_string==(cmpp_tt_shape_string & tok->ttype)
&& 0==(cmpp_token_content_NO_UNESCAPE & flags)
){
cmpp_b_unescape(b);
}
return rc;
}
case cmpp_tt_Word:
default:
return cmpp_b_append(b, tok->z, tok->n);
}
}
int cmpp__tt_tok_op(unsigned char const * z, uint8_t n){
int rc = 0;
switch(n){
case 1:
switch(*z){
case '+': rc = cmpp_tt_OpPlus; break;
case '-': rc = cmpp_tt_OpMinus; break;
case '<': rc = cmpp_tt_OpCmpLt; break;
case '>': rc = cmpp_tt_OpCmpGt; break;
case '=': rc = cmpp_tt_OpCmpEq; break;
case '!': rc = cmpp_tt_OpNot; break;
//case '^': rc = cmpp_tt_OpXOr; break;
//SQLite doesn't do that.
case '~': rc = cmpp_tt_OpBitwiseNegate; break;
case '&': rc = cmpp_tt_OpBitwiseAnd; break;
case '|': rc = cmpp_tt_OpBitwiseOr; break;
case '*': rc = cmpp_tt_OpMultiply; break;
case '/': rc = cmpp_tt_OpDivide; break;
case '%': rc = cmpp_tt_OpModulo; break;
}
break;
case 2:
switch(*z){
#define IFN(X,C,T) case X: if(C==z[1]) rc = T
IFN('n','e', cmpp_tt_OpCmpNeq); break;
IFN('=','=', cmpp_tt_OpCmpEq); break;
IFN('e','q', cmpp_tt_OpCmpEq); break;
IFN('o','r', cmpp_tt_OpOr); break;
IFN('|','|', cmpp_tt_OpOr); break;
IFN('&','&', cmpp_tt_OpAnd); break;
IFN('-','>', cmpp_tt_ThinArrowR); break;
//IFN('l','t', cmpp_tt_OpCmpLt); break;
//IFN('l','e', cmpp_tt_OpCmpLe); break;
//IFN('g','t', cmpp_tt_OpCmpGt); break;
//IFN('g','e', cmpp_tt_OpCmpGe); break;
#undef IFN
case '!':
switch(z[1]){
case '=': rc = cmpp_tt_OpCmpNeq; break;
case '!': rc = cmpp_tt_OpNot; break;
}
break;
case '<':
switch(z[1]){
case '<': rc = cmpp_tt_OpShiftL; break;
case '=': rc = cmpp_tt_OpCmpLe; break;
case '>': rc = cmpp_tt_OpCmpNeq; break;
case '-': rc = cmpp_tt_ThinArrowL; break;
}
break;
case '>':
switch(z[1]){
case '>': rc = cmpp_tt_OpShiftR; break;
case '=': rc = cmpp_tt_OpCmpGe; break;
}
break;
}
break;
case 3:
switch(*z){
case 'a': if( 'n'==z[1] && 'd'==z[2] ){
rc = cmpp_tt_OpAnd;
} break;
case 'm': if( 'o'==z[1] && 'd'==z[2] ){
rc = cmpp_tt_OpModulo;
} break;
case 'n': if( 'o'==z[1] && 't'==z[2] ){
rc = cmpp_tt_OpNot;
} break;
case '<': if( '<'==z[1] && '<'==z[2] ){
rc = cmpp_tt_OpShiftL3;
} break;
}
break;
case 4:
if( 'g'==*z && 0==memcmp(z,"glob",4) ){
rc = cmpp_tt_OpGlob;
} break;
case 7:
if( 'd'==*z && 0==memcmp(z,"defined",7) ){
rc = cmpp_tt_OpDefined;
} break;
}
if( 0==rc && n && '!'==*z ){
/* Kludge for NOT operator */;
unsigned char const * ze = z + n;
while( z<ze && '!'==*z ) ++z;
rc = z==ze ? cmpp_tt_OpNot : 0;
}
return rc;
}
CMPP__EXPORT(char const *, cmpp_token_cstr3)(cmpp_token const * t,
cmpp_size_t * n, bool inner){
if( inner && t->zInner != t->z ){
*n = t->nInner;
return (char const *)t->zInner;
}
*n = t->n;
return (char const *)t->z;
}
CMPP__EXPORT(int, cmpp_token_strcmp)(cmpp_token const * t, char const *z,
cmpp_strlen_t n){
cmpp_size_t const nn = cmpp_strlen(z, n);
cmpp_size_t const min = nn<t->n ? nn : t->n;
int const rc = memcmp(t->z, z, min);
return (rc || nn==t->n) ? rc : (int)nn - (int)t->n;
}
#if defined(CMPP_MAIN) /* add main() */
int main(int argc, char const * const * argv){
int rc = 0;
cmpp * pp = 0;
cmpp_flag32_t ctorFlags = 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 && 0==strcmp("--safe-mode", argv[1]) ){
ctorFlags |= cmpp_ctor_F_SAFEMODE;
--argc;
++argv;
}
}
cmpp_ctor_opt const cfg = {
.flags = ctorFlags,
/* CMPP_DB_FILE is an undocumented feature for testing
purposes. */
.dbFile = getenv("CMPP_DB_FILE")
};
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);
cmpp_flush(pp);
if( rc ){
g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr);
}
cmpp_dtor(pp);
}else if( rc ){
g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc));
}
sqlite3_shutdown();
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}
#endif /* CMPP_MAIN */
#endif /* include guard */