16878 lines
537 KiB
C
16878 lines
537 KiB
C
/**
|
|
This C file contains both the header and source file for c-pp,
|
|
a.k.a. libcmpp.
|
|
*/
|
|
#if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED)
|
|
#define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED
|
|
#if !defined(_POSIX_C_SOURCE)
|
|
# define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
|
|
#endif
|
|
#define CMPP_AMALGAMATION
|
|
#if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED)
|
|
/**
|
|
This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated
|
|
using:
|
|
|
|
./c-pp -I. -I./src -Dsrcdir=./src -Dsed=/usr/bin/sed -o libcmpp.h ./tool/libcmpp.c-pp.h -o libcmpp.c ./tool/libcmpp.c-pp.c
|
|
|
|
with libcmpp 2.0.x c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37 @ 2026-03-08 14:50:35.123 UTC
|
|
*/
|
|
#define CMPP_PACKAGE_NAME "libcmpp"
|
|
#define CMPP_LIB_VERSION "2.0.x"
|
|
#define CMPP_LIB_VERSION_HASH "c02f3e3e2d3f3573a9a33c1474c2e52fc48e52c70730404a90d0ae51517e7d37"
|
|
#define CMPP_LIB_VERSION_TIMESTAMP "2026-03-08 14:50:35.123 UTC"
|
|
#define CMPP_LIB_CONFIG_TIMESTAMP "2026-03-08 15:32 GMT"
|
|
#define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP
|
|
#define CMPP_PLATFORM_EXT_DLL ".so"
|
|
#define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp"
|
|
|
|
#if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_)
|
|
#define NET_WANDERINGHORSE_CMPP_H_INCLUDED_
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
**
|
|
** The C-minus Preprocessor: C-like preprocessor. Why? Because C
|
|
** preprocessors _can_ process non-C code but generally make quite a
|
|
** mess of it. The purpose of this library is a customizable
|
|
** preprocessor suitable for use with arbitrary UTF-8-encoded text.
|
|
**
|
|
** The supported preprocessor directives are documented in the
|
|
** README.md hosted with this file (or see the link below).
|
|
**
|
|
** Any mention of "#" in the docs, e.g. "#if", is symbolic. The
|
|
** directive delimiter is configurable and defaults to "##". Define
|
|
** CMPP_DEFAULT_DELIM to a string when compiling to define the default
|
|
** at build-time.
|
|
**
|
|
** This API is presented as a library but was evolved from a
|
|
** monolithic app. Thus is library interface is likely still missing
|
|
** some pieces needed to make it more readily usable as a library.
|
|
**
|
|
** Author(s):
|
|
**
|
|
** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
|
|
**
|
|
** Canonical homes:
|
|
**
|
|
** - https://fossil.wanderinghorse.net/r/c-pp
|
|
** - https://sqlite.org/src/file/ext/wasm/c-pp-lite.c
|
|
**
|
|
** With the former hosting this app's SCM and the latter being the
|
|
** original deployment of c-pp.c, from which this library
|
|
** evolved. SQLite uses a "lite" version of c-pp, whereas _this_ copy
|
|
** is its much-heavier-weight fork.
|
|
*/
|
|
|
|
#if defined(CMPP_HAVE_AUTOCONFIG_H)
|
|
#include "libcmpp-autoconfig.h"
|
|
#endif
|
|
#if defined(HAVE_AUTOCONFIG_H)
|
|
#include "autoconfig.h"
|
|
#endif
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
# if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD)
|
|
# define CMPP_EXPORT extern
|
|
# elif defined(BUILD_libcmpp)
|
|
# define CMPP_EXPORT extern __declspec(dllexport)
|
|
# else
|
|
# define CMPP_EXPORT extern __declspec(dllimport)
|
|
# endif
|
|
#else
|
|
# define CMPP_EXPORT extern
|
|
#endif
|
|
|
|
/**
|
|
cmpp_FILE is a portability hack for WASM builds, where we want to
|
|
elide the (FILE*)-using pieces to avoid having a dependency on
|
|
Emscripten's POSIX I/O proxies. In all non-WASM builds it is
|
|
guaranteed to be an alias for FILE. On WASM builds it is guaranteed
|
|
to be an alias for void and the cmpp APIs which use it become
|
|
inoperative in WASM builds.
|
|
|
|
That said: the code does not yet support completely compiling out
|
|
(FILE*) dependencies, and may not be able to because canonical
|
|
sqlite3 (upon which it is based) depends heavily on file
|
|
descriptors and slightly on FILE handles.
|
|
*/
|
|
#if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__)
|
|
typedef void cmpp_FILE;
|
|
# define CMPP_PLATFORM_IS_WASM 1
|
|
#else
|
|
#include <stdio.h>
|
|
typedef FILE cmpp_FILE;
|
|
# define CMPP_PLATFORM_IS_WASM 0
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <inttypes.h> /* PRIu32 and friends */
|
|
#include <stdbool.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
32-bit flag bitmask type. This typedef exists primarily to improve
|
|
legibility of function signatures and member structs by conveying
|
|
their intent for use as flags instead of result codes or lengths.
|
|
*/
|
|
typedef uint32_t cmpp_flag32_t;
|
|
//typedef uint16_t cmpp_flag16_t;
|
|
|
|
/**
|
|
An X-macro which invokes its argument (a macro name) to expand to
|
|
all possible values of cmpp_rc_e entries. The macro name passed to
|
|
it is invoked once for each entry and passed 3 arguments: the enum
|
|
entry's full name (CMPP_RC_...), its integer value, and a help-text
|
|
string.
|
|
*/
|
|
#define cmpp_rc_e_map(E) \
|
|
E(CMPP_RC_OK, 0, \
|
|
"The quintessential not-an-error value.") \
|
|
E(CMPP_RC_ERROR, 100, \
|
|
"Generic/unknown error.") \
|
|
E(CMPP_RC_NYI, 101, \
|
|
"A placeholder return value for not yet implemented functions.") \
|
|
E(CMPP_RC_OOM, 102, \
|
|
"Out of memory. Indicates that a resource allocation " \
|
|
"request failed.") \
|
|
E(CMPP_RC_MISUSE, 103, \
|
|
"API misuse (invalid args)") \
|
|
E(CMPP_RC_RANGE, 104, \
|
|
"A range was violated.") \
|
|
E(CMPP_RC_ACCESS, 105, \
|
|
"Access to or locking of a resource was denied " \
|
|
"by some security mechanism or other.") \
|
|
E(CMPP_RC_IO, 106, \
|
|
"Indicates an I/O error. Whether it was reading or " \
|
|
"writing is context-dependent.") \
|
|
E(CMPP_RC_NOT_FOUND, 107, \
|
|
"Requested resource not found.") \
|
|
E(CMPP_RC_ALREADY_EXISTS, 108, \
|
|
"Indicates that a to-be-created resource already exists.") \
|
|
E(CMPP_RC_CORRUPT, 109, \
|
|
"Data consistency problem.") \
|
|
E(CMPP_RC_SYNTAX, 110, \
|
|
"Some sort of syntax error.") \
|
|
E(CMPP_RC_NOOP, 111, \
|
|
"Special sentinel value for some APIs.") \
|
|
E(CMPP_RC_UNSUPPORTED, 112, \
|
|
"An unsupported operation was request.") \
|
|
E(CMPP_RC_DB, 113, \
|
|
"Indicates db-level error (e.g. statement prep failed). In such " \
|
|
"cases, the error state of the related db handle (cmpp_db) " \
|
|
"will be updated to contain more information directly from the " \
|
|
"db driver.") \
|
|
E(CMPP_RC_NOT_DEFINED, 114, \
|
|
"Failed to expand an undefined value.") \
|
|
E(CMPP_RC_ASSERT, 116, "An #assert failed.") \
|
|
E(CMPP_RC_TYPE, 118, \
|
|
"Indicates that some data type or logical type is incorrect.") \
|
|
E(CMPP_RC_CANNOT_HAPPEN, 140, \
|
|
"This is intended only for internal use, to " \
|
|
"report conditions which \"cannot possibly happen\".") \
|
|
E(CMPP_RC_HELP, 141, \
|
|
"--help was used in the arguments to cmpp_process_argv()") \
|
|
E(CMPP_RC_NO_DIRECTIVE, 142, \
|
|
"A special case of CMPP_RC_NOT_FOUND needed to disambiguate.") \
|
|
E(CMPP_RC_end,200, \
|
|
"Must be the final entry in the enum. Used for creating client-side " \
|
|
"result codes which are guaranteed to live outside of this one's " \
|
|
"range.")
|
|
|
|
/**
|
|
Most functions in this library which return an int return result
|
|
codes from the cmpp_rc_e enum. None of these entries are
|
|
guaranteed to have a specific value across library versions except
|
|
for CMPP_RC_OK, which is guaranteed to always be 0 (and the API
|
|
guarantees that no other code shall have a value of zero).
|
|
|
|
The only reasons numbers are hard-coded to the values is to
|
|
simplify debugging during development. Clients may use
|
|
cmpp_rc_cstr() to get some human-readable (or programmer-readable)
|
|
form for any given value in this enum.
|
|
*/
|
|
enum cmpp_rc_e {
|
|
#define E(N,V,H) N = V,
|
|
cmpp_rc_e_map(E)
|
|
#undef E
|
|
};
|
|
typedef enum cmpp_rc_e cmpp_rc_e;
|
|
|
|
/**
|
|
Returns the string form of the given cmpp_rc_e value or NULL if
|
|
it's not a member of that enum.
|
|
*/
|
|
char const * cmpp_rc_cstr(int rc);
|
|
|
|
/**
|
|
CMPP_BITNESS specifies whether the library should use 32- or 64-bit
|
|
integer types for its size/length measurements. It's difficult to
|
|
envision use cases for a preprocessor which would require counters
|
|
or rangers larger than 32 bits provide for, so the default is 32
|
|
bits. Builds created with different CMPP_BITNESS values are
|
|
not binary-compatible.
|
|
*/
|
|
#define CMPP_BITNESS 32
|
|
#if 32==CMPP_BITNESS
|
|
/**
|
|
Unsigned integer type for string/stream lengths. 32 bits is
|
|
sufficient for all but the weirdest of inputs and outputs.
|
|
*/
|
|
typedef uint32_t cmpp_size_t;
|
|
|
|
/**
|
|
A signed integer type indicating the maximum length of strings or
|
|
byte ranges in a stream. It is most frequently used in API
|
|
signatures where a negative value means "if it's negative then use
|
|
strlen() to count it".
|
|
*/
|
|
typedef int32_t cmpp_ssize_t;
|
|
|
|
/**
|
|
The printf-format-compatible format letter (or group of letters)
|
|
appropriate for use with cmpp_size_t. Contrary to popular usage,
|
|
size_t cannot be portably used with printf(), without careful
|
|
casting, because it has neither a fixed size nor a standardized
|
|
printf/scanf format specifier (like the stdint.h types do).
|
|
*/
|
|
#define CMPP_SIZE_T_PFMT PRIu32
|
|
#elif 64==CMPP_BITNESS
|
|
typedef uint64_t cmpp_size_t;
|
|
typedef int64_t cmpp_ssize_t;
|
|
#define CMPP_SIZE_T_PFMT PRIu64
|
|
#else
|
|
#error "Invalid CMPP_BITNESS value. Expecting 32 or 64."
|
|
#endif
|
|
|
|
/**
|
|
Generic interface for streaming in data. Implementations must read
|
|
(at most) *n bytes from their input, copy it to dest, assign *n to
|
|
the number of bytes actually read, return 0 on success, and return
|
|
non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO).
|
|
|
|
When called, *n is the max length to read. On return, *n must be
|
|
set to the amount actually read. Implementations may need to
|
|
internally distinguish a short read due to EOF from a short read
|
|
due to an I/O error, e.g. using feof() and/or ferror(). A short
|
|
read for EOF is not an error but a short read for input failure is.
|
|
This library invariably treats a short read as EOF.
|
|
|
|
The state parameter is the implementation-specified input
|
|
file/buffer/whatever channel.
|
|
*/
|
|
typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
Generic interface for streaming out data. Implementations must
|
|
write n bytes from src to their destination channel and return 0 on
|
|
success, or a value from the cmpp_rc_e enum on error
|
|
(e.g. CMPP_RC_IO). The state parameter is the
|
|
implementation-specified output channel.
|
|
|
|
It is implementation-defined whether an n of 0 is legal. This
|
|
library refrains from passing 0 to these functions.
|
|
|
|
In the context of cmpp, the library makes no guarantees that output
|
|
will always end at a character boundary. It may send any given
|
|
multibyte character as the end resp. start of two calls to this
|
|
function. If that is of a concern for implementors of these
|
|
functions (e.g. because they're appending the output to a UI
|
|
widget), they may need to buffer all of the output before applying
|
|
it (see cmpp_b), or otherwise account for partial characters.
|
|
|
|
That said: the core library, by an accident of design, will always
|
|
emit data at character boundaries, assuming that its input is
|
|
well-formed UTF-8 text (which cmpp does not validate to be the
|
|
case). Custom cmpp_dx_f() implementations are not strictly
|
|
required to do so but, because of how cmpp is used, almost
|
|
certainly will. But relying on that is ill-advised.
|
|
*/
|
|
typedef int (*cmpp_output_f)(void * state, void const * src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Generic interface for flushing arbitrary output streams. Must
|
|
return 0 on success, a non-0 cmpp_rc_e value on error. When in
|
|
doubt, return CMPP_RC_IO on error. The interpretation of the state
|
|
parameter is implementation-specific.
|
|
*/
|
|
typedef int (*cmpp_flush_f)(void * state);
|
|
|
|
typedef struct cmpp_pimpl cmpp_pimpl;
|
|
typedef struct cmpp_api_thunk cmpp_api_thunk;
|
|
typedef struct cmpp_outputer cmpp_outputer;
|
|
|
|
/**
|
|
The library's primary class. Each one of these represents a
|
|
separate preprocessor instance.
|
|
|
|
See also: cmpp_dx (the class which client-side extensions interact
|
|
with the most).
|
|
*/
|
|
struct cmpp {
|
|
|
|
/**
|
|
API thunk object to support use via loadable modules. Client code
|
|
does not normally need to access this member, but it's exposed
|
|
here to give loadable modules more flexibility in how they use
|
|
the thunk.
|
|
|
|
This pointer is _always_ the same singleton object. The library
|
|
never exposes a cmpp object with a NULL api member.
|
|
*/
|
|
cmpp_api_thunk const * const api;
|
|
|
|
/**
|
|
Private internal state.
|
|
*/
|
|
cmpp_pimpl * const pimpl;
|
|
};
|
|
typedef struct cmpp cmpp;
|
|
|
|
/**
|
|
Flags for use with cmpp_ctor_cfg::flags.
|
|
*/
|
|
enum cmpp_ctor_e {
|
|
/* Sentinel value. */
|
|
cmpp_ctor_F_none = 0,
|
|
/* Disables #include. */
|
|
cmpp_ctor_F_NO_INCLUDE = 0x01,
|
|
/* Disables #pipe. */
|
|
cmpp_ctor_F_NO_PIPE = 0x02,
|
|
/* Disables #attach, #detach, and #query. */
|
|
cmpp_ctor_F_NO_DB = 0x04,
|
|
/* Disables #module. */
|
|
cmpp_ctor_F_NO_MODULE = 0x08,
|
|
/**
|
|
Disable all built-in directives which may work with the filesystem
|
|
or invoke external processes. Client-defined directives with the
|
|
cmpp_d_F_NOT_IN_SAFEMODE flag are also disabled. Directives
|
|
disabled via the cmpp_ctor_F_NO_... flags (or equivalent library
|
|
built-time options) do not get registered, so will trigger
|
|
"unknown directive" errors rather than safe-mode violation errors.
|
|
*/
|
|
cmpp_ctor_F_SAFEMODE = 0x10,
|
|
};
|
|
|
|
/**
|
|
A configuration object for cmpp_ctor(). This type may be extended
|
|
as new construction-time customization opportunities are
|
|
discovered.
|
|
*/
|
|
struct cmpp_ctor_cfg {
|
|
/**
|
|
Bitmask from the cmpp_ctor_e enum.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
/**
|
|
If not NULL then this must name either an existing SQLite3 db
|
|
file or the name of one which can be created on demand. If NULL
|
|
then an in-memory or temporary database is used (which one is
|
|
unspecified). The library copies these bytes, so they need not be
|
|
valid after a call to cmpp_ctor().
|
|
*/
|
|
char const * dbFile;
|
|
};
|
|
typedef struct cmpp_ctor_cfg cmpp_ctor_cfg;
|
|
|
|
/**
|
|
Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return
|
|
value must eventually be passed to cmpp_dtor() to free it.
|
|
|
|
The cfg argument, if not NULL, holds config info for the new
|
|
instance. If NULL, an instance with unspecified defaults is
|
|
used. These configuration pieces may not be modified after the
|
|
instance is created.
|
|
|
|
It returns 0 if *pp is ready to use and non-0 if either allocation
|
|
fails (in which case *pp will be set to 0) or initialization of *pp
|
|
failed (in which case cmpp_err_get() can be used to determine why
|
|
it failed). In either case, the caller must eventually pass *pp to
|
|
cmpp_dtor() to free it.
|
|
|
|
If the library is built with the symbol CMPP_CTOR_INSTANCE_INIT
|
|
defined, it must refer to a function with this signature:
|
|
|
|
int CMPP_CTOR_INSTANCE_INIT(cmpp *);
|
|
|
|
The library calls this before returning and arranges to call it
|
|
lazily if pp gets reset. The intent is that the init function
|
|
installs custom directives using cmpp_d_register(). That
|
|
initialization, on error, is expected to set its argument's error
|
|
state with cmpp_err_set().
|
|
*/
|
|
CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_cfg const * cfg);
|
|
|
|
/**
|
|
If pp is not NULL, it is passed to cmpp_reset() and then freed.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dtor(cmpp *pp);
|
|
|
|
|
|
/**
|
|
realloc(3)-compatible allocator used by the library.
|
|
|
|
This API very specifically uses sqlite3_realloc() as its basis.
|
|
*/
|
|
CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n);
|
|
|
|
/**
|
|
malloc(3)-compatible allocator used by the library.
|
|
|
|
This API very specifically uses sqlite3_malloc() as its basis.
|
|
*/
|
|
CMPP_EXPORT void * cmpp_malloc(size_t n);
|
|
|
|
/**
|
|
free(3)-compatible deallocator. It can also be used as a destructor
|
|
for cmpp_d_register() _if_ the memory in question is allocated by
|
|
cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of
|
|
APIs.
|
|
|
|
This is not called cmpp_free() to try to avoid any confusion with
|
|
cmpp_dtor().
|
|
*/
|
|
CMPP_EXPORT void cmpp_mfree(void *);
|
|
|
|
/**
|
|
If m is NULL then pp's persistent error code is set to CMPP_RC_OOM,
|
|
else this is a no-op. Returns pp's error code.
|
|
|
|
To simplify certain uses, pp may be NULL, in which case this
|
|
function returns CMPP_RC_OOM if m is NULL and 0 if it's not.
|
|
*/
|
|
CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * const m );
|
|
|
|
/**
|
|
Re-initializes all state of pp. This saves some memory for reuse
|
|
but resets it all to default states. This closes the database and
|
|
will also reset any autoloader, policies, or delimiter
|
|
configurations to their compile-time defaults. It retains only a
|
|
small amount of state, like any configuration which was passed to
|
|
cmpp_ctor().
|
|
|
|
After calling this, pp is in a cleanly-initialized state and may be
|
|
re-used with the cmpp API. Its database will not be initialized
|
|
until an API which needs it is called, so pp can be used with
|
|
functions which may otherwise be prohibited after the db is
|
|
opened. (Do we still have any?)
|
|
|
|
As of this writing, this is the only way to reliably recover a cmpp
|
|
instance from any significant errors. Errors may do things like
|
|
leave savepoints out of balance, and this cleanup step resets all
|
|
of that state. However, it also loses state like the autoloader.
|
|
|
|
TODO?: we need(?) a partial-clear operation which keeps some of the
|
|
instance's state, most notably custom directives, the db handle,
|
|
and any cached prepared statements. See cmpp_err_set() for the
|
|
distinction between recoverable and non-recoverable errors.
|
|
*/
|
|
CMPP_EXPORT void cmpp_reset(cmpp *pp);
|
|
|
|
#if 0
|
|
Not yet;
|
|
/**
|
|
If called before pp has initialized its database, this sets the
|
|
file name used for that database. If called afterwards, pp's error
|
|
state is updated and CMPP_RC_MISUSE is returned. If called while
|
|
pp has error state set, that code is returned without side-effects.
|
|
|
|
This does not open the database. It is opened on demand when
|
|
processing starts.
|
|
|
|
On success it returns 0 and this function makes a copy of zName.
|
|
|
|
As a special case, zName may be NULL to use the default name, but
|
|
there is little reason to do so unless one changes their mind after
|
|
setting it to non-NULL.
|
|
*/
|
|
CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName);
|
|
#endif
|
|
|
|
/**
|
|
Returns true if the bytes in the range [zName, zName+n) comprise a
|
|
legal name for a directive or a define.
|
|
|
|
It disallows any control characters, spaces, and most punctuation,
|
|
but allows alphanumeric (but must not start with a number) as well
|
|
as any of: -./:_ (but it may not start with '-'). Any characters
|
|
with a high bit set are assumed to be UTF-8 and are permitted as
|
|
well.
|
|
|
|
The name's length is limited, rather arbitrarily, to 64 bytes.
|
|
|
|
If the key is not legal then false is returned and if zErrPos is
|
|
not NULL then *zErrPos is set to the position in zName of the first
|
|
offending character. If validation fails because n is too long then
|
|
*zErrPos (if zErrPos is not NULL) will be set to 0.
|
|
|
|
Design note: this takes unsigned characters because it most
|
|
commonly takes input from cmpp_args::z strings.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos);
|
|
|
|
/**
|
|
Adds the given `#define` macro name to the list of macros, overwriting
|
|
any previous value.
|
|
|
|
zKey must be NUL-terminated and legal as a key. The rules are the
|
|
same as for cmpp_is_legal_key() except that a '=' is also permitted
|
|
if it's not at the start of the string because...
|
|
|
|
If zVal is NULL then zKey may contain an '=', from which the value
|
|
will be extracted. If zVal is not NULL then zKey may _not_ contain
|
|
an '='.
|
|
|
|
The ability for zKey to contain a key=val was initially to
|
|
facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was
|
|
initially a CLI app (as opposed to a library). It's considered a
|
|
"legacy" feature, not recommended for most purposes, but it _is_
|
|
convenient for that particular purpose.
|
|
|
|
Returns 0 on success and updates pp's error state on error.
|
|
|
|
See: cmpp_define_v2()
|
|
See: cmpp_undef()
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey,
|
|
char const *zVal);
|
|
|
|
/**
|
|
Works like cmpp_define_legacy() except that it does not examine zKey to
|
|
see if it contains an '='.
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal);
|
|
|
|
/**
|
|
Removes the given `#define` macro name from the list of
|
|
macros. zKey is, in this case, treated as a GLOB pattern, and all
|
|
matching defines are deleted.
|
|
|
|
If nRemoved is not NULL then, on success, it is set to the number
|
|
of entries removed by this call.
|
|
|
|
Returns 0 on success and updates pp's error state on error. It is not
|
|
an error if no value was undefined.
|
|
|
|
This does _not_ affect defines made using cmpp_define_shadow().
|
|
*/
|
|
CMPP_EXPORT int cmpp_undef(cmpp *pp, const char * zKey,
|
|
unsigned int *nRemoved);
|
|
|
|
/**
|
|
This works similarly to cmpp_define_v2() except that:
|
|
|
|
- It does not permit its zKey argument to contain the value
|
|
part like that function does.
|
|
|
|
- The new define "shadows", rather than overwrites, an existing
|
|
define with the same name.
|
|
|
|
All APIs which look up define keys will get the value of the shadow
|
|
define. The shadow can be uninstalled with cmpp_define_unshadow(),
|
|
effectively restoring its previous value (if any). That function
|
|
should be called one time for each call to this one, passing the
|
|
same key to each call. A given key may be shadowed any number of
|
|
times by this routine. Each one saves the internal ID of the shadow
|
|
into *pId (and pId must not be NULL). That value must be passed to
|
|
cmpp_define_unshadow() to ensure that the "shadow stack" stays
|
|
balanced in the face of certain error-handling paths.
|
|
|
|
cmpp_undef() will _not_ undefine an entry added through this
|
|
interface.
|
|
|
|
Returns pp's persistent error code (0 on success).
|
|
|
|
Design note: this function was added to support adding a define
|
|
named __FILE__ to input scripts which works like it does in a C
|
|
preprocessor. Alas, supporting __LINE__ would be much more costly,
|
|
as it would have to be updated in the db from several places, so
|
|
its cost would outweigh its meager benefits.
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey,
|
|
char const *zVal,
|
|
int64_t * pId);
|
|
|
|
/**
|
|
Removes the most shadow define matching the zKey and id values
|
|
which where previously passed to cmpp_define_shadow(). It is not
|
|
an error if no match is found, in which case this function has no
|
|
visible side-effects.
|
|
|
|
Unlike cmpp_undef(), zKey is matched precisely, not against a glob.
|
|
|
|
In order to keep the "shadow stack" properly balanced, this will
|
|
delete any shadow entries for the given key which have the same id
|
|
or a newer one (i.e. they were left over from a missed call to
|
|
cmpp_define_unshadow()).
|
|
|
|
Returns pp's persistent error code (0 on success).
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey,
|
|
int64_t id);
|
|
|
|
/**
|
|
Adds the given dir to the list of includes. They are checked in the
|
|
order they are added.
|
|
*/
|
|
CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey);
|
|
|
|
/**
|
|
Sets pp's default output channel. If pp already has a channel, it
|
|
is closed[^1].
|
|
|
|
The second argument, if not NULL, is _bitwise copied_, which has
|
|
implications for the ownership of out->state (see below). If it is
|
|
is NULL, cmpp_outputer_empty is copied in its place, which makes
|
|
further output a no-op.
|
|
|
|
The third argument is a symbolic name for the channel (perhaps its
|
|
file name). It is used in debugging and error messages. cmpp does
|
|
_not_ copy it, so its bytes must outlive the cmpp instance. (In
|
|
practice, the byte names come from main()'s argv or scope-local
|
|
strings in the same scope as the cmpp instance.) This argument
|
|
should only be NULL if the second argument is.
|
|
|
|
cmpp_reset(), or opening another channel, will end up calling
|
|
out->cleanup() (if it's not NULL) and passing it a pointer to a
|
|
_different_ cmpp_outputer object, but with the _same_
|
|
cmpp_outputer::state pointer, which may invalidate out->state.
|
|
|
|
To keep cmpp from doing that, make a copy of the output object, set
|
|
the cleanup member of that copy to NULL, then pass that copy to this
|
|
function. It is then up to the client to call out->cleanup(out) when
|
|
the time is right.
|
|
|
|
For example:
|
|
|
|
```
|
|
cmpp_outputer my = cmpp_outputer_FILE;
|
|
my.state = cmpp_fopen("/some/file", "wb");
|
|
cmpp_outputer tmp = my;
|
|
tmp.cleanup = NULL;
|
|
cmpp_outputer_set( pp, &tmp, "my file");
|
|
...
|
|
my.cleanup(&my); // will cmpp_fclose(my.state)
|
|
```
|
|
|
|
Potential TODO: internally store the output channel as a pointer.
|
|
It's not clear whether that would resolve the above grief or
|
|
compound it.
|
|
|
|
[^1]: depending on the output channel, it might not _actually_ be
|
|
closed, but pp is disassociated from it, in any case.
|
|
*/
|
|
CMPP_EXPORT
|
|
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName);
|
|
|
|
/**
|
|
Treats the range (zIn,zIn+nIn] as a complete cmpp input and process
|
|
it appropriately. zName is the name of the input for purposes of
|
|
error messages. If nIn is negative, strlen() is used to calculate
|
|
it.
|
|
|
|
This is a no-op if pp has any error state set. It returns pp's
|
|
persistent error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName,
|
|
unsigned char const * zIn,
|
|
cmpp_ssize_t nIn);
|
|
|
|
/**
|
|
A thin proxy for cmpp_process_string() which reads its input from
|
|
the given file. Returns 0 on success, else returns pp's persistent
|
|
error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName);
|
|
|
|
/**
|
|
A thin proxy for cmpp_process_string() which reads its input from
|
|
the given input source, consuming it all before passing it
|
|
on. Returns 0 on success, else returns pp's persistent error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName,
|
|
cmpp_input_f src, void * srcState);
|
|
|
|
/**
|
|
Process the given main()-style arguments list. When calling from
|
|
main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0]
|
|
(the binary's name).
|
|
|
|
Each argument is expected to be one of the following:
|
|
|
|
1) One of --help or -?: causes this function to return CMPP_RC_HELP
|
|
without emitting any output.
|
|
|
|
2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to
|
|
Y.
|
|
|
|
3) -UX: unsets all defines matching glob X.
|
|
|
|
4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to
|
|
the contents of that file.
|
|
|
|
5) -IX: adds X to the "include path". If _no_ include path is
|
|
provided then cmpp assumes a path of ".", but if _any_ paths are
|
|
provided then it does not assume that "." is in the path.
|
|
|
|
6) --chomp-F: specifies whether subsequent -F flags should "chomp"
|
|
one trailing newline from their input files.
|
|
|
|
7) --delimiter|-d=X sets the directive delimiter to X. Its default
|
|
is a compile-time constant.
|
|
|
|
8) --output|-o=filename: sets the output channel to the given file.
|
|
A value of "-" means stdout. If no output channel is opened when
|
|
this is called, and files are to be processed, stdout is
|
|
assumed. (That's a historical artifact from earlier evolutions.)
|
|
To override that behavior use cmpp_outputer_set().
|
|
|
|
9) --file|-f=filename: sets the input channel to the given file.
|
|
A value of "-" means stdin.
|
|
|
|
10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it.
|
|
Because it's difficult to pack multiple lines of text into this,
|
|
it's really of use for testing #expr and #assert.
|
|
|
|
11) --@policy=X sets the @token@ parsing policy. X must be
|
|
one of (retain, elide, error, off) and defaults to off.
|
|
|
|
12) -@: shorthand for --@policy=error.
|
|
|
|
13) --sql-trace: enables tracing of all SQL statements to stderr.
|
|
This is useful for seeing how a script interacts with the
|
|
database. Use --no-sql-trace to disable it.
|
|
|
|
14) --sql-trace-x: like --sql-trace but replaces bound parameter
|
|
placeholders with their SQL values. Use --no-sql-trace to disable
|
|
it.
|
|
|
|
15) --dump-defines: emit all defines to stdout. They should
|
|
arguably go to stderr but that interferes with automated testing.
|
|
|
|
Any argument which does not match one of the above flags, and does
|
|
not start with a "-", is treated as if it were passed to the --file
|
|
flag.
|
|
|
|
Flags may start with either 1 or 2 dashes - they are equivalent.
|
|
|
|
Flags which take a value may either be in the form X=Y or X Y, i.e.
|
|
may be a single argv entry or a pair of them.
|
|
|
|
It performs two passes on the arguments: the first is for validation
|
|
checking for --help/-?. No processing of the input(s) and output(s)
|
|
happens unless the first pass completes. Similarly, no validation of
|
|
whether any provided filename are actually readable is performed
|
|
until the second pass.
|
|
|
|
Arguments are processed in the order they are given. Thus the following
|
|
have completely different meanings:
|
|
|
|
1) -f foo.in -Dfoo
|
|
2) -Dfoo -f foo.in
|
|
|
|
The former will process foo.in before defining foo.
|
|
|
|
This behavior makes it possible to process multiple input files in
|
|
a single go:
|
|
|
|
--output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in
|
|
|
|
It returns 0 on success. cmpp_err_get() can be used to fetch any
|
|
error message.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv);
|
|
|
|
/**
|
|
Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP.
|
|
It emits --help-style text to the given output stream.
|
|
As the first argument pass it either argv[0] or NULL. The second
|
|
should normally be stdout or stderr.
|
|
|
|
Reminder to self: this could take a (cmpp_output_f,void*) pair
|
|
instead, and should do so for the sake of WASI builds, but its impl
|
|
currently relies heavily on fprintf() formatting.
|
|
*/
|
|
CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName,
|
|
cmpp_FILE *os);
|
|
|
|
/**
|
|
Returns pp's current error number (from the cmpp_rc_e enum) and
|
|
sets *zMsg (if zMsg is not NULL) to the error string. The bytes are
|
|
owned by pp and may be invalidated by any functions which take pp
|
|
as an argument.
|
|
|
|
See cmpp_err_get() for more information.
|
|
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg);
|
|
|
|
/**
|
|
Sets or clears (if 0==rc) pp's persistent error state. zFmt may be
|
|
NULL or a format string compatible with sqlite3_mprintf().
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
rc without other side effects.
|
|
|
|
Returns rc with one exception: if allocation of a copy of the error
|
|
string fails then CMPP_RC_OOM will be returned (and pp will be
|
|
updated appropriately).
|
|
|
|
If pp is currently processing a script, the resulting error string
|
|
will be prefixed with the name of the current input script and the
|
|
line number of the directive which triggered the error.
|
|
|
|
It is legal for zFmt to be NULL or an empty string, in which case a
|
|
default, vague error message is used (without requiring allocation
|
|
of a new string).
|
|
|
|
Recoverable vs. unrecoverable errors:
|
|
|
|
Most cmpp APIs become no-ops if their cmpp object has error state
|
|
set, treating any error as unrecoverable. That approach simplifies
|
|
writing code for it by allowing multiple calls to be chained
|
|
without concern for whether the previous one succeeded.
|
|
|
|
ACHTUNG: simply clearing the error state by passing 0 as the 2nd
|
|
argument to this function is _not_ enough to recover from certain
|
|
errors. e.g. an error in the middle of a script may leave db
|
|
savepoints imbalanced. The only way to _fully_ recover from any
|
|
significant failures is to use cmpp_reset(), which resets all of
|
|
pp's state.
|
|
|
|
APIs which may set the error state but are recoverable by simply
|
|
clearing that state will document that. Errors from APIs which do
|
|
not claim to be recoverable in error cases must be treated as
|
|
unrecoverable.
|
|
|
|
See cmpp_err_get() for more information.
|
|
|
|
FIXME: we need a different variant for WASM builds, where variadics
|
|
aren't a usable thing.
|
|
|
|
Potential TODO: change the error-reporting interface to support
|
|
distinguishing from recoverable and non-recoverable errors. "The
|
|
problem" is that no current uses need that - they simply quit and
|
|
free up the cmpp instance on error. Maybe that's the way it
|
|
_should_ be.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_set(cmpp *pp, int rc, char const *zFmt, ...);
|
|
|
|
/**
|
|
A variant of cmpp_err_set() which is not variadic, as a consolation
|
|
for WASM builds. zMsg may be NULL. The given string, if not NULL,
|
|
is copied.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_set1(cmpp *pp, int rc, char const *zMsg);
|
|
|
|
#if 0
|
|
/**
|
|
Clears any error state in pp. Most cmpp APIs become no-ops if their
|
|
cmpp instance has its error flag set.
|
|
|
|
See cmpp_err_get() for important details about doing this.
|
|
*/
|
|
//CMPP_EXPORT void cmpp_err_clear(cmpp *pp);
|
|
|
|
/**
|
|
This works like a combination of cmpp_err_get() and
|
|
cmpp_err_clear(), in that it clears pp's error state by transferring
|
|
ownership of it to the caller. If pp has any error state, *zMsg is
|
|
set to the error string and the error code is returned, else 0 is
|
|
returned and *zMsg is set to 0.
|
|
|
|
The string returned via *zMsg must eventually be passed to
|
|
cmpp_mfree() to free it.
|
|
|
|
This function is provided simply as an optimization to avoid
|
|
having to copy the error string in some cases.
|
|
|
|
ACHTUNG: see the ACHTUNG in cmpp_err_clear().
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg);
|
|
#endif
|
|
|
|
/**
|
|
Returns pp's current error code, which will be 0 if it currently
|
|
has no error state.
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
0.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_has(cmpp const * pp);
|
|
|
|
/**
|
|
Returns true if pp was initialized in "safe mode". That is: if the
|
|
cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor().
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp);
|
|
|
|
/**
|
|
Starts a new SAVEPOINT in the database. Returns non-0, and updates
|
|
pp's persistent error state, on failure.
|
|
|
|
If this returns 0, the caller is obligated to later call either
|
|
cmpp_sp_commit() or cmpp_sp_rollback() later.
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_begin(cmpp *pp);
|
|
|
|
/**
|
|
Commits the most recently-opened savepoint UNLESS pp's error state
|
|
is set, in which case this behaves like cmpp_sp_rollback().
|
|
Returns 0 on success.
|
|
|
|
A call to cmpp_sp_begin() which returns 0 obligates the caller to
|
|
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
|
|
for either to be called in any other context.
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_commit(cmpp *pp);
|
|
|
|
/**
|
|
Rolls back the most recently-opened savepoint. Returns 0 on
|
|
success.
|
|
|
|
A call to cmpp_sp_begin() which returns 0 obligates the caller to
|
|
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
|
|
for either to be called in any other context.
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires state to be a (FILE*), which
|
|
this function passes the call on to fwrite(). Returns 0 on
|
|
success, CMPP_RC_IO on error.
|
|
|
|
If state is NULL then stdout is used.
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires state to be a ([const] int*)
|
|
referring to a writable file descriptor, which this function
|
|
dereferences and passes to write(2).
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_input_f() implementation which requires that state be
|
|
a readable (FILE*) handle, which it passes to fread(3).
|
|
*/
|
|
CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
A cmpp_input_f() implementation which requires that state be a
|
|
readable file descriptor, in the form of an ([const] int*), which
|
|
this function passes to write(2).
|
|
*/
|
|
CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened
|
|
for writing, which this function passes the call on to
|
|
fflush(). If fflush() returns 0, so does this function, else it
|
|
returns non-0.
|
|
*/
|
|
CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile);
|
|
|
|
/**
|
|
A generic streaming routine which copies data from an
|
|
cmpp_input_f() to an cmpp_outpuf_f().
|
|
|
|
Reads all data from inF(inState,...) in chunks of an unspecified
|
|
size and passes them on to outF(outState,...). It reads until inF()
|
|
returns fewer bytes than requested or returns non-0. Returns the
|
|
result of the last call to outF() or (if reading fails) inF().
|
|
Results are undefined if either of inState or outState arguments
|
|
are NULL and their callbacks require non-NULL. (This function
|
|
cannot know whether a NULL state argument is legal for the given
|
|
callbacks.)
|
|
|
|
Here is an example which basically does the same thing as the
|
|
cat(1) command on Unix systems:
|
|
|
|
```
|
|
cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout);
|
|
```
|
|
|
|
Or copy a FILE to a string buffer:
|
|
|
|
```
|
|
cmpp_b os = cmpp_b_empty;
|
|
FILE * f = cmpp_fopen(...);
|
|
rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os);
|
|
// On error os might be partially populated.
|
|
// Eventually clean up the buffer:
|
|
cmpp_b_clear(&os);
|
|
```
|
|
*/
|
|
CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState,
|
|
cmpp_output_f outF, void * outState);
|
|
|
|
/**
|
|
Reads the entire contents of the given input stream, allocating it
|
|
in a buffer. On success, returns 0, assigns *pOut to the buffer,
|
|
and *nOut to the number of bytes read (which will be fewer than are
|
|
allocated). It guarantees that on success it NUL-terminates the
|
|
buffer at one byte after the returned size, with one exception: if
|
|
the string has no input, both *pOut and *nOut will be set to 0.
|
|
|
|
On error it returns whatever code xIn() returns.
|
|
*/
|
|
CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn,
|
|
unsigned char **pOut, cmpp_size_t * nOut);
|
|
|
|
/**
|
|
_Almost_ equivalent to fopen(3) but:
|
|
|
|
- If name=="-", it returns one of stdin or stdout, depending on the
|
|
mode string: stdout is returned if 'w' or '+' appear, otherwise
|
|
stdin.
|
|
|
|
If it returns NULL, the global errno "should" contain a description
|
|
of the problem unless the problem was argument validation.
|
|
|
|
If at all possible, use cmpp_fclose() (as opposed to fclose()) to
|
|
close these handles, as it has logic to skip closing the three
|
|
standard streams.
|
|
*/
|
|
CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode);
|
|
|
|
/**
|
|
Passes f to fclose(3) unless f is NULL or one of the C-standard
|
|
handles (stdin, stdout, stderr), in which cases it does nothing at
|
|
all.
|
|
*/
|
|
CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f);
|
|
|
|
/**
|
|
A cleanup callback interface for use with cmpp_outputer::cleanup().
|
|
Implementations must handle self->state appropriately for its type,
|
|
and clear self->state if appropriate, but must not free the self
|
|
object. It is implementation-specified whether self->state and/or
|
|
self->name are set to NULL by this function. Whether they should be
|
|
often depends on how they're used.
|
|
*/
|
|
typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self);
|
|
|
|
/**
|
|
An interface which encapsulates data for managing a streaming
|
|
output destination, primarily intended for use with cmpp_stream()
|
|
but also used internally by cmpp for directing output to a buffer.
|
|
*/
|
|
struct cmpp_outputer {
|
|
/**
|
|
An optional descriptive name for the channel. The bytes
|
|
are owned elsewhere and are typically static or similarly
|
|
long-lived.
|
|
*/
|
|
char const * name;
|
|
|
|
/**
|
|
Output channel.
|
|
*/
|
|
cmpp_output_f out;
|
|
|
|
/**
|
|
flush() implementation. This may be NULL for most uses of this
|
|
class. Cases which specifically require it must document that
|
|
requirement so.
|
|
*/
|
|
cmpp_flush_f flush;
|
|
|
|
/**
|
|
Optional: if not NULL, it must behave appropriately for its state
|
|
type, cleaning up any memory it owns.
|
|
*/
|
|
cmpp_outputer_cleanup_f cleanup;
|
|
|
|
/**
|
|
State to be used when calling this->out() and this->flush(),
|
|
namely: this->out(this->state, ... ) and
|
|
this->flush(this->state).
|
|
|
|
Whether or not any given instance of this class owns the memory
|
|
pointed to by this member must be documented for their cleanup()
|
|
method.
|
|
|
|
Because cmpp_outputer instances frequently need to be stashed and
|
|
unstashed via bitwise copying, it is illegal to replace this
|
|
pointer after its initial assignment. The object it points to may
|
|
be mutated freely, but this pointer must stay stable for the life
|
|
of this object.
|
|
*/
|
|
void * state;
|
|
};
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
const-copy initialization.
|
|
*/
|
|
#define cmpp_outputer_empty_m \
|
|
{.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL}
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
non-const-copy initialization. These copies can, for purposes of
|
|
cmpp's output API, be used as-is to have cmpp process its inputs
|
|
but generate no output.
|
|
*/
|
|
CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty;
|
|
|
|
/**
|
|
If o->out is not NULL, the result of o->out(o->state,p,n) is
|
|
returned, else 0 is returned.
|
|
*/
|
|
CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n);
|
|
|
|
/**
|
|
If o->flush is not NULL, the result of o->flush(o->state) is
|
|
returned, else 0 is returned.
|
|
*/
|
|
CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o);
|
|
|
|
/**
|
|
If o->cleanup is not NULL, it is called, otherwise this is a no-op.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o);
|
|
|
|
/**
|
|
A cmpp_outputer initializer which uses cmpp_flush_f_FILE(),
|
|
cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its
|
|
implementation. After copying this, the state member must be
|
|
pointed to an opened-for-writing (FILE*).
|
|
*/
|
|
CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE;
|
|
|
|
/**
|
|
The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE. If
|
|
self->state is not NULL then it is passed to fclose() (_unless_ it
|
|
is stdin, stdout, or stderr) and set to NULL. self->name is
|
|
also set to NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self);
|
|
|
|
/**
|
|
Sets pp's current directive delimiter to a copy of the
|
|
NUL-terminated zDelim. The delimiter is the sequence which starts
|
|
line and distinguishes cmpp directives from other input, in the
|
|
same way that C preprocessors use '#' as a delimiter.
|
|
|
|
If zDelim is NULL then the default delimiter is used. The default
|
|
delimiter can be set when compiling the library by defining
|
|
CMPP_DEFAULT_DELIM to a quoted string value.
|
|
|
|
zDelim is assumed to be in UTF-8 encoding. If any bytes in the
|
|
range (0,32) are found, CMPP_RC_MISUSE is returned and pp's
|
|
persistent error state is set.
|
|
|
|
The delimiter must be short and syntactically unambiguous for the
|
|
intended inputs. It has a rather arbitrary maximum length of 12,
|
|
but it's difficult to envision it being remotely human-friendly
|
|
with a delimiter longer than 3 bytes. It's conceivable, but
|
|
seemingly far-fetched, that longer delimiters might be interesting
|
|
in some machine-generated cases, e.g. using a random sequence as
|
|
the delimiter.
|
|
|
|
Returns 0 on success. Returns non-0 if called when the delimiter
|
|
stack is empty, if it cannot copy the string or zDelim is deemed
|
|
unsuitable for use as a delimiter. Calling this when the stack is
|
|
empty represents a serious API misuse (indicating that
|
|
cmpp_delimiter_pop() was used out of scope) and will trigger an
|
|
assert() in debug builds. Except for that last case, errors from
|
|
this function are recoverable (see cmpp_err_set()).
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim);
|
|
|
|
/**
|
|
Fetches pp's current delimiter string, assigning it to *zDelim.
|
|
The string is owned by pp and will be invalidated by any call to
|
|
cmpp_delimiter_set() or the #delimiter script directive.
|
|
|
|
If, by some odd usage constellation, this is called after an
|
|
allocation of the delimiter stack has failed, this will set *zDelim
|
|
to the compile-time-default delimiter. That "cannot happen" in normal use because such a failure
|
|
would have been reacted to and this would not be called.
|
|
*/
|
|
CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim);
|
|
|
|
/**
|
|
Pushes zDelim as the current directive delimiter. Returns 0 on
|
|
success and non-zero on error (invalid zDelim value or allocation
|
|
error). If this returns 0 then the caller is obligated to
|
|
eventually call cmpp_delimiter_pop() one time. If it returns non-0
|
|
then they must _not_ call that function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim);
|
|
|
|
/**
|
|
Must be called one time for each successful call to
|
|
cmpp_delimiter_push(). It restores the directive delimimter to the
|
|
value it has when cmpp_delimiter_push() was last called.
|
|
|
|
Returns pp's current error code, and will set it to non-0 if called
|
|
when no cmpp_delimiter_push() is active. Popping an empty stack
|
|
represents a serious API misuse and may fail an assert() in debug
|
|
builds.
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp);
|
|
|
|
/**
|
|
If z[*n] ends on a \n or \r\n pair, it/they are stripped,
|
|
*z is NUL-terminated there, and *n is adjusted downwards
|
|
by 1 or 2. Returns true if it chomped, else false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n);
|
|
|
|
/**
|
|
A basic memory buffer class. This is primarily used with
|
|
cmpp_outputer_b to capture arbitrary output for later use.
|
|
It's also used for incrementally creating dynamic strings.
|
|
|
|
TODO: add the heuristic that an nAlloc of 0 with a non-NULL z
|
|
refers to externally-owned memory. This would change the
|
|
buffer-write APIs to automatically copy it before making any
|
|
changes. We have code for this in the trees this class derives
|
|
from, it just needs to be ported over. It would allow us to avoid
|
|
allocating in some cases where we need a buffer but it will always
|
|
(or commonly) be a copy of a static string, like a single space.
|
|
*/
|
|
struct cmpp_b {
|
|
/**
|
|
This buffer's memory, owned by this object. This library exclusively
|
|
uses sqlite3_realloc() and friends for memory management.
|
|
|
|
If this pointer is taken away from this object then it must
|
|
eventually be passed to cmpp_mfree().
|
|
*/
|
|
unsigned char * z;
|
|
/**
|
|
Number of bytes of this->z which are in use, not counting any
|
|
automatic NUL terminator which this class's APIs may add.
|
|
*/
|
|
cmpp_size_t n;
|
|
/**
|
|
Number of bytes allocated in this->z.
|
|
|
|
Potential TODO: use a value of zero here, with a non-zero
|
|
this->n, to mean that this->z is owned elsewhere. This would
|
|
cause cmpp_b_append() to copy its original source before
|
|
appending. Similarly, cmpp_b_clear() would necessarily _not_
|
|
free this->z. We've used that heuristic in a predecessor of this
|
|
class in another tree to good effect for years, but it's not
|
|
certain that we'd get the same level of utility out of that
|
|
capability as we do in that other project.
|
|
*/
|
|
cmpp_size_t nAlloc;
|
|
|
|
/**
|
|
cmpp_b APIs which may fail will set this. Similarly, most
|
|
of the cmpp_b APIs become no-ops if this is non-0.
|
|
*/
|
|
int errCode;
|
|
};
|
|
|
|
typedef struct cmpp_b cmpp_b;
|
|
|
|
/**
|
|
An empty-initialized cmpp_b struct for use in const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0}
|
|
|
|
/**
|
|
An empty-initialized cmpp_b struct for use in non-copy copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_b cmpp_b_empty;
|
|
|
|
/**
|
|
Frees s->z and zeroes out s but does not free s.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_clear(cmpp_b *s);
|
|
|
|
/**
|
|
If s has content, s->nUsed is set to 0 and s->z is NUL-terminated
|
|
at its first byte, else this is a no-op. s->errCode is
|
|
set to 0. Returns s.
|
|
*/
|
|
CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *s);
|
|
|
|
/**
|
|
Swaps all contents of the given buffers, including their persistent
|
|
error code.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r);
|
|
|
|
/**
|
|
If s->errCode is 0 and s->nAlloc is less than n, s->z is
|
|
reallocated to have at least n bytes, else this is a no-op. Returns
|
|
0 on success, CMPP_RC_OOM on error.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_reserve(cmpp_b *s, cmpp_size_t n);
|
|
|
|
/**
|
|
Works just like cmpp_b_reserve() but on allocation error it
|
|
updates pp's error state.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n);
|
|
|
|
/**
|
|
Appends n bytes from src to os, reallocating os as necessary.
|
|
Returns 0 on succes, CMPP_RC_OOM on allocation error.
|
|
|
|
Errors from this function, and the other cmpp_b_append...()
|
|
variants, are recoverable (see cmpp_err_set()).
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append(cmpp_b * os, void const *src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Works just like cmpp_b_append() but on allocation error it
|
|
updates pp's error state.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append4(cmpp * pp,
|
|
cmpp_b * os,
|
|
void const * src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Appends ch to the end of os->z, expanding as necessary, and
|
|
NUL-terminates os. Returns os->errCode and is a no-op if that is
|
|
non-0 when this is called. This is slightly more efficient than
|
|
passing length-1 strings to cmpp_b_append() _if_ os's memory
|
|
is pre-allocated with cmpp_b_reserve(), otherwise it may be
|
|
less efficient because it may need to allocate frequently if used
|
|
repeatedly.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch);
|
|
|
|
/**
|
|
Appends a decimal string representation of d to os. Returns
|
|
os->errCode and is a no-op if that is non-0 when this is called.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d);
|
|
|
|
/** int64_t counterpart of cmpp_b_append_i32(). */
|
|
CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d);
|
|
|
|
/**
|
|
A thin wrapper around cmpp_chomp() which chomps b->z.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires that its first argument be a
|
|
(cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes
|
|
of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in
|
|
reallocation error. On success it always NUL-terminates buffer->z.
|
|
A NULL buffer is treated as success but has no side effects.
|
|
|
|
Example usage:
|
|
|
|
```
|
|
cmpp_b os = cmpp_b_empty;
|
|
int rc = cmpp_stream(cmpp_input_f_FILE, stdin,
|
|
cmpp_output_f_b, &os);
|
|
...
|
|
cmpp_b_clear(&os);
|
|
```
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_outputer_cleanup_f() implementation which requires that
|
|
self->state be either NULL or a cmpp_b pointer. This function
|
|
passes it to cmpp_b_clear(). It does _not_ set self->state or
|
|
self->name to NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self);
|
|
|
|
/**
|
|
A cmpp_outputer prototype which can be copied to use a dynamic
|
|
string buffer as an output source. Its state member must be set (by
|
|
the client) to a cmpp_b instance. Its out() method is
|
|
cmpp_output_f_b(). Its cleanup() method is
|
|
cmpp_outputer_cleanup_f_b(). It has no flush() method.
|
|
*/
|
|
extern const cmpp_outputer cmpp_outputer_b;
|
|
|
|
/**
|
|
Returns a string containing version information in an unspecified
|
|
format.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_version(void);
|
|
|
|
/**
|
|
Type IDs for directive lines and argument-parsing tokens.
|
|
|
|
This is largely a historical artifact and work is underway
|
|
to factor this back out of the public API.
|
|
*/
|
|
enum cmpp_tt {
|
|
|
|
/**
|
|
X-macro which defines token types. It invokes E(X,Y) for each
|
|
entry, where X is the base name part of the token type and Y is the
|
|
token name as it appears in input scripts (if any, else it's 0).
|
|
|
|
Maintenance reminder: their ordering in this map is insignificant
|
|
except that None must be first and must have the value 0.
|
|
|
|
Some of the more significant ones are:
|
|
|
|
- Word: an unquoted word-like token.
|
|
|
|
- String: a quoted string.
|
|
|
|
- StringAt: an @"..." string.
|
|
|
|
- GroupParen, GroupBrace, GroupSquiggly: (), [], and {}
|
|
|
|
- All which start with D_ are directives. D_Line is a transitional
|
|
state between "unparsed" and another D_... value.
|
|
*/
|
|
#define cmpp_tt_map(E) \
|
|
E(None, 0) \
|
|
E(RawLine, 0) \
|
|
E(Unknown, 0) \
|
|
E(Word, 0) \
|
|
E(Noop, 0) \
|
|
E(Int, 0) \
|
|
E(Null, 0) \
|
|
E(String, 0) \
|
|
E(StringAt, 0) \
|
|
E(GroupParen, 0) \
|
|
E(GroupBrace, 0) \
|
|
E(GroupSquiggly,0) \
|
|
E(OpEq, "=") \
|
|
E(OpNeq, "!=") \
|
|
E(OpLt, "<") \
|
|
E(OpLe, "<=") \
|
|
E(OpGt, ">") \
|
|
E(OpGe, ">=") \
|
|
E(ArrowR, "->") \
|
|
E(ArrowL, "<-") \
|
|
E(Plus, "+") \
|
|
E(Minus, "-") \
|
|
E(ShiftR, ">>") \
|
|
E(ShiftL, "<<") \
|
|
E(ShiftL3, "<<<") \
|
|
E(OpNot, "not") \
|
|
E(OpAnd, "and") \
|
|
E(OpOr, "or") \
|
|
E(OpDefined, "defined") \
|
|
E(OpGlob, "glob") \
|
|
E(OpNotGlob, "not glob") \
|
|
E(AnyType, 0) \
|
|
E(Eof, 0)
|
|
|
|
#define E(N,TOK) cmpp_TT_ ## N,
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
/** Used by cmpp_d_register() to assign new IDs. */
|
|
cmpp_TT__last
|
|
};
|
|
typedef enum cmpp_tt cmpp_tt;
|
|
|
|
/**
|
|
For all of the cmpp_tt enum entries, returns a string form of the
|
|
enum entry name, e.g. "cmpp_TT_D_If". Returns NULL for any other
|
|
values
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_tt_cstr(int tt);
|
|
|
|
/**
|
|
Policies for how to handle undefined @tokens@ when performing
|
|
content filtering.
|
|
*/
|
|
enum cmpp_atpol_e {
|
|
/** Sentinel value. */
|
|
cmpp_atpol_invalid = -1,
|
|
/** Turn off @token@ parsing. */
|
|
cmpp_atpol_OFF = 0,
|
|
/** Retain undefined @token@ - emit it as-is. */
|
|
cmpp_atpol_RETAIN,
|
|
/** Elide undefined @token@. */
|
|
cmpp_atpol_ELIDE,
|
|
/** Error for undefined @token@. */
|
|
cmpp_atpol_ERROR,
|
|
/** A sentinel value for use with cmpp_dx_out_expand(). */
|
|
cmpp_atpol_CURRENT,
|
|
/**
|
|
This isn't _really_ the default. It's the default for the
|
|
--@policy CLI flag and #@pragma when it's given no value.
|
|
*/
|
|
cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR,
|
|
/**
|
|
The compile-time default for all cmpp instances.
|
|
*/
|
|
cmpp_atpol_DEFAULT = cmpp_atpol_OFF
|
|
|
|
};
|
|
typedef enum cmpp_atpol_e cmpp_atpol_e;
|
|
|
|
/**
|
|
Policies describing how cmpp should react to attempts to use
|
|
undefined keys.
|
|
*/
|
|
enum cmpp_unpol_e {
|
|
/* Sentinel. */
|
|
cmpp_unpol_invalid,
|
|
/** Treat undefined keys as NULL/falsy. This is the default. */
|
|
cmpp_unpol_NULL,
|
|
/** Trigger an error for undefined keys. This should probably be the
|
|
default. */
|
|
cmpp_unpol_ERROR,
|
|
/**
|
|
The compile-time default for all cmpp instances.
|
|
*/
|
|
cmpp_unpol_DEFAULT = cmpp_unpol_NULL
|
|
};
|
|
typedef enum cmpp_unpol_e cmpp_unpol_e;
|
|
|
|
typedef struct cmpp_arg cmpp_arg;
|
|
/**
|
|
A single argument for a directive. When a cmpp_d::flags have
|
|
cmpp_d_F_ARGS_V2 set then the part of the input immediately
|
|
following the directive (and on the same line) is parsed into a
|
|
cmpp_args, a container for these.
|
|
*/
|
|
struct cmpp_arg {
|
|
/** Token type. */
|
|
cmpp_tt ttype;
|
|
/**
|
|
The arg's string value, shorn of any opening/closing quotes or ()
|
|
or {} or []. The args-parsing process guarantees to NUL-terminate
|
|
this. The bytes are typically owned by a cmpp_args object, but
|
|
clients may direct them wherever the need to, so long as the
|
|
bytes are valid longer than this object is.
|
|
*/
|
|
unsigned char const * z;
|
|
/**
|
|
The arg's effective length, in bytes, after opening/closing chars
|
|
are stripped. That is, its string form is the range [z,z+n).
|
|
*/
|
|
unsigned n;
|
|
/**
|
|
The next argument in the list. It is owned by whatever code set
|
|
it up (typically cmpp_args_parse()).
|
|
*/
|
|
cmpp_arg const * next;
|
|
};
|
|
|
|
/**
|
|
Empty-initialized cmpp_arg instance, intended for
|
|
const-copy initialization.
|
|
*/
|
|
#define cmpp_arg_empty_m {cmpp_TT_None,0,0,0}
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
non-const-copy initialization.
|
|
*/
|
|
extern const cmpp_arg cmpp_arg_empty;
|
|
|
|
typedef struct cmpp_dx cmpp_dx;
|
|
typedef struct cmpp_dx_pimpl cmpp_dx_pimpl;
|
|
typedef struct cmpp_d cmpp_d;
|
|
|
|
/**
|
|
Flags for use with cmpp_d::flags.
|
|
*/
|
|
enum cmpp_d_e {
|
|
/** Sentinel value. */
|
|
cmpp_d_F_none = 0,
|
|
/**
|
|
cmpp_dx_next() will not parse the directive's arguments. Instead,
|
|
it makes cmpp_dx::arg0 encapsulate the whole line of the
|
|
directive (sans the directive's name) as a single argument. The
|
|
only transformation which is performed is the removal of
|
|
backslashes from backslash-escaped newlines. It is up to the
|
|
directive's callback to handle (or not) the arguments.
|
|
*/
|
|
cmpp_d_F_ARGS_RAW = 0x01,
|
|
|
|
/**
|
|
cmpp_dx_next() will parse the directive's arguments.
|
|
cmpp_dx::arg0 will point to the first argument in the list, or
|
|
NULL if there are no arguments.
|
|
|
|
If both cmpp_d_F_ARGS_LIST and cmpp_d_F_ARGS_RAW are specified,
|
|
cmpp_d_F_ARGS_LIST will win.
|
|
*/
|
|
cmpp_d_F_ARGS_LIST = 0x02,
|
|
|
|
/**
|
|
Indicates that the direction should not be available if the cmpp
|
|
instance is configured with any of the cmpp_ctor_F_SAFEMODE flags.
|
|
All directives when do any of the following are obligated to
|
|
set this flag:
|
|
|
|
- Filesystem or network access.
|
|
- Invoking external processes.
|
|
|
|
Or anything else which might be deamed "security-relevant".
|
|
|
|
When registering a directive which has both opener and closer
|
|
implementations, it is sufficient to set this only on the opener.
|
|
|
|
The library imposes this flag in the following places:
|
|
|
|
- Registration of a directive with this flag will fail if
|
|
cmpp_is_safemode() is true for that cmpp instance.
|
|
|
|
- cmpp_dx_process() will refuse to invoke a directive with this
|
|
flag when cmpp_is_safemode() is true.
|
|
*/
|
|
cmpp_d_F_NOT_IN_SAFEMODE = 0x04,
|
|
|
|
/**
|
|
Call-only directives are only usable in [directive ...] "call"
|
|
contexts. They are not permitted to have a closing directive.
|
|
*/
|
|
cmpp_d_F_CALL_ONLY = 0x08,
|
|
/**
|
|
Indicates that the directive is incapable of working in a [call]
|
|
context and an error should be trigger if it is. _Most_
|
|
directives which have a closing directive should have this
|
|
flag. The exceptions are directives which only conditionally use
|
|
a closing directive, like #query.
|
|
*/
|
|
cmpp_d_F_NO_CALL = 0x10,
|
|
|
|
/**
|
|
Mask of the client-usable range for this enum. Values outside of
|
|
this mask are reserved for internal use and will be stripped from
|
|
registrations made with cmpp_d_register().
|
|
*/
|
|
cmpp_d_F_MASK = 0x0000ffff
|
|
|
|
};
|
|
|
|
/**
|
|
Callback type for cmpp_d::impl::callback(). cmpp directives are all
|
|
implemented as functions with this signature. Implementations are
|
|
called only by cmpp_dx_process() (and only after cmpp_dx_next() has
|
|
found a preprocessor line), passed the current context object.
|
|
These callbacks are only ever passed directives which were
|
|
specifically registered with them (see cmpp_d_register()).
|
|
|
|
The first rule of callback is: to report errors (all of which end
|
|
processing of the current input) call cmpp_dx_err_set(), passing it
|
|
the callback's only argument, then clean up any local resources,
|
|
then return. The library will recognize the error and propagate it.
|
|
|
|
dx's memory is only valid for the duration of this call. It must
|
|
not be held on to longer than that. dx->args.arg0 has slightly different
|
|
lifetime: if this callback does _not_ call back in to
|
|
cmpp_dx_next() then dx->args.arg0 and its neighbors will survive until
|
|
this call is completed. Calling cmpp_dx_next(), or any API which
|
|
invokes it, invalidates dx->args.arg0's memory. Thus directives which
|
|
call into that must _copy_ any data they need from their own
|
|
arguments before doing so, as their arguments list will be
|
|
invalidated.
|
|
*/
|
|
typedef void (*cmpp_dx_f)(cmpp_dx * dx);
|
|
|
|
/**
|
|
A typedef for generic deallocation routines.
|
|
*/
|
|
typedef void (*cmpp_finalizer_f)(void *);
|
|
|
|
/**
|
|
State specific to concrete cmpp_d implementations.
|
|
|
|
TODO: move this, except for the state pointer, out of cmpp_d
|
|
so that directives cannot invoke these callbacks directly. Getting
|
|
that to work requires moving the builtin directives into the
|
|
dynamic directives list.
|
|
*/
|
|
struct cmpp_d_impl {
|
|
/**
|
|
Callback func. If any API other othan cmpp_dx_process() invokes
|
|
this, behavior is undefined.
|
|
*/
|
|
cmpp_dx_f callback;
|
|
|
|
/**
|
|
For custom directives with a non-NULL this->state, this will be
|
|
called, and passed that object, when the directive is cleaned
|
|
up. For directives with both an opening and a closing tag, this
|
|
destructor is only attached to the opening tag.
|
|
|
|
If any API other othan cmpp's internal cleanup routines invoke
|
|
this, behavior is undefined.
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
|
|
/**
|
|
State for the directive's callback. It is accessible in
|
|
cmpp_dx_f() impls via theDx->d->impl.state. For custom
|
|
directives with both an opening and closing directive, this
|
|
same state object gets assigned to both.
|
|
*/
|
|
void * state;
|
|
};
|
|
typedef struct cmpp_d_impl cmpp_d_impl;
|
|
#define cmpp_d_impl_empty_m {0,0,0}
|
|
|
|
/**
|
|
Each c-pp "directive" is modeled by one of these.
|
|
*/
|
|
struct cmpp_d {
|
|
|
|
struct {
|
|
/**
|
|
The directive's name, as it must appear after the directive
|
|
delimiter. Its bytes are assumed to be static or otherwise
|
|
outlive this object.
|
|
*/
|
|
const char *z;
|
|
/** Byte length of this->z. We record this to speed up searches. */
|
|
unsigned n;
|
|
} name;
|
|
|
|
/**
|
|
Bitmask of flags from cmpp_d_e plus possibly internal flags.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
|
|
/**
|
|
The directive which acts as this directive's closing element
|
|
element, or 0 if it has none.
|
|
*/
|
|
cmpp_d const * closer;
|
|
|
|
/**
|
|
State specific to concrete implementations.
|
|
*/
|
|
cmpp_d_impl impl;
|
|
};
|
|
|
|
/**
|
|
Each instance of the cmpp_dx class (a.k.a. "directive context")
|
|
manages a single input source. It's responsible for the
|
|
tokenization of all input, locating directives, and processing
|
|
("running") directives. The directive-specific work happens in
|
|
cmpp_dx_f() implementations, and this class internally manages the
|
|
setup, input traversal, and teardown.
|
|
|
|
These objects only exist while cmpp is actively processing
|
|
input. Client code interacts with them only through cmpp_dx_f()
|
|
implementations which the library invokes.
|
|
|
|
The process of filtering input to look for directives is to call
|
|
cmpp_dx_next() until it indicates either an error or that a
|
|
directive was found. In the latter case, the cmpp_dx object is
|
|
populated with info about the current directive. cmpp_dx_process()
|
|
will run that directive, but cmpp_dx_f() implementations sometimes
|
|
need to make decisions based on the located directive before doing
|
|
so (and sometimes they need to skip running it).
|
|
|
|
If cmpp_dx_next() finds no directive, the end of the input has been
|
|
reached and there is no further output to generate.
|
|
|
|
Content encountered before a directive is found is passed on to the
|
|
output stream via cmpp_dx_out_raw() or cmpp_dx_out_expand().
|
|
*/
|
|
struct cmpp_dx {
|
|
/**
|
|
The cmpp object which owns this context.
|
|
*/
|
|
cmpp * const pp;
|
|
|
|
/**
|
|
The directive on whose behalf this context is active.
|
|
*/
|
|
cmpp_d const *d;
|
|
|
|
/**
|
|
Name of the input for error reporting. Typically an input script
|
|
file name, but it need not refer to a file.
|
|
*/
|
|
unsigned const char * const sourceName;
|
|
|
|
/**
|
|
State related to arguments passed to the current directive.
|
|
|
|
It is important to keep in mind that the memory for the members
|
|
of this sub-struct may be modified or reallocated
|
|
(i.e. invalidated) by any APIs which call in to cmpp_dx_next().
|
|
cmpp_dx_f() implementations must take care not to use any of this
|
|
memory after calling into that function, cmpp_dx_consume(), or
|
|
similar. If needed, it must be copied (e.g. using
|
|
cmpp_args_clone() to create a local copy of the parsed
|
|
arguments).
|
|
*/
|
|
struct {
|
|
/**
|
|
Starting byte of unparsed arguments. This is for cmpp_d_f()
|
|
implementations which need custom argument parsing.
|
|
*/
|
|
unsigned const char * z;
|
|
|
|
/**
|
|
The byte length of z.
|
|
*/
|
|
cmpp_size_t nz;
|
|
|
|
/**
|
|
The parsed arg count for the this->arg0 list.
|
|
*/
|
|
unsigned argc;
|
|
|
|
/**
|
|
The first parsed arg or NULL. How this is set up is affected by
|
|
cmpp_d::flags.
|
|
|
|
This is specifically _NOT_ defined as a sequential array and
|
|
using pointer math to traverse it invokes undefined behavior.
|
|
|
|
To traverse the list:
|
|
|
|
for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){
|
|
...
|
|
}
|
|
*/
|
|
cmpp_arg const * arg0;
|
|
} args;
|
|
|
|
/**
|
|
Private impl details.
|
|
*/
|
|
cmpp_dx_pimpl * const pimpl;
|
|
};
|
|
|
|
/**
|
|
Thin proxy for cmpp_err_set(), replacing only the first argument.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_err_set(cmpp_dx *dx, int rc,
|
|
char const *zFmt, ...);
|
|
|
|
|
|
/**
|
|
Returns true if dx's current call into the API is the result
|
|
of a function call, else false. Any APIs which recurse into
|
|
input processing will reset this to false, so it needs to be
|
|
evaluated before doing any such work.
|
|
|
|
Design note: this flag is actually tied to dx's arguments, which
|
|
get reset by APIs which consume from the input stream.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx);
|
|
|
|
/**
|
|
Returns true if dx->pp has error state, else false. If this
|
|
function returns true, cmpp_dx_f() implementations are required to
|
|
stop working, clean up any local resources, and return. Continuing
|
|
to use dx when it's in an error state may exacerbate the problem.
|
|
*/
|
|
#define cmpp_dx_err_check(DX) (DX)->pp->api->err_has((DX)->pp)
|
|
|
|
/**
|
|
Scans dx to the next directive line, emitting all input before that
|
|
which is _not_ a directive line to dx->pp's output channel unless
|
|
it's elided due to being inside a block which elides its content
|
|
(e.g. #if).
|
|
|
|
Returns 0 if no errors were triggered, else a cmpp_rc_e code. This
|
|
is a no-op if dx->pp has persistent error state set, and that error
|
|
code is returned.
|
|
|
|
If it returns 0 then it sets *pGotOne to true if a directive was
|
|
found and false if not (in which case the end of the input has
|
|
been reached and further calls to this function for the same input
|
|
source will be no-ops). If it sets *pGotOne to true then it also
|
|
sets up dx's state for use with cmpp_dx_process(), which should
|
|
(normally) then be called.
|
|
|
|
ACHTUNG: calling this resets any argument-handling-related state of
|
|
dx. That is important for cmpp_dx_f() implemenations, which _must
|
|
not_ hold copies of any pointers from dx->args.arg0 or dx->args.z
|
|
beyond a call to this function. Any state they need must be
|
|
evaluated, potentially copied, before calling this function().
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne);
|
|
|
|
/**
|
|
This is only legal to call immediately after a successful call to
|
|
cmpp_dx_next(). It requires that cmpp_dx_next() has just located
|
|
the next directive. This function runs that directive. Returns 0
|
|
on success and all that.
|
|
|
|
Design note: directive-Search and directive-process are two
|
|
distinctly separate steps because directives which have both
|
|
open/closing tags frequently discard the closing directive without
|
|
running it (it exists to tell the directive how far to read). Those
|
|
closing directives exist independently, though, and will trigger
|
|
errors when encountered outside of the context of their opening
|
|
directive tag (e.g. an "#/if" without an "#if").
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx);
|
|
|
|
/**
|
|
A bitmask of flags for use with cmpp_dx_consume()
|
|
*/
|
|
enum cmpp_dx_consume_e {
|
|
/**
|
|
Tells cmpp_dx_consume() to process any directives it encounters
|
|
which are not in the specified set of closing directives. Its
|
|
default is to fail if another directive is seen.
|
|
*/
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01,
|
|
/**
|
|
Tells cmpp_dx_consume() that non-directive content encountered
|
|
before the designated closing directive(s) must use an at-policy
|
|
of cmpp_atpol_OFF. That is: the output target of that function will
|
|
get the raw, unfiltered content. This is for cases where the
|
|
consumer will later re-emit that content, delaying @token@
|
|
parsing until a later step (e.g. #query does this).
|
|
|
|
This may misinteract in unpredictable ways when used with
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs.
|
|
*/
|
|
cmpp_dx_consume_F_RAW = 0x02
|
|
};
|
|
|
|
/**
|
|
A helper for cmpp_dx_f() implementations which read in their
|
|
blocked-off content instead of passing it through the normal output
|
|
channel. e.g. `#define x <<` stores that content in a define named
|
|
"x".
|
|
|
|
This function runs a cmpp_dx_next() loop which does the following:
|
|
|
|
If the given output channel is not NULL then it first replaces the
|
|
output channel with the given one, such that all output which would
|
|
normally be produced will be sent there until this function
|
|
returns, at which point the output channel is restored. If the
|
|
given channel is NULL then output is not captured - it instead goes
|
|
dx's current output channel.
|
|
|
|
dClosers must be a list of legal closing tags nClosers entries
|
|
long. Typically this is the single closing directive/tag of the
|
|
current directive, available to the opening directive's cmpp_dx_f()
|
|
impl via dx->d->closer. Some special cases require multiple
|
|
candidates, however.
|
|
|
|
The flags argument may be 0 or a bitmask of values from the
|
|
cmpp_dx_consume_e enum.
|
|
|
|
If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set
|
|
then this function requires that the next directive in the input be
|
|
one specified by dClosers. If the next directive is not one of
|
|
those, it will fail with code CMPP_RC_SYNTAX.
|
|
|
|
If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it
|
|
will continue to search for and process directives until the
|
|
dCloser directive is found. Calling into other directives will
|
|
invalidate certain state that a cmpp_dx_f() has access to - see
|
|
below for details. If dCloser is not found before EOF, a
|
|
CMPP_RC_SYNTAX error is triggered.
|
|
|
|
Once one of dCloser is found, this function returns with dx->d
|
|
referring to the that directive. In practice, the caller should
|
|
_not_ call cmpp_dx_process() at that point - the closing directive
|
|
is typically a no-op placeholder which exists only to mark the end
|
|
of the block. If the closer has work to do, however, the caller of
|
|
this function should call cmpp_dx_process() at that point.
|
|
|
|
On success it returns 0, the input stream will have been consumed
|
|
between the directive dx and its closing tag, and dx->d will point
|
|
to the new directive. If os is not NULL then os will have been
|
|
sent any content.
|
|
|
|
On error, processing of the directive must end immediately,
|
|
returning from the cmpp_dx_f() impl after cleaning up any local
|
|
resources.
|
|
|
|
ACHTUNG: since this invokes cmpp_dx_next(), it invalidates
|
|
dx->args.arg0. Its dx->d is also replaced but the previous value
|
|
remains valid until the cmpp instance is cleaned up.
|
|
|
|
Example from the context of a cmpp_dx_f() implementation
|
|
|
|
```
|
|
// "dx" is the cmpp_dx arg to this function
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
cmpp_b os = cmpp_b_empty;
|
|
oss.state = &os;
|
|
if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 0) ){
|
|
cmpp_b_chomp( &os );
|
|
... maybe modify the buffer or decorate the output in some way...
|
|
cmpp_dx_out_raw(dx, os.z, os.n);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
```
|
|
|
|
Design issue: this API does not currently have a way to handle
|
|
directives which have multiple potential waypoints/endpoints, in
|
|
the way that an #if may optionally have an #elif or #else before
|
|
the #/if. Such processing has to be done in the directive's
|
|
impl.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os,
|
|
cmpp_d const *const * dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Equivalent to cmpp_dx_consume(), capturing to the given buffer
|
|
instead of a cmpp_outputer object.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b,
|
|
cmpp_d const * const * dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
If arg is not NULL, cleans up any resources owned by
|
|
arg but does not free arg.
|
|
|
|
As of this writing, they own none and some code still requires
|
|
that. That is Olde Thynking, though.
|
|
*/
|
|
CMPP_EXPORT void cmpp_arg_cleanup(cmpp_arg *arg);
|
|
|
|
/**
|
|
If arg is not NULL resets arg to be re-used. arg must have
|
|
initially been cleanly initialized by copying cmpp_arg_empty (or
|
|
equivalent, i.e. zeroing it out).
|
|
*/
|
|
CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg);
|
|
|
|
/**
|
|
This is the core argument-parsing function used by the library's
|
|
provided directives. Its is available in the public API as a
|
|
convenience for custom cmpp_dx_f() implementations, but custom
|
|
implementations are not required to make use of it.
|
|
|
|
Populates a cmpp_arg object by parsing the next token from its
|
|
input source.
|
|
|
|
Expects *pzIn to point to the start of input for parsing arguments
|
|
and zInEnd to be the logical EOF of that range. This function
|
|
populates pOut with the info of the parse. Returns 0 on success,
|
|
non-0 (and updates pp's error state) on error.
|
|
|
|
Output (the parsed token) is written to *pzOut. zOutEnd must be the
|
|
logical EOF of *pzOut. *pzOut needs to be, at most,
|
|
(zInEnd-*pzIn)+1 bytes long. This function range checks the output
|
|
and will not write to or past zOutEnd, but that will trigger a
|
|
CMPP_RC_RANGE error.
|
|
|
|
On success, *pzIn will be set to 1 byte after the last one parsed
|
|
for pOut and *pzOut will be set to one byte after the final output
|
|
(NUL-terminated). pOut->z will point to the start of *pzOut and
|
|
pOut->n will be set to the byte-length of pOut->z.
|
|
|
|
When the end of the input is reached, this function returns 0
|
|
and sets pOut->ttype to cmpp_TT_EOF.
|
|
|
|
General tokenization rules:
|
|
|
|
Tokens come in the following flavors:
|
|
|
|
- Quoted strings: single- or double-quoted. cmpp_arg::ttype:
|
|
cmpp_TT_String.
|
|
|
|
- "At-strings": @"..." and @'...'. cmpp_arg::ttype value:
|
|
cmpp_TT_StringAt.
|
|
|
|
- Decimal integers with an optional sign. cmpp_arg::ttype value:
|
|
cmpp_TT_Int.
|
|
|
|
- Groups: (...), {...}, and [...]. cmpp_arg::ttype values:
|
|
cmpp_TT_GroupParen, cmpp_TT_GroupSquiggly, and
|
|
cmpp_TT_GroupBrace. These types do not automatically get parsed
|
|
recursively. To recurse into one of these, pass cmpp_arg_parse()
|
|
the grouping argument's bytes as the input range.
|
|
|
|
- Word: anything which doesn't look like one of these above. Token
|
|
type IDs: cmpp_TT_Word. These are most often interpreted as
|
|
#define keys but cmpp_dx_f() implementations sometimes treat
|
|
them as literal values.
|
|
|
|
- A small subset of words and operator-like tokens, e.g. '=' and
|
|
'!=', get a very specific ttype, e.g. cmpp_TT_OpNeq, but these
|
|
can generally be treated as strings.
|
|
|
|
- Outside of strings and groups, spaces, tabs, carriage-returns,
|
|
and newlines are skipped.
|
|
|
|
These are explained in more detail in the user's manual
|
|
(a.k.a. README.md).
|
|
|
|
There are many other token types, mostly used internally.
|
|
|
|
This function supports _no_ backslash escape sequences in
|
|
tokens. All backslashes, with the obligatory exception of those
|
|
which make up backslash-escaped newlines in the input stream, are
|
|
retained as-is in all token types. That means, for example, that
|
|
strings may not contain their own quote character.
|
|
|
|
As an example of where this function is useful: cmpp_dx_f()
|
|
implementations which need to selectively parse a subset of the
|
|
directive's arguments can use this. As input, their dx argument's
|
|
args.z and args.nz members delineate the current directive line's
|
|
arguments. See c-pp.c:cmpp_dx_f_pipe() for an example.
|
|
*/
|
|
CMPP_EXPORT int cmpp_arg_parse(cmpp_dx * dx,
|
|
cmpp_arg *pOut,
|
|
unsigned char const **pzIn,
|
|
unsigned char const *zInEnd,
|
|
unsigned char ** pzOut,
|
|
unsigned char const * zOutEnd);
|
|
|
|
/**
|
|
True if (cmpp_arg const *)ARG's contents match the string literal
|
|
STR, else false.
|
|
*/
|
|
#define cmpp_arg_equals(ARG,STR) \
|
|
(sizeof(STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1))
|
|
|
|
/**
|
|
True if (cmpp_arg const *)ARG's contents match the string literal
|
|
STR or ("-" STR), else false. The intent is that "-flag" be passed
|
|
here to tolerantly accept either "-flag" or "--flag".
|
|
*/
|
|
#define cmpp_arg_isflag(ARG,STR) \
|
|
cmpp_arg_equals(ARG,STR) || cmpp_arg_equals(ARG, "-" STR)
|
|
|
|
/**
|
|
Creates a copy of arg->z. If allocation fails then pp's persistent
|
|
error code is set to CMPP_RC_OOM. If pp's error code is not 0 when
|
|
this is called then this is a no-op and returns NULL. In other
|
|
words, if this function returns NULL, pp's error state was either
|
|
already set when this was called or it was set because allocation
|
|
failed.
|
|
|
|
Ownership of the returned memory is transferred to the caller, who
|
|
must eventually free it using cmpp_mfree().
|
|
*/
|
|
CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg);
|
|
|
|
/**
|
|
Flag bitmasks for use with cmpp_arg_to_b(). With my apologies
|
|
for the long names (but consistency calls for them).
|
|
*/
|
|
enum cmpp_arg_to_b_e {
|
|
/**
|
|
Specifies that the argument's string value should be used as-is,
|
|
rather than expanding it (if the arg's ttype would normally cause
|
|
it to be expanded).
|
|
*/
|
|
cmpp_arg_to_b_F_FORCE_STRING = 0x01,
|
|
|
|
/**
|
|
Tells cmpp_arg_to_b() to not expand arguments with type
|
|
cmpp_TT_Word, which it normally treats as define keys. It instead
|
|
treats these as strings.
|
|
*/
|
|
cmpp_arg_to_b_F_NO_DEFINES = 0x02,
|
|
|
|
/**
|
|
If set, arguments with a ttype of cmpp_TT_GroupBrace will be
|
|
"called" by passing them to cmpp_call_arg(). The space-trimmed
|
|
result of the call becomes the output of the cmpp_arg_to_b()
|
|
call.
|
|
|
|
FIXME: make this opt-out instead of opt-in. We end up _almost_
|
|
always wanting this.
|
|
*/
|
|
cmpp_arg_to_b_F_BRACE_CALL = 0x04,
|
|
|
|
/**
|
|
Explicitly disable [call] expansion even if
|
|
cmpp_arg_to_b_F_BRACE_CALL is set in the flags.
|
|
*/
|
|
cmpp_arg_to_b_F_NO_BRACE_CALL = 0x08
|
|
|
|
/**
|
|
TODO? cmpp_arg_to_b_F_UNESCAPE
|
|
*/
|
|
};
|
|
|
|
/**
|
|
Appends some form of arg to the given buffer.
|
|
|
|
arg->ttype values of cmpp_TT_Word (define keys) and
|
|
cmpp_TT_StringAt cause the value to be expanded appropriately (the
|
|
latter according to dx->pp's current at-policy). Others get emitted
|
|
as-is.
|
|
|
|
The flags argument influences the expansion decisions, as documented
|
|
in the cmpp_arg_to_b_e enum.
|
|
|
|
Returns 0 on success and all that.
|
|
|
|
See: cmpp_atpol_get(), cmpp_atpol_set()
|
|
|
|
Reminder to self: though this function may, via script-side
|
|
function call resolution, recurse into the library, any such
|
|
recursion gets its own cmpp_dx instance. In this context that's
|
|
significant because it means this call won't invalidate arg's
|
|
memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on
|
|
where args came from - typically it's owned by dx but
|
|
cmpp_args_clone() exists solely to work around such potential
|
|
invalidation).
|
|
*/
|
|
CMPP_EXPORT int cmpp_arg_to_b(cmpp_dx * dx, cmpp_arg const *arg,
|
|
cmpp_b * os, cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Flags for use with cmpp_call_str() and friends.
|
|
*/
|
|
enum cmpp_call_e {
|
|
/** Do not trim a newline from the result. */
|
|
cmpp_call_F_NO_TRIM = 0x01,
|
|
/** Trim all leading and trailing space and newlines
|
|
from the result. */
|
|
cmpp_call_F_TRIM_ALL = 0x02
|
|
};
|
|
|
|
/**
|
|
This assumes that arg->z holds a "callable" directive
|
|
string in the form:
|
|
|
|
directiveName ...args
|
|
|
|
This function composes a new cmpp input source from that line
|
|
(prefixed with dx's current directive prefix if it's not already
|
|
got one), processes it with cmpp_process_string(), redirecting the
|
|
output to dest (which gets appended to, so be sure to
|
|
cmpp_b_reuse() it if needed before calling this).
|
|
|
|
To simplify common expected usage, by default the output is trimmed
|
|
of a single newline. The flags argument, 0 or a bitmask of values
|
|
from the cmpp_call_e enum, can be used to modify that behavior.
|
|
|
|
This is the basis of "function calls" in cmpp.
|
|
|
|
Returns 0 on success.
|
|
*/
|
|
int cmpp_call_str(cmpp *dx,
|
|
unsigned char const * z,
|
|
cmpp_ssize_t n,
|
|
cmpp_b * dest,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Convert an errno value to a cmpp_rc_e approximation, defaulting to
|
|
dflt if no known match is found. This is intended for use by
|
|
cmpp_dx_f implementations which use errno-using APIs.
|
|
*/
|
|
CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt);
|
|
|
|
/**
|
|
Configuration object for use with cmpp_d_register().
|
|
*/
|
|
struct cmpp_d_reg {
|
|
/**
|
|
The name of the directive as it will be used in
|
|
input scripts, e.g. "mydirective". It will be copied by
|
|
cmpp_d_register().
|
|
*/
|
|
char const *name;
|
|
/**
|
|
A combination of bits from the cmpp_d_e enum.
|
|
|
|
These flags are currently applied only to this->opener.
|
|
this->closer, because of how it's typically used, assumes
|
|
*/
|
|
struct {
|
|
/**
|
|
Callback for the directive's opening tag.
|
|
*/
|
|
cmpp_dx_f f;
|
|
/**
|
|
Flags from cmpp_d_e. Typically one of cmpp_d_F_ARGS_LIST or
|
|
cmpp_d_F_ARGS_RAW.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
} opener;
|
|
struct {
|
|
/**
|
|
Callback for the directive's closing tag, if any.
|
|
|
|
This is only relevant for directives which have both an open and
|
|
a closing tag (even if that closing tag is only needed in some
|
|
contexts, e.g. "#define X <<" (with a closer) vs "#define X Y"
|
|
(without)). See cmpp_dx_f_dangling_closer() for a default
|
|
implementation which triggers an error if it's seen in the input
|
|
and not consumed by its counterpart opening directive. That
|
|
implementation has proven useful for #define, #pipe, and friends.
|
|
|
|
Design notes: it's as yet unclear how to model, in the public
|
|
interface, directives which have a whole family of cooperating
|
|
directives, namely #if/#elif/#else.
|
|
*/
|
|
cmpp_dx_f f;
|
|
/**
|
|
Flags from cmpp_d_e. For closers this can typically be
|
|
left at 0.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
} closer;
|
|
/**
|
|
If not NULL then it is assigned to the directive's opener part
|
|
and will be called by the library in either of the following
|
|
cases:
|
|
|
|
- When the custom directive is cleaned up.
|
|
|
|
- If cmpp_d_register() fails (returns non-0), regardless of how
|
|
it fails.
|
|
|
|
It is passed this->state.
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
/**
|
|
Implementation state for the callbacks.
|
|
*/
|
|
void * state;
|
|
};
|
|
typedef struct cmpp_d_reg cmpp_d_reg;
|
|
/**
|
|
Empty-initialized cmpp_d_reg instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_d_reg_empty_m {0,{0,0},{0,0},0,0}
|
|
/**
|
|
Empty-initialized instance, intended for non-const-copy
|
|
initialization.
|
|
*/
|
|
//extern const cmpp_d_reg cmpp_d_reg_empty;
|
|
|
|
/**
|
|
Registers a new directive, or a pair of opening/closing directives,
|
|
with pp.
|
|
|
|
The semantics of r's members are documented in the cmpp_d_reg
|
|
class. r->name and r->opener.f are required. The remainder may be
|
|
0/NULL. Its members are copied - r need not live longer than this
|
|
call.
|
|
|
|
When the new directive is seen in a script, r->opener.f() will be
|
|
called. If the closing directive (if any) is seen in a script,
|
|
r->closer.f() is called. In both cases, the callback
|
|
implementation can get access to the r->state object via
|
|
cmdd_dx::d::impl::state (a.k.a dx->d->impl.state).
|
|
|
|
If r->closer.f is not NULL then the closing directive will be named
|
|
"/${zName}". (Design note: it is thought that forcing a common
|
|
end-directive syntax will lead to fewer issues than allowing
|
|
free-form closing tag names, e.g. fewer chances of a name collision
|
|
or not quite remembering the spelling of a given closing tag
|
|
(#endef vs #enddefine vs #/define).)
|
|
|
|
Returns 0 on success and updates pp's error state on error. Similarly,
|
|
this is a no-op if pp has an error code when this is called, in which
|
|
case it returns that result code without other side-effects.
|
|
|
|
On success, if pOut is not NULL then it is set to the directive
|
|
pointer, memory owned by pp until it cleans up its directives. This
|
|
is the only place in the API a non-const pointer to a directive can
|
|
be found, and it is provided only for very specific use-cases where
|
|
a directive needs to be manipulated (carefully) after
|
|
registration[^post-reg-manipulation]. If this function also
|
|
registered a closing directive, it is available as (*pOut)->closer.
|
|
pOut should normally be NULL.
|
|
|
|
Failure modes include:
|
|
|
|
- Returns CMPP_RC_RANGE if zName is not legal for use as a
|
|
directive name. See cmpp_is_legal_key().
|
|
|
|
- Returns CMPP_RC_OOM on an allocation error.
|
|
|
|
Errors from this function are recoverable (see cmpp_err_set()). A
|
|
failed registration, even one which doesn't fail until the
|
|
registration of the closing element, will leave pp in a
|
|
well-defined state (with neither of r's directives being
|
|
registered).
|
|
|
|
[^post-reg-manipulation]: The one known use case if the #if family
|
|
of directives, all of which use the same #/if closing
|
|
directive. The public registration API does not account for sharing
|
|
of closers that way, and whether it _should_ is still TBD. The
|
|
workaround, for this case, is to get the directives as they're
|
|
registered and point the cmpp_d::closer of each of #if, #elif, and
|
|
#else to #/if.
|
|
*/
|
|
CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r,
|
|
cmpp_d **pOut);
|
|
|
|
/**
|
|
A cmpp_dx_f() impl which is intended to be used as a callback for
|
|
directive closing tags for directives in which the opening tag's
|
|
implementation consumes the input up to the closing tag. This impl
|
|
triggers an error if called, indicating that the directive closing
|
|
was seen in the input without its accompanying directive opening.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_f_dangling_closer(cmpp_dx *dx);
|
|
|
|
/**
|
|
Writes the first n bytes of z to dx->pp's current output channel
|
|
without performing any @token@ parsing.
|
|
|
|
Returns dx->pp's persistent error code (0 on success) and sets that
|
|
code to non-0 on error. This is a no-op if dx->pp has a non-0 error
|
|
state, returning that code.
|
|
|
|
See: cmpp_dx_out_expand()
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_out_raw(cmpp_dx * dx, void const *z,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the
|
|
given policy says to (else it passes the content through as-is, as
|
|
per cmpp_dx_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's
|
|
current policy. A policy of cmpp_atpol_OFF behaves exactly like
|
|
cmpp_dx_out_raw().
|
|
|
|
Returns dx->pp's persistent error code (0 on success) and sets that
|
|
code to non-0 on error. This is a no-op if dx->pp has a non-0 error
|
|
state, returning that code.
|
|
|
|
If pOut is NULL then dx->pp's default channel is used, with the
|
|
caveat that atPolicy's only legal value in that case is
|
|
cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be
|
|
overridden for that particular output channel, to avoid accidental
|
|
filtering when it's not enabled. They do not impose that
|
|
restriction for other output channels, which are frequently used
|
|
for filtering intermediary results.)
|
|
|
|
See: cmpp_dx_out_raw()
|
|
|
|
Notes regarding how this is used internally:
|
|
|
|
- This function currently specifically does nothing when invoked in
|
|
skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode
|
|
except when evaluating #elif expressions (previous #if/#elifs
|
|
having failed and put us in skip-mode), where it's expanding
|
|
expression operands. That part currently (as of 2025-10-21) uses
|
|
dx->pp's current policy, and it's not clear whether that is
|
|
sufficient or whether we need to force it to expand (and which
|
|
policy to use when doing so). We could possibly get away with
|
|
always using cmpp_atpol_ERROR for purposes of evaluating at-string
|
|
expression operands.
|
|
|
|
[^1]: Skip-mode is the internal mechanism which keeps directives
|
|
from running, and content from being emitted, within a falsy branch
|
|
of an #if/#elif block. Only flow-control directives are ever run
|
|
when skip-mode is active, and client-provided directives cannot
|
|
easily provide flow-control support. Ergo, much of this paragraph
|
|
is not relevant for client-level code, but it is for this library's
|
|
own use of this function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_out_expand(cmpp_dx const * dx,
|
|
cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n,
|
|
cmpp_atpol_e policy);
|
|
|
|
/**
|
|
This creates a formatted string using sqlite3_mprintf() and emits it
|
|
using cmpp_dx_out_raw(). Returns CMPP_RC_OOM if allocation of the
|
|
string fails, else it returns whatever cmpp_dx_out_raw() returns.
|
|
|
|
This is a no-op if dx->pp is in an error state, returning
|
|
that code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_outf(cmpp_dx *dx, char const *zFmt, ...);
|
|
|
|
/**
|
|
Convenience form of cmpp_delimiter_get() which returns the
|
|
delimiter which was active at the time when the currently-running
|
|
cmpp_dx_f() was called. This memory may be invalidated by any calls
|
|
into cmpp_dx_process() or cmpp_delimiter_set(), so a copy of this
|
|
pointer must not be retained past such a point.
|
|
|
|
This function is primarily intended for use in generating debug and
|
|
error messages.
|
|
|
|
If the delimiter stack is empty, this function returns NULL.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx);
|
|
|
|
/**
|
|
Borrows a buffer from pp's buffer recycling pool, allocating one if
|
|
needed. It returns NULL only on allocation error, in which case it
|
|
updates pp's error state.
|
|
|
|
This transfers ownership of the buffer to the caller, who is
|
|
obligated to eventually do ONE of the following:
|
|
|
|
- Pass it to cmpp_b_return() with the same dx argument.
|
|
|
|
- Pass it to cmpp_b_clear() then cmpp_mfree().
|
|
|
|
The purpose of this function is a memory reuse optimization. Most
|
|
directives, and many internals, need to use buffers for something
|
|
or other and this gives them a way to reuse buffers.
|
|
|
|
Potential TODO: How this pool optimizes (or not) buffer allotment
|
|
is an internal detail. Maybe add an argument which provides a hint
|
|
about the buffer usage. e.g. argument-conversion buffers are
|
|
normally small but block content buffers can be arbitrarily large.
|
|
*/
|
|
CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *dx);
|
|
|
|
/**
|
|
Returns a buffer borrowed from cmpp_b_borrow(), transferring
|
|
ownership back to pp. Passing a non-NULL b which was not returned
|
|
by cmpp_b_borrow() invoked undefined behavior (possibly delayed
|
|
until the list is cleaned up). To simplify usage, b may be NULL.
|
|
|
|
After calling this, b must be considered "freed" - it must not be
|
|
used again. This function is free (as it were) to immediately free
|
|
the object's memory instead of recycling it.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_return(cmpp *dx, cmpp_b *b);
|
|
|
|
/**
|
|
If NUL-terminated z matches one of the strings listed below, its
|
|
corresponding cmpp_atpol_e entry is returned, else
|
|
cmpp_atpol_invalid is returned.
|
|
|
|
If pp is not NULL then (A) this also sets its current at-policy and
|
|
(B) it recognizes an additional string (see below). In this case,
|
|
if z is not a valid string then pp's persistent error state is set.
|
|
|
|
Its accepted values each correspond to a like-named policy value:
|
|
|
|
- "off" (the default): no processing of `@` is performed.
|
|
|
|
- "error": fail if an undefined `X` is referenced in @token@
|
|
parsing.
|
|
|
|
- "retain": emit any unresolved `@X@` tokens as-is to the output
|
|
stream. i.e. `@X@` renders as `@X@`.
|
|
|
|
- "elide": omit unresolved `@X@` from the output, as if their values
|
|
were empty. i.e. `@X@` renders as an empty string, i.e. is not
|
|
emitted at all.
|
|
|
|
- "current": if pp!=NULL then it returns the current policy, else
|
|
this string resolves to cmpp_atpol_invalid.
|
|
*/
|
|
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z);
|
|
|
|
/**
|
|
Returns pp's current at-token policy.
|
|
*/
|
|
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp);
|
|
|
|
/**
|
|
Sets pp's current at-token policy. Returns 0 if pol is valid, else
|
|
it updates pp's error state and returns CMPP_RC_RANGE. This is a
|
|
no-op if pp has error state, returning that code instead.
|
|
|
|
The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify
|
|
certain client-side usage.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol);
|
|
|
|
/**
|
|
Pushes pol as the current at-policy. Returns 0 on success and
|
|
non-zero on error (bad pol value or allocation error). If this
|
|
returns 0 then the caller is obligated to eventually call
|
|
cmpp_atpol_pop() one time. If it returns non-0 then they _must not_
|
|
call that function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol);
|
|
|
|
/**
|
|
Must be called one time for each successful call to
|
|
cmpp_atpol_push(). It restores the at-policy to the value it
|
|
has when cmpp_atpol_push() was last called.
|
|
|
|
If called when no cmpp_delimiter_push() is active then debug builds
|
|
will fail an assert(), else pp's error state is updated if it has
|
|
none already.
|
|
*/
|
|
CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp);
|
|
|
|
/**
|
|
The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It
|
|
behaves identically, just for a different policy group with
|
|
different names.
|
|
|
|
Its accepted values are: "null" and "error". The value "current" is
|
|
only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid.
|
|
*/
|
|
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z);
|
|
|
|
/**
|
|
Returns pp's current policy regarding use of undefined define keys.
|
|
*/
|
|
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp);
|
|
|
|
/**
|
|
Sets pp's current policy regarding use of undefined define keys.
|
|
Returns 0 if pol is valid, else it updates pp's error state and
|
|
returns CMPP_RC_RANGE.
|
|
*/
|
|
CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol);
|
|
|
|
/**
|
|
The undefined-policy counterpart of cmpp_atpol_push().
|
|
*/
|
|
CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol);
|
|
|
|
/**
|
|
The undefined-policy counterpart of cmpp_atpol_pop().
|
|
*/
|
|
CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp);
|
|
|
|
/**
|
|
The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen
|
|
(if zOpen is not NULL) to the opening delimiter and *zClose (if
|
|
zClose is not NULL) to the closing delimiter. The memory is owned
|
|
by pp and may be invalidated by any calls to cmpp_atdelim_set(),
|
|
cmpp_atdelim_push(), or any APIs which consume input. Each string
|
|
is NUL-terminated and must be copied by the caller if they need
|
|
these strings past a point where they might be invalidated.
|
|
|
|
If called when the the delimiter stack is empty, debug builds with
|
|
fail an assert() and non-debug builds will behave as if the stack
|
|
contains the compile-time default delimiters.
|
|
*/
|
|
CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp,
|
|
char const **zOpen,
|
|
char const **zClose);
|
|
/**
|
|
The `@token@`-delimiter counterpart of cmpp_delimeter_set().
|
|
|
|
This sets the delimiter for `@token@` content to the given opening
|
|
and closing strings (which the library makes a copy of). If zOpen
|
|
is NULL then the compile-time default is assumed. If zClose is NULL
|
|
then zOpen is assumed.
|
|
|
|
Returns 0 on success. Returns non-0 if called when the delimiter
|
|
stack is empty, if it cannot copy the string or zDelim is deemed
|
|
unsuitable for use as a delimiter.
|
|
|
|
In debug builds this will trigger an assert if no `@token@`
|
|
delimiter has been set, but pp starts with one level in place, so
|
|
it is safe to call without having made an explicit
|
|
cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp,
|
|
char const *zOpen,
|
|
char const *zClose);
|
|
|
|
/**
|
|
The `@token@`-delimiter counterpart of cmpp_delimeter_push().
|
|
|
|
See cmpp_atdelim_set() for the semantics of the arguments.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp,
|
|
char const *zOpen,
|
|
char const *zClose);
|
|
|
|
/**
|
|
The @token@-delimiter counterpart of cmpp_delimiter_pop().
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp);
|
|
|
|
/**
|
|
Searches the given path (zPath), split on the given path separator
|
|
(pathSep), for the given file (zBaseName), optionally with the
|
|
given file extension (zExt).
|
|
|
|
If zBaseName or zBaseName+zExt are found as-is, without any
|
|
search path prefix, that will be the result, else the result
|
|
is either zBaseName or zBaseName+zExt prefixed by one of the
|
|
search directories.
|
|
|
|
On success, returns a new string, transfering ownership to the
|
|
caller (who must eventually pass it to cmpp_mfree() to deallocate).
|
|
|
|
If no match is found, or on error, returns NULL. On a genuine
|
|
error, pp's error state is updated and the error is unlikely to be
|
|
recoverable (see cmpp_err_set()).
|
|
|
|
This function is a no-op if called when pp's error state is set,
|
|
returning NULL.
|
|
|
|
Results are undefined (in the sense of whether it will work or not,
|
|
as opposed to whether it will crash or not) if pathSep is a control
|
|
character.
|
|
|
|
Design note: this is implemented as a Common Table Expression
|
|
query.
|
|
*/
|
|
CMPP_EXPORT char * cmpp_path_search(cmpp *pp,
|
|
char const *zPath,
|
|
char pathSep,
|
|
char const *zBaseName,
|
|
char const *zExt);
|
|
|
|
/**
|
|
Scans [*zPos,zEnd) for the next chSep character. Sets *zPos to one
|
|
after the last consumed byte, so its result includes the separator
|
|
character unless EOF is hit before then. If pCounter is not NULL
|
|
then it does ++*pCounter when finding chSep.
|
|
|
|
Returns true if any input is consumed, else false (EOF). When it
|
|
returns false, *zPos will have the same value it had when this was
|
|
called. If it returns true, *zPos will be greater than it was
|
|
before this call and <= zEnd.
|
|
|
|
Usage:
|
|
|
|
```
|
|
unsigned char const * zBegin = ...;
|
|
unsigned char const * const zEnd = zBegin + strlen(zBegin);
|
|
unsigned char const * zEol = zBegin;
|
|
cmpp_size_t nLn = 0;
|
|
while( cmpp_next_chunk(&zEol, zEnd, '\n', &nLn) ){
|
|
...
|
|
}
|
|
```
|
|
*/
|
|
CMPP_EXPORT
|
|
bool cmpp_next_chunk(unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
unsigned char chSep,
|
|
cmpp_size_t *pCounter);
|
|
|
|
/**
|
|
Flags and constants related to the cmpp_args type.
|
|
*/
|
|
enum cmpp_args_e {
|
|
/**
|
|
cmpp_args_parse() flag which tells cmpp_args_parse() not to
|
|
dive into (...) group tokens. It insteads leaves them to be parsed
|
|
(or not) by downstream code. The only reason to parse them in
|
|
advance is to catch syntax errors sooner rather than later.
|
|
*/
|
|
cmpp_args_F_NO_PARENS = 0x01
|
|
};
|
|
|
|
/**
|
|
An internal detail of cmpp_args.
|
|
*/
|
|
typedef struct cmpp_args_pimpl cmpp_args_pimpl;
|
|
|
|
/**
|
|
A container for parsing a line's worth of cmpp_arg
|
|
objects.
|
|
|
|
Instances MUST be cleanly initialized by bitwise-copying either
|
|
cmpp_args_empty or (depending on the context) cmpp_args_empty_m.
|
|
|
|
Instances MUST eventually be passed to cmpp_args_cleanup().
|
|
|
|
Design notes: this class is provided to the public API as a
|
|
convenience, not as a core/required component. It offers one of
|
|
many possible solutions for dealing with argument lists and is not
|
|
the End All/Be All of solutions. I didn't _really_ want to expose
|
|
this class in the public API at all but I also want client-side
|
|
directives to have the _option_ to to do some of the things
|
|
currently builtin directives can do which are (as of this writing)
|
|
unavailable in the public API, e.g. evaluate expressions (in that
|
|
limited form which this library supports). A stepping stone to
|
|
doing so is making this class public.
|
|
*/
|
|
struct cmpp_args {
|
|
/**
|
|
Number of parsed args. In the context of a cmpp_dx_f(), argument
|
|
lists do not include their directive's name as an argument.
|
|
*/
|
|
unsigned argc;
|
|
|
|
/**
|
|
The list of args. This is very specifically NOT an array (or at
|
|
least not one which client code can rely on to behave
|
|
sensibly). Some internal APIs adjust a cmpp_args's arg list,
|
|
re-linking the entries via cmpp_arg::next and making array-style
|
|
traversal a foot-gun.
|
|
|
|
To loop over them:
|
|
|
|
for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...}
|
|
|
|
This really ought to be const but it currenty cannot be for
|
|
internal reasons. Client code really should not modify these
|
|
objects, though. Doing so invokes undefined behavior.
|
|
|
|
For directives with the cmpp_d_F_ARGS_RAW flag, this member will,
|
|
after a successful call to cmpp_dx_next(), point to a single
|
|
argument which holds the directive's entire argument string,
|
|
stripped of leading spaces.
|
|
*/
|
|
cmpp_arg * arg0;
|
|
|
|
/**
|
|
Internal implementation details. This is initialized via
|
|
cmpp_args_parse() and freed via cmpp_args_cleanup().
|
|
*/
|
|
cmpp_args_pimpl * pimpl;
|
|
};
|
|
typedef struct cmpp_args cmpp_args;
|
|
|
|
/**
|
|
Empty-initialized cmpp_args instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_args_empty_m { \
|
|
.argc = 0, \
|
|
.arg0 = 0, \
|
|
.pimpl = 0 \
|
|
}
|
|
|
|
/**
|
|
Empty-initialized instance, intended for non-const-copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_args cmpp_args_empty;
|
|
|
|
/**
|
|
Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg
|
|
objects by iteratively processing that range with cmpp_arg_parse().
|
|
If nIn is negative, strlen() is used to calculate it.
|
|
|
|
Requires that arg be a cleanly-initialized instance (via
|
|
bitwise-copying cmpp_args_empty) or that it have been successfully
|
|
used with this function before. Behavior is undefined if pArgs was
|
|
not properly initialized.
|
|
|
|
The 3rd argument is an optional bitmask of flags from the
|
|
cmpp_args_e enum.
|
|
|
|
On success it populates arg, noting that an empty list is valid.
|
|
The memory pointed to by the arguments made available via
|
|
arg->arg0 is all owned by arg and will be invalidated by either a
|
|
subsequent call to this function (the memory will be overwritten or
|
|
reallocated) or cmpp_args_cleanup() (the memory will be freed).
|
|
|
|
On error, returns non-0 and updates pp's error state with info
|
|
about the problem.
|
|
*/
|
|
CMPP_EXPORT int cmpp_args_parse(cmpp_dx * dx,
|
|
cmpp_args * pOut,
|
|
unsigned char const * zInBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Frees any resources owned by its argument but does not free the
|
|
argument (which is typically stack-allocated). After calling this,
|
|
the object may again be used with cmpp_args_parse() (in which case
|
|
it eventually needs to be passed to this again).
|
|
|
|
This is a harmless no-op if `a` is already cleaned up but `a` must
|
|
not be NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a);
|
|
|
|
/**
|
|
A wrapper around cmpp_args_parse() which uses dx->args.z as an
|
|
input source. This is sometimes convenient in cmpp_dx_f()
|
|
implementations which use cmpp_dx_next(), or similar, to read and
|
|
process custom directives, as doing so invalidates dx->arg's
|
|
memory.
|
|
|
|
On success, returns 0 and populates args. On error, returns non-0
|
|
and sets dx->pp's error state.
|
|
|
|
cmpp_dx_args_clone() does essentially the same thing, but is more
|
|
efficient when dx->args.arg0 is is already parsed.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args);
|
|
|
|
/**
|
|
Populates pOut, replacing any current content, with a copy of each
|
|
arg in dx->args.arg0 (traversing arg0->next).
|
|
|
|
*pOut MUST be cleanly initialized via copying cmpp_args_empty or it
|
|
must have previously been used with either cmpp_args_parse() (which
|
|
has the same initialization requirement) or this function has
|
|
undefined results.
|
|
|
|
On success, pOut->argc and pOut->arg0 will refer to pOut's copy
|
|
of the arguments.
|
|
|
|
Copying of arguments is necessary in cmpp_dx_f() implementations
|
|
which need to hold on to arguments for use _after_ calling
|
|
cmpp_dx_next() or any API which calls that (which most directives
|
|
don't do). See that function for why.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut);
|
|
|
|
/** Flags for cmpp_popen(). */
|
|
enum cmpp_popen_e {
|
|
/**
|
|
Use execl[p](CMD, CMD,0) instead of
|
|
execl[p]("/bin/sh","-c",CMD,0).
|
|
*/
|
|
cmpp_popen_F_DIRECT = 0x01,
|
|
/** Use execlp() or execvp() instead of execl() or execv(). */
|
|
cmpp_popen_F_PATH = 0x02
|
|
};
|
|
|
|
/**
|
|
Result state for cmpp_popen() and friends.
|
|
*/
|
|
struct cmpp_popen_t {
|
|
/**
|
|
The child process ID.
|
|
*/
|
|
int childPid;
|
|
/**
|
|
The child process's stdout.
|
|
*/
|
|
int fdFromChild;
|
|
/**
|
|
If not NULL, cmpp_popen() will set *fpToChild to a FILE handle
|
|
mapped to the child process's stdin. If it is NULL, the child
|
|
process's stdin will be closed instead.
|
|
*/
|
|
cmpp_FILE **fpToChild;
|
|
};
|
|
typedef struct cmpp_popen_t cmpp_popen_t;
|
|
/**
|
|
Empty-initialized cmpp_popen_t instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_popen_t_empty_m {-1,-1,0}
|
|
/**
|
|
Empty-initialized instance, intended for non-const-copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_popen_t cmpp_popen_t_empty;
|
|
|
|
/**
|
|
Uses fork()/exec() to run a command in a separate process and open
|
|
a two-way stream to it. It is provided in this API to facilitate
|
|
the creation of custom directives which shell out to external
|
|
processes.
|
|
|
|
zCmd must contain the NUL-terminated command to run and any flags
|
|
for that command, e.g. "myapp --flag --other-flag". It is passed as
|
|
the 4th argument to:
|
|
|
|
execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL)
|
|
|
|
The po object MUST be cleanly initialized before calling this by
|
|
bitwise copying cmpp_popen_t_empty or (depending on the context)
|
|
cmpp_popen_t_empty_m.
|
|
|
|
Flags:
|
|
|
|
- cmpp_popen_F_DIRECT: zCmd is passed to execl(zCmd, zCmd, NULL).
|
|
instead of exec(). That can only work if zCmd is a single command
|
|
without arguments.
|
|
|
|
- cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which
|
|
performs path lookup of its initial argument. Again, that can
|
|
only work if zCmd is a single command without arguments.
|
|
|
|
On success:
|
|
|
|
- po->childPid will be set to the PID of the child process.
|
|
|
|
- po->fdFromChild is set to the child's stdout file
|
|
descriptor. read(2) from it to read from the child.
|
|
|
|
- If po->fpToChild is not NULL then *po->fpToChild is set to a
|
|
buffered output handle to the child's stdin. fwrite(3) to it to
|
|
send the child stuff. Be sure to fflush(3) and/or fclose(3) it to
|
|
keep it from hanging forever. If po->fpToChild is NULL then the
|
|
stdin of the child is closed. (Why buffered instead of unbuffered?
|
|
My attempts at getting unbuffered child stdin to work have all
|
|
failed when write() is called on it.)
|
|
|
|
On success, the caller is obligated to pass po to cmpp_pclose().
|
|
The caller may pass pi to cmpp_pclose() on error, if that's easier
|
|
for them, provided that the po argument was cleanly initialized
|
|
before passing it to this function.
|
|
|
|
If the caller fclose(3)s *po->fpToChild then they must set it to
|
|
NULL so that passing it to cmpp_pclose() knows not to close it.
|
|
|
|
On error: you know the drill. This function is a no-op if pp has
|
|
error state when it's called, and the current error code is
|
|
returned instead.
|
|
|
|
This function is only available on non-WASM Unix-like environments.
|
|
On others it will always trigger a CMPP_RC_UNSUPPORTED error.
|
|
|
|
Bugs: because the command is run via /bin/sh -c ... we cannot tell
|
|
if it's actually found. All we can tell is that /bin/sh ran.
|
|
|
|
Also: this doesn't capture stderr, so commands should redirect
|
|
stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is
|
|
a potential TODO without a current use case.
|
|
|
|
See: cmpp_pclose()
|
|
See: cmpp_popenv()
|
|
*/
|
|
CMPP_EXPORT int cmpp_popen(cmpp *pp, unsigned char const *zCmd,
|
|
cmpp_flag32_t flags, cmpp_popen_t *po);
|
|
|
|
/**
|
|
Works like cmpp_popen() except that:
|
|
|
|
- It takes it arguments in the form of a main()-style array of
|
|
strings because it uses execv() instead of exec(). The
|
|
cmpp_popen_F_PATH flag causes it to use execvp().
|
|
|
|
- It does not honor the cmpp_popen_F_DIRECT flag because all
|
|
arguments have to be passed in via the arguments array.
|
|
|
|
As per execv()'s requirements: azCmd _MUST_ end with a NULL entry.
|
|
*/
|
|
CMPP_EXPORT int cmpp_popenv(cmpp *pp, char * const * azCmd,
|
|
cmpp_flag32_t flags, cmpp_popen_t *po);
|
|
|
|
/**
|
|
Closes handles returned by cmpp_popen() and zeroes out po. If the
|
|
caller fclose()d *po->fpToChild then they need to set it to NULL so
|
|
that this function does not double-close it.
|
|
|
|
Returns the result code of the child process.
|
|
|
|
After calling this, po may again be used as an argument to
|
|
cmpp_popen().
|
|
*/
|
|
CMPP_EXPORT int cmpp_pclose(cmpp_popen_t *po);
|
|
|
|
/**
|
|
A cmpp_popenv() proxy which builds up an execv()-style array of
|
|
arguments from the given args. It has a hard, and mostly arbitrary,
|
|
upper limit on the number of args it can take in order to avoid
|
|
extra allocation.
|
|
*/
|
|
CMPP_EXPORT int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args,
|
|
cmpp_popen_t *p);
|
|
|
|
|
|
/**
|
|
Callback type for use with cmpp_kav_each().
|
|
|
|
cmpp_kav_each() calls this one time per key/value in such a list,
|
|
passing it the relevant key/value strings and lengths, plus the
|
|
opaque state pointer which is passed to cmpp_kav_each().
|
|
|
|
Must return 0 on success or update (or propagate) dx->pp's error
|
|
state on error.
|
|
*/
|
|
typedef int cmpp_kav_each_f(
|
|
cmpp_dx *dx,
|
|
unsigned char const *zKey, cmpp_size_t nKey,
|
|
unsigned char const *zVal, cmpp_size_t nVal,
|
|
void* callbackState
|
|
);
|
|
|
|
/**
|
|
Flag bitmask for use with cmpp_kav_each() and cmpp_str_each().
|
|
*/
|
|
enum cmpp_kav_each_e {
|
|
/**
|
|
The key argument should be expanded using cmpp_arg_to_b()
|
|
with a 0 flags value. This flag should normally not be used.
|
|
*/
|
|
cmpp_kav_each_F_EXPAND_KEY = 0x01,
|
|
/**
|
|
The key argument should be expanded using cmpp_arg_to_b()
|
|
with a 0 flags value. This flag should normally be used.
|
|
*/
|
|
cmpp_kav_each_F_EXPAND_VAL = 0x02,
|
|
/**
|
|
Treat (...) value tokens (ttype=cmpp_TT_GroupParen) as integer
|
|
expressions. Keys are never treated this way. Without this flag,
|
|
the token expands to the ... part of (...).
|
|
*/
|
|
cmpp_kav_each_F_PARENS_EXPR = 0x04,
|
|
/**
|
|
Indicates that an empty input list is an error. If this flag is
|
|
not set and the list is empty, the callback will not be called
|
|
and no error will be triggered.
|
|
*/
|
|
cmpp_kav_each_F_NOT_EMPTY = 0x08,
|
|
/**
|
|
Indicates that the list does not have the '->' part(s). That is,
|
|
the list needs to be in pairs of KEY VAL rather than triples of
|
|
KEY -> VALUE.
|
|
*/
|
|
cmpp_kav_each_F_NO_ARROW = 0x10,
|
|
|
|
/**
|
|
If set, keys get the cmpp_arg_to_b_F_BRACE_CALL flag added
|
|
to them. This implies cmpp_kav_each_F_EXPAND_KEY.
|
|
*/
|
|
cmpp_kav_each_F_CALL_KEY = 0x20,
|
|
/** Value counterpart of cmpp_kav_each_F_CALL_KEY. */
|
|
cmpp_kav_each_F_CALL_VAL = 0x40,
|
|
/** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */
|
|
cmpp_kav_each_F_CALL = 0x60,
|
|
|
|
//TODO: append to defines which already exist
|
|
cmpp_kav_each_F_APPEND = 0,
|
|
cmpp_kav_each_F_APPEND_SPACE = 0,
|
|
cmpp_kav_each_F_APPEND_NL = 0
|
|
};
|
|
|
|
/**
|
|
A helper for cmpp_dx_f() implementations in processing directive
|
|
arguments which are lists in this form:
|
|
|
|
{ a -> b c -> d ... }
|
|
|
|
("kav" is short for "key arrow value".)
|
|
|
|
The range [zBegin,zBegin+nIn) contains the raw list (not including
|
|
any containing braces, parentheses, quotes, or the like). If nIn is
|
|
negative, strlen() is used to calculate it.
|
|
|
|
The range is parsed using cmpp_args_parse().
|
|
|
|
For each key/arrow/value triplet in that list, callback() is passed
|
|
the stringified form of the key and the value, plus the
|
|
callbackState pointer.
|
|
|
|
The flags argument controls whether the keys and values get
|
|
expanded or not. (Typically the keys should not be expanded but the
|
|
values should.)
|
|
|
|
Returns 0 on success. If the callback returns non-0, it is expected
|
|
to have updated dx's error state. callback() will never be called
|
|
when dx's error state is non-0.
|
|
|
|
Error results include:
|
|
|
|
- CMPP_RC_RANGE: the list is empty does not contain the correct
|
|
number of entries (groups of 3, or 2 if flags has
|
|
cmpp_kav_each_F_NO_ARROW).
|
|
|
|
- CMPP_RC_OOM: allocation error.
|
|
|
|
- Any value returned by cmpp_args_parse().
|
|
|
|
- Any number of errors can be triggered during expansion of
|
|
keys and values.
|
|
*/
|
|
CMPP_EXPORT int cmpp_kav_each(
|
|
cmpp_dx *dx,
|
|
unsigned char const *zBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_kav_each_f callback, void *callbackState,
|
|
cmpp_flag32_t flags
|
|
);
|
|
|
|
/**
|
|
This works like cmpp_kav_each() except that it treats each token in
|
|
the list as a single entry.
|
|
|
|
When the callback is triggered, the "key" part will be the raw
|
|
token and the "value" part will be the expanded form of that
|
|
value. Its flags may contain most of the cmpp_kav_each_F_... flags,
|
|
with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect
|
|
here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the
|
|
callback receives the same string for both the key and value.
|
|
*/
|
|
CMPP_EXPORT int cmpp_str_each(
|
|
cmpp_dx *dx,
|
|
unsigned char const *zBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_kav_each_f callback, void *callbackState,
|
|
cmpp_flag32_t flags
|
|
);
|
|
|
|
/**
|
|
An interface for clients to provide directives to the library
|
|
on-demand.
|
|
|
|
This is called when pp has encountered a directive name is does not
|
|
know. It is passed the cmpp object, the name of the directive, and
|
|
the opaque state pointer which was passed to cmpp_d_autoloader_et().
|
|
|
|
Implementations should compare dname to any directives they know
|
|
about. If they find no match they must return CMPP_RC_NO_DIRECTIVE
|
|
_without_ using cmpp_err_set() to make the error persistent.
|
|
|
|
If they find a match, they must use cmpp_d_register() to register
|
|
it and (on success) return 0. The library will then look again in
|
|
the registered directive list for the directive before giving up.
|
|
|
|
If they find a match but registration fails then the result of that
|
|
failure must be returned.
|
|
|
|
For implementation-specific errors, e.g. trying to load a directive
|
|
from a DLL but the loading of the DLL fails, implementations are
|
|
expected to use cmpp_err_set() to report the error and to return
|
|
that result code after performing any necessary cleanup.
|
|
|
|
It is legal for an implementation to register multiple directives
|
|
in a single invocation (in particular a pair of opening/closing
|
|
directives), as well as to register directives other than the one
|
|
requested (if necessary). Regardless of which one(s) it registers,
|
|
it must return 0 only if it registers one named dname.
|
|
*/
|
|
typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state);
|
|
|
|
/**
|
|
A c-pp directive "autoloader". See cmpp_d_autoloader_set()
|
|
and cmpp_d_autoloader_take().
|
|
*/
|
|
struct cmpp_d_autoloader {
|
|
/** The autoloader callback. */
|
|
cmpp_d_autoloader_f f;
|
|
/**
|
|
Finalizer for this->state. After calling this, if there's any
|
|
chance that this object might be later used, then it is important
|
|
that this->state be set to 0 (which this finalizer cannot
|
|
do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty
|
|
over any instances immediately after calling dtor().
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
/**
|
|
Implementation-specific state, to be passed as the final argument
|
|
to this->f and this->dtor.
|
|
*/
|
|
void * state;
|
|
};
|
|
typedef struct cmpp_d_autoloader cmpp_d_autoloader;
|
|
/**
|
|
Empty-initialized cmpp_d_autoloader instance, intended for
|
|
const-copy initialization.
|
|
*/
|
|
#define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0}
|
|
/**
|
|
Empty-initialized cmpp_d_autoloader instance, intended for
|
|
non-const-copy initialization.
|
|
*/
|
|
extern const cmpp_d_autoloader cmpp_d_autoloader_empty;
|
|
|
|
/**
|
|
Sets pp's "directive autoloader". Each cmpp instance has but a
|
|
single autoloader but this API is provided so that several
|
|
instances may be chained from client-side code.
|
|
|
|
This function will call the existing autoloader's destructor (if
|
|
any), invalidating any pointers to its state object.
|
|
|
|
If pNew is not NULL then pp's autoloader is set to a bitwise copy
|
|
of *pNew, otherwise it is zeroed out. This transfers ownership of
|
|
pNew->state to pp.
|
|
|
|
See cmpp_d_autoloader_f()'s docs for how pNew must behave.
|
|
|
|
This function has no error conditions but downstream results are
|
|
undefined if if pNew and an existing autoloader refer to the same
|
|
dtor/state values (a gateway to double-frees).
|
|
*/
|
|
CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew);
|
|
|
|
/**
|
|
Moves pp's current autoloader state into pOld, transerring
|
|
ownership of it to the caller.
|
|
|
|
This obligates the caller to eventually either pass that same
|
|
pointer to cmpp_d_autoloader_set() (to transfer ownership back to
|
|
pp) or to call pOld->dtor() (if it's not NULL), passing it it
|
|
pOld->state (even if pOld->state is NULL). In either case, all
|
|
contents of pOld are semantically invalidated and perhaps freed.
|
|
|
|
This would normally be a prelude to cmpp_d_autoloader_set() to
|
|
install a custom, perhaps chained, autoloader.
|
|
*/
|
|
CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld);
|
|
|
|
/**
|
|
True only for ' ' and '\t'.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_isspace(int ch);
|
|
|
|
/**
|
|
Reassigns *p to the address of the first non-space character at or
|
|
after the initial *p value. It stops looking if it reaches zEnd.
|
|
|
|
If `*p` does not point to memory before zEnd, or is not a part of
|
|
the same logical string, results are undefined.
|
|
|
|
|
|
Achtung: do not pass this the address of a cmpp_b::z,
|
|
or similar, as that will effectively corrupt the buffer's
|
|
memory. To trim a whole buffer, use something like:
|
|
|
|
```
|
|
cmpp_b ob = cmpp_b_empty;
|
|
... populate ob...;
|
|
// get the trimmed range:
|
|
unsigned char const *zB = ob.z;
|
|
unsigned char const *zE = zB + n;
|
|
cmpp_skip_snl(&zB, zE);
|
|
assert( zB<=zE );
|
|
cmpp_skip_snl_trailing(zB, &zE);
|
|
assert( zE>=zB );
|
|
printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB);
|
|
```
|
|
|
|
Those assert()s are not error handling - they're demonstrating
|
|
invariants of the calls made before them.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_space( unsigned char const **p,
|
|
unsigned char const *zEnd );
|
|
|
|
/**
|
|
Works just like cmpp_skip_space() but it also
|
|
skips newlines.
|
|
|
|
FIXME (2026-02-21): it does not recognize CRNL pairs as
|
|
atomic newlines.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p,
|
|
unsigned char const *zEnd );
|
|
|
|
/**
|
|
"Trims" trailing cmpp_isspace() characters from the range [zBegin,
|
|
*p). *p must initially point to one byte after the end of zBegin
|
|
(i.e. its NUL byte or virtual EOF). Upon return *p will be modified
|
|
leftwards (if at all) until a non-space is found or *p==zBegin.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin,
|
|
unsigned char const **p );
|
|
|
|
/**
|
|
Works just like cmpp_skip_space_trailing() but
|
|
skips cmpp_skip_snl() characters.
|
|
|
|
FIXME (2026-02-21): it does not recognize CRNL pairs as
|
|
atomic newlines.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin,
|
|
unsigned char const **p );
|
|
|
|
|
|
/**
|
|
Generic array-of-T list memory-reservation routine.
|
|
|
|
*list is the input array-of-T. nDesired is the number of entries to
|
|
reserve (list entry count, not byte length). *nAlloc is the number
|
|
of entries allocated in the list. sizeOfEntry is the sizeof(T) for
|
|
each entry in *list. T may be either a value type or a pointer
|
|
type and sizeofEntry must match, i.e. it must be sizeof(T*) for a
|
|
list-of-pointers and sizeof(T) for a list-of-objects.
|
|
|
|
If pp is not NULL then this function updates pp's error state on
|
|
error, else it simply returns CMPP_RC_OOM on error. If pp is not
|
|
NULL then this function is a no-op if called when pp's error state
|
|
is set, returning that code without other side-effects.
|
|
|
|
If nDesired > *nAlloc then *list is reallocated to contain at least
|
|
nDesired entries, else this function returns without side effects.
|
|
|
|
On success *list is re-assigned to the reallocated list memory, all
|
|
newly-(re)allocated memory is zeroed out, and *nAlloc is updated to
|
|
the new allocation size of *list (the number of list entries, not
|
|
the number of bytes).
|
|
|
|
On failure neither *list nor *nAlloc are modified.
|
|
|
|
Returns 0 on success or CMPP_RC_OOM on error. Errors generated by
|
|
this routine are, at least in principle, recoverable (see
|
|
cmpp_err_set()), though that simply means that the pp object is
|
|
left in a well-defined state, not that the app can necessarily
|
|
otherwise recover from an OOM.
|
|
|
|
This seemingly-out-of-API-scope routine is in the public API as a
|
|
convenience for client-level cmpp_dx_f() implementations[^1]. This API
|
|
internally has an acute need for basic list management and non-core
|
|
extensions inherit that as well.
|
|
|
|
[^1]: this project's own directives are written as if they were
|
|
client-side whenever feasible. Some require cmpp-internal state to
|
|
do their jobs, though.
|
|
*/
|
|
CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
|
|
cmpp_size_t * nAlloc, unsigned sizeOfEntry);
|
|
|
|
|
|
/**
|
|
The current cmpp_api_thunk::apiVersion value.
|
|
See cmpp_api_thunk_map.
|
|
*/
|
|
#define cmpp_api_thunk_version 20260206
|
|
|
|
/**
|
|
A helper for use with cmpp_api_thunk.
|
|
|
|
V() defines the API version number. It invokes
|
|
V(NAME,TYPE,VERSION) once. NAME is the member name for the
|
|
cmpp_api_thunk struct. TYPE is an integer type. VERSION is the
|
|
cmpp_api_thunk object version. This is initially 0 and will
|
|
eventually be given a number which increments which new members
|
|
appended. This is to enable DLLs to check whether their
|
|
cmpp_api_thunk object has the methods they're looking for.
|
|
|
|
Then it invokes F(NAME,RETTYPE,PARAMS) and O(NAME,TYPE)
|
|
once for each cmpp_api_thunk member in an unspecified order, and
|
|
and A(VERSION) an arbitrary number of times.
|
|
|
|
F() is for functions. O() is for objects, which are exposed here as
|
|
pointers to those objects so that we don't copy them. A() is
|
|
injected at each point where a new API version was introduced, and
|
|
that number (an integer) is its only argument. A()'s definition
|
|
can normally be empty.
|
|
|
|
In all cases, NAME is the public API symbol name minus the "cmpp_"
|
|
prefix. RETTYPE is the function return type or object type. PARAMS
|
|
is the function parameters, wrapped in (...). For O(), TYPE is the
|
|
const-qualified type of the object referred to by
|
|
NAME. cmpp_api_thunk necessarily exposes those as pointers, but
|
|
that pointer is not part of the TYPE argument.
|
|
|
|
See cmpp_api_thunk for details.
|
|
|
|
In order to help DLLs to not inadvertently use invalid areas of the
|
|
API object by referencing members which they loading c-pp version
|
|
does not have, this list must only ever be modified by appending to
|
|
it. That enables DLLs to check their compile-time
|
|
cmpp_api_thunk_version against the dx->pp->api->apiVersion. If
|
|
the runtime version is older (less than) than their compile-time
|
|
version, the DLL must not access any methods added after
|
|
dx->pp->api->apiVersion.
|
|
*/
|
|
#define cmpp_api_thunk_map(A,V,F,O) \
|
|
A(0) \
|
|
V(apiVersion,unsigned,cmpp_api_thunk_version) \
|
|
F(mrealloc,void *,(void * p, size_t n)) \
|
|
F(malloc,void *,(size_t n)) \
|
|
F(mfree,void,(void *)) \
|
|
F(ctor,int,(cmpp **pp, cmpp_ctor_cfg const *)) \
|
|
F(dtor,void,(cmpp *pp)) \
|
|
F(reset,void,(cmpp *pp)) \
|
|
F(check_oom,int,(cmpp * const pp, void const * m)) \
|
|
F(is_legal_key,bool,(unsigned char const *, cmpp_size_t n, \
|
|
unsigned char const **)) \
|
|
F(define_legacy,int,(cmpp *, const char *,char const *)) \
|
|
F(define_v2,int,(cmpp *, const char *, char const *)) \
|
|
F(undef,int,(cmpp *, const char *, unsigned int *)) \
|
|
F(define_shadow,int,(cmpp *, char const *, char const *, \
|
|
int64_t *)) \
|
|
F(define_unshadow,int,(cmpp *, char const *, int64_t)) \
|
|
F(process_string,int,(cmpp *, const char *, \
|
|
unsigned char const *, cmpp_ssize_t)) \
|
|
F(process_file,int,(cmpp *, const char *)) \
|
|
F(process_stream,int,(cmpp *, const char *, \
|
|
cmpp_input_f, void *)) \
|
|
F(process_argv,int,(cmpp *, int, char const * const *)) \
|
|
F(err_get,int,(cmpp *, char const **)) \
|
|
F(err_set,int,(cmpp *, int, char const *, ...)) \
|
|
F(err_set1,int,(cmpp *, int, char const *)) \
|
|
F(err_has,int,(cmpp const *)) \
|
|
F(is_safemode,bool,(cmpp const *)) \
|
|
F(sp_begin,int,(cmpp *)) \
|
|
F(sp_commit,int,(cmpp *)) \
|
|
F(sp_rollback,int,(cmpp *)) \
|
|
F(output_f_FILE,int,(void *, void const *, cmpp_size_t)) \
|
|
F(output_f_fd,int,(void *, void const *, cmpp_size_t)) \
|
|
F(input_f_FILE,int,(void *, void *, cmpp_size_t *)) \
|
|
F(input_f_fd,int,(void *, void *, cmpp_size_t *)) \
|
|
F(flush_f_FILE,int,(void *)) \
|
|
F(stream,int,(cmpp_input_f, void *, \
|
|
cmpp_output_f, void *)) \
|
|
F(slurp,int,(cmpp_input_f, void *, \
|
|
unsigned char **, cmpp_size_t *)) \
|
|
F(fopen,cmpp_FILE *,(char const *, char const *)) \
|
|
F(fclose,void,(cmpp_FILE * )) \
|
|
F(outputer_out,int,(cmpp_outputer *, void const *, cmpp_size_t)) \
|
|
F(outputer_flush,int,(cmpp_outputer *)) \
|
|
F(outputer_cleanup,void,(cmpp_outputer *)) \
|
|
F(outputer_cleanup_f_FILE,void,(cmpp_outputer *)) \
|
|
F(delimiter_set,int,(cmpp *, char const *)) \
|
|
F(delimiter_get,void,(cmpp const *, char const **)) \
|
|
F(chomp,bool,(unsigned char *, cmpp_size_t *)) \
|
|
F(b_clear,void,(cmpp_b *)) \
|
|
F(b_reuse,cmpp_b *,(cmpp_b *)) \
|
|
F(b_swap,void,(cmpp_b *, cmpp_b *)) \
|
|
F(b_reserve,int,(cmpp_b *, cmpp_size_t)) \
|
|
F(b_reserve3,int,(cmpp *, cmpp_b *,cmpp_size_t)) \
|
|
F(b_append,int,(cmpp_b *, void const *,cmpp_size_t)) \
|
|
F(b_append4,int,(cmpp *,cmpp_b *,void const *, \
|
|
cmpp_size_t)) \
|
|
F(b_append_ch, int,(cmpp_b *, char)) \
|
|
F(b_append_i32,int,(cmpp_b *, int32_t)) \
|
|
F(b_append_i64,int,(cmpp_b *, int64_t)) \
|
|
F(b_chomp,bool,(cmpp_b *)) \
|
|
F(output_f_b,int,(void *, void const *,cmpp_size_t)) \
|
|
F(outputer_cleanup_f_b,void,(cmpp_outputer *self)) \
|
|
F(version,char const *,(void)) \
|
|
F(tt_cstr,char const *,(int tt)) \
|
|
F(dx_err_set,int,(cmpp_dx *dx, int rc, char const *zFmt, ...)) \
|
|
F(dx_next,int,(cmpp_dx * dx, bool * pGotOne)) \
|
|
F(dx_process,int,(cmpp_dx * dx)) \
|
|
F(dx_consume,int,(cmpp_dx *, cmpp_outputer *, \
|
|
cmpp_d const *const *, unsigned, cmpp_flag32_t)) \
|
|
F(dx_consume_b,int,(cmpp_dx *, cmpp_b *, cmpp_d const * const *, \
|
|
unsigned, cmpp_flag32_t)) \
|
|
F(arg_parse,int,(cmpp_dx * dx, cmpp_arg *, \
|
|
unsigned char const **, unsigned char const *, \
|
|
unsigned char ** , unsigned char const * )) \
|
|
F(arg_strdup,char *,(cmpp *pp, cmpp_arg const *arg)) \
|
|
F(arg_to_b,int,(cmpp_dx * dx, cmpp_arg const *arg, \
|
|
cmpp_b * os, cmpp_flag32_t flags)) \
|
|
F(errno_rc,int,(int errNo, int dflt)) \
|
|
F(d_register,int,(cmpp * pp, cmpp_d_reg const * r, cmpp_d **pOut)) \
|
|
F(dx_f_dangling_closer,void,(cmpp_dx *dx)) \
|
|
F(dx_out_raw,int,(cmpp_dx * dx, void const *z, cmpp_size_t n)) \
|
|
F(dx_out_expand,int,(cmpp_dx const * dx, cmpp_outputer * pOut, \
|
|
unsigned char const * zFrom, cmpp_size_t n, \
|
|
cmpp_atpol_e policy)) \
|
|
F(dx_outf,int,(cmpp_dx *dx, char const *zFmt, ...)) \
|
|
F(dx_delim,char const *,(cmpp_dx const *dx)) \
|
|
F(atpol_from_str,cmpp_atpol_e,(cmpp * pp, char const *z)) \
|
|
F(atpol_get,cmpp_atpol_e,(cmpp const * const pp)) \
|
|
F(atpol_set,int,(cmpp * const pp, cmpp_atpol_e pol)) \
|
|
F(atpol_push,int,(cmpp * pp, cmpp_atpol_e pol)) \
|
|
F(atpol_pop,void,(cmpp * pp)) \
|
|
F(unpol_from_str,cmpp_unpol_e,(cmpp * pp,char const *z)) \
|
|
F(unpol_get,cmpp_unpol_e,(cmpp const * const pp)) \
|
|
F(unpol_set,int,(cmpp * const pp, cmpp_unpol_e pol)) \
|
|
F(unpol_push,int,(cmpp * pp, cmpp_unpol_e pol)) \
|
|
F(unpol_pop,void,(cmpp * pp)) \
|
|
F(path_search,char *,(cmpp *pp, char const *zPath, char pathSep, \
|
|
char const *zBaseName, char const *zExt)) \
|
|
F(args_parse,int,(cmpp_dx * dx, cmpp_args * pOut, \
|
|
unsigned char const * zInBegin, \
|
|
cmpp_ssize_t nIn, cmpp_flag32_t flags)) \
|
|
F(args_cleanup,void,(cmpp_args *a)) \
|
|
F(dx_args_clone,int,(cmpp_dx * dx, cmpp_args *pOut)) \
|
|
F(popen,int,(cmpp *, unsigned char const *, cmpp_flag32_t, \
|
|
cmpp_popen_t *)) \
|
|
F(popenv,int,(cmpp *pp, char * const * azCmd, cmpp_flag32_t flags, \
|
|
cmpp_popen_t *po)) \
|
|
F(pclose,int,(cmpp_popen_t *po)) \
|
|
F(popen_args,int,(cmpp_dx *, cmpp_args const *, cmpp_popen_t *)) \
|
|
F(kav_each,int, (cmpp_dx *,unsigned char const *, cmpp_ssize_t, \
|
|
cmpp_kav_each_f, void *, cmpp_flag32_t)) \
|
|
F(d_autoloader_set,void,(cmpp *pp, cmpp_d_autoloader const * pNew)) \
|
|
F(d_autoloader_take,void,(cmpp *pp, cmpp_d_autoloader * pOld)) \
|
|
F(isspace,bool,(int ch)) \
|
|
F(skip_space,void,(unsigned char const **, unsigned char const *)) \
|
|
F(skip_snl,void,(unsigned char const **, unsigned char const *)) \
|
|
F(skip_space_trailing,void,(unsigned char const *zBegin, \
|
|
unsigned char const **p)) \
|
|
F(skip_snl_trailing,void,(unsigned char const *zBegin, \
|
|
unsigned char const **p)) \
|
|
F(array_reserve,int,(cmpp *pp, void **list, cmpp_size_t nDesired, \
|
|
cmpp_size_t * nAlloc, unsigned sizeOfEntry)) \
|
|
F(module_load,int,(cmpp *, char const *,char const *)) \
|
|
F(module_dir_add,int,(cmpp *, const char *)) \
|
|
O(outputer_FILE,cmpp_outputer const) \
|
|
O(outputer_b,cmpp_outputer const) \
|
|
O(outputer_empty,cmpp_outputer const) \
|
|
O(b_empty,cmpp_b const) \
|
|
A(20251116) \
|
|
F(next_chunk,bool,(unsigned char const **,unsigned char const *, \
|
|
unsigned char,cmpp_size_t*)) \
|
|
A(20251118) \
|
|
F(atdelim_get,void,(cmpp const *,char const **,char const **)) \
|
|
F(atdelim_set,int,(cmpp *,char const *,char const *)) \
|
|
F(atdelim_push,int,(cmpp *,char const *,char const *)) \
|
|
F(atdelim_pop,int,(cmpp *)) \
|
|
A(20251224) \
|
|
F(dx_pos_save,void,(cmpp_dx const *, cmpp_dx_pos *)) \
|
|
F(dx_pos_restore,void,(cmpp_dx *, cmpp_dx_pos const *)) \
|
|
A(20260130) \
|
|
F(dx_is_call,bool,(cmpp_dx * const)) \
|
|
A(20260206) \
|
|
F(b_borrow,cmpp_b *,(cmpp *dx)) \
|
|
F(b_return,void,(cmpp *dx, cmpp_b*)) \
|
|
A(1+cmpp_api_thunk_version)
|
|
|
|
|
|
/**
|
|
Callback signature for cmpp module import routines.
|
|
|
|
This is called by the library after having first encountering this
|
|
module (typically after looking for it in a DLL, but static
|
|
instances are supported).
|
|
|
|
The primary intended purpose of this interface is for
|
|
implementations to call cmpp_d_register() (any number of times). It
|
|
is also legal to use APIs which set or query defines. This
|
|
interface is not intended to interact with pp's I/O in any way
|
|
(that's the job of the directives which these functions
|
|
register). Violating that will invoke undefined results, perhaps
|
|
stepping on the toes of any being-processed directive which
|
|
triggered the dynamic load of this directive.
|
|
|
|
Errors in module initialization must be reported via cmpp_err_set()
|
|
and that code must be returned.
|
|
|
|
Implementations must typically call cmpp_api_init(pp) as their
|
|
first operation.
|
|
|
|
See the files named d-*.c in libcmpp's source tree for examples.
|
|
*/
|
|
typedef int (*cmpp_module_init_f)(cmpp * pp);
|
|
|
|
/**
|
|
Holds information for mapping a cmpp_module_init_f to a name.
|
|
Its purpose is to get installed by the CMPP_MODULE_xxx family of
|
|
macros and referenced later via a module-loading mechanism.
|
|
*/
|
|
struct cmpp_module{
|
|
/**
|
|
Symbolic name of the module.
|
|
*/
|
|
char const * name;
|
|
|
|
/**
|
|
The initialization routine for the module.
|
|
*/
|
|
cmpp_module_init_f init;
|
|
};
|
|
|
|
/** Convenience typedef. */
|
|
typedef struct cmpp_module cmpp_module;
|
|
|
|
/** @def CMPP_MODULE_DECL
|
|
|
|
Declares an extern (cmpp_module*) symbol called
|
|
cmpp_module__#\#CNAME.
|
|
|
|
Use CMPP_MODULE_IMPL2() or CMPP_MODULE_IMPL3() to create the
|
|
matching implementation code.
|
|
|
|
This macro should be used in the C or H file for a loadable module.
|
|
It may be compined in a file with a single CMPP_MODULE_IMPL_SOLO()
|
|
declaration with the same name, such that the module can be loaded
|
|
both with and without the explicit symbol name.
|
|
*/
|
|
#define CMPP_MODULE_DECL(CNAME) \
|
|
extern const cmpp_module * cmpp_module__##CNAME
|
|
|
|
/** @def CMPP_MODULE_IMPL
|
|
|
|
Intended to be used to implement module declarations. If a module
|
|
has both C and H files, CMPP_MODULE_DECL(CNAME) should be used in the
|
|
H file and CMPP_MODULE_IMPL2() should be used in the C file. If the
|
|
DLL has only a C file (or no public H file), CMPP_MODULE_DECL is
|
|
unnecessary.
|
|
|
|
If the module's human-use name is a legal C identifier,
|
|
CMPP_MODULE_IMPL2() is slightly easier to use than this macro.
|
|
|
|
Implements a static cmpp_module object named
|
|
cmpp_module__#\#CNAME#\#_impl and a non-static
|
|
(cmpp_module*) named cmpp_module__#\#CNAME which points to
|
|
cmpp_module__#\#CNAME#\#_impl. (The latter symbol may optionally be
|
|
declared in a header file via CMPP_MODULE_DECL.) NAME is used as
|
|
the cmpp_module::name value.
|
|
|
|
INIT_F must be a cmpp_module_init_f() function pointer. That function
|
|
is called when cmpp_module_load() loads the module.
|
|
|
|
This macro may be combined in a file with a single
|
|
CMPP_MODULE_IMPL_SOLO() declaration using the same CNAME value,
|
|
such that the module can be loaded both with and without the
|
|
explicit symbol name.
|
|
|
|
Example usage, in a module's header file, if any:
|
|
|
|
```
|
|
CMPP_MODULE_DECL(mymodule);
|
|
```
|
|
|
|
(The declaration is not strictly necessary - it is more of a matter
|
|
of documentation.)
|
|
|
|
And in the C file:
|
|
|
|
```
|
|
CMPP_MODULE_IMPL3(mymodule,"mymodule",mymodule_install);
|
|
// OR:
|
|
CMPP_MODULE_IMPL2(mymodule,mymodule_install);
|
|
```
|
|
|
|
If it will be the only module in the target DLL, one can also add
|
|
this:
|
|
|
|
```
|
|
CMPP_MODULE_IMPL2(mymodule,mymodule_install);
|
|
// _OR_ (every so slightly different):
|
|
CMPP_MODULE_STANDALONE_IMPL2(mymodule,mymodule_install);
|
|
```
|
|
|
|
Which simplifies client-side module loading by allowing them to
|
|
leave out the module name when loading, but that approach only
|
|
works if modules are compiled one per DLL (as opposed to being
|
|
packaged together in one DLL).
|
|
|
|
@see CMPP_MODULE_DECL
|
|
@see CMPP_MODULE_IMPL_SOLO
|
|
*/
|
|
#define CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F) \
|
|
static const cmpp_module \
|
|
cmpp_module__##CNAME##_impl = { NAME, INIT_F }; \
|
|
const cmpp_module * \
|
|
cmpp_module__##CNAME = &cmpp_module__##CNAME##_impl
|
|
|
|
/** @def CMPP_MODULE_IMPL3
|
|
|
|
A simplier form of CMPP_MODULE_IMPL3() for cases where a module name
|
|
is a legal C symbol name.
|
|
*/
|
|
#define CMPP_MODULE_IMPL2(CNAME,INIT_F) \
|
|
CMPP_MODULE_IMPL3(CNAME,#CNAME,INIT_F)
|
|
|
|
/** @def CMPP_MODULE_IMPL_SOLO
|
|
|
|
Implements a static cmpp_module symbol called
|
|
cmpp_module1_impl and a non-static (cmpp_module*) named
|
|
cmpp_module1 which points to cmpp_module1_impl
|
|
|
|
INIT_F must be a cmpp_module_init_f.
|
|
|
|
This macro must only be used in the C file for a loadable module
|
|
when that module is to be the only one in the resuling DLL. Do not
|
|
use it when packaging multiple modules into one DLL: use
|
|
CMPP_MODULE_IMPL for those cases (CMPP_MODULE_IMPL can also be used
|
|
together with this macro).
|
|
|
|
@see CMPP_MODULE_IMPL
|
|
@see CMPP_MODULE_DECL
|
|
@see CMPP_MODULE_STANDALONE_IMPL
|
|
*/
|
|
#define CMPP_MODULE_IMPL_SOLO(NAME,INIT_F) \
|
|
static const cmpp_module \
|
|
cmpp_module1_impl = { NAME, INIT_F }; \
|
|
const cmpp_module * cmpp_module1 = &cmpp_module1_impl
|
|
/** @def CMPP_MODULE_STANDALONE_IMPL
|
|
|
|
CMPP_MODULE_STANDALONE_IMPL2() works like CMPP_MODULE_IMPL_SOLO()
|
|
but is only fully expanded if the preprocessor variable
|
|
CMPP_MODULE_STANDALONE is defined (to any value). If
|
|
CMPP_MODULE_STANDALONE is not defined, this macro expands to a
|
|
dummy placeholder which does nothing (but has to expand to
|
|
something to avoid leaving a trailing semicolon in the C code,
|
|
which upsets the compiler (the other alternative would be to not
|
|
require a semicolon after the macro call, but that upsets emacs'
|
|
sense of indentation (and keeping emacs happy is more important
|
|
than keeping compilers happy (all of these parens are _not_ a
|
|
reference to emacs lisp, by the way)))).
|
|
|
|
This macro may be used in the same source file as
|
|
CMPP_MODULE_IMPL.
|
|
|
|
The intention is that DLLs prefer this option over
|
|
CMPP_MODULE_IMPL_SOLO, to allow that the DLLs can be built as
|
|
standalone DLLs, multi-plugin DLLs, and compiled directly into a
|
|
project (in which case the code linking it in needs to resolve and
|
|
call the cmpp_module entry for each built-in module).
|
|
|
|
@see CMPP_MODULE_IMPL_SOLO
|
|
@see CMPP_MODULE_REGISTER
|
|
*/
|
|
#if defined(CMPP_MODULE_STANDALONE)
|
|
# define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \
|
|
CMPP_MODULE_IMPL_SOLO(NAME,INIT_F)
|
|
//arguably too much magic in one place:
|
|
//# if !defined(CMPP_API_THUNK)
|
|
//# define CMPP_API_THUNK
|
|
//# endif
|
|
#else
|
|
# define CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F) \
|
|
extern void cmpp_module__dummy_does_not_exist__(void)
|
|
#endif
|
|
|
|
/** @def CMPP_MODULE_REGISTER3
|
|
|
|
Performs all the necessary setup for registering a loadable module,
|
|
including declaration and definition. NAME is the stringified name
|
|
of the module. This is normally called immediately after defining
|
|
the plugin's init func (which is passed as the 3rd argument to this
|
|
macro).
|
|
|
|
See CMPP_MODULE_IMPL3() and CMPP_MODULE_STANDALONE_IMPL2() for
|
|
the fine details.
|
|
*/
|
|
#define CMPP_MODULE_REGISTER3(CNAME,NAME,INIT_F) \
|
|
CMPP_MODULE_IMPL3(CNAME,NAME,INIT_F); \
|
|
CMPP_MODULE_STANDALONE_IMPL2(NAME,INIT_F)
|
|
|
|
/**
|
|
Slight convenience form of CMPP_MODULE_REGISTER3() which assumes a
|
|
registration function name of cpp_ext_${CNAME}_register().
|
|
*/
|
|
#define CMPP_MODULE_REGISTER2(CNAME,NAME) \
|
|
CMPP_MODULE_REGISTER3(CNAME,NAME,cmpp_module__ ## CNAME ## _register)
|
|
|
|
/**
|
|
Slight convenience form of CMPP_MODULE_REGISTER2() for cases when
|
|
CNAME and NAME are the same.
|
|
*/
|
|
#define CMPP_MODULE_REGISTER1(CNAME) \
|
|
CMPP_MODULE_REGISTER3(CNAME,#CNAME,cmpp_module__ ## CNAME ## _register)
|
|
|
|
/**
|
|
This looks for a DLL file named fname. If found, it is dlopen()ed
|
|
(or equivalent) and searched for a symbol named symName. If found,
|
|
it is assumed to be a cmpp_module instance and its init() method is
|
|
invoked.
|
|
|
|
If fname is NULL then the module is looked up in the
|
|
currently-running program.
|
|
|
|
If symName is NULL then the name "cmpp_module1" is assumed, which
|
|
is the name used by CMPP_MODULE_IMPL_SOLO() and friends (for use
|
|
when a module is the only one in its DLL).
|
|
|
|
If no match is found, or there's a problem loading the DLL or
|
|
resolving the name, non-0 is returned. Similarly, if the init()
|
|
method fails, non-0 is returned.
|
|
|
|
The file name is searched using the cmpp_module_dir_add() path, and
|
|
if fname is an exact match, or an exact when the system's
|
|
conventional DLL file extension is appended to it, that is used
|
|
rather than any potential match from the search path.
|
|
|
|
On error, pp's error state will contain more information. It's
|
|
indeterminate which errors from this API are recoverable.
|
|
|
|
This function is a no-op if called when pp's error state is set,
|
|
returning that code.
|
|
|
|
If built without module-loading support then this will always
|
|
fail with CMPP_RC_UNSUPPORTED.
|
|
*/
|
|
CMPP_EXPORT int cmpp_module_load(cmpp * pp, char const * fname,
|
|
char const * symName);
|
|
|
|
/**
|
|
Adds the directory or directories listed in zDirs to the search
|
|
path used by cmpp_module_load(). The entries are expected to be
|
|
either colon- or semicolon-delimited, depending on the platform the
|
|
library was built for.
|
|
|
|
If zDirs is NULL and pp's library path is empty then it looks for
|
|
the environment variable CMPP_MODULE_PATH. If that is set, it is
|
|
used in place of zDirs, otherwise the library's compile-time
|
|
default is used (as set by the CMPP_MODULE_PATH compile-time value,
|
|
which defaults to ".:$prefix/lib/cmpp" in the canonical builds).
|
|
This should only be done once per cmpp instance, as the path will
|
|
otherwise be extended each time. (The current list structure does
|
|
not make it easy to recognize duplicates.)
|
|
|
|
Returns 0 on success or if zDirs is empty. Returns CMPP_RC_OOM on
|
|
allocation error (ostensibly recoverable - see cmpp_err_set()).
|
|
|
|
This is a no-op if called when pp has error state, returning that
|
|
code without other side-effects.
|
|
|
|
If modules are not enabled then this function is a no-op and always
|
|
returns CMPP_RC_UNSUPPORTED _without_ setting pp's error state (as
|
|
it's not an error, per se). That can typically be ignored as a
|
|
non-error.
|
|
*/
|
|
CMPP_EXPORT int cmpp_module_dir_add(cmpp *pp, const char * zDirs);
|
|
|
|
|
|
/**
|
|
State for a cmpp_dx_pimpl which we need in order to snapshot the
|
|
parse position for purposes of restoring it later. This is
|
|
basically to support that #query can contain other #query
|
|
directives, but this same capability is required by any directives
|
|
which want to both process directives in their content block and
|
|
loop over the content block.
|
|
*/
|
|
struct cmpp_dx_pos {
|
|
/** Current parse pos. */
|
|
unsigned char const *z;
|
|
/** Current line number. */
|
|
cmpp_size_t lineNo;
|
|
};
|
|
typedef struct cmpp_dx_pos cmpp_dx_pos;
|
|
#define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}//,.dline=CmppDLine_empty_m}
|
|
|
|
/**
|
|
Stores dx's current input position into pos. pos gets completely
|
|
initialized by this routine - it need not (in contrast to many
|
|
other functions in this library) be cleanly initialized by the
|
|
caller first.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos);
|
|
|
|
/**
|
|
Restores dx's input position from pos. Results are undefined if pos
|
|
is not populated with the result of having passed the same dx/pos
|
|
pointer combination to cmpp_dx_pos_save().
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos);
|
|
|
|
/**
|
|
A "thunk" for use with loadable modules, encapsulating all of the
|
|
functions from the public cmpp API into an object. This allows
|
|
loadable modules to call into the cmpp API if the binary which
|
|
loads them not built in such a way that it exports libcmpp's
|
|
symbols to the DLL. (On Linux systems, that means if it's not
|
|
linked with -rdynamic.)
|
|
|
|
For every public cmpp function, this struct has a member with the
|
|
same signature and name, minus the "cmpp_" name prefix. Thus
|
|
cmpp_foo(...) is accessible as api->foo(...).
|
|
|
|
Object-type exports, e.g. cmpp_b_empty, are exposed here as
|
|
pointers instead of objects. The CMPP_API_THUNK-installed API
|
|
wrapper macros account for that.
|
|
|
|
There is only one instance of this class and it gets passed into
|
|
cmpp_module_init_f() methods. It is also assigned to the
|
|
cmpp_dx::api member of cmpp_dx instances which get passed to
|
|
cmpp_dx_f() implementations.
|
|
|
|
Loadable modules "should" use this interface to access the API,
|
|
rather than the global symbols. If they don't then the module may,
|
|
depending on how the loading application was linked, throw
|
|
unresolved symbols errors when loading.
|
|
*/
|
|
struct cmpp_api_thunk {
|
|
#define A(VER)
|
|
#define V(N,T,VER) T N;
|
|
#define F(N,T,P) T (*N)P;
|
|
#define O(N,T) T * const N;
|
|
cmpp_api_thunk_map(A,V,F,O)
|
|
#undef F
|
|
#undef O
|
|
#undef V
|
|
#undef A
|
|
};
|
|
|
|
/**
|
|
For loadable modules to be able portably access the cmpp API,
|
|
without requiring that their loading binary be linked with
|
|
-rdynamic, we need a "thunk". The API exposes cmpp_api_thunk
|
|
for that purpose. The following macros set up the thunk for
|
|
a given compilation unit. They are intended to only be used
|
|
by loadable modules, not generic client code.
|
|
|
|
Before including this header, define CMPP_API_THUNK with no value
|
|
and/or define CMPP_API_THUNK_NAME to a C symbol name. The latter
|
|
macro implies the former and defines the name of the static symbol
|
|
to be the local cmpp_api_thunk instance, defaulting to cmppApi.
|
|
|
|
The first line of a module's registration function should then be:
|
|
|
|
cmpp_api_init(pp);
|
|
|
|
where pp is the name of the sole argument to the registration
|
|
callback. After that is done, the cmpp_...() APIs may be used via
|
|
the macros defined below, all of which route through the thunk
|
|
object.
|
|
*/
|
|
#if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME)
|
|
# if !defined(CMPP_API_THUNK)
|
|
# define CMPP_API_THUNK
|
|
# endif
|
|
# if !defined(CMPP_API_THUNK_NAME)
|
|
# define CMPP_API_THUNK_NAME cmppApi
|
|
# endif
|
|
# if !defined(CMPP_API_THUNK__defined)
|
|
# define CMPP_API_THUNK__defined
|
|
static cmpp_api_thunk const * CMPP_API_THUNK_NAME = 0;
|
|
# endif
|
|
/**
|
|
cmpp_api_init() must be invoked from the module's registration
|
|
function, passed the only argument to that function. It sets the
|
|
global symbol CMPP_API_THUNK_NAME to its argument. From that point
|
|
on, the thunk's API is accessible via cmpp_foo macros which proxy
|
|
theThunk->foo.
|
|
|
|
It is safe to call this from, e.g. a cmpp_dx_f() implementation, as
|
|
it will always have the same pointer, so long as it is not passed
|
|
NULL, which would make the next cmpp_...() call segfault.
|
|
*/
|
|
# if !defined(CMPP_API_THUNK__assigned)
|
|
# define CMPP_API_THUNK__assigned
|
|
# define cmpp_api_init(PP) CMPP_API_THUNK_NAME = (PP)->api
|
|
# else
|
|
# define cmpp_api_init(PP) (void)(PP)/*CMPP_API_THUNK_NAME*/
|
|
# endif
|
|
/* What follows is generated code from c-pp's (#pragma api-thunk). */
|
|
/* Thunk APIs which follow are available as of version 0... */
|
|
#define cmpp_mrealloc CMPP_API_THUNK_NAME->mrealloc
|
|
#define cmpp_malloc CMPP_API_THUNK_NAME->malloc
|
|
#define cmpp_mfree CMPP_API_THUNK_NAME->mfree
|
|
#define cmpp_ctor CMPP_API_THUNK_NAME->ctor
|
|
#define cmpp_dtor CMPP_API_THUNK_NAME->dtor
|
|
#define cmpp_reset CMPP_API_THUNK_NAME->reset
|
|
#define cmpp_check_oom CMPP_API_THUNK_NAME->check_oom
|
|
#define cmpp_is_legal_key CMPP_API_THUNK_NAME->is_legal_key
|
|
#define cmpp_define_legacy CMPP_API_THUNK_NAME->define_legacy
|
|
#define cmpp_define_v2 CMPP_API_THUNK_NAME->define_v2
|
|
#define cmpp_undef CMPP_API_THUNK_NAME->undef
|
|
#define cmpp_define_shadow CMPP_API_THUNK_NAME->define_shadow
|
|
#define cmpp_define_unshadow CMPP_API_THUNK_NAME->define_unshadow
|
|
#define cmpp_process_string CMPP_API_THUNK_NAME->process_string
|
|
#define cmpp_process_file CMPP_API_THUNK_NAME->process_file
|
|
#define cmpp_process_stream CMPP_API_THUNK_NAME->process_stream
|
|
#define cmpp_process_argv CMPP_API_THUNK_NAME->process_argv
|
|
#define cmpp_err_get CMPP_API_THUNK_NAME->err_get
|
|
#define cmpp_err_set CMPP_API_THUNK_NAME->err_set
|
|
#define cmpp_err_set1 CMPP_API_THUNK_NAME->err_set1
|
|
#define cmpp_err_has CMPP_API_THUNK_NAME->err_has
|
|
#define cmpp_is_safemode CMPP_API_THUNK_NAME->is_safemode
|
|
#define cmpp_sp_begin CMPP_API_THUNK_NAME->sp_begin
|
|
#define cmpp_sp_commit CMPP_API_THUNK_NAME->sp_commit
|
|
#define cmpp_sp_rollback CMPP_API_THUNK_NAME->sp_rollback
|
|
#define cmpp_output_f_FILE CMPP_API_THUNK_NAME->output_f_FILE
|
|
#define cmpp_output_f_fd CMPP_API_THUNK_NAME->output_f_fd
|
|
#define cmpp_input_f_FILE CMPP_API_THUNK_NAME->input_f_FILE
|
|
#define cmpp_input_f_fd CMPP_API_THUNK_NAME->input_f_fd
|
|
#define cmpp_flush_f_FILE CMPP_API_THUNK_NAME->flush_f_FILE
|
|
#define cmpp_stream CMPP_API_THUNK_NAME->stream
|
|
#define cmpp_slurp CMPP_API_THUNK_NAME->slurp
|
|
#define cmpp_fopen CMPP_API_THUNK_NAME->fopen
|
|
#define cmpp_fclose CMPP_API_THUNK_NAME->fclose
|
|
#define cmpp_outputer_out CMPP_API_THUNK_NAME->outputer_out
|
|
#define cmpp_outputer_flush CMPP_API_THUNK_NAME->outputer_flush
|
|
#define cmpp_outputer_cleanup CMPP_API_THUNK_NAME->outputer_cleanup
|
|
#define cmpp_outputer_cleanup_f_FILE CMPP_API_THUNK_NAME->outputer_cleanup_f_FILE
|
|
#define cmpp_delimiter_set CMPP_API_THUNK_NAME->delimiter_set
|
|
#define cmpp_delimiter_get CMPP_API_THUNK_NAME->delimiter_get
|
|
#define cmpp_chomp CMPP_API_THUNK_NAME->chomp
|
|
#define cmpp_b_clear CMPP_API_THUNK_NAME->b_clear
|
|
#define cmpp_b_reuse CMPP_API_THUNK_NAME->b_reuse
|
|
#define cmpp_b_swap CMPP_API_THUNK_NAME->b_swap
|
|
#define cmpp_b_reserve CMPP_API_THUNK_NAME->b_reserve
|
|
#define cmpp_b_reserve3 CMPP_API_THUNK_NAME->b_reserve3
|
|
#define cmpp_b_append CMPP_API_THUNK_NAME->b_append
|
|
#define cmpp_b_append4 CMPP_API_THUNK_NAME->b_append4
|
|
#define cmpp_b_append_ch CMPP_API_THUNK_NAME->b_append_ch
|
|
#define cmpp_b_append_i32 CMPP_API_THUNK_NAME->b_append_i32
|
|
#define cmpp_b_append_i64 CMPP_API_THUNK_NAME->b_append_i64
|
|
#define cmpp_b_chomp CMPP_API_THUNK_NAME->b_chomp
|
|
#define cmpp_output_f_b CMPP_API_THUNK_NAME->output_f_b
|
|
#define cmpp_outputer_cleanup_f_b CMPP_API_THUNK_NAME->outputer_cleanup_f_b
|
|
#define cmpp_version CMPP_API_THUNK_NAME->version
|
|
#define cmpp_tt_cstr CMPP_API_THUNK_NAME->tt_cstr
|
|
#define cmpp_dx_err_set CMPP_API_THUNK_NAME->dx_err_set
|
|
#define cmpp_dx_next CMPP_API_THUNK_NAME->dx_next
|
|
#define cmpp_dx_process CMPP_API_THUNK_NAME->dx_process
|
|
#define cmpp_dx_consume CMPP_API_THUNK_NAME->dx_consume
|
|
#define cmpp_dx_consume_b CMPP_API_THUNK_NAME->dx_consume_b
|
|
#define cmpp_arg_parse CMPP_API_THUNK_NAME->arg_parse
|
|
#define cmpp_arg_strdup CMPP_API_THUNK_NAME->arg_strdup
|
|
#define cmpp_arg_to_b CMPP_API_THUNK_NAME->arg_to_b
|
|
#define cmpp_errno_rc CMPP_API_THUNK_NAME->errno_rc
|
|
#define cmpp_d_register CMPP_API_THUNK_NAME->d_register
|
|
#define cmpp_dx_f_dangling_closer CMPP_API_THUNK_NAME->dx_f_dangling_closer
|
|
#define cmpp_dx_out_raw CMPP_API_THUNK_NAME->dx_out_raw
|
|
#define cmpp_dx_out_expand CMPP_API_THUNK_NAME->dx_out_expand
|
|
#define cmpp_dx_outf CMPP_API_THUNK_NAME->dx_outf
|
|
#define cmpp_dx_delim CMPP_API_THUNK_NAME->dx_delim
|
|
#define cmpp_atpol_from_str CMPP_API_THUNK_NAME->atpol_from_str
|
|
#define cmpp_atpol_get CMPP_API_THUNK_NAME->atpol_get
|
|
#define cmpp_atpol_set CMPP_API_THUNK_NAME->atpol_set
|
|
#define cmpp_atpol_push CMPP_API_THUNK_NAME->atpol_push
|
|
#define cmpp_atpol_pop CMPP_API_THUNK_NAME->atpol_pop
|
|
#define cmpp_unpol_from_str CMPP_API_THUNK_NAME->unpol_from_str
|
|
#define cmpp_unpol_get CMPP_API_THUNK_NAME->unpol_get
|
|
#define cmpp_unpol_set CMPP_API_THUNK_NAME->unpol_set
|
|
#define cmpp_unpol_push CMPP_API_THUNK_NAME->unpol_push
|
|
#define cmpp_unpol_pop CMPP_API_THUNK_NAME->unpol_pop
|
|
#define cmpp_path_search CMPP_API_THUNK_NAME->path_search
|
|
#define cmpp_args_parse CMPP_API_THUNK_NAME->args_parse
|
|
#define cmpp_args_cleanup CMPP_API_THUNK_NAME->args_cleanup
|
|
#define cmpp_dx_args_clone CMPP_API_THUNK_NAME->dx_args_clone
|
|
#define cmpp_popen CMPP_API_THUNK_NAME->popen
|
|
#define cmpp_popenv CMPP_API_THUNK_NAME->popenv
|
|
#define cmpp_pclose CMPP_API_THUNK_NAME->pclose
|
|
#define cmpp_popen_args CMPP_API_THUNK_NAME->popen_args
|
|
#define cmpp_kav_each CMPP_API_THUNK_NAME->kav_each
|
|
#define cmpp_d_autoloader_set CMPP_API_THUNK_NAME->d_autoloader_set
|
|
#define cmpp_d_autoloader_take CMPP_API_THUNK_NAME->d_autoloader_take
|
|
#define cmpp_isspace CMPP_API_THUNK_NAME->isspace
|
|
#define cmpp_isnl CMPP_API_THUNK_NAME->isnl
|
|
#define cmpp_issnl CMPP_API_THUNK_NAME->issnl
|
|
#define cmpp_skip_space CMPP_API_THUNK_NAME->skip_space
|
|
#define cmpp_skip_snl CMPP_API_THUNK_NAME->skip_snl
|
|
#define cmpp_skip_space_trailing CMPP_API_THUNK_NAME->skip_space_trailing
|
|
#define cmpp_skip_snl_trailing CMPP_API_THUNK_NAME->skip_snl_trailing
|
|
#define cmpp_array_reserve CMPP_API_THUNK_NAME->array_reserve
|
|
#define cmpp_module_load CMPP_API_THUNK_NAME->module_load
|
|
#define cmpp_module_dir_add CMPP_API_THUNK_NAME->module_dir_add
|
|
#define cmpp_outputer_FILE (*CMPP_API_THUNK_NAME->outputer_FILE)
|
|
#define cmpp_outputer_b (*CMPP_API_THUNK_NAME->outputer_b)
|
|
#define cmpp_outputer_empty (*CMPP_API_THUNK_NAME->outputer_empty)
|
|
#define cmpp_b_empty (*CMPP_API_THUNK_NAME->b_empty)
|
|
/* Thunk APIs which follow are available as of version 20251116... */
|
|
#define cmpp_next_chunk CMPP_API_THUNK_NAME->next_chunk
|
|
/* Thunk APIs which follow are available as of version 20251118... */
|
|
#define cmpp_atdelim_get CMPP_API_THUNK_NAME->atdelim_get
|
|
#define cmpp_atdelim_set CMPP_API_THUNK_NAME->atdelim_set
|
|
#define cmpp_atdelim_push CMPP_API_THUNK_NAME->atdelim_push
|
|
#define cmpp_atdelim_pop CMPP_API_THUNK_NAME->atdelim_pop
|
|
/* Thunk APIs which follow are available as of version 20251224... */
|
|
#define cmpp_dx_pos_save CMPP_API_THUNK_NAME->dx_pos_save
|
|
#define cmpp_dx_pos_restore CMPP_API_THUNK_NAME->dx_pos_restore
|
|
/* Thunk APIs which follow are available as of version 20260130... */
|
|
#define cmpp_dx_is_call CMPP_API_THUNK_NAME->dx_is_call
|
|
/* Thunk APIs which follow are available as of version 20260206... */
|
|
#define cmpp_b_borrow CMPP_API_THUNK_NAME->b_borrow
|
|
#define cmpp_b_return CMPP_API_THUNK_NAME->b_return
|
|
|
|
|
|
#else /* not CMPP_API_THUNK */
|
|
/**
|
|
cmpp_api_init() is a no-op when not including a file-local API
|
|
thunk.
|
|
*/
|
|
# define cmpp_api_init(PP) (void)0
|
|
#endif /* CMPP_API_THUNK */
|
|
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif
|
|
#endif /* include guard */
|
|
#endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */
|
|
#if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED)
|
|
#define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED
|
|
/**
|
|
This file houses declarations and macros for the private/internal
|
|
libcmpp APIs.
|
|
*/
|
|
#include "sqlite3.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
/* write() and friends */
|
|
#if defined(_WIN32) || defined(WIN32)
|
|
# include <io.h>
|
|
# include <fcntl.h>
|
|
# ifndef access
|
|
# define access(f,m) _access((f),(m))
|
|
# endif
|
|
#else
|
|
# include <unistd.h>
|
|
# include <sys/wait.h>
|
|
#endif
|
|
|
|
#ifndef CMPP_DEFAULT_DELIM
|
|
#define CMPP_DEFAULT_DELIM "##"
|
|
#endif
|
|
|
|
#ifndef CMPP_ATSIGN
|
|
#define CMPP_ATSIGN (unsigned char)'@'
|
|
#endif
|
|
|
|
#ifndef CMPP_MODULE_PATH
|
|
#define CMPP_MODULE_PATH "."
|
|
#endif
|
|
|
|
#if defined(NDEBUG)
|
|
#define cmpp__staticAssert(NAME,COND) (void)1
|
|
#else
|
|
#define cmpp__staticAssert(NAME,COND) \
|
|
static const char staticAssert_ ## NAME[ (COND) ? 1 : -1 ] = {0}; \
|
|
(void)staticAssert_ ## NAME
|
|
#endif
|
|
|
|
#if defined(CMPP_OMIT_ALL_UNSAFE)
|
|
#undef CMPP_OMIT_D_PIPE
|
|
#define CMPP_OMIT_D_PIPE
|
|
#undef CMPP_OMIT_D_DB
|
|
#define CMPP_OMIT_D_DB
|
|
#undef CMPP_OMIT_D_INCLUDE
|
|
#define CMPP_OMIT_D_INCLUDE
|
|
#undef CMPP_OMIT_D_MODULE
|
|
#define CMPP_OMIT_D_MODULE
|
|
#endif
|
|
|
|
#if !defined(CMPP_VERSION)
|
|
#error "exporting CMPP_VERSION to have been set up"
|
|
#endif
|
|
|
|
#define CMPP__DB_MAIN_NAME "cmpp"
|
|
|
|
#if defined(CMPP_AMALGAMATION)
|
|
#define CMPP_PRIVATE static
|
|
#else
|
|
#define CMPP_PRIVATE
|
|
#endif
|
|
|
|
#if CMPP_PLATFORM_IS_WASM
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "wasm"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# define CMPP__EXPORT_NAMED(X) __attribute__((export_name(#X),used,visibility("default")))
|
|
// See also:
|
|
//__attribute__((export_name("theExportedName"), used, visibility("default")))
|
|
# define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */
|
|
# if !defined(CMPP_PLATFORM_EXT_DLL)
|
|
# define CMPP_PLATFORM_EXT_DLL ""
|
|
# endif
|
|
#else
|
|
//# define CMPP_WASM_EXPORT
|
|
# define CMPP__EXPORT_NAMED(X)
|
|
# if defined(_WIN32) || defined(WIN32)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 1
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "windows"
|
|
# define CMPP_PATH_SEPARATOR ';'
|
|
//# include <io.h>
|
|
# elif defined(__MINGW32__) || defined(__MINGW64__)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 1
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "windows"
|
|
# define CMPP_PATH_SEPARATOR ':' /*?*/
|
|
# elif defined(__CYGWIN__)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 1
|
|
# define CMPP_PLATFORM_PLATFORM "unix"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# else
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 1
|
|
# define CMPP_PLATFORM_PLATFORM "unix"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# endif
|
|
#endif
|
|
|
|
#define CMPP__EXPORT(RETTYPE,NAME) CMPP__EXPORT_NAMED(NAME) RETTYPE NAME
|
|
|
|
#if !defined(CMPP_PLATFORM_EXT_DLL)
|
|
# error "Expecting CMPP_PLATFORM_EXT_DLL to have been set by the auto-configured bits"
|
|
# define CMPP_PLATFORM_EXT_DLL "???"
|
|
#endif
|
|
|
|
#if 1
|
|
# define CMPP_NORETURN __attribute__((noreturn))
|
|
#else
|
|
# define CMPP_NORETURN
|
|
#endif
|
|
|
|
/** @def CMPP_HAVE_DLOPEN
|
|
|
|
If set to true, use dlopen() and friends. Requires
|
|
linking to -ldl on some platforms.
|
|
|
|
Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be
|
|
true.
|
|
*/
|
|
/** @def CMPP_HAVE_LTDLOPEN
|
|
|
|
If set to true, use lt_dlopen() and friends. Requires
|
|
linking to -lltdl on most platforms.
|
|
|
|
Only one of CMPP_HAVE_DLOPEN and CMPP_HAVE_LTDLOPEN may be
|
|
true.
|
|
*/
|
|
#if !defined(CMPP_HAVE_DLOPEN)
|
|
# if defined(HAVE_DLOPEN)
|
|
# define CMPP_HAVE_DLOPEN HAVE_DLOPEN
|
|
# else
|
|
# define CMPP_HAVE_DLOPEN 0
|
|
# endif
|
|
#endif
|
|
|
|
#if !defined(CMPP_HAVE_LTDLOPEN)
|
|
# if defined(HAVE_LTDLOPEN)
|
|
# define CMPP_HAVE_LTDLOPEN HAVE_LTDLOPEN
|
|
# else
|
|
# define CMPP_HAVE_LTDLOPEN 0
|
|
# endif
|
|
#endif
|
|
|
|
#if !defined(CMPP_ENABLE_DLLS)
|
|
# define CMPP_ENABLE_DLLS (CMPP_HAVE_LTDLOPEN || CMPP_HAVE_DLOPEN)
|
|
#endif
|
|
#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
|
|
# define CMPP_D_MODULE 1
|
|
#else
|
|
# define CMPP_D_MODULE 0
|
|
#endif
|
|
|
|
/**
|
|
Many years of practice have taught that it is literally impossible
|
|
to safely close DLLs because simply opening one may trigger
|
|
arbitrary code (at least for C++ DLLs) which "might" be used by the
|
|
application. e.g. some classloaders use DLL initialization to inject
|
|
new classes into the application without the app having to do
|
|
anything more than open the DLL. (That's precisely what the cmpp
|
|
port of this code is doing except that we don't call it classloading
|
|
here.)
|
|
|
|
So cmpp does not close DLLs. Except (...sigh...) to please valgrind.
|
|
|
|
When CMPP_CLOSE_DLLS is true then this API will keep track of DLL
|
|
handles so that they can be closed, and offers the ability for
|
|
higher-level clients to close them (all at once, not individually).
|
|
*/
|
|
#if !defined(CMPP_CLOSE_DLLS)
|
|
#define CMPP_CLOSE_DLLS 1
|
|
#endif
|
|
|
|
/** Proxy for cmpp_malloc() which (A) is a no-op if ppCode
|
|
and (B) sets pp->err on OOM.
|
|
*/
|
|
CMPP_PRIVATE void * cmpp__malloc(cmpp* pp, cmpp_size_t n);
|
|
|
|
/**
|
|
Internal-use-only flags for use with cmpp_d::flags.
|
|
|
|
These supplement the ones from the public API's cmpp_d_e.
|
|
*/
|
|
enum cmpp_d_ext_e {
|
|
/**
|
|
Mask of flag bits from this enum and cmpp_d_e which are for
|
|
internal use only and are disallowed in client-side directives.
|
|
*/
|
|
cmpp_d_F_MASK_INTERNAL = ~cmpp_d_F_MASK,
|
|
|
|
/**
|
|
If true, and if cmpp_d_F_ARGS_LIST is set, then cmpp_args_parse()
|
|
will pass its results to cmpp_args__not_simplify(). Only
|
|
directives which eval cmpp_arg expressions need this, and the
|
|
library does not expose the pieces for evaluating such
|
|
expressions. As such, this flag is for internal use only. This
|
|
only has an effect if cmpp_d_F_ARGS_LIST is also used.
|
|
*/
|
|
cmpp_d_F_NOT_SIMPLIFY = 0x10000,
|
|
|
|
/**
|
|
Most directives are inert when they are seen in the "falsy" part
|
|
of an if/else. The callbacks for such directives are skipped, as
|
|
opposed to requiring each directive's callback to check whether
|
|
they should be skipping. This flag indicates that a directive
|
|
must always be run, even when skipping content (e.g. inside of an
|
|
#if 0 block). Only flow-control directives may have the
|
|
FLOW_CONTROL bit set. The library API does not expose enough of
|
|
its internals for client-defined directives to make flow-control
|
|
decisions.
|
|
|
|
i really want to get rid of this flag but it seems to be a
|
|
necessary evil.
|
|
*/
|
|
cmpp_d_F_FLOW_CONTROL = 0x20000
|
|
};
|
|
|
|
/**
|
|
A single directive line from an input stream.
|
|
*/
|
|
struct CmppDLine {
|
|
/** Line number in the source input. */
|
|
cmpp_size_t lineNo;
|
|
/** Start of the line within its source input. */
|
|
unsigned char const * zBegin;
|
|
/** One-past-the-end byte of the line. A virtual EOF. It will only
|
|
actually be NUL-terminated if it is the last line of the input
|
|
and that input has no trailing newline. */
|
|
unsigned char const * zEnd;
|
|
};
|
|
typedef struct CmppDLine CmppDLine;
|
|
#define CmppDLine_empty_m {0U,0,0}
|
|
|
|
/**
|
|
A snippet from a string.
|
|
*/
|
|
struct CmppSnippet {
|
|
/* Start of the range. */
|
|
unsigned char const *z;
|
|
/* Number of bytes. */
|
|
unsigned int n;
|
|
};
|
|
typedef struct CmppSnippet CmppSnippet;
|
|
#define CmppSnippet_empty_m {0,0}
|
|
|
|
/**
|
|
CmppLvl represents one "level" of parsing, pushing one level
|
|
for each of `#if` and popping one for each `#/if`.
|
|
|
|
These pieces are ONLY for use with flow-control directives. It's
|
|
not proven that they can be of any use to more than a single
|
|
flow-control directive. e.g. if we had a hypothetical #foreach, we
|
|
may need to extend this.
|
|
*/
|
|
struct CmppLvl {
|
|
#if 0
|
|
/**
|
|
The directive on whose behalf this level was opened.
|
|
*/
|
|
cmpp_d const * d;
|
|
/**
|
|
Opaque directive-specific immutable state. It's provided as a way
|
|
for a directive to see whether the top of the stack is correct
|
|
after it processes inner directives.
|
|
*/
|
|
void const * state;
|
|
#endif
|
|
/**
|
|
Bitmask of CmppLvl_F_...
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
/**
|
|
The directive line number which started this level. This is used for
|
|
reporting the starting lines of erroneously unclosed block
|
|
constructs.
|
|
*/
|
|
cmpp_size_t lineNo;
|
|
};
|
|
typedef struct CmppLvl CmppLvl;
|
|
#define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U}
|
|
|
|
/**
|
|
Declares struct T as a container for a list-of-MT. MT may be
|
|
pointer-qualified. cmpp__ListType_impl() with the same arguments
|
|
implements T_reserve() for basic list allocation. Cleanup, alas, is
|
|
MT-dependent.
|
|
*/
|
|
#define cmpp__ListType_decl(T,MT) \
|
|
struct T { \
|
|
MT * list; \
|
|
cmpp_size_t n; \
|
|
cmpp_size_t nAlloc; \
|
|
}; \
|
|
typedef struct T T; \
|
|
int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min)
|
|
#define CMPP__MAX(X,Y) ((X)<=(Y) ? (X) : (Y))
|
|
#define cmpp__ListType_impl(T,MT) \
|
|
int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \
|
|
return cmpp_array_reserve(pp, (void**)&li->list, min, \
|
|
&li->nAlloc, sizeof(MT)); \
|
|
}
|
|
|
|
#define cmpp__LIST_T_empty_m {.list=0,.n=0,.nAlloc=0}
|
|
|
|
/**
|
|
A dynamically-allocated list of CmppLvl objects.
|
|
*/
|
|
cmpp__ListType_decl(CmppLvlList,CmppLvl*);
|
|
#define CmppLvlList_empty_m cmpp__LIST_T_empty_m
|
|
CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx);
|
|
CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx);
|
|
CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl);
|
|
CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on);
|
|
CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl);
|
|
CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx);
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_b objects.
|
|
*/
|
|
cmpp__ListType_decl(cmpp_b_list,cmpp_b*);
|
|
#define cmpp_b_list_empty_m cmpp__LIST_T_empty_m
|
|
extern const cmpp_b_list cmpp_b_list_empty;
|
|
CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li);
|
|
//CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li);
|
|
//CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li);
|
|
/**
|
|
cmpp_b_list sorting policies. NULL entries must
|
|
always sort last.
|
|
*/
|
|
enum cmpp_b_list_e {
|
|
cmpp_b_list_UNSORTED,
|
|
/* Smallest first. */
|
|
cmpp_b_list_ASC,
|
|
/* Largest first. */
|
|
cmpp_b_list_DESC
|
|
};
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_arg objects. Used by
|
|
cmmp_args.
|
|
*/
|
|
cmpp__ListType_decl(CmppArgList,cmpp_arg);
|
|
#define CmppArgList_empty_m cmpp__LIST_T_empty_m
|
|
|
|
/** Allocate a new arg, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. */
|
|
CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li);
|
|
|
|
/**
|
|
The internal part of the cmpp_args interface.
|
|
*/
|
|
struct cmpp_args_pimpl {
|
|
/**
|
|
We need(?) a (cmpp*) here for finalization/recycling purposes.
|
|
*/
|
|
cmpp *pp;
|
|
bool isCall;
|
|
/**
|
|
Next entry in the free-list.
|
|
*/
|
|
cmpp_args_pimpl * nextFree;
|
|
/** Version 3 of the real args memory. */
|
|
CmppArgList argli;
|
|
/**
|
|
cmpp_args_parse() copies each argument's bytes into here,
|
|
each one NUL-terminated.
|
|
*/
|
|
cmpp_b argOut;
|
|
};
|
|
#define cmpp_args_pimpl_empty_m { \
|
|
.pp = 0, \
|
|
.isCall = false, \
|
|
.nextFree = 0, \
|
|
.argli = CmppArgList_empty_m, \
|
|
.argOut = cmpp_b_empty_m \
|
|
}
|
|
extern const cmpp_args_pimpl cmpp_args_pimpl_empty;
|
|
void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p);
|
|
|
|
/**
|
|
The internal part of the cmpp_dx interface.
|
|
*/
|
|
struct cmpp_dx_pimpl {
|
|
/** Start of input. */
|
|
unsigned const char * zBegin;
|
|
/** One-after-the-end of input. */
|
|
unsigned const char * zEnd;
|
|
/**
|
|
Current input position. Generally speaking, only
|
|
cmpp_dx_delim_search() should update this, but it turns out that
|
|
the ability to rewind the input is necessary for looping
|
|
constructs, like #query, when they want to be able to include
|
|
other directives in their bodies.
|
|
*/
|
|
cmpp_dx_pos pos;
|
|
/**
|
|
Currently input line.
|
|
*/
|
|
CmppDLine dline;
|
|
/** Number of active #savepoints. */
|
|
unsigned nSavepoint;
|
|
/** Current directive's args. */
|
|
cmpp_args args;
|
|
/**
|
|
A stack of state used by #if and friends to inform the innards
|
|
that they must not generate output. This is largely historical
|
|
and could have been done differently had this code started as a
|
|
library instead of a monolithic app.
|
|
|
|
TODO is to figure out how best to move this state completely into
|
|
the #if handler, rather than fiddle with this all throughout the
|
|
processing. We could maybe move this stack into CmppIfState?
|
|
*/
|
|
CmppLvlList dxLvl;
|
|
struct {
|
|
/**
|
|
A copy of this->d's input line which gets translated
|
|
slightly from its native form for futher processing.
|
|
*/
|
|
cmpp_b line;
|
|
/**
|
|
Holds the semi-raw input line, stripped only of backslash-escaped
|
|
newlines and leading spaces. This is primarily for debug output
|
|
but also for custom arg parsing for some directives.
|
|
*/
|
|
cmpp_b argsRaw;
|
|
} buf;
|
|
|
|
/**
|
|
Record IDs for/from cmpp_[un]define_shadow().
|
|
*/
|
|
struct {
|
|
/** ID for __FILE__. */
|
|
int64_t sidFile;
|
|
/** Rowid for #include path entry. */
|
|
int64_t ridInclPath;
|
|
} shadow;
|
|
|
|
struct {
|
|
/**
|
|
Set when we're searching for directives so that we know whether
|
|
cmpp_out_expand() should count newlines.
|
|
*/
|
|
unsigned short countLines;
|
|
/**
|
|
True if the next directive is the start of a [call].
|
|
*/
|
|
bool nextIsCall;
|
|
} flags;
|
|
};
|
|
/**
|
|
Initializes or resets a. Returns non-0 on OOM.
|
|
*/
|
|
CMPP_PRIVATE int cmpp_args__init(cmpp *pp, cmpp_args *a);
|
|
|
|
/**
|
|
If a has state then it's recycled for reuse, else this zeroes out a
|
|
except for a->pimpl, which is retained (but may be NULL).
|
|
*/
|
|
CMPP_PRIVATE void cmpp_args_reuse(cmpp_args *a);
|
|
|
|
#define cmpp_dx_pimpl_empty_m { \
|
|
.zBegin=0, .zEnd=0, \
|
|
.pos=cmpp_dx_pos_empty_m, \
|
|
.dline=CmppDLine_empty_m, \
|
|
.nSavepoint=0, \
|
|
.args = cmpp_args_empty_m, \
|
|
.dxLvl = CmppLvlList_empty_m, \
|
|
.buf = { \
|
|
cmpp_b_empty_m, \
|
|
cmpp_b_empty_m \
|
|
}, \
|
|
.shadow = { \
|
|
.sidFile = 0, \
|
|
.ridInclPath = 0 \
|
|
}, \
|
|
.flags = { \
|
|
.countLines = 0, \
|
|
.nextIsCall = false \
|
|
} \
|
|
}
|
|
|
|
/**
|
|
A level of indirection for CmppDList in order to be able to
|
|
manage ownership of their name (string) lifetimes.
|
|
*/
|
|
struct CmppDList_entry {
|
|
/** this->d.name.z points to this, which is owned by the CmppDList
|
|
which manages this object. */
|
|
char * zName;
|
|
/* Potential TODO: move d->id into here. That doesn't eliminate our
|
|
dependency on it, though. */
|
|
cmpp_d d;
|
|
//cmpp_d_reg reg;
|
|
};
|
|
typedef struct CmppDList_entry CmppDList_entry;
|
|
#define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/}
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs.
|
|
*/
|
|
cmpp__ListType_decl(CmppDList,CmppDList_entry*);
|
|
#define CmppDList_empty_m cmpp__LIST_T_empty_m
|
|
|
|
/**
|
|
State for keeping track of DLL handles, a.k.a. shared-object
|
|
handles, a.k.a. "soh".
|
|
|
|
Instances of this must be either cleanly initialized by bitwise
|
|
copying CmppSohList_empty, memset() (or equivalent) them to 0, or
|
|
allocating them with CmppSohList_new().
|
|
*/
|
|
cmpp__ListType_decl(CmppSohList,void*);
|
|
#define CmppSohList_empty_m cmpp__LIST_T_empty_m
|
|
|
|
/**
|
|
Closes all handles which have been CmppSohList_append()ed to soli
|
|
and frees any memory it owns, but does not free soli (which might
|
|
be stack-allocated or part of another struct).
|
|
|
|
Special case: if built without DLL-closing support then this
|
|
is no-op.
|
|
*/
|
|
CMPP_PRIVATE void CmppSohList_close(CmppSohList *soli);
|
|
|
|
/**
|
|
Operators and operator policies for use with X=Y-format keys. This
|
|
is legacy stuff, actually, but some of the #define management still
|
|
needs it.
|
|
*/
|
|
#define CmppKvp_op_map(E) \
|
|
E(none,"") \
|
|
E(eq1,"=")
|
|
|
|
enum CmppKvp_op_e {
|
|
#define E(N,S) CmppKvp_op_ ## N,
|
|
CmppKvp_op_map(E)
|
|
#undef E
|
|
};
|
|
typedef enum CmppKvp_op_e CmppKvp_op_e;
|
|
|
|
/**
|
|
Result type for CmppKvp_parse().
|
|
*/
|
|
struct CmppKvp {
|
|
/* Key part of the kvp. */
|
|
CmppSnippet k;
|
|
/* Key part of the kvp. Might be empty. */
|
|
CmppSnippet v;
|
|
/* Operator part of kvp, if any. */
|
|
CmppKvp_op_e op;
|
|
};
|
|
typedef struct CmppKvp CmppKvp;
|
|
extern const CmppKvp CmppKvp_empty;
|
|
|
|
/**
|
|
Parses X or X=Y into p. Sets pp's error state on error.
|
|
|
|
If nKey is negative then strlen() is used to calculate it.
|
|
|
|
The third argument specifies whether/how to permit/treat the '='
|
|
part of X=Y.
|
|
*/
|
|
CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p,
|
|
unsigned char const *zKey,
|
|
cmpp_ssize_t nKey,
|
|
CmppKvp_op_e opPolicy);
|
|
|
|
|
|
/**
|
|
Stack of POD values. Intended for use with cmpp at-token and
|
|
undefined key policies.
|
|
*/
|
|
#define cmpp__PodList_decl(ST,ET) \
|
|
struct ST { \
|
|
/* current stack index */ \
|
|
cmpp_size_t n; \
|
|
cmpp_size_t na; \
|
|
ET * stack; \
|
|
}; typedef struct ST ST; \
|
|
void ST ## _wipe(ST * s, ET v); \
|
|
int ST ## _push(cmpp *pp, ST * s, ET v); \
|
|
void ST ## _set(ST * s, ET v); \
|
|
void ST ## _finalize(ST * s); \
|
|
void ST ## _pop(ST *s); \
|
|
int ST ## _reserve(cmpp *, ST *, cmpp_size_t min)
|
|
|
|
#define cmpp__PodList_impl(ST,ET) \
|
|
void ST ## _wipe(ST * const s, ET v){ \
|
|
if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na); \
|
|
s->n = 0; \
|
|
} \
|
|
int ST ## _reserve(cmpp * const pp, ST * const s, \
|
|
cmpp_size_t min){ \
|
|
return cmpp_array_reserve(pp, (void**)&s->stack, min>0 \
|
|
? min : (s->n \
|
|
? (s->n==s->na-1 \
|
|
? s->na*2 : s->n+1) \
|
|
: 8), \
|
|
&s->na, sizeof(ET)); \
|
|
} \
|
|
int ST ## _push(cmpp * const pp, ST * s, ET v){ \
|
|
if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v; \
|
|
return ppCode; \
|
|
} \
|
|
void ST ## _set(ST * s, ET v){ \
|
|
assert(s->n); \
|
|
if( 0== ST ## _reserve(NULL, s, 0) ){ \
|
|
s->stack[s->n] = v; \
|
|
} \
|
|
} \
|
|
ET ST ## _get(ST const * const s){ \
|
|
assert(s->na && s->na >=s->n); \
|
|
return s->stack[s->n]; \
|
|
} \
|
|
void ST ## _pop(ST *s){ \
|
|
assert(s->n); \
|
|
if(s->n) --s->n; \
|
|
} \
|
|
void ST ## _finalize(ST *s){ \
|
|
cmpp_mfree(s->stack); \
|
|
s->stack = NULL; \
|
|
s->n = s->na = 0; \
|
|
}
|
|
|
|
cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e);
|
|
cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e);
|
|
|
|
#define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH
|
|
#define cmpp__policy(PP,WHICH) \
|
|
cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n]
|
|
|
|
/**
|
|
A "delimiter" object. That is, the "#" referred to in the libcmpp
|
|
docs. It's also outfitted for a second delimiter so that it can be
|
|
used for the opening/closing delimiters of @tokens@.
|
|
*/
|
|
struct cmpp__delim {
|
|
/**
|
|
Bytes of the directive delimiter/prefix or the @token@ opening
|
|
delimiter. Owned elsewhere but often points at this->zOwns.
|
|
*/
|
|
CmppSnippet open;
|
|
/**
|
|
Closing @token@ delimiter. This has no meaning for the directive
|
|
delimiter.
|
|
*/
|
|
CmppSnippet close;
|
|
/**
|
|
Memory, owned by this object, for this->open and this->close. In
|
|
the latter case, it's one string with both delimiters encoded in
|
|
it.
|
|
*/
|
|
unsigned char * zOwns;
|
|
};
|
|
typedef struct cmpp__delim cmpp__delim;
|
|
#define cmpp__delim_empty_m { \
|
|
.open={ \
|
|
.z=(unsigned char*)CMPP_DEFAULT_DELIM, \
|
|
.n=sizeof(CMPP_DEFAULT_DELIM)-1 \
|
|
}, \
|
|
.close=CmppSnippet_empty_m, \
|
|
.zOwns=0 \
|
|
}
|
|
|
|
extern const cmpp__delim cmpp__delim_empty;
|
|
void cmpp__delim_cleanup(cmpp__delim *d);
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp__delim objects.
|
|
*/
|
|
cmpp__ListType_decl(cmpp__delim_list,cmpp__delim);
|
|
#define cmpp__delim_list_empty_m {0,0,0}
|
|
extern const cmpp__delim_list cmpp__delim_list_empty;
|
|
|
|
CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li);
|
|
static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){
|
|
return li->n ? li->list+(li->n-1) : NULL;
|
|
}
|
|
static inline void cmpp__delim_list_pop(cmpp__delim_list *li){
|
|
assert(li->n);
|
|
if( li->n ) cmpp__delim_cleanup(li->list + --li->n);
|
|
}
|
|
static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){
|
|
while( li->n ) cmpp__delim_cleanup(li->list + --li->n);
|
|
}
|
|
|
|
/**
|
|
An untested experiment: an output buffer proxy. Integrating this
|
|
fully would require some surgery, but it might also inspire me to
|
|
do the same with input and stream it rather than slurp it all at
|
|
once.
|
|
*/
|
|
#define CMPP__OBUF 0
|
|
|
|
typedef struct cmpp__obuf cmpp__obuf;
|
|
#if CMPP__OBUF
|
|
/**
|
|
An untested experiment.
|
|
*/
|
|
struct cmpp__obuf {
|
|
/** Start of the output buffer. */
|
|
unsigned char * begin;
|
|
/** One-after-the-end of this->begin. Virtual EOF. */
|
|
unsigned char const * end;
|
|
/** Current write position. Must initially be
|
|
this->begin. */
|
|
unsigned char * cursor;
|
|
/**
|
|
True if this object owns this->begin, which must have been
|
|
allocated using cmpp_malloc() or cmpp_realloc().
|
|
*/
|
|
bool ownsMemory;
|
|
/** Propagating result code. */
|
|
int rc;
|
|
/**
|
|
The output channel to buffer for. Flushing
|
|
*/
|
|
cmpp_outputer dest;
|
|
};
|
|
|
|
#define cmpp__obuf_empty_m { \
|
|
.begin=0, .end=0, .cursor=0, .ownsMemory=false, \
|
|
.rc=0, .dest=cmpp_outputer_empty_m \
|
|
}
|
|
extern const cmpp__obuf cmpp__obuf_empty;
|
|
extern const cmpp_outputer cmpp_outputer_obuf;
|
|
#endif /* CMPP__OBUF */
|
|
/**
|
|
The main public-API context type for this library.
|
|
*/
|
|
struct cmpp_pimpl {
|
|
/* Internal workhorse. */
|
|
struct {
|
|
sqlite3 * dbh;
|
|
/**
|
|
Optional filename. Memory is owned by this object.
|
|
*/
|
|
char * zName;
|
|
} db;
|
|
/**
|
|
Current directive context. It's const, primarily to help protect
|
|
cmpp_dx_f()'s from inadvertent side effects of changes which
|
|
lower-level APIs might make to it. Maybe it shouldn't be: if it
|
|
were not then we could update dx->zDelim from
|
|
cmpp__delimiter_set().
|
|
*/
|
|
cmpp_dx const * dx;
|
|
/* Output channel. */
|
|
cmpp_outputer out;
|
|
/**
|
|
Delimiters version 2.
|
|
*/
|
|
struct {
|
|
/**
|
|
Directive delimiter.
|
|
*/
|
|
cmpp__delim_list d;
|
|
/**
|
|
@token@ delimiters.
|
|
*/
|
|
cmpp__delim_list at;
|
|
} delim;
|
|
struct {
|
|
|
|
#define CMPP__SEL_V_FROM(N) \
|
|
"(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \
|
|
" ORDER BY source LIMIT 1)"
|
|
|
|
/**
|
|
One entry for each distinct query used by cmpp: E(X,SQL), where
|
|
X is the member's name and SQL is its SQL.
|
|
*/
|
|
#define CMPP_SAVEPOINT_NAME "_cmpp_"
|
|
#define CmppStmt_map(E) \
|
|
E(sdefIns, \
|
|
"INSERT INTO " \
|
|
CMPP__DB_MAIN_NAME ".sdef" \
|
|
"(t,k,v) VALUES(?1,?2,?3) RETURNING id") \
|
|
E(defIns, \
|
|
"INSERT OR REPLACE INTO " \
|
|
CMPP__DB_MAIN_NAME ".def" \
|
|
"(t,k,v) VALUES(?1,?2,?3)") \
|
|
E(defDel, \
|
|
"DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".def" \
|
|
" WHERE k GLOB ?1") \
|
|
E(sdefDel, \
|
|
"DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".sdef" \
|
|
" WHERE k=?1 AND id>=?2") \
|
|
E(defHas, \
|
|
"SELECT 1 FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1") \
|
|
E(defGet, \
|
|
"SELECT source,t,k,v FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1 ORDER BY source LIMIT 1") \
|
|
E(defGetBool, \
|
|
"SELECT cmpp_truthy(v) FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1" \
|
|
" ORDER BY source LIMIT 1") \
|
|
E(defGetInt, \
|
|
"SELECT CAST(v AS INTEGER)" \
|
|
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1" \
|
|
" ORDER BY source LIMIT 1") \
|
|
E(defSelAll, "SELECT t,k,v" \
|
|
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
|
|
" ORDER BY source, k") \
|
|
E(inclIns," INSERT OR FAIL INTO " \
|
|
CMPP__DB_MAIN_NAME ".incl(" \
|
|
" file,srcFile, srcLine" \
|
|
") VALUES(?,?,?)") \
|
|
E(inclDel, "DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
|
|
E(inclHas, "SELECT 1 FROM " \
|
|
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
|
|
E(inclPathAdd, "INSERT INTO " \
|
|
CMPP__DB_MAIN_NAME ".inclpath(priority,dir) " \
|
|
"VALUES(coalesce(?1,0),?2) " \
|
|
"ON CONFLICT DO NOTHING " \
|
|
"RETURNING rowid /*xlates to 0 on conflict*/") \
|
|
E(inclPathRmId, "DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 " \
|
|
"RETURNING rowid") \
|
|
E(inclSearch, \
|
|
"SELECT ?1 fn WHERE cmpp_file_exists(fn) " \
|
|
"UNION ALL SELECT fn FROM (" \
|
|
" SELECT replace(dir||'/'||?1, '//','/') AS fn " \
|
|
" FROM " CMPP__DB_MAIN_NAME ".inclpath" \
|
|
" WHERE cmpp_file_exists(fn) " \
|
|
" ORDER BY priority DESC, rowid LIMIT 1" \
|
|
")") \
|
|
E(cmpVV, "SELECT cmpp_compare(?1,?2)") \
|
|
E(cmpDV, \
|
|
"SELECT cmpp_compare(" \
|
|
CMPP__SEL_V_FROM(1) ", ?2" \
|
|
")") \
|
|
E(cmpVD, \
|
|
"SELECT cmpp_compare(" \
|
|
"?1," CMPP__SEL_V_FROM(2) \
|
|
")") \
|
|
E(cmpDD, \
|
|
"SELECT cmpp_compare(" \
|
|
CMPP__SEL_V_FROM(1) \
|
|
"," \
|
|
CMPP__SEL_V_FROM(2) \
|
|
")") \
|
|
E(dbAttach, \
|
|
"ATTACH ?1 AS ?2") \
|
|
E(dbDetach, \
|
|
"DETACH ?1") \
|
|
E(spBegin, "SAVEPOINT " CMPP_SAVEPOINT_NAME) \
|
|
E(spRollback, \
|
|
"ROLLBACK TO SAVEPOINT " CMPP_SAVEPOINT_NAME) \
|
|
E(spRelease, \
|
|
"RELEASE SAVEPOINT " CMPP_SAVEPOINT_NAME) \
|
|
E(insTtype, \
|
|
"INSERT INTO " CMPP__DB_MAIN_NAME ".ttype" \
|
|
"(t,n,s) VALUES(?1,?2,?3)") \
|
|
E(selPathSearch, \
|
|
/* sqlite.org/forum/forumpost/840c98a8e87c2207 */ \
|
|
"WITH path(basename, sep, ext, path) AS (\n" \
|
|
" select\n" \
|
|
" ?1 basename,\n" \
|
|
" ?2 sep,\n" \
|
|
" ?3 ext,\n" \
|
|
" ?4 path\n" \
|
|
"),\n" \
|
|
"pathsplit(i, l, c, r) AS (\n" \
|
|
"-- i = sequential ID\n" \
|
|
"-- l = Length remaining\n" \
|
|
"-- c = text remaining\n" \
|
|
"-- r = current unpacked value\n" \
|
|
" SELECT 1,\n" \
|
|
" length(p.path)+length(p.sep),\n" \
|
|
" p.path||p.sep, ''\n" \
|
|
" FROM path p\n" \
|
|
" UNION ALL\n" \
|
|
" SELECT i+1, instr( c, p.sep ) l,\n" \
|
|
" substr( c, instr( c, p.sep ) + 1) c,\n" \
|
|
" trim( substr( c, 1,\n" \
|
|
" instr( c, p.sep) - 1) ) r\n" \
|
|
" FROM pathsplit, path p\n" \
|
|
" WHERE l > 0\n" \
|
|
"),\n" \
|
|
"thefile (f) AS (\n" \
|
|
" select basename f FROM path\n" \
|
|
" union all\n" \
|
|
" select basename||ext\n" \
|
|
" from path where ext is not null\n" \
|
|
")\n" \
|
|
"select 0 i, replace(f,'//','/') AS fn\n" \
|
|
"from thefile where cmpp_file_exists(fn)\n" \
|
|
"union all\n" \
|
|
"select i, replace(r||'/'||f,'//','/') fn\n" \
|
|
"from pathsplit, thefile\n" \
|
|
"where r<>'' and cmpp_file_exists(fn)\n" \
|
|
"order by i\n" \
|
|
"limit 1;")
|
|
|
|
/* trivia: selPathSearch (^^^) was generated using
|
|
cmpp's #c-code directive. */
|
|
|
|
#define E(N,S) sqlite3_stmt * N;
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
|
|
} stmt;
|
|
|
|
/** Error state. */
|
|
struct {
|
|
/** Result code. */
|
|
int code;
|
|
/** Error string owned by this object. */
|
|
char * zMsg;
|
|
/** Either this->zMsg or an external error string. */
|
|
char const * zMsgC;
|
|
} err;
|
|
|
|
/** State for SQL tracing. */
|
|
struct {
|
|
bool expandSql;
|
|
cmpp_size_t counter;
|
|
cmpp_outputer out;
|
|
} sqlTrace;
|
|
|
|
struct {
|
|
/** If set properly, cmpp_dtor() will free this
|
|
object, else it will not. */
|
|
void const * allocStamp;
|
|
/**
|
|
How many dirs we believe are in the #include search list. We
|
|
only do this for the sake of the historical "if no path was
|
|
added, assume '.'" behavior. This really ought to go away.
|
|
*/
|
|
unsigned nIncludeDir;
|
|
/**
|
|
The current depth of cmpp_process_string() calls. We do this so
|
|
the directory part of #include'd files can get added to the
|
|
#include path and be given a higher priority than previous
|
|
include path entries in the stack.
|
|
*/
|
|
int nDxDepth;
|
|
/* Number of active #savepoints. */
|
|
unsigned nSavepoint;
|
|
/* If >0, enables certain debugging output. */
|
|
char doDebug;
|
|
/* If true, chomp() files read via -Fx=file. */
|
|
unsigned char chompF;
|
|
/* Flags passed to cmpp_ctor(). */
|
|
cmpp_flag32_t newFlags;
|
|
|
|
/**
|
|
An ugly hack for getting cmpp_d_register() to get
|
|
syntactically-illegal directive names, like "@policy",
|
|
to register.
|
|
*/
|
|
bool isInternalDirectiveReg;
|
|
/**
|
|
True if the next directive is the start of a [call]. This is
|
|
used for:
|
|
|
|
1) To set cmpp_dx::isCall, which is useful in certain
|
|
directives.
|
|
|
|
2) So that the cmpp_dx object created for the call can inherit
|
|
the line number from its parent context. That's significant
|
|
for error reporting.
|
|
|
|
3) So that #2's cmpp_dx object can communicate that flag to
|
|
cmpp_dx_next().
|
|
*/
|
|
bool nextIsCall;
|
|
|
|
/**
|
|
True until the cmpp's (sometimes) lazy init has been run. This
|
|
is essentially a kludge to work around a wrench cmpp_reset()
|
|
throws into cmpp state. Maybe we should just remove
|
|
cmpp_reset() from the interface, since error recovery in this
|
|
context is not really a thing.
|
|
*/
|
|
bool needsLazyInit;
|
|
} flags;
|
|
|
|
/** Policies. */
|
|
struct {
|
|
/** @token@-parsing policy. */
|
|
PodList__atpol at;
|
|
/** Policy towards referencing undefined symbols. */
|
|
PodList__unpol un;
|
|
} policy;
|
|
|
|
/**
|
|
Directive state.
|
|
*/
|
|
struct {
|
|
/**
|
|
Runtime-installed directives.
|
|
*/
|
|
CmppDList list;
|
|
/**
|
|
Directive autoloader/auto-registerer.
|
|
*/
|
|
cmpp_d_autoloader autoload;
|
|
} d;
|
|
|
|
struct {
|
|
/**
|
|
List of DLL handles opened by cmpp_module_extract().
|
|
*/
|
|
CmppSohList sohList;
|
|
/**
|
|
Search path for DLLs, delimited by this->pathSep.
|
|
*/
|
|
cmpp_b path;
|
|
/**
|
|
File extension for DLLs.
|
|
*/
|
|
char const * soExt;
|
|
/** Separator char for this->path. */
|
|
char pathSep;
|
|
} mod;
|
|
|
|
struct {
|
|
/**
|
|
Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return().
|
|
*/
|
|
cmpp_b_list buf;
|
|
/** How/whether this->list is sorted. */
|
|
enum cmpp_b_list_e bufSort;
|
|
/**
|
|
Head of the free-list.
|
|
*/
|
|
cmpp_args_pimpl * argPimpl;
|
|
} recycler;
|
|
};
|
|
|
|
/** IDs Distinct for each cmpp::stmt member. */
|
|
enum CmppStmt_e {
|
|
CmppStmt_none = 0,
|
|
#define E(N,S) CmppStmt_ ## N,
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
};
|
|
|
|
static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){
|
|
return cmpp__delim_list_get(&pp->pimpl->delim.d);
|
|
}
|
|
static inline char const * cmpp__pp_zdelim(cmpp const *pp){
|
|
cmpp__delim const * const d = cmpp__pp_delim(pp);
|
|
return d ? (char const *)d->open.z : NULL;
|
|
}
|
|
#define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp)
|
|
#define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp)
|
|
|
|
/**
|
|
Emit [z,(char*)z+n) to the given output channel if
|
|
(A) pOut->out is not NULL and (B) pp has no error state and (C)
|
|
n>0. On error, pp's error state is updated. Returns pp->err.code.
|
|
|
|
Skip level is not honored.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n);
|
|
|
|
CMPP_PRIVATE void cmpp__err_clear(cmpp *pp);
|
|
|
|
|
|
/**
|
|
Initialize pp->db.dbh. If it's already open or ppCode!=0
|
|
then ppCode is returned.
|
|
*/
|
|
int cmpp__db_init(cmpp *pp);
|
|
|
|
/**
|
|
Returns the pp->pimpl->stmt.X corresponding to `which`, initializing it if
|
|
needed. If it returns NULL then either this was called when pp has
|
|
its error state set or this function will set the error state.
|
|
|
|
If prepEvenIfErr is true then the ppCode check is bypassed, but it
|
|
will still fail if pp->pimpl->db is not opened or if the preparation itself
|
|
fails.
|
|
*/
|
|
sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
|
|
bool prepEvenIfErr);
|
|
|
|
/**
|
|
Reminder to self: this must return an SQLITE_... code, not a
|
|
CMPP_RC_... code.
|
|
|
|
On success it returns 0, SQLITE_ROW, or SQLITE_DONE. On error it
|
|
returns another non-0 SQLITE_... code and updates pp->pimpl->err.
|
|
|
|
This is a no-op if called when pp has an error set, returning
|
|
SQLITE_ERROR.
|
|
|
|
If resetIt is true, q is passed to cmpp__stmt_reset(), else the
|
|
caller must eventually reset it.
|
|
*/
|
|
int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt);
|
|
|
|
/** Resets and clear bindings from q (if q is not NULL). */
|
|
void cmpp__stmt_reset(sqlite3_stmt * const q);
|
|
|
|
/**
|
|
Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or
|
|
SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err
|
|
is updated with pp->db's current error state. zMsgSuffix is an
|
|
optional prefix for the error message.
|
|
*/
|
|
int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsgSuffix);
|
|
|
|
/* Proxy for sqlite3_bind_int64(). */
|
|
int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
|
|
|
|
/**
|
|
Proxy for cmpp__bind_text() which encodes val as a string.
|
|
|
|
For queries which compare values, it's important that they all have
|
|
the same type, so some cases where we might want an int needs to be
|
|
bound as text instead. See #query for one such case.
|
|
*/
|
|
int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
|
|
|
|
/* Proxy for sqlite3_bind_null(). */
|
|
int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col);
|
|
|
|
/* Proxy for sqlite3_bind_text() which updates pp->err on error. */
|
|
int cmpp__bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr);
|
|
|
|
/* Proxy for sqlite3_bind_text() which updates pp->err on error. */
|
|
int cmpp__bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col,
|
|
unsigned const char *zStr, cmpp_ssize_t len);
|
|
|
|
/**
|
|
Adds zDir to the include path, using the given priority value (use
|
|
0 except for the implicit cwd path which #include should (but does
|
|
not yet) set). If pRowid is not NULL then *pRowid gets set to
|
|
either 0 (if zDir was already in the path) or the row id of the
|
|
newly-inserted record, which can later be used to delete just that
|
|
entry.
|
|
|
|
If this returns a non-zero value via pRowid, the caller is
|
|
obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(),
|
|
even if pp is in an error state.
|
|
|
|
TODO: normalize zDir (at least remove trailing slashes) before
|
|
insertion to avoid that both a/b and a/b/ get be inserted.
|
|
*/
|
|
int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid);
|
|
|
|
/**
|
|
Deletes the include path entry with the given rowid. This will make
|
|
make the attempt even if pp is in an error state but also retains
|
|
any existing error rather than overwriting it if this operation
|
|
somehow fails. Returns pp's error code.
|
|
|
|
It is not an error for the given entry to not exist.
|
|
*/
|
|
int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid);
|
|
|
|
|
|
#if 0
|
|
/**
|
|
Proxy for sqlite3_bind_text(). It uses sqlite3_str_vappendf() so
|
|
supports all of its formatting options.
|
|
*/
|
|
int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
|
|
const char * zFmt, ...);
|
|
#endif
|
|
|
|
/**
|
|
Proxy for sqlite3_str_finish() which updates pp's error state if s
|
|
has error state. Returns s's string on success and NULL on
|
|
error. The returned string must eventualy be passed to
|
|
cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so
|
|
callers must check pp->err to see if NULL is an error.
|
|
|
|
If n is not NULL then on success it is set to the byte length of
|
|
the returned string.
|
|
*/
|
|
char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n);
|
|
|
|
/**
|
|
Searches pp's list of directives. If found, return it else return
|
|
NULL. See cmpp__d_search3().
|
|
*/
|
|
cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName);
|
|
|
|
/**
|
|
Flags for use with the final argument to
|
|
cmpp__d_search3().
|
|
*/
|
|
enum cmpp__d_search3_e {
|
|
/** Internal delayed-registered directives. */
|
|
cmpp__d_search3_F_DELAYED = 0x01,
|
|
/** Directive autoloader. */
|
|
cmpp__d_search3_F_AUTOLOADER = 0x02,
|
|
/** Search for a DLL. */
|
|
cmpp__d_search3_F_DLL = 0x04,
|
|
/** Options which do not trigger DLL lookup. */
|
|
cmpp__d_search3_F_NO_DLL = 0
|
|
| cmpp__d_search3_F_DELAYED
|
|
| cmpp__d_search3_F_AUTOLOADER,
|
|
/** All options. */
|
|
cmpp__d_search3_F_ALL = 0
|
|
| cmpp__d_search3_F_DELAYED
|
|
| cmpp__d_search3_F_AUTOLOADER
|
|
| cmpp__d_search3_F_DLL
|
|
};
|
|
|
|
/**
|
|
Like cmpp__d_search() but if no match is found then it will search
|
|
through its other options and, if found, register it.
|
|
|
|
The final argument specifies where to search. cmpp__d_search()
|
|
always checked first. After that, depending on "what", the search
|
|
order is: (1) internal delayed-load modules, (2) autoloader, (3)
|
|
DLL.
|
|
|
|
This may update pp's error state, in which case it will return
|
|
NULL.
|
|
*/
|
|
cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
|
|
cmpp_flag32_t what);
|
|
|
|
/**
|
|
Sets pp's error state (A) if it's not set already and (B) if
|
|
!cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is
|
|
legal (to support legacy CLI pieces). Returns ppCode.
|
|
*/
|
|
int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_ssize_t nKey,
|
|
bool permitEqualSign);
|
|
|
|
/**
|
|
Appends DLL handle soh to soli. Returns 0 on success, CMPP_RC_OOM
|
|
on error. If pp is not NULL then its error state is updated as
|
|
well.
|
|
|
|
Results are undefined if soli was not cleanly initialized (by
|
|
copying CmppSohList_empty or using CmppSohList_new()).
|
|
|
|
Special case: if built without DLL-closing support, this is a no-op
|
|
returning 0.
|
|
*/
|
|
int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh);
|
|
|
|
/** True if arg is of type cmpp_TT_Word and it looks like it
|
|
_might_ be a filename or flag argument. Might. */
|
|
bool cmpp__arg_wordIsPathOrFlag(cmpp_arg const * const arg);
|
|
|
|
/**
|
|
Helper for #query and friends. Binds aVal's value to column bindNdx
|
|
of q.
|
|
|
|
It expands cmpp_TT_StringAt and cmpp_TT_Word aVal. cmpp_TT_String
|
|
and cmpp_TT_Int are bound as strings. A cmpp_TT_GroupParen aVal is
|
|
eval'ed as an integer and that int gets bound as a string.
|
|
|
|
This function strictly binds everything as strings, even if the
|
|
value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that
|
|
comparison queries will work as expected.
|
|
|
|
Returns ppCode.
|
|
*/
|
|
int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * q,
|
|
int bindNdx, cmpp_arg const * aVal);
|
|
|
|
/**
|
|
Helper for #query's bind X part, where aGroup is that X.
|
|
|
|
A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be
|
|
either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have
|
|
non-empty content. cmpp_TT_GroupBrace treats it as a list of values
|
|
to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt
|
|
column in one of these forms:
|
|
|
|
:bindName -> value
|
|
$bindName -> value
|
|
|
|
Each LHS refers to a same-named binding in q's SQL, including the
|
|
':' or '$' prefix. (SQLite supports an '@' prefix but we don't
|
|
allow it here to avoid confusion with cmpp_TT_StringAt tokens.)
|
|
|
|
Each bound value is passed to cmpp__bind_arg() for processing.
|
|
|
|
On success, each aGroup entry is bound to q. On error q's state is
|
|
unspecified. Returns ppCode.
|
|
|
|
See cmpp__bind_arg() for notes about the bind data type.
|
|
*/
|
|
int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q,
|
|
cmpp_arg const * const aGroup);
|
|
|
|
/**
|
|
Returns true if the given key is already in the `#define` list,
|
|
else false. Sets pp's error state on db error.
|
|
|
|
nName is the length of the key part of zName (which might have
|
|
a following =y part. If it's negative, strlen() is used to
|
|
calculate it.
|
|
*/
|
|
int cmpp_has(cmpp *pp, const char * zName, cmpp_ssize_t nName);
|
|
|
|
/**
|
|
Returns true if the given key is already in the `#define` list, and
|
|
it has a truthy value (is not empty and not equal to '0'), else
|
|
false. If zName contains an '=' then only the part preceding that
|
|
is used as the key.
|
|
|
|
nName is the length of zName, or <0 to use strlen() to figure
|
|
it out.
|
|
|
|
Updates ppCode on error.
|
|
*/
|
|
int cmpp__get_bool(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName);
|
|
|
|
/**
|
|
Fetches the given define. If found, sets *pOut to it, else pOut is
|
|
unmodified. Returns pp->err.code. If bRequire is true and no entry
|
|
is found p->err.code is updated.
|
|
*/
|
|
int cmpp__get_int(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName, int *pOut);
|
|
|
|
/**
|
|
Searches for a define where (k GLOB zName). If one is found, a copy
|
|
of it is assigned to *zVal (the caller must eventually db_free()
|
|
it), *nVal (if nVal is not NULL) is assigned its strlen, and
|
|
returns non-0. If no match is found, 0 is returned and neither
|
|
*zVal nor *nVal are modified. If more than one result matches, a
|
|
fatal error is triggered.
|
|
|
|
It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
|
|
non-0. That just means that the key was defined with no value part.
|
|
|
|
Fixme: return 0 on success and set output *gotOne=0|1.
|
|
*/
|
|
int cmpp__get(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName,
|
|
unsigned char **zVal, unsigned int *nVal);
|
|
|
|
/**
|
|
Like cmp__get() but puts its output in os.
|
|
*/
|
|
int cmpp__get_b(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName, cmpp_b * os,
|
|
bool enforceUndefPolicy);
|
|
|
|
|
|
/**
|
|
Helper for #query and friends.
|
|
|
|
It expects that q has just been stepped. For each column in the
|
|
row, it sets a define named after the column. If q has row data
|
|
then the values come from there. If q has no row then: if
|
|
defineIfNoRow is true then it defines each column name to an empty
|
|
value else it defines nothing.
|
|
*/
|
|
int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
|
|
bool defineIfNoRow);
|
|
|
|
/** Start a new savepoint for dx. */
|
|
int cmpp__dx_sp_begin(cmpp_dx * const dx);
|
|
/** Commit and close dx's current savepoint. */
|
|
int cmpp__dx_sp_commit(cmpp_dx * const dx);
|
|
/** Roll back and close dx's current savepoint. */
|
|
int cmpp__dx_sp_rollback(cmpp_dx * const dx);
|
|
|
|
/**
|
|
Append's dx's file/line information to sstr. It returns void
|
|
because that's how sqlite3_str_appendf() and friends work.
|
|
*/
|
|
void cmpp__dx_append_script_info(cmpp_dx const * dx,
|
|
sqlite3_str * sstr);
|
|
|
|
/**
|
|
If zName matches one of the delayed-load directives, that directive
|
|
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
|
|
no match is found, but pp's error state is not updated in that
|
|
case. If a match is found and registration fails, that result code
|
|
will propagate via pp.
|
|
*/
|
|
int cmpp__d_delayed_load(cmpp *pp, char const *zName);
|
|
|
|
void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent);
|
|
|
|
/**
|
|
Like cmpp_tt_cstr(), but if bSymbolName is false then it returns
|
|
the higher-level token name, which is NULL for most token types.
|
|
*/
|
|
char const * cmpp__tt_cstr(int tt, bool bSymbolName);
|
|
|
|
/**
|
|
Expects **zPos to be one of ('(' '{' '[' '"' '\'') and zEnd to be
|
|
the logical EOF for *zPos.
|
|
|
|
This looks for a matching closing token, accounting for nesting. On
|
|
success, returns 0 and sets *zPos to the closing character.
|
|
|
|
On error it update's pp's error state and returns that code. pp may
|
|
be NULL.
|
|
|
|
If pNl is not NULL then *pNl is incremented for each '\n' character
|
|
seen while looking for the closing character.
|
|
*/
|
|
int cmpp__find_closing2(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t *pNl);
|
|
|
|
#define cmpp__find_closing(PP,Z0,Z1) \
|
|
cmpp__find_closing2(PP, Z0, Z1, NULL)
|
|
|
|
static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_ssize_t n){
|
|
return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n;
|
|
}
|
|
static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_ssize_t n){
|
|
return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n;
|
|
}
|
|
|
|
/**
|
|
If ppCode is not set and pol resolves to cmpp_atpol_OFF then this
|
|
updates ppCode with a message about the lack of support for
|
|
at-strings. If cmpp_atpol_CURRENT==pol then pp's current policy is
|
|
checked. Returns ppCode.
|
|
*/
|
|
int cmpp__StringAtIsOk(cmpp * const pp, cmpp_atpol_e pol);
|
|
|
|
/**
|
|
"define"s zKey to zVal, recording the value type as tType.
|
|
*/
|
|
int cmpp__define2(cmpp *pp,
|
|
unsigned char const * zKey,
|
|
cmpp_ssize_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_ssize_t nVal,
|
|
cmpp_tt tType);
|
|
|
|
/**
|
|
Evals pArgs's arguments as an integer expression. On success, sets
|
|
*pResult to the value.
|
|
|
|
Returns ppCode.
|
|
*/
|
|
int cmpp__args_evalToInt(cmpp_dx * dx, cmpp_args const *pArgs,
|
|
int * pResult);
|
|
|
|
/** Passes the contents of arg through to cmpp__args_evalToInt(). */
|
|
int cmpp__arg_evalSubToInt(cmpp_dx *dx, cmpp_arg const *arg,
|
|
int * pResult);
|
|
|
|
/**
|
|
Evaluated arg as an integer/bool, placing the result in *pResult
|
|
and setting *pNext to the first argument to arg's right which this
|
|
routine did not consume. Non-0 on error and all that.
|
|
*/
|
|
int cmpp__arg_toBool(cmpp_dx * dx, cmpp_arg const *arg,
|
|
int * pResult, cmpp_arg const **pNext);
|
|
|
|
/**
|
|
If thisTtype==cmpp_TT_AnyType or thisTtype==arg->ttype and arg->z
|
|
looks like it might contain an at-string then os is re-used to hold
|
|
the @token@-expanded version of arg's content. os is unconditionally
|
|
passed to cmpp_b_reuse() before it begines work.
|
|
|
|
It uses the given atPolicy to determine whether or not the content
|
|
is expanded, as per cmpp_dx_out_expand().
|
|
|
|
Returns 0 on success. If it expands content then *pExp is set to
|
|
os->z, else *pExp is set to arg->z. If nExp is not NULL then *nExp
|
|
gets set to the length of *pExp (geither os->n or arg->n).
|
|
|
|
Returns ppCode.
|
|
|
|
Much later: what does this give us that cmpp_arg_to_b()
|
|
doesn't? Oh - that one calls into this one. i.e. this one is
|
|
lower-level.
|
|
*/
|
|
int cmpp__arg_expand_ats(cmpp_dx const * const dx,
|
|
cmpp_b * os,
|
|
cmpp_atpol_e atPolicy,
|
|
cmpp_arg const * const arg,
|
|
cmpp_tt thisTtype,
|
|
unsigned char const **pExp,
|
|
cmpp_size_t * nExp);
|
|
|
|
typedef struct cmpp_argOp cmpp_argOp;
|
|
typedef void (*cmpp_argOp_f)(cmpp_dx *dx,
|
|
cmpp_argOp const *op,
|
|
cmpp_arg const *vLhs,
|
|
cmpp_arg const **pvRhs,
|
|
int *pResult);
|
|
struct cmpp_argOp {
|
|
int ttype;
|
|
/* 1 or 2 */
|
|
unsigned char arity;
|
|
/* 0=none/left, 1=right (unary ops only) */
|
|
signed char assoc;
|
|
cmpp_argOp_f xCall;
|
|
};
|
|
|
|
cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt);
|
|
|
|
|
|
bool cmpp__is_int(unsigned char const *z, unsigned n,
|
|
int *pOut);
|
|
bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut);
|
|
|
|
char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p);
|
|
char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p);
|
|
|
|
/**
|
|
Uncerimoniously bitwise-replaces pp's output channel with oNew. It
|
|
does _not_ clean up the previous channel, on the assumption that
|
|
the caller is taking any necessary measures.
|
|
|
|
Apropos necessary measures for cleanup: if oPrev is not NULL,
|
|
*oPrev is set to a bitwise copy of the previous channel.
|
|
|
|
Intended usage:
|
|
|
|
```
|
|
cmpp_outputer oMine = cmpp_outputer_b;
|
|
cmpp_b bMine = cmpp_b_empty;
|
|
cmpp_outputer oOld = {0};
|
|
oMine.state = &bMine;
|
|
cmpp_outputer_swap(pp, &myOut, &oOld);
|
|
...do some work then ALWAYS do...
|
|
cmpp_outputer_swap(pp, &oOld, &oMine);
|
|
```
|
|
|
|
Because this involves bitwise copying, care must be taken with
|
|
stream state, e.g. bMine.z (above) can be reallocated, so we have
|
|
to be sure to swap it back before using bMine again.
|
|
*/
|
|
void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
|
|
cmpp_outputer *oPrev);
|
|
|
|
/**
|
|
Init code which is usually run as part of the ctor but may have to
|
|
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
|
|
because that could leave post-reset in an error state, which is
|
|
icky. This call is a no-op after the first.
|
|
*/
|
|
int cmpp__lazy_init(cmpp *pp);
|
|
|
|
CMPP_NORETURN void cmpp__fatalv_base(char const *zFile, int line,
|
|
char const *zFmt, va_list);
|
|
#define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__)
|
|
CMPP_NORETURN void cmpp__fatal_base(char const *zFile, int line,
|
|
char const *zFmt, ...);
|
|
#define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__)
|
|
|
|
/**
|
|
Outputs a printf()-formatted message to stderr.
|
|
*/
|
|
void g_stderr(char const *zFmt, ...);
|
|
#define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
|
|
#define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg)
|
|
#if 0
|
|
#define g_debug(PP,lvl,pfexpr) (void)0
|
|
#else
|
|
#define g_debug(PP,lvl,pfexpr) \
|
|
if(lvl<=(PP)->pimpl->flags.doDebug) { \
|
|
if( (PP)->pimpl->dx ){ \
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ", \
|
|
(PP)->pimpl->dx->sourceName, \
|
|
(PP)->pimpl->dx->pimpl->dline.lineNo); \
|
|
} \
|
|
g_stderr("%s():%d: ", \
|
|
__func__,__LINE__); \
|
|
g_stderr pfexpr; \
|
|
} (void)0
|
|
#endif
|
|
|
|
/** Returns true if zFile is readable, else false. */
|
|
bool cmpp__file_is_readable(char const *zFile);
|
|
|
|
#define ustr_c(X) ((unsigned char const *)X)
|
|
#define ustr_nc(X) ((unsigned char *)X)
|
|
#define ppCode pp->pimpl->err.code
|
|
#define dxppCode dx->ppCode
|
|
#define cmpp__pi(PP) cmpp_pimpl * const pi = PP->pimpl
|
|
#define cmpp__dx_pi(DX) cmpp_dx_pimpl * const dpi = DX->pimpl
|
|
#define serr(...) cmpp_err_set(pp, CMPP_RC_SYNTAX, __VA_ARGS__)
|
|
#define dxserr(...) cmpp_err_set(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__)
|
|
#define cmpp__li_reserve1_size(li,nInitial) \
|
|
(li->n ? (li->n==li->nAlloc ? li->nAlloc * 2 : li->n+1) : nInitial)
|
|
|
|
#define MARKER(pfexp) \
|
|
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
|
|
printf pfexp; \
|
|
} while(0)
|
|
|
|
#endif /* include guard */
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the core of libcmpp (it used to house all of it,
|
|
** but the library grew beyond the confines of a single file).
|
|
**
|
|
** See the accompanying c-pp.h and README.md and/or c-pp.h for more
|
|
** details.
|
|
*/
|
|
#include "sqlite3.h"
|
|
|
|
char const * cmpp_version(void){ return CMPP_VERSION; }
|
|
|
|
const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m;
|
|
const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m;
|
|
const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m;
|
|
const cmpp_outputer cmpp_outputer_FILE = {
|
|
.out = cmpp_output_f_FILE,
|
|
.flush = cmpp_flush_f_FILE,
|
|
.cleanup = cmpp_outputer_cleanup_f_FILE,
|
|
.state = NULL
|
|
};
|
|
const cmpp_b_list cmpp_b_list_empty =
|
|
cmpp_b_list_empty_m;
|
|
const cmpp_outputer cmpp_outputer_b = {
|
|
.out = cmpp_output_f_b,
|
|
.flush = 0,
|
|
.cleanup = cmpp_outputer_cleanup_f_b,
|
|
.state = NULL
|
|
};
|
|
|
|
/**
|
|
Default delimiters for @tokens@.
|
|
*/
|
|
static const cmpp__delim delimAtDefault = {
|
|
.open = { .z = ustr_c("@"), .n = 1 },
|
|
.close = { .z = ustr_c("@"), .n = 1 },
|
|
.zOwns = NULL
|
|
};
|
|
|
|
static const cmpp_api_thunk cmppApiMethods = {
|
|
#define A(V)
|
|
#define V(N,T,V) .N = V,
|
|
#define F(N,T,P) .N = cmpp_ ##N,
|
|
#define O(N,T) .N = &cmpp_ ##N,
|
|
cmpp_api_thunk_map(A,V,F,O)
|
|
#undef F
|
|
#undef O
|
|
#undef V
|
|
#undef A
|
|
};
|
|
|
|
/* Fatally exits the app with the given printf-style message. */
|
|
|
|
CMPP__EXPORT(bool, cmpp_isspace)(int ch){
|
|
return ' '==ch || '\t'==ch;
|
|
}
|
|
|
|
//CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){}
|
|
static inline int cmpp_isnl(unsigned char const * z, unsigned char const *zEnd){
|
|
//assert(z<zEnd && "Caller-level pointer mis-traversal");
|
|
switch( z<zEnd ? *z : 0 ){
|
|
case 0: return 0;
|
|
case '\r': return ((z+1<zEnd) && '\n'==z[1]) ? 2 : 0;
|
|
default: return '\n'==*z;
|
|
}
|
|
}
|
|
|
|
static inline bool cmpp_issnl(int ch){
|
|
/* TODO: replace this in line with cmpp_isnl(), but it needs
|
|
a new interface for that. It's only used in two places and they
|
|
have different traversal directions, so we can probably
|
|
get rid of this and do the direct CRNL check in each of those
|
|
places. */
|
|
return ' '==ch || '\t'==ch || '\n'==ch;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_space)(
|
|
unsigned char const **p,
|
|
unsigned char const *zEnd
|
|
){
|
|
assert( *p <= zEnd );
|
|
unsigned char const * z = *p;
|
|
while( z<zEnd && cmpp_isspace(*z) ) ++z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_snl)( unsigned char const **p,
|
|
unsigned char const *zEnd ){
|
|
unsigned char const * z = *p;
|
|
/* FIXME: CRNL. */
|
|
while( z<zEnd && cmpp_issnl(*z) ) ++z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_space_trailing)( unsigned char const *zBegin,
|
|
unsigned char const **p ){
|
|
assert( *p >= zBegin );
|
|
unsigned char const * z = *p;
|
|
while( z>zBegin && cmpp_isspace(z[-1]) ) --z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_snl_trailing)( unsigned char const *zBegin,
|
|
unsigned char const **p ){
|
|
assert( *p >= zBegin );
|
|
unsigned char const * z = *p;
|
|
/* FIXME: CRNL. */
|
|
while( z>zBegin && cmpp_issnl(*z) ) --z;
|
|
*p = z;
|
|
}
|
|
|
|
/* Set pp's error state. */
|
|
static int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list);
|
|
/**
|
|
Sets pp's error state.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_err_set)(cmpp *pp, int rc, char const *zFmt, ...);
|
|
#define cmpp__err cmpp_err_set
|
|
#define cmpp_dx_err cmpp_dx_err_set
|
|
|
|
/* Open/close pp's output channel. */
|
|
static int cmpp__out_fopen(cmpp *pp, const char *zName);
|
|
static void cmpp__out_close(cmpp *pp);
|
|
|
|
#define CmppKvp_empty_m \
|
|
{CmppSnippet_empty_m,CmppSnippet_empty_m,CmppKvp_op_none}
|
|
const CmppKvp CmppKvp_empty = CmppKvp_empty_m;
|
|
|
|
/* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just
|
|
supported cmpp_FILE input. */
|
|
typedef struct FileWrapper FileWrapper;
|
|
struct FileWrapper {
|
|
/* File's name. */
|
|
char const *zName;
|
|
/* cmpp_FILE handle. */
|
|
cmpp_FILE * pFile;
|
|
/* Where FileWrapper_slurp() stores the file's contents. */
|
|
unsigned char * zContent;
|
|
/* Size of this->zContent, as set by FileWrapper_slurp(). */
|
|
cmpp_size_t nContent;
|
|
};
|
|
#define FileWrapper_empty_m {0,0,0,0}
|
|
static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
|
|
|
|
/**
|
|
Proxy for cmpp_fclose() and frees all memory owned by p. It is not
|
|
an error if p is already closed.
|
|
*/
|
|
static void FileWrapper_close(FileWrapper * p);
|
|
|
|
/** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */
|
|
static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
|
|
|
|
/* Populates p->zContent and p->nContent from p->pFile. */
|
|
//static int FileWrapper_slurp(FileWrapper * p, int bCloseFile );
|
|
|
|
/**
|
|
If p->zContent ends in \n or \r\n, that part is replaced with 0 and
|
|
p->nContent is adjusted. Returns true if it chomps, else false.
|
|
*/
|
|
static bool FileWrapper_chomp(FileWrapper * p);
|
|
|
|
/*
|
|
** Outputs a printf()-formatted message to stderr.
|
|
*/
|
|
static void g_stderrv(char const *zFmt, va_list);
|
|
|
|
CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){
|
|
switch((cmpp_rc_e)rc){
|
|
#define E(N,V,H) case N: return # N;
|
|
cmpp_rc_e_map(E)
|
|
#undef E
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_mfree)(void *p){
|
|
/* This MUST be a proxy for sqlite3_free() because allocate memory
|
|
exclusively using sqlite3_malloc() and friends. */
|
|
sqlite3_free(p);
|
|
}
|
|
|
|
CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){
|
|
return sqlite3_realloc64(p, n);
|
|
}
|
|
|
|
CMPP__EXPORT(void *, cmpp_malloc)(size_t n){
|
|
#if 1
|
|
return sqlite3_malloc64(n);
|
|
#else
|
|
void * p = sqlite3_malloc64(n);
|
|
if( p ) memset(p, 0, n);
|
|
return p;
|
|
#endif
|
|
}
|
|
|
|
cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){
|
|
cmpp_FILE *f;
|
|
if(zName && ('-'==*zName && !zName[1])){
|
|
f = (strchr(zMode, 'w') || strchr(zMode,'+'))
|
|
? stdout
|
|
: stdin
|
|
;
|
|
}else{
|
|
f = fopen(zName, zMode);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void cmpp_fclose( cmpp_FILE * f ){
|
|
if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
int cmpp_slurp(cmpp_input_f fIn, void *sIn,
|
|
unsigned char **pOut, cmpp_size_t * nOut){
|
|
unsigned char zBuf[1024 * 16];
|
|
unsigned char * pDest = 0;
|
|
unsigned nAlloc = 0;
|
|
unsigned nOff = 0;
|
|
int rc = 0;
|
|
cmpp_size_t nr = 0;
|
|
while( 0==rc ){
|
|
nr = sizeof(zBuf);
|
|
if( (rc = fIn(sIn, zBuf, &nr)) ){
|
|
break;
|
|
}
|
|
if(nr>0){
|
|
if(nAlloc < nOff + nr + 1){
|
|
nAlloc = nOff + nr + 1;
|
|
pDest = cmpp_mrealloc(pDest, nAlloc);
|
|
}
|
|
memcpy(pDest + nOff, zBuf, nr);
|
|
nOff += nr;
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
if( 0==rc ){
|
|
if(pDest) pDest[nOff] = 0;
|
|
*pOut = pDest;
|
|
*nOut = nOff;
|
|
}else{
|
|
cmpp_mfree(pDest);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void FileWrapper_close(FileWrapper * p){
|
|
if(p->pFile) cmpp_fclose(p->pFile);
|
|
if(p->zContent) cmpp_mfree(p->zContent);
|
|
*p = FileWrapper_empty;
|
|
}
|
|
|
|
int FileWrapper_open(FileWrapper * p, const char * zName,
|
|
const char * zMode){
|
|
FileWrapper_close(p);
|
|
if( (p->pFile = cmpp_fopen(zName, zMode)) ){
|
|
p->zName = zName;
|
|
return 0;
|
|
}else{
|
|
return cmpp_errno_rc(errno, CMPP_RC_IO);
|
|
}
|
|
}
|
|
|
|
int FileWrapper_slurp(FileWrapper * p, int bCloseFile){
|
|
assert(!p->zContent);
|
|
assert(p->pFile);
|
|
int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile,
|
|
&p->zContent, &p->nContent);
|
|
if( bCloseFile ){
|
|
cmpp_fclose(p->pFile);
|
|
p->pFile = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){
|
|
if( *n && '\n'==z[*n-1] ){
|
|
z[--*n] = 0;
|
|
if( *n && '\r'==z[*n-1] ){
|
|
z[--*n] = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FileWrapper_chomp(FileWrapper * p){
|
|
return cmpp_chomp(p->zContent, &p->nContent);
|
|
}
|
|
|
|
|
|
#if 0
|
|
/**
|
|
Returns the number newline characters between the given starting
|
|
point and inclusive ending point. Results are undefined if zFrom is
|
|
greater than zTo.
|
|
*/
|
|
static unsigned cmpp__count_lines(unsigned char const * zFrom,
|
|
unsigned char const *zTo);
|
|
|
|
unsigned cmpp__count_lines(unsigned char const * zFrom,
|
|
unsigned char const *zTo){
|
|
unsigned ln = 0;
|
|
assert(zFrom && zTo);
|
|
assert(zFrom <= zTo);
|
|
for(; zFrom < zTo; ++zFrom){
|
|
if((unsigned char)'\n' == *zFrom) ++ln;
|
|
}
|
|
return ln;
|
|
}
|
|
#endif
|
|
|
|
char const * cmpp__tt_cstr(int tt, bool bSymbolName){
|
|
switch(tt){
|
|
#define E(N,TOK) case cmpp_TT_ ## N: \
|
|
return bSymbolName ? "cmpp_TT_" #N : TOK;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char const * cmpp_tt_cstr(int tt){
|
|
return cmpp__tt_cstr(tt, true);
|
|
}
|
|
|
|
/** Flags and constants related to CmppLvl. */
|
|
enum CmppLvl_e {
|
|
/**
|
|
Flag indicating that all ostensible output for a CmpLevel should
|
|
be elided. This also suppresses non-flow-control directives from
|
|
being processed.
|
|
*/
|
|
CmppLvl_F_ELIDE = 0x01,
|
|
/**
|
|
Mask of CmppLvl::flags which are inherited when
|
|
CmppLvl_push() is used.
|
|
*/
|
|
CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE
|
|
};
|
|
|
|
//static const CmppDLine CmppDLine_empty = CmppDLine_empty_m;
|
|
|
|
/** Free all memory owned by li but does not free li. */
|
|
static void CmppLvlList_cleanup(CmppLvlList *li);
|
|
|
|
/**
|
|
Allocate a list entry, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. It is expected
|
|
that the caller will populate the entry's zName using
|
|
sqlite3_mprintf() or equivalent.
|
|
*/
|
|
static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li);
|
|
|
|
/** Returns the most-recently-appended element of li back to li's
|
|
free-list. It expects to receive that value as a sanity-checking
|
|
measure and may fail fatally of that's not upheld. */
|
|
static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl);
|
|
|
|
static const cmpp_dx_pimpl cmpp_dx_pimpl_empty =
|
|
cmpp_dx_pimpl_empty_m;
|
|
|
|
#define cmpp_dx_empty_m { \
|
|
.pp=0, \
|
|
.d=0, \
|
|
.sourceName=0, \
|
|
.args={ \
|
|
.z=0, .nz=0, \
|
|
.argc=0, .arg0=0 \
|
|
}, \
|
|
.pimpl = 0 \
|
|
}
|
|
|
|
const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m;
|
|
#define cmpp_d_empty_m {{0,0},0,0,cmpp_d_impl_empty_m}
|
|
//static const cmpp_d cmpp_d_empty = cmpp_d_empty_m;
|
|
|
|
static const CmppDList_entry CmppDList_entry_empty =
|
|
CmppDList_entry_empty_m;
|
|
|
|
/** Free all memory owned by li but does not free li. */
|
|
static void CmppDList_cleanup(CmppDList *li);
|
|
/**
|
|
Allocate a list entry, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. It is expected
|
|
that the caller will populate the entry's zName using
|
|
sqlite3_mprintf() or equivalent.
|
|
*/
|
|
static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li);
|
|
/** Returns the most-recently-appended element of li back to li's
|
|
free-list. */
|
|
static void CmppDList_unappend(CmppDList *li);
|
|
/** Resets li's list for re-use but does not free it. Returns li. */
|
|
//static CmppDList * CmppDList_reuse(CmppDList *li);
|
|
static CmppDList_entry * CmppDList_search(CmppDList const * li,
|
|
char const *zName);
|
|
|
|
/** Reset dx and free any memory it may own. */
|
|
static void cmpp_dx_cleanup(cmpp_dx * const dx);
|
|
/**
|
|
Reset some of dx's parsing-related state in prep for fetching the
|
|
next line.
|
|
*/
|
|
static void cmpp_dx__reset(cmpp_dx * const dx);
|
|
|
|
/* Returns dx's current directive. */
|
|
static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){
|
|
return dx->d;
|
|
}
|
|
|
|
static const cmpp_pimpl cmpp_pimpl_empty = {
|
|
.db = {
|
|
.dbh = 0,
|
|
.zName = 0
|
|
},
|
|
.dx = 0,
|
|
.out = cmpp_outputer_empty_m,
|
|
.delim = {
|
|
.d = cmpp__delim_list_empty_m,
|
|
.at = cmpp__delim_list_empty_m
|
|
},
|
|
.stmt = {
|
|
#define E(N,S) .N = 0,
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
},
|
|
.err = {
|
|
.code = 0,
|
|
.zMsg = 0,
|
|
.zMsgC = 0
|
|
},
|
|
.sqlTrace = {
|
|
.expandSql = false,
|
|
.counter = 0,
|
|
.out = cmpp_outputer_empty_m
|
|
},
|
|
.flags = {
|
|
.allocStamp = 0,
|
|
.nIncludeDir = 0,
|
|
.nDxDepth = 0,
|
|
.nSavepoint = 0,
|
|
.doDebug = 0,
|
|
.chompF = 0,
|
|
.newFlags = 0,
|
|
.isInternalDirectiveReg = false,
|
|
.nextIsCall = false,
|
|
.needsLazyInit = true
|
|
},
|
|
.policy = {
|
|
.at = {0,0,0},
|
|
.un = {0,0,0}
|
|
},
|
|
.d = {
|
|
.list = CmppDList_empty_m,
|
|
.autoload = cmpp_d_autoloader_empty_m
|
|
},
|
|
.mod = {
|
|
.sohList = CmppSohList_empty_m,
|
|
.path = cmpp_b_empty_m,
|
|
.soExt = CMPP_PLATFORM_EXT_DLL,
|
|
/* Yes, '*'. It makes sense in context. */
|
|
.pathSep = '*'
|
|
// 0x1e /* "record separator" */ doesn't work. Must be non-ctrl.
|
|
},
|
|
.recycler = {
|
|
.buf = cmpp_b_list_empty_m,
|
|
.bufSort = cmpp_b_list_UNSORTED,
|
|
.argPimpl = NULL
|
|
}
|
|
};
|
|
|
|
#if 0
|
|
static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){
|
|
return cmpp__out2(pp, &pp->out, z, n);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Returns an approximate cmpp_tt for the given SQLITE_... value from
|
|
sqlite3_column_type() or sqlite3_value_type().
|
|
*/
|
|
static cmpp_tt cmpp__tt_for_sqlite(int sqType);
|
|
|
|
/**
|
|
Init code which is usually run as part of the ctor but may have to
|
|
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
|
|
because that could leave post-reset in an error state, which is
|
|
icky.
|
|
*/
|
|
int cmpp__lazy_init(cmpp *pp){
|
|
if( !ppCode && pp->pimpl->flags.needsLazyInit ){
|
|
pp->pimpl->flags.needsLazyInit = false;
|
|
cmpp__delim_list * li = &pp->pimpl->delim.d;
|
|
if( !li->n ) cmpp_delimiter_push(pp, NULL);
|
|
li = &pp->pimpl->delim.at;
|
|
if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL);
|
|
#if defined(CMPP_CTOR_INSTANCE_INIT)
|
|
if( !ppCode ){
|
|
extern int CMPP_CTOR_INSTANCE_INIT(cmpp*);
|
|
int const rc = CMPP_CTOR_INSTANCE_INIT(pp);
|
|
if( rc && !ppCode ){
|
|
cmpp__err(pp, rc,
|
|
"Initialization via CMPP_CTOR_INSTANCE_INIT() failed "
|
|
"with code %d/%s.", rc, cmpp_rc_cstr(rc) );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static void cmpp__wipe_policies(cmpp *pp){
|
|
if( 0==ppCode ){
|
|
PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0);
|
|
PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0);
|
|
if( 0==ppCode ){
|
|
PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT);
|
|
PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_cfg const * cfg){
|
|
cmpp_pimpl * pi = 0;
|
|
cmpp * pp = 0;
|
|
void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi));
|
|
if( mv ){
|
|
if( !cfg ){
|
|
static const cmpp_ctor_cfg dfltCfg = {0};
|
|
cfg = &dfltCfg;
|
|
}
|
|
cmpp const x = {
|
|
.api = &cmppApiMethods,
|
|
.pimpl = (cmpp_pimpl*)((unsigned char *)mv + sizeof(cmpp))
|
|
/* ^^^ (T const * const) members */
|
|
};
|
|
memcpy(mv, &x, sizeof(x))
|
|
/* FWIW, i'm convinced that this is a legal way to transfer
|
|
these const-pointers-to-const. If not, we'll need to change
|
|
those cmpp members from (T const * const) to (T const *). */;
|
|
pp = mv;
|
|
assert(pp->api == &cmppApiMethods);
|
|
assert(pp->pimpl);
|
|
pi = pp->pimpl;
|
|
*pOut = pp;
|
|
*pi = cmpp_pimpl_empty;
|
|
assert( pi->flags.needsLazyInit );
|
|
pi->flags.newFlags = cfg->flags;
|
|
pi->flags.allocStamp = &cmpp_pimpl_empty;
|
|
if( cfg->dbFile ){
|
|
pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile);
|
|
cmpp_check_oom(pp, pi->db.zName);
|
|
}
|
|
if( 0==ppCode ){
|
|
cmpp__wipe_policies(pp);
|
|
cmpp__lazy_init(pp);
|
|
}
|
|
}
|
|
return pp ? ppCode : CMPP_RC_OOM;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
cmpp_outputer_cleanup(&pi->sqlTrace.out);
|
|
pi->sqlTrace.out = cmpp_outputer_empty;
|
|
if( pi->d.autoload.dtor ){
|
|
pi->d.autoload.dtor(pi->d.autoload.state);
|
|
}
|
|
pi->d.autoload = cmpp_pimpl_empty.d.autoload;
|
|
cmpp_b_clear(&pi->mod.path);
|
|
if( pi->stmt.spRelease && pi->stmt.spRollback ){
|
|
/* Cleanly kill all savepoint levels. This is truly superfluous,
|
|
as they'll all be rolled back (if the db is persistent) or
|
|
nuked (if using a :memory: db) momentarily. However, we'll
|
|
eventually need this for a partial-clear operation which leaves
|
|
the db and custom directives intact. For now it lives here but
|
|
will eventually move to wherever that ends up being.
|
|
|
|
2025-11-16: or not. It's fine here, really.
|
|
*/
|
|
sqlite3_reset(pi->stmt.spRelease);
|
|
while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){
|
|
sqlite3_reset(pi->stmt.spRollback);
|
|
sqlite3_step(pi->stmt.spRollback);
|
|
sqlite3_reset(pi->stmt.spRelease);
|
|
}
|
|
}
|
|
cmpp__out_close(pp);
|
|
CmppDList_cleanup(&pi->d.list);
|
|
#define E(N,S) \
|
|
if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;}
|
|
CmppStmt_map(E) (void)0;
|
|
#undef E
|
|
if( pi->db.dbh ){
|
|
if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){
|
|
sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL)
|
|
/* ignoring error */;
|
|
}
|
|
sqlite3_close(pi->db.dbh);
|
|
pi->db.dbh = 0;
|
|
}
|
|
cmpp__delim_list_reuse(&pi->delim.d);
|
|
cmpp__delim_list_reuse(&pi->delim.at);
|
|
//why? cmpp_b_list_reuse(&pi->cache.buf);
|
|
cmpp__err_clear(pp);
|
|
{/* Zero out pi but save some pieces for later, when pp is
|
|
cmpp_dtor()'d */
|
|
cmpp_pimpl const tmp = *pi;
|
|
*pi = cmpp_pimpl_empty;
|
|
pi->db = tmp.db /* restore db.zName */;
|
|
pi->recycler = tmp.recycler;
|
|
pi->policy = tmp.policy;
|
|
pi->delim = tmp.delim;
|
|
pi->mod.sohList = tmp.mod.sohList;
|
|
cmpp__wipe_policies(pp);
|
|
pi->flags.allocStamp = tmp.flags.allocStamp;
|
|
pi->flags.newFlags = tmp.flags.newFlags;
|
|
pi->flags.needsLazyInit = true;
|
|
}
|
|
}
|
|
|
|
static void cmpp__delim_list_cleanup(cmpp__delim_list *li);
|
|
|
|
CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){
|
|
if( pp ){
|
|
cmpp__pi(pp);
|
|
cmpp_reset(pp);
|
|
cmpp_mfree(pi->db.zName);
|
|
PodList__atpol_finalize(&cmpp__epol(pp,at));
|
|
assert(!cmpp__epol(pp,at).na);
|
|
PodList__unpol_finalize(&cmpp__epol(pp,un));
|
|
assert(!cmpp__epol(pp,un).na);
|
|
cmpp_b_list_cleanup(&pi->recycler.buf);
|
|
cmpp__delim_list_cleanup(&pi->delim.d);
|
|
cmpp__delim_list_cleanup(&pi->delim.at);
|
|
for( cmpp_args_pimpl * apNext = 0,
|
|
* ap = pi->recycler.argPimpl;
|
|
ap; ap = apNext ){
|
|
apNext = ap->nextFree;
|
|
ap->nextFree = 0;
|
|
cmpp_args_pimpl_cleanup(ap);
|
|
cmpp_mfree(ap);
|
|
}
|
|
CmppSohList_close(&pi->mod.sohList);
|
|
if( &cmpp_pimpl_empty==pi->flags.allocStamp ){
|
|
pi->flags.allocStamp = 0;
|
|
cmpp_mfree(pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){
|
|
return pp ? 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) : false;
|
|
}
|
|
|
|
/** Sets ppCode if m is NULL. Returns ppCode. */
|
|
CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){
|
|
int rc;
|
|
if( pp ){
|
|
if( !m ){
|
|
//assert(!"oom");
|
|
cmpp__err(pp, CMPP_RC_OOM, 0);
|
|
}
|
|
rc = ppCode;
|
|
}else{
|
|
rc = m ? 0 : CMPP_RC_OOM;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//CxMPP_WASM_EXPORT
|
|
void *cmpp__malloc(cmpp *pp, cmpp_size_t n){
|
|
void *p = 0;
|
|
if( 0==ppCode ){
|
|
p = cmpp_malloc(n);
|
|
cmpp_check_oom(pp, p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
If ppCode is not 0 then it flushes pp's output channel. If that
|
|
fails, it sets ppCode. Returns ppCode.
|
|
*/
|
|
static int cmpp__flush(cmpp *pp){
|
|
if( !ppCode && pp->pimpl->out.flush ){
|
|
int const rc = pp->pimpl->out.flush(pp->pimpl->out.state);
|
|
if( rc && !ppCode ){
|
|
cmpp_err_set(pp, rc, "Flush failed.");
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp__out_close(cmpp *pp){
|
|
cmpp__flush(pp)/*ignoring result*/;
|
|
cmpp_outputer_cleanup(&pp->pimpl->out);
|
|
pp->pimpl->out = cmpp_pimpl_empty.out;
|
|
}
|
|
|
|
int cmpp__out_fopen(cmpp *pp, const char *zName){
|
|
cmpp__out_close(pp);
|
|
if( !ppCode ){
|
|
cmpp_FILE * const f = cmpp_fopen(zName, "wb");
|
|
if( f ){
|
|
ppCode = 0;
|
|
pp->pimpl->out = cmpp_outputer_FILE;
|
|
pp->pimpl->out.state = f;
|
|
pp->pimpl->out.name = zName;
|
|
}else{
|
|
ppCode = cmpp__err(
|
|
pp, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Error opening file %s", zName
|
|
);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw,
|
|
const char * zName,
|
|
const char * zMode){
|
|
int const rc = FileWrapper_open(fw, zName, zMode);
|
|
if( rc ){
|
|
cmpp__err(pp, rc, "Error %s opening file [%s] "
|
|
"with mode [%s]",
|
|
cmpp_rc_cstr(rc), zName, zMode);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){
|
|
assert( fw->pFile );
|
|
int const rc = FileWrapper_slurp(fw, 1);
|
|
if( rc ){
|
|
cmpp__err(pp, rc, "Error %s slurping file %s",
|
|
cmpp_rc_cstr(rc), fw->zName);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void g_stderrv(char const *zFmt, va_list va){
|
|
vfprintf(0 ? stdout : stderr, zFmt, va);
|
|
}
|
|
|
|
void g_stderr(char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
g_stderrv(zFmt, va);
|
|
va_end(va);
|
|
}
|
|
|
|
CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){
|
|
return (char const *)cmpp__dx_zdelim(dx);
|
|
}
|
|
|
|
int cmpp__out2(cmpp *pp, cmpp_outputer *pOut,
|
|
void const *z, cmpp_size_t n){
|
|
assert( pOut );
|
|
if( !ppCode && pOut->out && n ){
|
|
int const rc = pOut->out(pOut->state, z, n);
|
|
if( rc ){
|
|
cmpp__err(pp, rc,
|
|
"Write of %" CMPP_SIZE_T_PFMT
|
|
" bytes to output stream failed.", n);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_out_raw)(cmpp_dx * dx, void const *z, cmpp_size_t n){
|
|
if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode;
|
|
return cmpp__out2(dx->pp, &dx->pp->pimpl->out, z, n);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){
|
|
assert( out );
|
|
if( !ppCode && zFmt && *zFmt && out->out ){
|
|
char * s = sqlite3_vmprintf(zFmt, va);
|
|
if( 0==cmpp_check_oom(pp, s) ){
|
|
cmpp__out2(pp, out, s, cmpp__strlen(s, -1));
|
|
}
|
|
cmpp_mfree(s);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){
|
|
assert( out );
|
|
if( !ppCode && zFmt && *zFmt && out->out ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp_outfv2(pp, out, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){
|
|
return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){
|
|
if( !ppCode ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_outf)(cmpp_dx *dx, char const *zFmt, ...){
|
|
if( !dxppCode && zFmt && *zFmt && dx->pp->pimpl->out.out ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp_outfv(dx->pp, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
static int cmpp__affirm_undef_policy(cmpp *pp,
|
|
unsigned char const *zName,
|
|
cmpp_size_t nName){
|
|
if( 0==ppCode
|
|
&& cmpp_unpol_ERROR==cmpp__policy(pp,un) ){
|
|
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
|
|
"Key '%.*s' was not found and the undefined-value "
|
|
"policy is 'error'.",
|
|
(int)nName, zName);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n, cmpp_atpol_e atPolicy){
|
|
enum state_e {
|
|
/* looking for @token@ opening @ */
|
|
state_opening,
|
|
/* looking for @token@ closing @ */
|
|
state_closing
|
|
};
|
|
cmpp__pi(pp);
|
|
if( ppCode ) return ppCode;
|
|
if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at);
|
|
assert( cmpp_atpol_invalid!=atPolicy );
|
|
unsigned char const *zLeft = zFrom;
|
|
unsigned char const * const zEnd = zFrom + n;
|
|
unsigned char const *z =
|
|
(cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy)
|
|
? zEnd
|
|
: zLeft;
|
|
unsigned char const chEol = (unsigned char)'\n';
|
|
cmpp__delim const * delim =
|
|
cmpp__delim_list_get(&pp->pimpl->delim.at);
|
|
if( !delim && z<zEnd ){
|
|
return cmpp_err_set(pp, CMPP_RC_ERROR,
|
|
"@token@ delimiter stack is empty.");
|
|
}
|
|
enum state_e state = state_opening;
|
|
cmpp_b * const bCall = cmpp_b_borrow(pp);
|
|
cmpp_b * const bVal = cmpp_b_borrow(pp);
|
|
if( !bCall || !bVal ) return ppCode;
|
|
if(0) g_warn("looking to expand from %d bytes: [%.*s]", (int)n,
|
|
(int)n,zLeft);
|
|
if( !pOut ){
|
|
if( 0 && atPolicy!=cmpp__policy(pp,at) ){
|
|
/* This might be too strict. It was initially in place to ensure
|
|
that we did not _accidentally_ do @token@ parsing to the main
|
|
output channel. We frequently use it internally on other
|
|
output channels to buffer/build results.
|
|
|
|
Advantage to removing this check: #query could impose
|
|
its own at-policy without having to use an intermediary
|
|
buffer.
|
|
|
|
Disadvantage: less protection against accidentally
|
|
@-filtering the output when we shouldn't.
|
|
*/
|
|
return cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"%s(): when sending to the default "
|
|
"output channel, the @policy must be "
|
|
"cmpp_atpol_CURRENT.", __func__);
|
|
}
|
|
pOut = &pi->out;
|
|
}
|
|
assert( pi->dx ? !cmpp_dx_is_eliding(pi->dx) : 1 );
|
|
|
|
#define tflush \
|
|
if(z>zEnd) z=zEnd; \
|
|
if(zLeft<z){ \
|
|
if(0) g_warn("flush %d [%.*s]", (int)(z-zLeft), (int)(z-zLeft), zLeft); \
|
|
cmpp__out2(pp, pOut, zLeft, (z-zLeft)); \
|
|
} zLeft = z
|
|
cmpp_dx_pimpl * const dxp = pp->pimpl->dx ? pp->pimpl->dx->pimpl : NULL;
|
|
for( ; z<zEnd && 0==ppCode; ++z ){
|
|
zLeft = z;
|
|
for( ;z<zEnd && 0==ppCode; ++z ){
|
|
again:
|
|
if( chEol==*z ){
|
|
#if 0
|
|
broken;
|
|
if( dxp && dxp->flags.countLines ){
|
|
++dxp->lineNo;
|
|
}
|
|
#endif
|
|
state = state_opening;
|
|
continue;
|
|
}
|
|
if( state_opening==state ){
|
|
if( z + delim->open.n < zEnd
|
|
&& 0==memcmp(z, delim->open.z, delim->open.n) ){
|
|
tflush;
|
|
z += delim->open.n;
|
|
if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
|
|
if( 0 ){
|
|
g_warn("\nzLeft..=[%s]\nz=[%s]", zLeft, z);
|
|
}
|
|
state = state_closing;
|
|
#if 1
|
|
/* Handle call of @[directive ...args]@
|
|
|
|
i'm not a huge fan of this syntax, but that may go away
|
|
if we replace the single-char separator with a pair of
|
|
opening/closing delimiters.
|
|
*/
|
|
if( z<zEnd && '['==*z ){
|
|
unsigned char const * zb = z;
|
|
cmpp_size_t nl = 0;
|
|
//g_warn("Scanning: <<%.*s>>", (int)(zEnd-zb), zb);
|
|
if( cmpp__find_closing2(pp, &zb, zEnd, &nl) ){
|
|
break;
|
|
}
|
|
//g_warn("Found: <<%.*s>>", (int)(zb+1-z), z);
|
|
if( zb + delim->close.n >= zEnd
|
|
|| 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){
|
|
serr("Expecting '%s' after closing ']'.", delim->close.z);
|
|
break;
|
|
}
|
|
if( nl && dxp && dxp->flags.countLines ){
|
|
dxp->pos.lineNo +=nl;
|
|
}
|
|
cmpp_call_str(pp, z+delim->open.n,
|
|
(zb - z - delim->open.n),
|
|
cmpp_b_reuse(bCall), 0);
|
|
if( 0==ppCode ){
|
|
cmpp__out2(pp, pOut, bCall->z, bCall->n);
|
|
state = state_opening;
|
|
zLeft = z = zb + delim->close.n + 1;
|
|
//g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z);
|
|
}
|
|
}
|
|
#endif
|
|
if( z>=zEnd ) break;
|
|
goto again /* avoid adjusting z again */;
|
|
}
|
|
}else{/*we're looking for delim->closer*/
|
|
assert( state_closing==state );
|
|
if( z + delim->close.n <= zEnd
|
|
&& 0==memcmp(z, delim->close.z, delim->close.n ) ){
|
|
/* process the ... part of @...@ */
|
|
assert( state_closing==state );
|
|
assert( zLeft<z );
|
|
assert( z<=zEnd );
|
|
if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
|
|
if( 0 ) g_warn("zLeft..=%s", zLeft);
|
|
assert( 0==memcmp(zLeft, delim->open.z, delim->open.n) );
|
|
unsigned char const *zKey =
|
|
zLeft + delim->open.n;
|
|
cmpp_ssize_t const nKey = z - zLeft - delim->open.n;
|
|
if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey);
|
|
assert( nKey>= 0 );
|
|
if( !nKey ){
|
|
serr("Empty key is not permitted in %s...%s.",
|
|
delim->open.z, delim->close.z);
|
|
break;
|
|
}
|
|
if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){
|
|
if(0){
|
|
g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n,
|
|
(int)bVal->n, bVal->z);
|
|
}
|
|
if( bVal->n ){
|
|
cmpp__out2(pp, pOut, bVal->z, bVal->n);
|
|
}else{
|
|
/* Elide it */
|
|
}
|
|
zLeft = z + delim->close.n;
|
|
assert( zLeft<=zEnd );
|
|
}else if( !ppCode ){
|
|
assert( !bVal->n );
|
|
/* No matching define . */
|
|
switch( atPolicy ){
|
|
case cmpp_atpol_ELIDE: zLeft = z + delim->close.n; break;
|
|
case cmpp_atpol_RETAIN: tflush; break;
|
|
case cmpp_atpol_ERROR:
|
|
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
|
|
"Undefined %skey%s: %.*s",
|
|
delim->open.z, delim->close.z, nKey, zKey);
|
|
break;
|
|
case cmpp_atpol_invalid:
|
|
case cmpp_atpol_CURRENT:
|
|
case cmpp_atpol_OFF:
|
|
assert(!"this shouldn't be reachable" );
|
|
cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d",
|
|
atPolicy);
|
|
break;
|
|
}
|
|
}/* process @...@ */
|
|
state = state_opening;
|
|
assert( z<=zEnd );
|
|
}/*matched a closer*/
|
|
}/*state_closer==state*/
|
|
assert( z<=zEnd );
|
|
}/*per-line loop*/
|
|
}/*outer loop*/
|
|
#if 0
|
|
if( 0==ppCode && state_closer==state ){
|
|
serr("Opening '%s' found without a closing '%s'.",
|
|
delim->open.z, delim->close.z);
|
|
}
|
|
#endif
|
|
tflush;
|
|
#undef tflush
|
|
cmpp_b_return(pp, bCall);
|
|
cmpp_b_return(pp, bVal);
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_out_expand)(cmpp_dx const * const dx,
|
|
cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n,
|
|
cmpp_atpol_e atPolicy){
|
|
if( dxppCode || cmpp_dx_is_eliding(dx) ) return dxppCode;
|
|
return cmpp__out_expand(dx->pp, pOut, zFrom, n, atPolicy);
|
|
}
|
|
|
|
CmppLvl * CmppLvl_get(cmpp_dx const *dx){
|
|
return dx->pimpl->dxLvl.n
|
|
? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1]
|
|
: 0;
|
|
}
|
|
|
|
static const CmppLvl CmppLvl_empty = CmppLvl_empty_m;
|
|
|
|
CmppLvl * CmppLvl_push(cmpp_dx *dx){
|
|
CmppLvl * p = 0;
|
|
if( !dxppCode ){
|
|
CmppLvl * const pPrev = CmppLvl_get(dx);
|
|
p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl);
|
|
if( p ){
|
|
*p = CmppLvl_empty;
|
|
p->lineNo = dx->pimpl->dline.lineNo;
|
|
//p->d = dx->d;
|
|
if( pPrev ){
|
|
p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags);
|
|
//if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE;
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){
|
|
CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl);
|
|
}
|
|
|
|
void CmppLvl_elide(CmppLvl *lvl, bool on){
|
|
if( on ) lvl->flags |= CmppLvl_F_ELIDE;
|
|
else lvl->flags &= ~CmppLvl_F_ELIDE;
|
|
}
|
|
|
|
bool CmppLvl_is_eliding(CmppLvl const *lvl){
|
|
return lvl && !!(lvl->flags & CmppLvl_F_ELIDE);
|
|
}
|
|
|
|
#if 0
|
|
void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){
|
|
CmppLvl_elide(CmppLvl_get(dx), on);
|
|
}
|
|
#endif
|
|
|
|
bool cmpp_dx_is_eliding(cmpp_dx const *dx){
|
|
return CmppLvl_is_eliding(CmppLvl_get(dx));
|
|
}
|
|
|
|
|
|
char * cmpp_str_finish(cmpp *pp, sqlite3_str *s, int * n){
|
|
char * z = 0;
|
|
int const rc = sqlite3_str_errcode(s);
|
|
cmpp__db_rc(pp, rc, "sqlite3_str_errcode()");
|
|
if(0==rc){
|
|
int const nStr = sqlite3_str_length(s);
|
|
if(n) *n = nStr;
|
|
z = sqlite3_str_finish(s);
|
|
if( !z ){
|
|
assert( 0==nStr && "else rc!=0" );
|
|
}
|
|
}else{
|
|
cmpp_mfree( sqlite3_str_finish(s) );
|
|
}
|
|
return z;
|
|
}
|
|
|
|
int cmpp__bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val){
|
|
return ppCode
|
|
? ppCode
|
|
: cmpp__db_rc(pp, sqlite3_bind_int64(pStmt, col, val),
|
|
"from cmpp__bind_int()");
|
|
}
|
|
|
|
int cmpp__bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col,
|
|
int64_t val){
|
|
unsigned char buf[32];
|
|
snprintf((char *)buf, sizeof(buf), "%" PRIi64, val);
|
|
return cmpp__bind_textn(pp, pStmt, col, buf, -1);
|
|
}
|
|
|
|
int cmpp__bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col){
|
|
return ppCode
|
|
? ppCode
|
|
: cmpp__db_rc(pp, sqlite3_bind_null(pStmt, col),
|
|
"from cmpp__bind_null()");
|
|
}
|
|
|
|
static int cmpp__bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr, cmpp_ssize_t n,
|
|
void (*dtor)(void *)){
|
|
if( 0==ppCode ){
|
|
cmpp__db_rc(
|
|
pp, (zStr && n)
|
|
? sqlite3_bind_text(pStmt, col,
|
|
(char const *)zStr,
|
|
(int)n, dtor)
|
|
: sqlite3_bind_null(pStmt, col),
|
|
sqlite3_sql(pStmt)
|
|
);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__bind_textn(cmpp *pp, sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr, cmpp_ssize_t n){
|
|
return cmpp__bind_textx(pp, pStmt, col, zStr, (int)n,
|
|
SQLITE_TRANSIENT);
|
|
}
|
|
|
|
int cmpp__bind_text(cmpp *pp, sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr){
|
|
return cmpp__bind_textn(pp, pStmt, col, zStr, -1);
|
|
}
|
|
|
|
#if 0
|
|
int cmpp__bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
|
|
const char * zFmt, ...){
|
|
if( 0==p->err.code ){
|
|
int rc;
|
|
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
|
|
int n = 0;
|
|
char * z;
|
|
va_list va;
|
|
if( !str ) return ppCode;
|
|
va_start(va,zFmt);
|
|
sqlite3_str_vappendf(str, zFmt, va);
|
|
va_end(va);
|
|
z = cmpp_str_finish(str, &n);
|
|
cmpp__db_rc(
|
|
pp, z
|
|
? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free)
|
|
: sqlite3_bind_null(pStmt, col),
|
|
sqlite3_sql(pStmt)
|
|
);
|
|
cmpp_mfree(z);
|
|
}
|
|
return p->err.code;
|
|
}
|
|
#endif
|
|
|
|
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out,
|
|
char const *zName){
|
|
cmpp__pi(pp);
|
|
cmpp_outputer_cleanup(&pi->out);
|
|
if( out ) pi->out = *out;
|
|
else pi->out = cmpp_outputer_empty;
|
|
pi->out.name = zName;
|
|
}
|
|
|
|
void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
|
|
cmpp_outputer *oPrev){
|
|
if( oPrev ){
|
|
*oPrev = pp->pimpl->out;
|
|
}
|
|
pp->pimpl->out = *oNew;
|
|
}
|
|
|
|
#if 0
|
|
static void delim__list_dump(cmpp const *pp){
|
|
cmpp__delim_list const *li = &pp->pimpl->delim.d;
|
|
if( li->n ){
|
|
g_warn0("delimiter stack:");
|
|
for(cmpp_size_t i = 0; i < li->n; ++i ){
|
|
g_warn("#%d: %s", (int)i, li->list[i].z);
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
static bool cmpp__valid_delim(cmpp * const pp,
|
|
char const *z,
|
|
char const *zEnd){
|
|
char const * const zB = z;
|
|
for( ; z < zEnd; ++z ){
|
|
if( *z<33 || 127==*z ){
|
|
cmpp_err_set(pp, CMPP_RC_SYNTAX,
|
|
"Delimiters may not contain "
|
|
"control characters.");
|
|
return false;
|
|
}
|
|
}
|
|
if( zB==z ){
|
|
cmpp_err_set(pp, CMPP_RC_SYNTAX,
|
|
"Delimiters may not be empty.");
|
|
}
|
|
return z>zB;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){
|
|
if( ppCode ) return ppCode;
|
|
unsigned n;
|
|
if( zDelim ){
|
|
n = cmpp__strlen(zDelim, -1);
|
|
if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){
|
|
return ppCode;
|
|
}else if( n>12 /* arbitrary but seems sensible enough */ ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Invalid delimiter (too long): %s", zDelim);
|
|
}
|
|
}
|
|
cmpp__pi(pp);
|
|
if( pi->delim.d.n ){
|
|
cmpp__delim * const delim = cmpp__pp_delim(pp);
|
|
if( !cmpp_check_oom(pp, delim) ){
|
|
cmpp__delim_cleanup(delim);
|
|
if( zDelim ){
|
|
delim->open.n = n;
|
|
delim->open.z = delim->zOwns =
|
|
(unsigned char*)sqlite3_mprintf("%.*s", n, zDelim);
|
|
cmpp_check_oom(pp, delim->zOwns);
|
|
}else{
|
|
assert( delim->open.z );
|
|
assert( !delim->zOwns );
|
|
assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 );
|
|
}
|
|
}
|
|
}else{
|
|
assert(!"Cannot set delimiter on an empty stack!");
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"Directive delimter stack is empty.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){
|
|
cmpp__delim const * d = cmpp__pp_delim(pp);
|
|
if( !d ) d = &cmpp__delim_empty;
|
|
*zDelim = (char const *)d->open.z;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){
|
|
cmpp__delim * const d =
|
|
cmpp__delim_list_push(pp, &pp->pimpl->delim.d);
|
|
if( d && cmpp_delimiter_set(pp, zDelim) ){
|
|
cmpp__delim_list_pop(&pp->pimpl->delim.d);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){
|
|
cmpp__delim_list * const li = &pp->pimpl->delim.d;
|
|
if( li->n ){
|
|
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
|
|
cmpp__delim_list_pop(li);
|
|
if( 0 && li->n ){
|
|
g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp));
|
|
}
|
|
}else if( !ppCode ){
|
|
assert(!"Attempt to pop an empty delimiter stack.");
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"Cannot pop an empty delimiter stack.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp,
|
|
char const *zOpen,
|
|
char const *zClose){
|
|
if( 0==ppCode ){
|
|
cmpp__pi(pp);
|
|
cmpp__delim * const d = pi->delim.at.n
|
|
? &pi->delim.at.list[pi->delim.at.n-1]
|
|
: NULL;
|
|
assert( d );
|
|
if( !d ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"@token@ delimiter stack is currently empty.");
|
|
}
|
|
if( 0==zOpen ){
|
|
zOpen = (char const *)delimAtDefault.open.z;
|
|
zClose = (char const *)delimAtDefault.close.z;
|
|
}else if( 0==zClose ){
|
|
zClose = zOpen;
|
|
}
|
|
cmpp_size_t const nO = cmpp__strlen(zOpen, -1);
|
|
cmpp_size_t const nC = cmpp__strlen(zClose, -1);
|
|
assert( zOpen && zClose );
|
|
if( !cmpp__valid_delim(pp, zOpen, zOpen+nO)
|
|
|| !cmpp__valid_delim(pp, zClose, zClose+nC) ){
|
|
return ppCode;
|
|
}
|
|
cmpp_b b = cmpp_b_empty
|
|
/* Don't use cmpp_b_borrow() here because we'll unconditionally
|
|
transfer ownership of b.z to d. */;
|
|
if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){
|
|
#ifndef NDEBUG
|
|
unsigned char const * const zReallocCheck = b.z;
|
|
#endif
|
|
/* Copy the open/close tokens to a single string to simplify
|
|
management. */
|
|
cmpp_b_append4(pp, &b, zOpen, nO);
|
|
cmpp_b_append_ch(&b, '\0');
|
|
cmpp_b_append4(pp, &b, zClose, nC);
|
|
assert( zReallocCheck==b.z
|
|
&& "Else buffer was not properly pre-sized" );
|
|
cmpp__delim_cleanup(d);
|
|
d->open.z = b.z;
|
|
d->open.n = nO;
|
|
d->close.z = d->open.z + nO + 1/*NUL*/;
|
|
d->close.n = nC;
|
|
d->zOwns = b.z;
|
|
b = cmpp_b_empty /* transfer memory ownership */;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen,
|
|
char const *zClose){
|
|
cmpp__delim * const d =
|
|
cmpp__delim_list_push(pp, &pp->pimpl->delim.at);
|
|
if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){
|
|
cmpp__delim_list_pop(&pp->pimpl->delim.at);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){
|
|
cmpp__delim_list * const li = &pp->pimpl->delim.at;
|
|
if( li->n ){
|
|
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
|
|
cmpp__delim_list_pop(li);
|
|
}else if( !ppCode ){
|
|
assert(!"Attempt to pop an empty @token@ delim stack.");
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"Cannot pop an empty @token@ delimiter stack.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp,
|
|
char const **zOpen,
|
|
char const **zClose){
|
|
cmpp__delim const * d
|
|
= cmpp__delim_list_get(&pp->pimpl->delim.at);
|
|
assert( d );
|
|
if( !d ) d = &delimAtDefault;
|
|
if( zClose ) *zClose = (char const *)d->close.z;
|
|
if( zOpen ) *zOpen = (char const *)d->open.z;
|
|
}
|
|
|
|
#define cmpp__scan_int2(SZ,PFMT,Z,N,TGT) \
|
|
(N<SZ) && 1==sscanf((char const *)Z, "%" #SZ PFMT, TGT)
|
|
|
|
bool cmpp__is_int(unsigned char const *z, unsigned n,
|
|
int *pOut){
|
|
int d = 0;
|
|
return cmpp__scan_int2(16, PRIi32, z, n, pOut ? pOut : &d);
|
|
}
|
|
|
|
bool cmpp__is_int64(unsigned char const *z, unsigned n, int64_t *pOut){
|
|
int64_t d = 0;
|
|
return cmpp__scan_int2(24, PRIi64, z, n, pOut ? pOut : &d);
|
|
}
|
|
#undef cmpp__scan_int2
|
|
|
|
/**
|
|
Impl for the -Fx=filename flag.
|
|
|
|
TODO?: refactor to take an optional zVal to make it suitable for
|
|
the public API. This impl requires that zKey contain
|
|
"key=filename".
|
|
*/
|
|
static int cmpp__set_file(cmpp *pp, unsigned const char * zKey,
|
|
cmpp_ssize_t nKey){
|
|
if(ppCode) return ppCode;
|
|
CmppKvp kvp = CmppKvp_empty;
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
if( CmppKvp_parse(pp, &kvp, zKey, nKey, CmppKvp_op_eq1) ){
|
|
return ppCode;
|
|
}
|
|
sqlite3_stmt * q = 0;
|
|
FileWrapper fw = FileWrapper_empty;
|
|
if( cmpp__FileWrapper_open(pp, &fw, (char const *)kvp.v.z, "rb") ){
|
|
assert(ppCode);
|
|
return ppCode;
|
|
}
|
|
cmpp__FileWrapper_slurp(pp, &fw);
|
|
q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( q && 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, (int)kvp.k.n) ){
|
|
//g_warn("zKey=%.*s", (int)kvp.k.n, kvp.k.z);
|
|
if( pp->pimpl->flags.chompF ){
|
|
FileWrapper_chomp(&fw);
|
|
}
|
|
if( fw.nContent ){
|
|
cmpp__bind_textx(pp, q, 3, fw.zContent,
|
|
(cmpp_ssize_t)fw.nContent, sqlite3_free);
|
|
fw.zContent = 0 /* transferred ownership */;
|
|
fw.nContent = 0;
|
|
}else{
|
|
cmpp__bind_null(pp, q, 2);
|
|
}
|
|
cmpp__step(pp, q, true);
|
|
g_debug(pp,2,("define: %s%s%s\n",
|
|
kvp.k.z,
|
|
kvp.v.z ? " with value " : "",
|
|
kvp.v.z ? (char const *)kvp.v.z : ""));
|
|
}
|
|
FileWrapper_close(&fw);
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_has)(cmpp *pp, const char * zName, cmpp_ssize_t nName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false);
|
|
if( q ){
|
|
nName = cmpp__strlen(zName, nName);
|
|
cmpp__bind_textn(pp, q, 1, ustr_c(zName), nName);
|
|
if(SQLITE_ROW == cmpp__step(pp, q, true)){
|
|
rc = 1;
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
g_debug(pp,1,("has [%s] ?= %d\n",zName, rc));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_ssize_t nName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp__bind_textn(pp, q, 1, zName, nName);
|
|
assert(0==ppCode);
|
|
if(SQLITE_ROW == cmpp__step(pp, q, false)){
|
|
rc = sqlite3_column_int(q, 0);
|
|
}else{
|
|
rc = 0;
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__get_int(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName, int *pOut ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp__bind_textn(pp, q, 1, zName, nName);
|
|
assert(0==ppCode);
|
|
if(SQLITE_ROW == cmpp__step(pp, q, false)){
|
|
*pOut = sqlite3_column_int(q,0);
|
|
}else{
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__get_b(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName, cmpp_b * os, bool enforceUndefPolicy){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp__bind_textn(pp, q, 1, zName, nName);
|
|
int n = 0;
|
|
if(SQLITE_ROW == cmpp__step(pp, q, false)){
|
|
const unsigned char * z = sqlite3_column_text(q, 3);
|
|
n = sqlite3_column_bytes(q, 3);
|
|
cmpp_b_append4(pp, os, z, (cmpp_size_t)n);
|
|
rc = 1;
|
|
}else{
|
|
if( enforceUndefPolicy ){
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
rc = 0;
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
|
|
nName, zName, rc, os->n, os->z));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__get(cmpp *pp, unsigned const char * zName,
|
|
cmpp_ssize_t nName, unsigned char **zVal,
|
|
unsigned int *nVal){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp__bind_textn(pp, q, 1, zName, nName);
|
|
int n = 0;
|
|
if(SQLITE_ROW == cmpp__step(pp, q, false)){
|
|
const unsigned char * z = sqlite3_column_text(q, 3);
|
|
n = sqlite3_column_bytes(q, 3);
|
|
if( nVal ) *nVal = (unsigned)n;
|
|
*zVal = ustr_nc(sqlite3_mprintf("%.*s", n, z))
|
|
/* TODO? Return NULL for the n==0 case? */;
|
|
if( n && cmpp_check_oom(pp, *zVal) ){
|
|
assert(!*zVal);
|
|
}else{
|
|
rc = 1;
|
|
}
|
|
}else{
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
rc = 0;
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
|
|
nName, zName, rc,
|
|
*zVal ? n : 0,
|
|
*zVal ? (char const *)*zVal : "<NULL>"));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_undef)(cmpp *pp, const char * zKey,
|
|
unsigned int *nRemoved){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDel, false);
|
|
if( q ){
|
|
unsigned int const n = strlen(zKey);
|
|
cmpp__bind_textn(pp, q, 1, ustr_c(zKey), (cmpp_ssize_t)n);
|
|
cmpp__step(pp, q, true);
|
|
if( nRemoved ){
|
|
*nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh);
|
|
}
|
|
g_debug(pp,2,("undefine: %.*s\n",n, zKey));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid){
|
|
if( pRowid ) *pRowid = 0;
|
|
if( !ppCode && zDir && *zDir ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false);
|
|
if( q ){
|
|
/* TODO: normalize zDir before insertion so that a/b and a/b/
|
|
are equivalent. The relavent code is in another tree,
|
|
awaiting a decision on whether to import it or re-base cmpp
|
|
on top of that library (which would, e.g., replace cmpp_b
|
|
with that one, which is more mature).
|
|
*/
|
|
cmpp__bind_int(pp, q, 1, priority);
|
|
cmpp__bind_textn(pp, q, 2, ustr_c(zDir), -1);
|
|
int const rc = cmpp__step(pp, q, false);
|
|
if( SQLITE_ROW==rc ){
|
|
++pp->pimpl->flags.nIncludeDir;
|
|
if( pRowid ){
|
|
*pRowid = sqlite3_column_int64(q, 0);
|
|
}
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
/*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s",
|
|
rc, pRowid ? *pRowid : 0, priority, zDir);*/
|
|
g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir));
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){
|
|
return cmpp__include_dir_add(pp, zDir, 0, NULL);
|
|
}
|
|
|
|
int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
|
|
if( q ){
|
|
/* Hoop-jumping to allow this to work even if pp's in an error
|
|
state. */
|
|
int rc = sqlite3_bind_int64(q, 1, rowid);
|
|
if( 0==rc ){
|
|
rc = sqlite3_step(q);
|
|
if( SQLITE_ROW==rc ){
|
|
--pp->pimpl->flags.nIncludeDir;
|
|
rc = 0;
|
|
}else if( SQLITE_DONE==rc ){
|
|
rc = 0;
|
|
}
|
|
}
|
|
if( rc && !ppCode ){
|
|
cmpp__db_rc(pp, rc, sqlite3_sql(q));
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){
|
|
#if CMPP_ENABLE_DLLS
|
|
if( !ppCode ){
|
|
cmpp_b * const ob = &pp->pimpl->mod.path;
|
|
if( !zDirs && !ob->n ){
|
|
zDirs = getenv("CMPP_MODULE_PATH");
|
|
if( !zDirs ){
|
|
zDirs = CMPP_MODULE_PATH;
|
|
}
|
|
}
|
|
if( !zDirs || !*zDirs ) return 0;
|
|
char const * z = zDirs;
|
|
char const * const zEnd = zDirs + strlen(zDirs);
|
|
if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){
|
|
unsigned char * zo = ob->z + ob->n;
|
|
unsigned i = 0;
|
|
for( ; z < zEnd && !ppCode; ++z ){
|
|
switch( *z ){
|
|
case CMPP_PATH_SEPARATOR:
|
|
*zo++ = pp->pimpl->mod.pathSep;
|
|
break;
|
|
default:
|
|
if( 1==++i && ob->n ){
|
|
cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep);
|
|
}
|
|
*zo++ = *z;
|
|
break;
|
|
}
|
|
}
|
|
*zo = 0;
|
|
ob->n = (zo - ob->z);
|
|
}
|
|
}
|
|
return ppCode;
|
|
#else
|
|
return CMPP_RC_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){
|
|
if( 0==ppCode ){
|
|
cmpp__pi(pp);
|
|
if( pi->db.dbh ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"DB name cannot be set after db initialization.");
|
|
}
|
|
if( zName ){
|
|
char * const z = sqlite3_mprintf("%s", zName);
|
|
if( 0==cmpp_check_oom(pp, z) ){
|
|
cmpp_mfree(pi->db.zName);
|
|
pi->db.zName = z;
|
|
}
|
|
}else{
|
|
cmpp_mfree(pi->db.zName);
|
|
pi->db.zName = 0;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
bool cmpp__is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos,
|
|
bool equalIsLegal){
|
|
if( !n || n>64/*arbitrary*/ ){
|
|
if( zErrPos ) *zErrPos = 0;
|
|
return false;
|
|
}
|
|
unsigned char const * z = zName;
|
|
unsigned char const * const zEnd = zName + n;
|
|
for( ; z<zEnd; ++z ){
|
|
if( !((*z>='a' && *z<='z')
|
|
|| (*z>='A' && *z<='Z')
|
|
|| (z>zName &&
|
|
('-'==*z
|
|
/* This is gonna bite us if we extend the expresions to
|
|
support +/-. Expressions currently parse X=Y (no
|
|
spaces) as the three tokens X = Y, but we'd need to
|
|
require a space between X-Y in expressions because
|
|
'-' is a legal symbol character. i've looked at
|
|
making '-' illegal but it's just too convenient for
|
|
use in define keys. Once one is used to
|
|
tcl-style-naming of stuff, it's painful to have to go
|
|
back to snake_case.
|
|
*/
|
|
|| (*z>='0' && *z<='9')))
|
|
|| (*z>='.' && *z<='/')
|
|
|| (*z==':')
|
|
|| (*z=='_')
|
|
|| (equalIsLegal && z>zName && '='==*z)
|
|
|| (*z & 0x80)
|
|
) ){
|
|
if( zErrPos ) *zErrPos = z;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cmpp_is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos){
|
|
return cmpp__is_legal_key(zName, n, zErrPos, false);
|
|
}
|
|
|
|
int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_ssize_t nKey, bool permitEqualSign){
|
|
if( !ppCode ){
|
|
unsigned char const *zAt = 0;
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){
|
|
cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Illegal character 0x%02x in key [%.*s]",
|
|
(int)*zAt, nKey, zKey);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_next_chunk)(unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
unsigned char chSep,
|
|
cmpp_size_t *pCounter){
|
|
assert( zPos );
|
|
assert( *zPos );
|
|
assert( zEnd );
|
|
if( *zPos >= zEnd ) return false;
|
|
unsigned char const * z = *zPos;
|
|
while( z<zEnd ){
|
|
if( chSep==*z ){
|
|
++z;
|
|
if( pCounter ) ++*pCounter;
|
|
break;
|
|
}
|
|
++z;
|
|
}
|
|
if( *zPos==z ) return false;
|
|
*zPos = z;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Scans dx for the next newline. It updates ln to contain the result,
|
|
which includes the trailing newline unless EOF is hit before a
|
|
newline.
|
|
|
|
Returns true if it has input, false at EOF. It has no error
|
|
conditions beyond invalid dx->pimpl state, which "doesn't happen".
|
|
*/
|
|
//static
|
|
bool cmpp__dx_next_line(cmpp_dx * const dx, CmppDLine *ln){
|
|
assert( !dxppCode );
|
|
cmpp_dx_pimpl * const dxp = dx->pimpl;
|
|
if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
|
|
assert( dxp->zEnd );
|
|
if( dxp->pos.z>=dxp->zEnd ){
|
|
return false;
|
|
}
|
|
assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
|
|
&& "Else we've mismanaged something.");
|
|
cmpp__dx_pi(dx);
|
|
ln->lineNo = dpi->pos.lineNo;
|
|
ln->zBegin = dpi->pos.z;
|
|
ln->zEnd = ln->zBegin;
|
|
return cmpp_next_chunk(&ln->zEnd, dpi->zEnd, (unsigned char)'\n',
|
|
&dpi->pos.lineNo);
|
|
}
|
|
|
|
/**
|
|
Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any
|
|
non-delimiter output found along the way to dx->pp's output
|
|
channel.
|
|
|
|
This updates dx->pimpl->pos.z and dx->pimpl->pos.lineNo as it goes.
|
|
|
|
If a delimiter is found, it sets *gotOne to true and updates
|
|
dx->pimpl->dline to point to the remainder of that line. On no match
|
|
*gotOne will be false and EOF will have been reached.
|
|
|
|
Returns dxppCode. If it returns non-0 then the state of dx's
|
|
tokenization pieces are unspecified. i.e. it's illegal to call this
|
|
again without a reset.
|
|
*/
|
|
static int cmpp_dx_delim_search(cmpp_dx * const dx, bool * gotOne){
|
|
if( dxppCode ) return dxppCode;
|
|
cmpp_dx_pimpl * const dxp = dx->pimpl;
|
|
if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
|
|
if( dxp->pos.z>=dxp->zEnd ){
|
|
*gotOne = false;
|
|
return 0;
|
|
}
|
|
assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
|
|
&& "Else we've mismanaged something.");
|
|
cmpp__pi(dx->pp);
|
|
cmpp__delim const * const delim = cmpp__dx_delim(dx);
|
|
if(!delim) {
|
|
return cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"The directive delimiter stack is empty.");
|
|
}
|
|
unsigned char const * const zD = delim->open.z;
|
|
unsigned short const nD = delim->open.n;
|
|
unsigned char const * const zEnd = dxp->zEnd;
|
|
unsigned char const * zLeft = dxp->pos.z;
|
|
unsigned char const * z = zLeft;
|
|
assert(zD);
|
|
assert(nD);
|
|
#if 0
|
|
assert( 0==*zEnd && "Else we'll misinteract with strcspn()" );
|
|
if( *zEnd ){
|
|
return cmpp_dx_err(dx, CMPP_RC_RANGE,
|
|
"Input must be NUL-terminated.");
|
|
}
|
|
#endif
|
|
++dxp->flags.countLines;
|
|
while( z<zEnd && '\n'==*z ){
|
|
/* Skip leading newlines. We have to delay the handling of
|
|
leading whitepace until later so that:
|
|
|
|
| #if
|
|
|^^ those two spaces do not get emitted.
|
|
*/
|
|
++dxp->pos.lineNo;
|
|
++z;
|
|
}
|
|
#define tflush \
|
|
if( z>zEnd ) z=zEnd; \
|
|
if( z>zLeft && cmpp_dx_out_expand(dx, &pi->out, zLeft, \
|
|
(cmpp_size_t)(z-zLeft), \
|
|
cmpp_atpol_CURRENT) ){ \
|
|
--dxp->flags.countLines; \
|
|
return dxppCode; \
|
|
} zLeft = z
|
|
|
|
CmppDLine * const dline = &dxp->dline;
|
|
bool atBOL = true /* At the start of a line? Successful calls to
|
|
this always end at either BOL or EOF. */;
|
|
if( 0 ){
|
|
g_warn("scanning... <<%.*s...>>",
|
|
(zEnd-z)>20?20:(zEnd-z), z);
|
|
}
|
|
while( z<zEnd && !dxppCode ){
|
|
if( !atBOL ){
|
|
/* We're continuing the scan of a line on which the first bytes
|
|
didn't match a delimiter. */
|
|
while( z<zEnd ){
|
|
while((z<zEnd && '\n'==*z)
|
|
|| (z+1<zEnd && '\r'==*z && '\n'==z[1]) ){
|
|
++dxp->pos.lineNo;
|
|
z += 1 + ('\r'==*z);
|
|
atBOL = true;
|
|
}
|
|
if( atBOL ){
|
|
break;
|
|
}
|
|
++z;
|
|
}
|
|
if( !atBOL ) break;
|
|
}
|
|
if( 0 ){
|
|
g_warn("at BOL... <<%.*s...>>",
|
|
(zEnd-z) > 20 ? 20 : (zEnd-z), z);
|
|
}
|
|
|
|
/* We're at BOL. Check for a delimiter with optional leading
|
|
spaces. */
|
|
tflush;
|
|
cmpp_skip_space(&z, zEnd);
|
|
int const skip = cmpp_isnl(z, zEnd);
|
|
if( skip ){
|
|
/* Special case: a line comprised solely of whitespace. If we
|
|
don't catch this here, we won't recognize a delimiter which
|
|
starts on the next line. */
|
|
tflush;
|
|
z += skip;
|
|
++dxp->pos.lineNo;
|
|
continue;
|
|
}
|
|
if( 0 ){
|
|
g_warn("at BOL... <<%.*s...>>",
|
|
(zEnd-z) > 20 ? 20 : (zEnd-z), z);
|
|
}
|
|
if( z + nD>zEnd ){
|
|
/* Too short for a delimiter. We'll catch the z+nD==zEnd corner
|
|
case in a moment. */
|
|
z = zEnd;
|
|
break;
|
|
}
|
|
if( memcmp(z, zD, nD) ){
|
|
/* Not a delimiter. Keep trying. */
|
|
atBOL = false;
|
|
++z;
|
|
continue;
|
|
}
|
|
|
|
/* z now points to a delimiter which sits at the start of a line
|
|
(ignoring leading spaces). */
|
|
z += nD /* skip the delimiter */;
|
|
cmpp_skip_space(&z, zEnd) /* skip spaces immediately following
|
|
the delimiter. */;
|
|
if( z>=zEnd || cmpp_isnl(z, zEnd) ){
|
|
dxserr("No directive name found after %s.", zD);
|
|
/* We could arguably treat this as no match and pass this line
|
|
through as-is but that currently sounds like a pothole. */
|
|
break;
|
|
}
|
|
/* Set up dx->pimpl->dline to encompass the whole directive line sans
|
|
delimiter and leading spaces. */
|
|
dline->zBegin = z
|
|
/* dx->pimpl->dline starts at the directive name and extends until the
|
|
next EOL/EOF. We don't yet know if it's a legal directive
|
|
name - cmpp_dx_next() figures that part out. */;
|
|
dline->lineNo = dxp->pos.lineNo;
|
|
/* Now find the end of the line or EOF, accounting for
|
|
backslash-escaped newlines and _not_ requiring backslashes to
|
|
escape newlines inside of {...}, (...), or [...]. We could also
|
|
add the double-quotes to this, but let's start without that. */
|
|
bool keepGoing = true;
|
|
zLeft = z;
|
|
while( keepGoing && z<zEnd ){
|
|
switch( *z ){
|
|
case '(': case '{': case '[':{
|
|
zLeft = z;
|
|
if( cmpp__find_closing2(dx->pp, &z, zEnd, &dxp->pos.lineNo) ){
|
|
--dxp->flags.countLines;
|
|
return dxppCode;
|
|
}
|
|
++z /* group-closing character */;
|
|
/*
|
|
Sidebar: this only checks top-level groups. It is
|
|
possible that an inner group is malformed, e.g.:
|
|
|
|
{ ( }
|
|
|
|
It's also possible that that's perfectly legal for a
|
|
specific use case.
|
|
|
|
Such cases will, if they're indeed syntax errors, be
|
|
recognized as such in the arguments-parsing
|
|
steps. Catching them here would require that we
|
|
recursively validate all of [zLeft,z) for group
|
|
constructs, whereas that traversal happens as a matter of
|
|
course in argument parsing. It would also require the
|
|
assumption that such constructs are not legal, which is
|
|
invalid once we start dealing with free-form input like
|
|
#query SQL.
|
|
*/
|
|
break;
|
|
}
|
|
case '\n':
|
|
assert( z!=dline->zBegin && "Checked up above" );
|
|
if( '\\'==z[-1]
|
|
|| (z>zLeft+1 && '\r'==z[-1] && '\\'==z[-2]) ){
|
|
/* Backslash-escaped newline. */
|
|
++z;
|
|
}else{
|
|
/* EOL for this directive. */
|
|
keepGoing = false;
|
|
}
|
|
++dxp->pos.lineNo;
|
|
break;
|
|
default:
|
|
++z;
|
|
}
|
|
}
|
|
assert( z==zEnd || '\n'==*z );
|
|
dline->zEnd = z;
|
|
dxp->pos.z = dline->zEnd + 1
|
|
/* For the next call to this function, skip the trailing newline
|
|
or EOF */;
|
|
assert( dline->zBegin < dline->zEnd && "Was checked above" );
|
|
if( 0 ){
|
|
g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin),
|
|
(dline->zEnd-dline->zBegin), dline->zBegin);
|
|
}
|
|
*gotOne = true;
|
|
assert( !dxppCode );
|
|
--dxp->flags.countLines;
|
|
return 0;
|
|
}
|
|
/* No directives found. We're now at EOL or EOF. Flush any pending
|
|
LHS content. */
|
|
tflush;
|
|
dx->pimpl->pos.z = z;
|
|
*gotOne = false;
|
|
return dxppCode;
|
|
#undef tflush
|
|
}
|
|
|
|
int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey,
|
|
cmpp_ssize_t nKey, CmppKvp_op_e opPolicy){
|
|
if(ppCode) return ppCode;
|
|
char chEq = 0;
|
|
char opLen = 0;
|
|
*p = CmppKvp_empty;
|
|
p->k.z = zKey;
|
|
p->k.n = cmpp__strlenu(zKey, nKey);
|
|
switch( opPolicy ){
|
|
case CmppKvp_op_none:// break;
|
|
case CmppKvp_op_eq1:
|
|
chEq = '=';
|
|
opLen = 1;
|
|
break;
|
|
default:
|
|
assert(!"don't use these");
|
|
/* no longer todo: ==, !=, <=, <, >, >= */
|
|
chEq = '=';
|
|
opLen = 1;
|
|
break;
|
|
}
|
|
assert( chEq );
|
|
p->op = CmppKvp_op_none;
|
|
unsigned const char * const zEnd = p->k.z + p->k.n;
|
|
for(unsigned const char * zPos = p->k.z ; *zPos && zPos<zEnd ; ++zPos) {
|
|
if( chEq==*zPos ){
|
|
if( CmppKvp_op_none==opPolicy ){
|
|
cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Illegal operator in key: %s", zKey);
|
|
}else{
|
|
p->op = CmppKvp_op_eq1;
|
|
p->k.n = (unsigned)(zPos - ustr_c(zKey));
|
|
zPos += opLen;
|
|
assert( zPos <= zEnd );
|
|
p->v.z = zPos;
|
|
p->v.n = (unsigned)(zEnd - zPos);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
cmpp__legal_key_check(pp, p->k.z, p->k.n, false);
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
|
|
cmpp_size_t * nAlloc, unsigned sizeOfEntry){
|
|
int rc = pp ? ppCode : 0;
|
|
if( 0==rc && nDesired > *nAlloc ){
|
|
cmpp_size_t const nA = nDesired < 10 ? 10 : nDesired;
|
|
void * const p = cmpp_mrealloc(*list, sizeOfEntry * nA);
|
|
rc = cmpp_check_oom(pp, p);
|
|
if( p ){
|
|
memset((unsigned char *)p +
|
|
(sizeOfEntry * *nAlloc), 0,
|
|
sizeOfEntry * (nA - *nAlloc));
|
|
*list = p;
|
|
*nAlloc = nA;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li){
|
|
CmppLvl * p = 0;
|
|
assert( li->list ? li->nAlloc : 0==li->nAlloc );
|
|
if( 0==ppCode
|
|
&& 0==CmppLvlList_reserve(pp, li,
|
|
cmpp__li_reserve1_size(li,5)) ){
|
|
p = li->list[li->n];
|
|
if( !p ){
|
|
p = cmpp__malloc(pp, sizeof(*p));
|
|
}
|
|
if( p ){
|
|
li->list[li->n++] = p;
|
|
*p = CmppLvl_empty;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li,
|
|
CmppLvl * const lvl){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
if( lvl==li->list[li->n-1] ){
|
|
*lvl = CmppLvl_empty;
|
|
cmpp_mfree(lvl);
|
|
li->list[--li->n] = 0;
|
|
}else{
|
|
if( pp ){
|
|
cmpp_err_set(pp, CMPP_RC_ASSERT,
|
|
"Misuse of %s(): not passed the top of the stack. "
|
|
"The CmppLvl stack is now out of whack.",
|
|
__func__);
|
|
}else{
|
|
cmpp__fatal("Misuse of %s(): not passed the top of the stack",
|
|
__func__);
|
|
}
|
|
/* do not free it - CmppLvlList_cleanup() will catch it. */
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmppLvlList_cleanup(CmppLvlList *li){
|
|
const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m;
|
|
while( li->nAlloc ){
|
|
cmpp_mfree( li->list[--li->nAlloc] );
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = CmppLvlList_empty;
|
|
}
|
|
|
|
static inline void CmppDList_entry_clean(CmppDList_entry * const e){
|
|
if( e->d.impl.dtor ){
|
|
e->d.impl.dtor( e->d.impl.state );
|
|
}
|
|
cmpp_mfree(e->zName);
|
|
*e = CmppDList_entry_empty;
|
|
}
|
|
|
|
#if 0
|
|
CmppDList * CmppDList_reuse(CmppDList *li){
|
|
while( li->n ){
|
|
CmppDList_entry_clean( li->list[--li->n] );
|
|
}
|
|
return li;
|
|
}
|
|
#endif
|
|
|
|
void CmppDList_cleanup(CmppDList *li){
|
|
static const CmppDList CmppDList_empty = CmppDList_empty_m;
|
|
while( li->n ){
|
|
CmppDList_entry_clean( li->list[--li->n] );
|
|
cmpp_mfree( li->list[li->n] );
|
|
li->list[li->n] = 0;
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = CmppDList_empty;
|
|
}
|
|
|
|
void CmppDList_unappend(CmppDList *li){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
CmppDList_entry_clean(li->list[--li->n]);
|
|
}
|
|
}
|
|
|
|
|
|
/** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */
|
|
static
|
|
int CmppDList_entry_cmp_pp(const void *p1, const void *p2){
|
|
CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1;
|
|
CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2;
|
|
return eL->d.name.n==eR->d.name.n
|
|
? memcmp(eL->d.name.z, eR->d.name.z, eL->d.name.n)
|
|
: strcmp((char const *)eL->d.name.z,
|
|
(char const *)eR->d.name.z);
|
|
}
|
|
|
|
static void CmppDList_sort(CmppDList * const li){
|
|
if( li->n>1 ){
|
|
qsort(li->list, li->n, sizeof(CmppDList_entry*),
|
|
CmppDList_entry_cmp_pp);
|
|
}
|
|
}
|
|
|
|
CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){
|
|
CmppDList_entry * p = 0;
|
|
assert( li->list ? li->nAlloc : 0==li->nAlloc );
|
|
if( 0==ppCode
|
|
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
|
|
cmpp__li_reserve1_size(li, 15),
|
|
&li->nAlloc, sizeof(p)) ){
|
|
p = li->list[li->n];
|
|
if( !p ){
|
|
li->list[li->n] = p = cmpp__malloc(pp, sizeof(*p));
|
|
}
|
|
if( p ){
|
|
++li->n;
|
|
*p = CmppDList_entry_empty;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
CmppDList_entry * CmppDList_search(CmppDList const * li,
|
|
char const *zName){
|
|
if( li->n > 2 ){
|
|
CmppDList_entry const key = {
|
|
.d = {
|
|
.name = {
|
|
.z = zName,
|
|
.n = strlen(zName)
|
|
}
|
|
}
|
|
};
|
|
CmppDList_entry const * pKey = &key;
|
|
CmppDList_entry ** pRv
|
|
= bsearch(&pKey, li->list, li->n, sizeof(li->list[0]),
|
|
CmppDList_entry_cmp_pp);
|
|
//g_warn("search in=%s out=%s", zName, (pRv ? (*pRv)->d.name.z : "<null>"));
|
|
return pRv ? *pRv : 0;
|
|
}else{
|
|
cmpp_size_t const nName = cmpp__strlen(zName, -1);
|
|
for( cmpp_size_t i = 0; i < li->n; ++i ){
|
|
CmppDList_entry * const e = li->list[i];
|
|
if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){
|
|
//g_warn("search in=%s out=%s", zName, e->d.name.z);
|
|
return e;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void cmpp__delim_cleanup(cmpp__delim *d){
|
|
cmpp__delim const dd = cmpp__delim_empty_m;
|
|
cmpp_mfree(d->zOwns);
|
|
*d = dd;
|
|
assert(!d->zOwns);
|
|
assert(d->open.z);
|
|
assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM));
|
|
assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1);
|
|
}
|
|
|
|
cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){
|
|
cmpp__delim * p = 0;
|
|
assert( li->list ? li->nAlloc : 0==li->nAlloc );
|
|
if( 0==ppCode
|
|
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
|
|
cmpp__li_reserve1_size(li,4),
|
|
&li->nAlloc, sizeof(cmpp__delim)) ){
|
|
p = &li->list[li->n++];
|
|
*p = cmpp__delim_empty;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void cmpp__delim_list_cleanup(cmpp__delim_list *li){
|
|
while( li->nAlloc ) cmpp__delim_cleanup(li->list + --li->nAlloc);
|
|
cmpp_mfree(li->list);
|
|
*li = cmpp__delim_list_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){
|
|
if( dxppCode ) return dxppCode;
|
|
|
|
CmppDLine * const tok = &dx->pimpl->dline;
|
|
if( !dx->pimpl->zBegin ){
|
|
*pGotOne = false;
|
|
return 0;
|
|
}
|
|
assert(dx->pimpl->zEnd);
|
|
assert(dx->pimpl->zEnd > dx->pimpl->zBegin);
|
|
*pGotOne = false;
|
|
cmpp_dx__reset(dx);
|
|
bool foundDelim = false;
|
|
if( cmpp_dx_delim_search(dx, &foundDelim) || !foundDelim ){
|
|
return dxppCode;
|
|
}
|
|
if( cmpp_args__init(dx->pp, &dx->pimpl->args) ){
|
|
return dxppCode;
|
|
}
|
|
cmpp_skip_space( &tok->zBegin, tok->zEnd );
|
|
g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n",
|
|
tok->lineNo,
|
|
(int)(tok->zEnd-tok->zBegin), tok->zBegin));
|
|
/* Normalize the directive's line and parse arguments */
|
|
const unsigned lineLen = (unsigned)(tok->zEnd - tok->zBegin);
|
|
if(!lineLen){
|
|
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
|
|
"Line #%u has no directive after %s",
|
|
tok->lineNo, cmpp_dx_delim(dx));
|
|
}
|
|
unsigned char const * zi = tok->zBegin /* Start of input */;
|
|
unsigned char const * ziEnd = tok->zEnd /* Input EOF */;
|
|
cmpp_b * const bufLine =
|
|
cmpp_b_reuse(&dx->pimpl->buf.line)
|
|
/* Slightly-transformed copy of the input. */;
|
|
if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){
|
|
return dxppCode;
|
|
}
|
|
unsigned char * zo = bufLine->z /* Start of output */;
|
|
unsigned char const * const zoEnd =
|
|
zo + bufLine->nAlloc /* Output EOF. */;
|
|
g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n",
|
|
tok->lineNo, lineLen, lineLen, tok->zBegin));
|
|
//memset(bufLine->z, 0, bufLine->nAlloc);
|
|
#define out(CH) if(zo==zoEnd) break; (*zo++)=CH
|
|
/*
|
|
bufLine is now populated with a copy of the whole input line.
|
|
Now normalize that buffer a bit before trying to parse it.
|
|
*/
|
|
unsigned char const * zEsc = 0;
|
|
cmpp_dx_pimpl * const pimpl = dx->pimpl;
|
|
for( ; zi<ziEnd && *zi && zo<zoEnd;
|
|
++zi ){
|
|
/* Write the line to bufLine for the upcoming args parsing to deal
|
|
with. Strip backslashes from backslash-escaped newlines. We
|
|
leave the newlines intact so that downstream error reporting
|
|
can get more precise location info. Backslashes which do not
|
|
precede a newline are retained.
|
|
*/
|
|
switch((int)*zi){
|
|
case (int)'\\':
|
|
if( !zi[1] || zi==ziEnd-1 ){
|
|
// special case: ending input with a backslash
|
|
out(*zi);
|
|
zEsc = 0;
|
|
}else if( zEsc ){
|
|
assert( zEsc==zi-1 );
|
|
/* Put them both back. */
|
|
out(*zEsc);
|
|
out(*zi);
|
|
zEsc = 0;
|
|
}else{
|
|
zEsc = zi;
|
|
}
|
|
break;
|
|
case (int)'\n':
|
|
out(*zi);
|
|
zEsc = 0;
|
|
break;
|
|
default:
|
|
if(zEsc){
|
|
assert( zEsc==zi-1 );
|
|
out(*zEsc);
|
|
zEsc = 0;
|
|
}
|
|
out(*zi);
|
|
break;
|
|
}
|
|
}
|
|
if( zo>=zoEnd ){
|
|
return cmpp_dx_err(dx, CMPP_RC_RANGE,
|
|
"Ran out of argument-processing space.");
|
|
}
|
|
*zo = 0;
|
|
#undef out
|
|
bufLine->n = (cmpp_size_t)(zo - bufLine->z);
|
|
if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
|
|
/* Line has now been normalized into bufLine->z. */
|
|
for( zo = bufLine->z; zo<zoEnd && *zo; ++zo ){
|
|
/* NULL-terminate the directive so we can search for it. */
|
|
if( cmpp_isspace(*zo) ){
|
|
*zo = 0;
|
|
break;
|
|
}
|
|
}
|
|
unsigned char * const zDirective = bufLine->z;
|
|
dx->d = cmpp__d_search3(dx->pp, (char const *)zDirective,
|
|
cmpp__d_search3_F_ALL);
|
|
if( dxppCode ){
|
|
return dxppCode;
|
|
}else if(!dx->d){
|
|
return cmpp_dx_err(dx, CMPP_RC_NOT_FOUND,
|
|
"Unknown directive at line %"
|
|
CMPP_SIZE_T_PFMT ": %.*s\n",
|
|
(unsigned)tok->lineNo,
|
|
(int)bufLine->n, bufLine->z);
|
|
}
|
|
assert( zDirective == bufLine->z );
|
|
const bool isCall
|
|
= dx->pimpl->args.pimpl->isCall
|
|
= dx->pimpl->flags.nextIsCall;
|
|
dx->pimpl->flags.nextIsCall = false;
|
|
if( isCall ){
|
|
if( cmpp_d_F_NO_CALL & dx->d->flags ){
|
|
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
|
|
"%s%s cannot be used in a [call] context.",
|
|
cmpp_dx_delim(dx),
|
|
dx->d->name.z);
|
|
}
|
|
}else if( cmpp_d_F_CALL_ONLY & dx->d->flags ){
|
|
return cmpp_dx_err(dx, CMPP_RC_TYPE,
|
|
"'%s' is a call-only directive, "
|
|
"not legal here.", dx->d->name.z);
|
|
}
|
|
if( bufLine->n > dx->d->name.n ){
|
|
dx->args.z = zDirective + dx->d->name.n + 1;
|
|
assert( dx->args.z > bufLine->z );
|
|
assert( dx->args.z <= bufLine->z+bufLine->n );
|
|
dx->args.nz = cmpp__strlenu(dx->args.z, -1);
|
|
assert( bufLine->nAlloc > dx->args.nz );
|
|
}else{
|
|
dx->args.z = ustr_c("\0");
|
|
dx->args.nz = 0;
|
|
}
|
|
if( 0 ){
|
|
g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>",
|
|
bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z);
|
|
}
|
|
cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz);
|
|
if(0){
|
|
g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}
|
|
assert( !pimpl->buf.argsRaw.n );
|
|
if( dx->args.nz ){
|
|
if( 0 ){
|
|
g_warn("lineLen=%u zargs len=%u: [%.*s]\n",
|
|
(unsigned)lineLen,
|
|
(int)dx->args.nz, (int)dx->args.nz,
|
|
dx->args.z
|
|
);
|
|
}
|
|
if( cmpp_b_append4(dx->pp, &pimpl->buf.argsRaw,
|
|
dx->args.z, dx->args.nz) ){
|
|
return dxppCode;
|
|
}
|
|
}
|
|
assert( !pimpl->args.arg0 );
|
|
assert( !pimpl->args.argc );
|
|
assert( !pimpl->args.pimpl->argOut.n );
|
|
assert( !pimpl->args.pimpl->argli.n );
|
|
assert( dx->args.z );
|
|
if( //1 || //pleases valgrind. Well, it did at one point.
|
|
!cmpp_dx_is_eliding(dx) || 0!=(cmpp_d_F_FLOW_CONTROL & dx->d->flags) ){
|
|
if( cmpp_d_F_ARGS_LIST & dx->d->flags ){
|
|
cmpp_dx_args_parse(dx, &pimpl->args);
|
|
}else if( cmpp_d_F_ARGS_RAW & dx->d->flags ){
|
|
/* Treat rest of line as one token */
|
|
cmpp_arg * const arg =
|
|
CmppArgList_append(dx->pp, &pimpl->args.pimpl->argli);
|
|
if( !arg ) return dxppCode;
|
|
pimpl->args.arg0 = arg;
|
|
pimpl->args.argc = 1;
|
|
arg->ttype = cmpp_TT_RawLine;
|
|
arg->z = pimpl->buf.argsRaw.z;
|
|
arg->n = pimpl->buf.argsRaw.n;
|
|
//g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z);
|
|
}
|
|
}
|
|
if( 0==dxppCode ){
|
|
dx->args.arg0 = pimpl->args.arg0;
|
|
dx->args.argc = pimpl->args.argc;
|
|
}
|
|
*pGotOne = true;
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP_EXPORT bool cmpp_dx_is_call(cmpp_dx * const dx){
|
|
return dx->pimpl->args.pimpl->isCall;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r,
|
|
cmpp_d ** dOut){
|
|
CmppDList_entry * e1 = 0, * e2 = 0;
|
|
bool const isCallOnly =
|
|
(cmpp_d_F_CALL_ONLY & r->opener.flags);
|
|
if( ppCode ){
|
|
goto end;
|
|
}
|
|
if( (cmpp_d_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags))
|
|
&& (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags) ){
|
|
cmpp__err(pp, CMPP_RC_ACCESS,
|
|
"Directive %s%s flag cmpp_d_F_NOT_IN_SAFE_MODE is set "
|
|
"and the preprocessor is running in safe mode.",
|
|
cmpp__pp_zdelim(pp), r->name);
|
|
goto end;
|
|
}
|
|
if( isCallOnly && r->closer.f ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Call-only directives may not have a closing directive.");
|
|
goto end;
|
|
}
|
|
#if 0
|
|
if( pp->pimpl->dx ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Directives may not be added while a "
|
|
"directive is running."
|
|
/* because that might reallocate being-run directives.
|
|
2025-10-25: that's since been resolved but we need a
|
|
use case before enabling this.
|
|
*/);
|
|
goto end;
|
|
}
|
|
#endif
|
|
if( !pp->pimpl->flags.isInternalDirectiveReg
|
|
&& !cmpp_is_legal_key(ustr_c(r->name),
|
|
cmpp__strlen(r->name,-1), NULL) ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"\"%s\" is not a legal directive name.", r->name);
|
|
goto end;
|
|
}
|
|
if( cmpp__d_search(pp, r->name) ){
|
|
cmpp__err(pp, CMPP_RC_ALREADY_EXISTS,
|
|
"Directive name '%s' is already in use.",
|
|
r->name);
|
|
goto end;
|
|
}
|
|
e1 = CmppDList_append(pp, &pp->pimpl->d.list);
|
|
if( !e1 ) goto end;
|
|
e1->d.impl.callback = r->opener.f;
|
|
e1->d.impl.state = r->state;
|
|
e1->d.impl.dtor = r->dtor;
|
|
if( pp->pimpl->flags.isInternalDirectiveReg ){
|
|
e1->d.flags = r->opener.flags;
|
|
}else{
|
|
e1->d.flags = r->opener.flags & cmpp_d_F_MASK;
|
|
}
|
|
e1->zName = sqlite3_mprintf("%s", r->name);
|
|
if( 0==cmpp_check_oom(pp, e1->zName) ){
|
|
//e1->reg = *r; e1->reg.zName = e1->zName;
|
|
e1->d.name.z = e1->zName;
|
|
e1->d.name.n = strlen(e1->zName);
|
|
if( r->closer.f
|
|
&& (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){
|
|
e2->d.impl.callback = r->closer.f;
|
|
e2->d.impl.state = r->state;
|
|
if( pp->pimpl->flags.isInternalDirectiveReg ){
|
|
e2->d.flags = r->closer.flags;
|
|
}else{
|
|
e2->d.flags = r->closer.flags & cmpp_d_F_MASK;
|
|
}
|
|
e1->d.closer = &e2->d;
|
|
e2->zName = sqlite3_mprintf("/%s", r->name);
|
|
if( 0==cmpp_check_oom(pp, e2->zName) ){
|
|
e2->d.name.z = e2->zName;
|
|
e2->d.name.n = e1->d.name.n + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
end:
|
|
if( ppCode ){
|
|
if( e2 ) CmppDList_unappend(&pp->pimpl->d.list);
|
|
if( e1 ) CmppDList_unappend(&pp->pimpl->d.list);
|
|
else if( r->dtor ){
|
|
r->dtor( r->state );
|
|
}
|
|
}else{
|
|
CmppDList_sort(&pp->pimpl->d.list);
|
|
if( dOut ){
|
|
*dOut = &e1->d;
|
|
}
|
|
if( 0 ){
|
|
g_warn("Registered: %s%s%s", e1->zName,
|
|
e2 ? " and " : "",
|
|
e2 ? e2->zName : "");
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os,
|
|
cmpp_d const * const * const dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags){
|
|
assert( !dxppCode );
|
|
bool gotOne = false;
|
|
cmpp_outputer const oldOut = dx->pp->pimpl->out;
|
|
bool const allowOtherDirectives =
|
|
(flags & cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
cmpp_d const * const d = cmpp_dx_d(dx);
|
|
cmpp_size_t const lineNo = dx->pimpl->dline.lineNo;
|
|
bool const pushAt = (cmpp_dx_consume_F_RAW & flags);
|
|
if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){
|
|
return dxppCode;
|
|
}
|
|
if( os ){
|
|
dx->pp->pimpl->out = *os;
|
|
}
|
|
while( 0==dxppCode
|
|
&& 0==cmpp_dx_next(dx, &gotOne)
|
|
/* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){
|
|
if( !gotOne ){
|
|
dxserr("No closing directive found for "
|
|
"%s%s opened on line %" CMPP_SIZE_T_PFMT ".",
|
|
cmpp_dx_delim(dx), d->name.z, lineNo);
|
|
}else{
|
|
cmpp_d const * const d2 = cmpp_dx_d(dx);
|
|
gotOne = false;
|
|
for( unsigned i = 0; !gotOne && i < nClosers; ++i ){
|
|
gotOne = d2==dClosers[i];
|
|
}
|
|
//g_warn("gotOne=%d d2=%s", gotOne, d2->name.z);
|
|
if( gotOne ) break;
|
|
else if( !allowOtherDirectives ){
|
|
dxserr("%s%s at line %" CMPP_SIZE_T_PFMT
|
|
" may not contain %s%s.",
|
|
cmpp_dx_delim(dx), d->name.z, lineNo,
|
|
cmpp_dx_delim(dx), d2->name.z);
|
|
}else{
|
|
cmpp_dx_process(dx);
|
|
}
|
|
}
|
|
}
|
|
if( pushAt ){
|
|
cmpp_atpol_pop(dx->pp);
|
|
}
|
|
if( os ){
|
|
dx->pp->pimpl->out = oldOut;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b,
|
|
cmpp_d const * const * dClosers,
|
|
unsigned nClosers, cmpp_flag32_t flags){
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
oss.state = b;
|
|
return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags);
|
|
}
|
|
|
|
char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){
|
|
again:
|
|
switch(p){
|
|
case cmpp_atpol_CURRENT:{
|
|
if( pp ){
|
|
assert( p!=cmpp__policy(pp, at) );
|
|
p = cmpp__policy(pp, at);
|
|
pp = 0;
|
|
goto again;
|
|
}
|
|
return NULL;
|
|
}
|
|
case cmpp_atpol_invalid: return NULL;
|
|
case cmpp_atpol_OFF: return "off";
|
|
case cmpp_atpol_RETAIN: return "retain";
|
|
case cmpp_atpol_ELIDE: return "elide";
|
|
case cmpp_atpol_ERROR: return "error";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){
|
|
cmpp_atpol_e rv = cmpp_atpol_invalid;
|
|
if( 0==strcmp(z, "retain") ) rv = cmpp_atpol_RETAIN;
|
|
else if( 0==strcmp(z, "elide") ) rv = cmpp_atpol_ELIDE;
|
|
else if( 0==strcmp(z, "error") ) rv = cmpp_atpol_ERROR;
|
|
else if( 0==strcmp(z, "off") ) rv = cmpp_atpol_OFF;
|
|
if( pp ){
|
|
if( cmpp_atpol_invalid==rv
|
|
&& 0==strcmp(z, "current") ){
|
|
rv = cmpp__policy(pp,at);
|
|
}else if( cmpp_atpol_invalid==rv ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"Invalid @ policy value: %s."
|
|
" Try one of retain|elide|error|off|current.", z);
|
|
}else{
|
|
cmpp__policy(pp,at) = rv;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int cmpp__StringAtIsOk(cmpp * pp, cmpp_atpol_e pol){
|
|
if( 0==ppCode ){
|
|
if( pol==cmpp_atpol_CURRENT ) pol=cmpp__policy(pp,at);
|
|
if(cmpp_atpol_OFF==pol ){
|
|
cmpp_err_set(pp, CMPP_RC_UNSUPPORTED,
|
|
"@policy is \"off\", so cannot use @\"strings\".");
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e)
|
|
cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e)
|
|
|
|
int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){
|
|
if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at);
|
|
assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." );
|
|
if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol)
|
|
&& 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){
|
|
PodList__atpol_pop(&cmpp__epol(pp,at));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp_atpol_pop(cmpp * pp){
|
|
assert( cmpp__epol(pp,at).n );
|
|
if( cmpp__epol(pp,at).n ){
|
|
PodList__atpol_pop(&cmpp__epol(pp,at));
|
|
}else if( !ppCode ){
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"%s() called when no cmpp_atpol_push() is active.",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){
|
|
if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol)
|
|
&& cmpp_unpol_set(pp, pol)/*for validation*/ ){
|
|
PodList__unpol_pop(&cmpp__epol(pp,un));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp_unpol_pop(cmpp * pp){
|
|
assert( cmpp__epol(pp,un).n );
|
|
if( cmpp__epol(pp,un).n ){
|
|
PodList__unpol_pop(&cmpp__epol(pp,un));
|
|
}else if( !ppCode ){
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"%s() called when no cmpp_unpol_push() is active.",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){
|
|
return cmpp__epol(pp,at).na
|
|
? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){
|
|
if( 0==ppCode ){
|
|
switch(pol){
|
|
case cmpp_atpol_OFF:
|
|
case cmpp_atpol_RETAIN:
|
|
case cmpp_atpol_ELIDE:
|
|
case cmpp_atpol_ERROR:
|
|
assert(cmpp__epol(pp,at).na);
|
|
cmpp__policy(pp,at) = pol;
|
|
break;
|
|
case cmpp_atpol_CURRENT:
|
|
break;
|
|
default:
|
|
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
|
|
(int)pol);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
|
|
char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){
|
|
(void)pp;
|
|
switch(p){
|
|
case cmpp_unpol_NULL: return "null";
|
|
case cmpp_unpol_ERROR: return "error";
|
|
case cmpp_unpol_invalid: return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp,
|
|
char const *z){
|
|
cmpp_unpol_e rv = cmpp_unpol_invalid;
|
|
if( 0==strcmp(z, "null") ) rv = cmpp_unpol_NULL;
|
|
else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR;
|
|
if( pp ){
|
|
if( cmpp_unpol_invalid==rv
|
|
&& 0==strcmp(z, "current") ){
|
|
rv = cmpp__policy(pp,un);
|
|
}else if( cmpp_unpol_invalid==rv ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"Invalid undefined key policy value: %s."
|
|
" Try one of null|error.", z);
|
|
}else{
|
|
cmpp_unpol_set(pp, rv);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){
|
|
return cmpp__epol(pp,un).na
|
|
? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){
|
|
if( 0==ppCode ){
|
|
switch(pol){
|
|
case cmpp_unpol_NULL:
|
|
case cmpp_unpol_ERROR:
|
|
cmpp__policy(pp,un) = pol;
|
|
break;
|
|
default:
|
|
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
|
|
(int)pol);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
Reminders to self re. savepoint tracking:
|
|
|
|
cmpp_dx tracks per-input-source savepoints. We always want
|
|
savepoints which are created via scripts to be limited to that
|
|
script. cmpp instances, on the other hand, don't care about that.
|
|
|
|
Thus we have two different APIs for starting/ending savepoints.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){
|
|
if( 0==ppCode ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
|
|
++pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_begin(cmpp_dx * const dx){
|
|
if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){
|
|
++dx->pimpl->nSavepoint;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){
|
|
/* Remember that rollback must (mostly) ignore the
|
|
pending error state. */
|
|
if( !pp->pimpl->flags.nSavepoint ){
|
|
if( 0==ppCode ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Cannot roll back: no active savepoint");
|
|
}
|
|
}else{
|
|
sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
|
|
q = cmpp__stmt(pp, CmppStmt_spRelease, true);
|
|
if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
|
|
--pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_rollback(cmpp_dx * const dx){
|
|
/* Remember that rollback must (mostly) ignore the pending error state. */
|
|
if( !dx->pimpl->nSavepoint ){
|
|
if( 0==dxppCode ){
|
|
cmpp_dx_err(dx, CMPP_RC_MISUSE,
|
|
"Cannot roll back: no active savepoint");
|
|
}
|
|
}else{
|
|
cmpp_sp_rollback(dx->pp);
|
|
--dx->pimpl->nSavepoint;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){
|
|
if( 0==ppCode ){
|
|
if( !pp->pimpl->flags.nSavepoint ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Cannot commit: no active savepoint");
|
|
}else{
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp__step(pp, q, true) ){
|
|
--pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
}else{
|
|
cmpp_sp_rollback(pp);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_commit(cmpp_dx * const dx){
|
|
if( 0==dxppCode ){
|
|
if( !dx->pimpl->nSavepoint ){
|
|
cmpp_dx_err(dx, CMPP_RC_MISUSE,
|
|
"Cannot commit: no active savepoint");
|
|
}else if( 0==cmpp_sp_commit(dx->pp) ){
|
|
--dx->pimpl->nSavepoint;
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
static void cmpp_dx_pimpl_reuse(cmpp_dx_pimpl *p){
|
|
#if 0
|
|
/* no: we need most of the state to remain
|
|
intact. */
|
|
cmpp_dx_pimpl const tmp = *p;
|
|
*p = cmpp_dx_pimpl_empty;
|
|
p->buf = tmp.buf;
|
|
p->args = tmp.args;
|
|
#endif
|
|
cmpp_b_reuse(&p->buf.line);
|
|
cmpp_b_reuse(&p->buf.argsRaw);
|
|
cmpp_args_reuse(&p->args);
|
|
}
|
|
|
|
void cmpp_dx_pimpl_cleanup(cmpp_dx_pimpl *p){
|
|
cmpp_b_clear(&p->buf.line);
|
|
cmpp_b_clear(&p->buf.argsRaw);
|
|
cmpp_args_cleanup(&p->args);
|
|
*p = cmpp_dx_pimpl_empty;
|
|
}
|
|
|
|
void cmpp_dx__reset(cmpp_dx * const dx){
|
|
dx->args = cmpp_dx_empty.args;
|
|
cmpp_dx_pimpl_reuse(dx->pimpl);
|
|
dx->d = 0;
|
|
//no: dx->sourceName = 0;
|
|
}
|
|
|
|
void cmpp_dx_cleanup(cmpp_dx * const dx){
|
|
unsigned prev = 0;
|
|
CmppLvlList_cleanup(&dx->pimpl->dxLvl);
|
|
while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){
|
|
prev = dx->pimpl->nSavepoint;
|
|
cmpp__dx_sp_rollback(dx);
|
|
}
|
|
cmpp_dx_pimpl_cleanup(dx->pimpl);
|
|
memset(dx, 0, sizeof(*dx));
|
|
}
|
|
|
|
int cmpp__find_closing2(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t * pNl){
|
|
unsigned char const * z = *zPos;
|
|
unsigned char const opener = *z;
|
|
unsigned char closer = 0;
|
|
switch(opener){
|
|
case '(': closer = ')'; break;
|
|
case '[': closer = ']'; break;
|
|
case '{': closer = '}'; break;
|
|
case '"': case '\'': closer = opener; break;
|
|
default:
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Invalid starting char (0x%x) for %s()",
|
|
(int)opener, __func__);
|
|
}
|
|
int count = 1;
|
|
for( ++z; z < zEnd; ++z ){
|
|
if( closer == *z && 0==--count ){
|
|
/* Have to check this first for the case of "" and ''. */
|
|
break;
|
|
}else if( opener == *z ){
|
|
++count;
|
|
}else if( pNl && '\n'==*z ){
|
|
++*pNl;
|
|
}
|
|
}
|
|
if( closer!=*z ){
|
|
if( 0 ){
|
|
g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
|
|
(zEnd - *zPos), *zPos);
|
|
}
|
|
//assert(!"here");
|
|
cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Unbalanced %c%c: %.*s",
|
|
opener, closer,
|
|
(int)(z-*zPos), *zPos);
|
|
}else{
|
|
if( 0 ){
|
|
g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos);
|
|
}
|
|
*zPos = z;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
cmpp_tt cmpp__tt_for_sqlite(int sqType){
|
|
cmpp_tt rv;
|
|
switch( sqType ){
|
|
case SQLITE_INTEGER: rv = cmpp_TT_Int; break;
|
|
case SQLITE_NULL: rv = cmpp_TT_Null; break;
|
|
default: rv = cmpp_TT_String; break;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
|
|
bool defineIfNoRow){
|
|
if( 0==ppCode ){
|
|
int const nCol = sqlite3_column_count(q);
|
|
assert( sqlite3_data_count(q)>0 || defineIfNoRow);
|
|
/* Create a #define for each column */
|
|
bool const hasRow = sqlite3_data_count(q)>0;
|
|
for( int i = 0; !ppCode && i < nCol; ++i ){
|
|
char const * const zCol = sqlite3_column_name(q, i);
|
|
if( hasRow ){
|
|
unsigned char const * const zVal = sqlite3_column_text(q, i);
|
|
int const nVal = sqlite3_column_bytes(q, i);
|
|
cmpp_tt const ttype =
|
|
cmpp__tt_for_sqlite(sqlite3_column_type(q,i));
|
|
cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype);
|
|
}else if(defineIfNoRow){
|
|
cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_TT_Null);
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){
|
|
cmpp_d const * d = 0;//cmpp__d_search(zName);
|
|
if( !d ){
|
|
CmppDList_entry const * e =
|
|
CmppDList_search(&pp->pimpl->d.list, zName);
|
|
if( e ) d = &e->d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
|
|
cmpp_flag32_t what){
|
|
cmpp_d const * d = cmpp__d_search(pp, zName);
|
|
if( !d ){
|
|
CmppDList_entry const * e = 0;
|
|
if( cmpp__d_search3_F_DELAYED & what ){
|
|
int rc = cmpp__d_delayed_load(pp, zName);
|
|
if( 0==rc ){
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}else if( CMPP_RC_NO_DIRECTIVE!=rc ){
|
|
assert( ppCode );
|
|
return NULL;
|
|
}
|
|
}
|
|
if( !e
|
|
&& (cmpp__d_search3_F_AUTOLOADER & what)
|
|
&& pp->pimpl->d.autoload.f
|
|
&& 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}
|
|
#if CMPP_D_MODULE
|
|
if( !e
|
|
&& !ppCode
|
|
&& (cmpp__d_search3_F_DLL & what) ){
|
|
char * z = sqlite3_mprintf("libcmpp-d-%s", zName);
|
|
cmpp_check_oom(pp, z);
|
|
int rc = cmpp_module_load(pp, z, NULL);
|
|
sqlite3_free(z);
|
|
if( rc ){
|
|
if( CMPP_RC_NOT_FOUND==rc ){
|
|
cmpp__err_clear(pp);
|
|
}
|
|
return NULL;
|
|
}
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}
|
|
#endif
|
|
if( e ) d = &e->d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
int cmpp_dx_process(cmpp_dx * const dx){
|
|
if( 0==dxppCode ){
|
|
cmpp_d const * const d = cmpp_dx_d(dx);
|
|
assert( d );
|
|
if( !cmpp_dx_is_eliding(dx) || (d->flags & cmpp_d_F_FLOW_CONTROL) ){
|
|
if( (cmpp_d_F_NOT_IN_SAFEMODE & d->flags)
|
|
&& (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.newFlags) ){
|
|
cmpp_dx_err(dx, CMPP_RC_ACCESS,
|
|
"Directive %s%s is disabled by safe mode.",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
}else{
|
|
assert(d->impl.callback);
|
|
d->impl.callback(dx);
|
|
}
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
|
|
static void cmpp_dx__setup_include_path(cmpp_dx * dx){
|
|
/* Add the leading dir part of dx->sourceName as the
|
|
highest-priority include path. It gets removed
|
|
in cmpp_dx__teardown(). */
|
|
assert( dx->sourceName );
|
|
enum { BufSize = 512 * 4 };
|
|
unsigned char buf[BufSize] = {0};
|
|
unsigned char *z = &buf[0];
|
|
cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1);
|
|
if( n > (unsigned)BufSize-1 ) return;
|
|
memcpy(z, dx->sourceName, n);
|
|
buf[n] = 0;
|
|
cmpp_ssize_t i = n - 1;
|
|
for( ; i > 0; --i ){
|
|
if( '/'==z[i] || '\\'==z[i] ){
|
|
z[i] = 0;
|
|
n = i;
|
|
break;
|
|
}
|
|
}
|
|
if( n>(cmpp_size_t)i ){
|
|
/* No path separator found. Assuming '.'. This is intended to
|
|
replace the historical behavior of automatically adding '.' if
|
|
no -I flags are used. Potential TODO is getcwd() here instead
|
|
of using '.' */
|
|
n = 1;
|
|
buf[0] = '.';
|
|
buf[1] = 0;
|
|
}
|
|
int64_t rowid = 0;
|
|
cmpp__include_dir_add(dx->pp, (char const*)buf,
|
|
dx->pp->pimpl->flags.nDxDepth,
|
|
&rowid);
|
|
if( rowid ){
|
|
//g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z);
|
|
dx->pimpl->shadow.ridInclPath = rowid;
|
|
}
|
|
}
|
|
|
|
static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx,
|
|
unsigned char const * zIn,
|
|
cmpp_ssize_t nIn){
|
|
if( 0==ppCode ){
|
|
assert( dx->sourceName );
|
|
assert( dx->pimpl );
|
|
assert( pp==dx->pp );
|
|
nIn = cmpp__strlenu(zIn, nIn);
|
|
if( !nIn ) return 0;
|
|
pp->pimpl->dx = dx;
|
|
dx->pimpl->zBegin = zIn;
|
|
dx->pimpl->zEnd = zIn + nIn;
|
|
cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName,
|
|
&dx->pimpl->shadow.sidFile);
|
|
++dx->pp->pimpl->flags.nDxDepth;
|
|
cmpp_dx__setup_include_path(dx);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static void cmpp_dx__teardown(cmpp_dx *dx){
|
|
if( dx->pimpl->shadow.ridInclPath>0 ){
|
|
cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath);
|
|
dx->pimpl->shadow.ridInclPath = 0;
|
|
}
|
|
if( dx->pimpl->shadow.sidFile ){
|
|
cmpp_define_unshadow(dx->pp, "__FILE__",
|
|
dx->pimpl->shadow.sidFile);
|
|
}
|
|
--dx->pp->pimpl->flags.nDxDepth;
|
|
cmpp_dx_cleanup(dx);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_process_string)(
|
|
cmpp *pp, const char * zName,
|
|
unsigned char const * zIn,
|
|
cmpp_ssize_t nIn
|
|
){
|
|
if( !zName ) zName = "";
|
|
if( 0==cmpp__db_init(pp) ){
|
|
cmpp_dx const * const oldDx = pp->pimpl->dx;
|
|
cmpp_dx_pimpl dxp = cmpp_dx_pimpl_empty;
|
|
cmpp_dx dx = {
|
|
.pp = pp,
|
|
.sourceName = ustr_c(zName),
|
|
.args = cmpp_dx_empty.args,
|
|
.pimpl = &dxp
|
|
};
|
|
dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall;
|
|
pp->pimpl->flags.nextIsCall = false;
|
|
if( dxp.flags.nextIsCall ){
|
|
assert( pp->pimpl->dx );
|
|
dxp.pos.lineNo = pp->pimpl->dx->pimpl->pos.lineNo;
|
|
}
|
|
bool gotOne = false;
|
|
(void)cmpp__stmt(pp, CmppStmt_sdefIns, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_sdefDel, true)
|
|
/* hack: ensure that those queries are allocated now, as an
|
|
error in processing may keep them from being created
|
|
later. We might want to rethink the
|
|
prepare-no-statements-on-error bits, but will have to go back
|
|
and fix routines which currently rely on that. */;
|
|
cmpp_dx__setup(pp, &dx, zIn, nIn);
|
|
while(0==ppCode
|
|
&& 0==cmpp_dx_next(&dx, &gotOne)
|
|
&& gotOne){
|
|
cmpp_dx_process(&dx);
|
|
}
|
|
if(0==ppCode && 0!=dx.pimpl->dxLvl.n){
|
|
CmppLvl const * const lv = CmppLvl_get(&dx);
|
|
cmpp_dx_err(&dx, CMPP_RC_SYNTAX,
|
|
"Input ended inside an unterminated nested construct "
|
|
"opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName,
|
|
lv ? lv->lineNo : (cmpp_size_t)0);
|
|
}
|
|
cmpp_dx__teardown(&dx);
|
|
pp->pimpl->dx = oldDx;
|
|
}
|
|
if( !ppCode ){
|
|
cmpp_outputer_flush(&pp->pimpl->out)
|
|
/* We're going to ignore a result code just this once. */;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp_process_file(cmpp *pp, const char * zName){
|
|
if( 0==ppCode ){
|
|
FileWrapper fw = FileWrapper_empty;
|
|
if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb")
|
|
&& 0==cmpp__FileWrapper_slurp(pp, &fw) ){
|
|
cmpp_process_string(pp, zName, fw.zContent, fw.nContent);
|
|
}
|
|
FileWrapper_close(&fw);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName,
|
|
cmpp_input_f src, void * srcState){
|
|
if( 0==ppCode ){
|
|
cmpp_b * const os = cmpp_b_borrow(pp);
|
|
int const rc = os
|
|
? cmpp_stream(src, srcState, cmpp_output_f_b, os)
|
|
: ppCode;
|
|
if( 0==rc ){
|
|
cmpp_process_string(pp, zName, os->z, os->n);
|
|
}else{
|
|
cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName);
|
|
}
|
|
cmpp_b_return(pp, os);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_call_str)(
|
|
cmpp *pp, unsigned char const * z, cmpp_ssize_t n,
|
|
cmpp_b * dest, cmpp_flag32_t flags
|
|
){
|
|
if( ppCode ) return ppCode;
|
|
cmpp_args args = cmpp_args_empty;
|
|
cmpp_b * const b = cmpp_b_borrow(pp);
|
|
cmpp_b * const bo = cmpp_b_borrow(pp);
|
|
cmpp_outputer oB = cmpp_outputer_b;
|
|
if( !b || !bo ) return ppCode;
|
|
cmpp__pi(pp);
|
|
oB.state = bo;
|
|
oB.name = pi->out.name;//"[call]";
|
|
n = cmpp__strlenu(z, n);
|
|
//g_warn("calling: <<%.*s>>", (int)n, z);
|
|
unsigned char const * zEnd = z+n;
|
|
cmpp_skip_snl(&z, zEnd);
|
|
cmpp_skip_snl_trailing(z, &zEnd);
|
|
n = (zEnd-z);
|
|
if( !n ){
|
|
cmpp_err_set(pp, CMPP_RC_SYNTAX,
|
|
"Empty [call] is not permitted.");
|
|
goto end;
|
|
}
|
|
//g_warn("calling: <<%.*s>>", (int)n, z);
|
|
cmpp__delim const * const delim = cmpp__pp_delim(pp);
|
|
assert(delim);
|
|
if( (cmpp_size_t)n<=delim->open.n
|
|
|| 0!=memcmp(z, delim->open.z, delim->open.n) ){
|
|
/* If it doesn't start with the current delimiter,
|
|
prepend one. */
|
|
cmpp_b_reserve3(pp, b, delim->open.n + n + 2);
|
|
cmpp_b_append4(pp, b, delim->open.z, delim->open.n);
|
|
}
|
|
cmpp_b_append4(pp, b, z, n);
|
|
if( !ppCode ){
|
|
cmpp_outputer oOld = cmpp_outputer_empty;
|
|
pi->flags.nextIsCall = true
|
|
/* Convey (indirectly) that the first cmpp_dx_next() call made
|
|
via cmpp_process_string() is a call context. */;
|
|
cmpp__outputer_swap(pp, &oB, &oOld);
|
|
cmpp_process_string(pp, (char*)b->z, b->z, b->n);
|
|
cmpp__outputer_swap(pp, &oOld, &oB);
|
|
assert( !pi->flags.nextIsCall || ppCode );
|
|
pi->flags.nextIsCall = false;
|
|
}
|
|
if( !ppCode ){
|
|
unsigned char const * zz = bo->z;
|
|
unsigned char const * zzEnd = bo->z + bo->n;
|
|
if( cmpp_call_F_TRIM_ALL & flags ){
|
|
cmpp_skip_snl(&zz, zzEnd);
|
|
cmpp_skip_snl_trailing(zz, &zzEnd);
|
|
}else if( 0==(cmpp_call_F_NO_TRIM & flags) ){
|
|
cmpp_b_chomp(bo);
|
|
zzEnd = bo->z + bo->n;
|
|
}
|
|
if( (zzEnd-zz) ){
|
|
cmpp_b_append4(pp, dest, zz, (zzEnd-zz));
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(pp, b);
|
|
cmpp_b_return(pp, bo);
|
|
cmpp_args_cleanup(&args);
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){
|
|
switch(errNo){
|
|
/* Please expand on this as tests/use cases call for it... */
|
|
case 0:
|
|
return 0;
|
|
case EINVAL:
|
|
return CMPP_RC_MISUSE;
|
|
case ENOMEM:
|
|
return CMPP_RC_OOM;
|
|
case EROFS:
|
|
case EACCES:
|
|
case EBUSY:
|
|
case EPERM:
|
|
case EDQUOT:
|
|
case EAGAIN:
|
|
case ETXTBSY:
|
|
return CMPP_RC_ACCESS;
|
|
case EISDIR:
|
|
case ENOTDIR:
|
|
return CMPP_RC_TYPE;
|
|
case ENAMETOOLONG:
|
|
case ELOOP:
|
|
case ERANGE:
|
|
return CMPP_RC_RANGE;
|
|
case ENOENT:
|
|
case ESRCH:
|
|
return CMPP_RC_NOT_FOUND;
|
|
case EEXIST:
|
|
case ENOTEMPTY:
|
|
return CMPP_RC_ALREADY_EXISTS;
|
|
case EIO:
|
|
return CMPP_RC_IO;
|
|
default:
|
|
return dflt;
|
|
}
|
|
}
|
|
|
|
int cmpp_flush_f_FILE(void * _FILE){
|
|
return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
|
|
}
|
|
|
|
int cmpp_output_f_FILE( void * state,
|
|
void const * src, cmpp_size_t n ){
|
|
return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout))
|
|
? 0 : CMPP_RC_IO;
|
|
}
|
|
|
|
int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){
|
|
int const fd = *((int*)state);
|
|
ssize_t const wn = write(fd, src, n);
|
|
return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
|
|
}
|
|
|
|
int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){
|
|
cmpp_FILE * f = state;
|
|
cmpp_size_t const rn = *n;
|
|
*n = (cmpp_size_t)fread(dest, 1, rn, f);
|
|
return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO);
|
|
}
|
|
|
|
int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){
|
|
int const fd = *((int*)state);
|
|
ssize_t const rn = read(fd, dest, *n);
|
|
if( rn<0 ){
|
|
return cmpp_errno_rc(errno, CMPP_RC_IO);
|
|
}else{
|
|
*n = (cmpp_size_t)rn;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){
|
|
if( self->state ){
|
|
cmpp_fclose( self->state );
|
|
self->name = NULL;
|
|
self->state = NULL;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){
|
|
if( self->state ) cmpp_b_clear(self->state);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){
|
|
return o->out ? o->out(o->state, p, n) : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){
|
|
return o->flush ? o->flush(o->state) : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){
|
|
if( o->cleanup ){
|
|
o->cleanup( o );
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState,
|
|
cmpp_output_f outF, void * outState ){
|
|
int rc = 0;
|
|
enum { BufSize = 1024 * 4 };
|
|
unsigned char buf[BufSize];
|
|
cmpp_size_t rn = BufSize;
|
|
while( 0==rc
|
|
&& (rn==BufSize)
|
|
&& (0==(rc=inF(inState, buf, &rn))) ){
|
|
if(rn) rc = outF(outState, buf, rn);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
void cmpp__fatalv_base(char const *zFile, int line,
|
|
char const *zFmt, va_list va){
|
|
cmpp_FILE * const fp = stderr;
|
|
fflush(stdout);
|
|
fprintf(fp, "\n%s:%d: ", zFile, line);
|
|
if(zFmt && *zFmt){
|
|
vfprintf(fp, zFmt, va);
|
|
fputc('\n', fp);
|
|
}
|
|
fflush(fp);
|
|
exit(1);
|
|
}
|
|
|
|
void cmpp__fatal_base(char const *zFile, int line,
|
|
char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp__fatalv_base(zFile, line, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){
|
|
if( zMsg && ppCode ) *zMsg = pp->pimpl->err.zMsg;
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){
|
|
int const rc = ppCode;
|
|
if( rc ){
|
|
*zMsg = pp->pimpl->err.zMsg;
|
|
pp->pimpl->err = cmpp_pimpl_empty.err;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//CMPP_WASM_EXPORT
|
|
void cmpp__err_clear(cmpp *pp){
|
|
cmpp_mfree(pp->pimpl->err.zMsg);
|
|
pp->pimpl->err = cmpp_pimpl_empty.err;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){
|
|
return pp ? pp->pimpl->err.code : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){
|
|
*pos = dx->pimpl->pos;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){
|
|
dx->pimpl->pos = *pos;
|
|
}
|
|
|
|
|
|
//CMPP_WASM_EXPORT
|
|
void cmpp__dx_append_script_info(cmpp_dx const * dx,
|
|
sqlite3_str * const sstr){
|
|
sqlite3_str_appendf(
|
|
sstr,
|
|
"%s%s@ %s line %" CMPP_SIZE_T_PFMT,
|
|
dx->d ? dx->d->name.z : "",
|
|
dx->d ? " " : "",
|
|
(dx->sourceName
|
|
&& 0==strcmp("-", (char const *)dx->sourceName))
|
|
? "<stdin>"
|
|
: (char const *)dx->sourceName,
|
|
dx->pimpl->dline.lineNo
|
|
);
|
|
}
|
|
|
|
int cmpp__errv(cmpp *pp, int rc, char const *zFmt, va_list va){
|
|
if( pp ){
|
|
cmpp__err_clear(pp);
|
|
ppCode = rc;
|
|
if( 0==rc ) return rc;
|
|
if( CMPP_RC_OOM==rc ){
|
|
oom:
|
|
pp->pimpl->err.zMsgC = "An allocation failed.";
|
|
return pp->pimpl->err.code = CMPP_RC_OOM;
|
|
}
|
|
assert( !pp->pimpl->err.zMsg );
|
|
if( pp->pimpl->dx || (zFmt && *zFmt) ){
|
|
sqlite3_str * sstr = 0;
|
|
sstr = sqlite3_str_new(pp->pimpl->db.dbh);
|
|
if( pp->pimpl->dx ){
|
|
cmpp__dx_append_script_info(pp->pimpl->dx, sstr);
|
|
sqlite3_str_append(sstr, ": ", 2);
|
|
}
|
|
if( zFmt && *zFmt ){
|
|
sqlite3_str_vappendf(sstr, zFmt, va);
|
|
}else{
|
|
sqlite3_str_appendf(sstr, "No error info provided.");
|
|
}
|
|
pp->pimpl->err.zMsgC =
|
|
pp->pimpl->err.zMsg = sqlite3_str_finish(sstr);
|
|
if( !pp->pimpl->err.zMsg ){
|
|
goto oom;
|
|
}
|
|
}else{
|
|
pp->pimpl->err.zMsgC = "No error info provided.";
|
|
}
|
|
rc = ppCode;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//CMPP_WASM_EXPORT no - variadic
|
|
int cmpp_err_set(cmpp *pp, int rc,
|
|
char const *zFmt, ...){
|
|
if( pp ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
rc = cmpp__errv(pp, rc, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
const cmpp_d_autoloader cmpp_d_autoloader_empty =
|
|
cmpp_d_autoloader_empty_m;
|
|
|
|
CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){
|
|
if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state);
|
|
if( pNew ) pp->pimpl->d.autoload = *pNew;
|
|
else pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){
|
|
*pOld = pp->pimpl->d.autoload;
|
|
pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
|
|
}
|
|
|
|
//CMPP_WASM_EXPORT no - variadic
|
|
int cmpp_dx_err_set(cmpp_dx *dx, int rc,
|
|
char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
rc = cmpp__errv(dx->pp, rc, zFmt, va);
|
|
va_end(va);
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_err_set1)(cmpp *pp, int rc, char const *zMsg){
|
|
return cmpp_err_set(pp, rc, (zMsg && *zMsg) ? "%s" : 0, zMsg);
|
|
}
|
|
|
|
//no: CMPP_WASM_EXPORT
|
|
char * cmpp_path_search(cmpp *pp,
|
|
char const *zPath,
|
|
char pathSep,
|
|
char const *zBaseName,
|
|
char const *zExt){
|
|
char * zrc = 0;
|
|
if( !ppCode ){
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(pp, CmppStmt_selPathSearch, false);
|
|
if( q ){
|
|
unsigned char sep[2] = {pathSep, 0};
|
|
cmpp__bind_text(pp, q, 1, ustr_c(zBaseName));
|
|
cmpp__bind_text(pp, q, 2, sep);
|
|
cmpp__bind_text(pp, q, 3, ustr_c((zExt ? zExt : "")));
|
|
cmpp__bind_text(pp, q, 4, ustr_c((zPath ? zPath: "")));
|
|
int const dbrc = cmpp__step(pp, q, false);
|
|
if( SQLITE_ROW==dbrc ){
|
|
unsigned char const * s = sqlite3_column_text(q, 1);
|
|
zrc = sqlite3_mprintf("%s", s);
|
|
cmpp_check_oom(pp, zrc);
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
}
|
|
return zrc;
|
|
}
|
|
|
|
#if CMPP__OBUF
|
|
int cmpp__obuf_flush(cmpp__obuf * b){
|
|
if( 0==b->rc && b->cursor > b->begin ){
|
|
if( b->dest.out ){
|
|
b->rc = b->dest.out(b->dest.state, b->begin,
|
|
b->cursor-b->begin);
|
|
}
|
|
b->cursor = b->begin;
|
|
}
|
|
if( 0==b->rc && b->dest.flush ){
|
|
b->rc = b->dest.flush(b->dest.state);
|
|
}
|
|
return b->rc;
|
|
}
|
|
|
|
void cmpp__obuf_cleanup(cmpp__obuf * b){
|
|
if( b ){
|
|
cmpp__obuf_flush(b);/*ignoring result*/;
|
|
if( b->ownsMemory ){
|
|
cmpp_mfree(b->begin);
|
|
}
|
|
*b = cmpp__obuf_empty;
|
|
}
|
|
}
|
|
|
|
int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n){
|
|
assert( b );
|
|
if( n && !b->rc && b->dest.out ){
|
|
assert( b->end );
|
|
assert( b->cursor );
|
|
assert( b->cursor <= b->end );
|
|
assert( b->end>b->begin );
|
|
if( b->cursor + n >= b->end ){
|
|
if( 0==cmpp_flush_f_obuf(b) ){
|
|
if( b->cursor + n >= b->end ){
|
|
/* Go ahead and write it all */
|
|
b->rc = b->dest.out(b->dest.state, src, n);
|
|
}else{
|
|
goto copy_it;
|
|
}
|
|
}
|
|
}else{
|
|
copy_it:
|
|
memcpy(b->cursor, src, n);
|
|
b->cursor += n;
|
|
}
|
|
}
|
|
return b->rc;
|
|
}
|
|
|
|
int cmpp_flush_f_obuf(void * b){
|
|
return cmpp__obuf_flush(b);
|
|
}
|
|
|
|
int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n){
|
|
return cmpp__obuf_write(state, src, n);
|
|
}
|
|
|
|
void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o){
|
|
cmpp__obuf_cleanup(o->state);
|
|
}
|
|
#endif /* CMPP__OBUF */
|
|
|
|
//cmpp__ListType_impl(cmpp__delim_list,cmpp__delim)
|
|
//cmpp__ListType_impl(CmppDList,CmppDList_entry*)
|
|
//cmpp__ListType_impl(CmppSohList,void*)
|
|
cmpp__ListType_impl(CmppArgList,cmpp_arg)
|
|
cmpp__ListType_impl(cmpp_b_list,cmpp_b*)
|
|
cmpp__ListType_impl(CmppLvlList,CmppLvl*)
|
|
|
|
/**
|
|
Expects that *ndx points to the current argv entry and that it is a
|
|
flag which expects a value. This function checks for --flag=val and
|
|
(--flag val) forms. If a value is found then *ndx is adjusted (if
|
|
needed) to point to the next argument after the value and *zVal is
|
|
* pointed to the value. If no value is found then it returns false.
|
|
*/
|
|
static bool get_flag_val(int argc,
|
|
char const * const * argv, int * ndx,
|
|
char const **zVal){
|
|
char const * zEq = strchr(argv[*ndx], '=');
|
|
if( zEq ){
|
|
*zVal = zEq+1;
|
|
return 1;
|
|
}else if(*ndx+1>=argc){
|
|
return 0;
|
|
}else{
|
|
*zVal = argv[++*ndx];
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static
|
|
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
|
|
char const **zValIfEqX );
|
|
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
|
|
char const **zValIfEqX ){
|
|
if( zValIfEqX ) *zValIfEqX = 0;
|
|
if( 0==strcmp(zFlag, zArg) ) return true;
|
|
char const * z = strchr(zArg,'=');
|
|
if( z && z>zArg ){
|
|
/* compare the part before the '=' */
|
|
if( 0==strncmp(zFlag, zArg, z-zArg) ){
|
|
if( !zFlag[z-zArg] ){
|
|
if( zValIfEqX ) *zValIfEqX = z+1;
|
|
return true;
|
|
}
|
|
/* Else it was a prefix match. */
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false);
|
|
if( q ){
|
|
while( SQLITE_ROW==sqlite3_step(q) ){
|
|
int const tt = sqlite3_column_int(q, 0);
|
|
unsigned char const * zK = sqlite3_column_text(q, 1);
|
|
unsigned char const * zV = sqlite3_column_text(q, 2);
|
|
int const nK = sqlite3_column_bytes(q, 1);
|
|
int const nV = sqlite3_column_bytes(q, 2);
|
|
char const * zTt = cmpp__tt_cstr(tt, true);
|
|
if( tt && zTt ) zTt += 3;
|
|
else zTt = "String";
|
|
fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "",
|
|
nK, zK, zTt, nV, zV);
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
}
|
|
|
|
/**
|
|
This is what was originally the main() of cmpp v1, back when it was
|
|
a monolithic app. It still serves as the driver for main() but is
|
|
otherwise unused.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc,
|
|
char const * const * argv){
|
|
if( ppCode ) return ppCode;
|
|
int nFile = 0 /* number of files/-e scripts seen */;
|
|
|
|
#define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \
|
|
cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \
|
|
argv[i]); \
|
|
break; \
|
|
}
|
|
#define M(X) cmpp__arg_is_flag(X, zArg, &zVal)
|
|
#define ISFLAG(X) else if(M(X))
|
|
#define ISFLAG2(X,Y) else if(M(X) || M(Y))
|
|
#define NOVAL if( zVal ){ \
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \
|
|
break; \
|
|
} (void)0
|
|
|
|
#define open_output_if_needed \
|
|
if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break
|
|
|
|
cmpp__staticAssert(TT_None,0==(int)cmpp_TT_None);
|
|
cmpp__staticAssert(Mask1, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_FLOW_CONTROL);
|
|
cmpp__staticAssert(Mask2, cmpp_d_F_MASK_INTERNAL & cmpp_d_F_NOT_SIMPLIFY);
|
|
cmpp__staticAssert(Mask3, 0==(cmpp_d_F_MASK_INTERNAL & cmpp_d_F_MASK));
|
|
|
|
for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){
|
|
/**
|
|
Loop through the flags twice. The first time we just validate
|
|
and look for --help/-?. The second time we process the flags.
|
|
This approach allows us to easily chain multiple files and
|
|
flags:
|
|
|
|
./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y
|
|
|
|
Which, it turns out, is a surprisingly useful way to work.
|
|
*/
|
|
#define DOIT if(1==doIt)
|
|
for(int i = 0; i < argc && 0==ppCode; ++i){
|
|
char const * zVal = 0;
|
|
int isNoFlag = 0;
|
|
char const * zArg = argv[i];
|
|
//g_stderr("i=%d zArg=%s\n", i, zArg);
|
|
zVal = 0;
|
|
while('-'==*zArg) ++zArg;
|
|
if(zArg==argv[i]/*not a flag*/){
|
|
zVal = zArg;
|
|
goto do_infile;
|
|
}
|
|
//g_warn("zArg=%s", zArg);
|
|
if( 0==strncmp(zArg,"no-",3) ){
|
|
zArg += 3;
|
|
isNoFlag = 1;
|
|
}
|
|
if( M("?") || M("help") ){
|
|
NOVAL;
|
|
cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]);
|
|
break;
|
|
}else if('D'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D");
|
|
}else DOIT {
|
|
cmpp_define_legacy(pp, zArg, 0);
|
|
}
|
|
}else if('F'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F");
|
|
}else DOIT {
|
|
cmpp__set_file(pp, ustr_c(zArg), -1);
|
|
}
|
|
}
|
|
ISFLAG("e"){
|
|
ARGVAL;
|
|
DOIT {
|
|
++nFile;
|
|
open_output_if_needed;
|
|
cmpp_process_string(pp, "-e script",
|
|
(unsigned char const *)zVal, -1);
|
|
}
|
|
}else if('U'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U");
|
|
}else DOIT {
|
|
cmpp_undef(pp, zArg, NULL);
|
|
}
|
|
}else if('I'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I");
|
|
}else DOIT {
|
|
cmpp_include_dir_add(pp, zArg);
|
|
}
|
|
}else if('L'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L");
|
|
}else DOIT {
|
|
cmpp_module_dir_add(pp, zArg);
|
|
}
|
|
}
|
|
ISFLAG2("o","outfile"){
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp__out_fopen(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG2("f","file"){
|
|
ARGVAL;
|
|
do_infile:
|
|
DOIT {
|
|
if( !pp->pimpl->mod.path.z ){
|
|
cmpp_module_dir_add(pp, NULL);
|
|
}
|
|
++nFile;
|
|
if( 0
|
|
&& !pp->pimpl->flags.nIncludeDir
|
|
&& cmpp_include_dir_add(pp, ".") ){
|
|
break;
|
|
}
|
|
open_output_if_needed;
|
|
cmpp_process_file(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG("@"){
|
|
NOVAL;
|
|
DOIT {
|
|
assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF );
|
|
cmpp_atpol_set(pp, isNoFlag
|
|
? cmpp_atpol_OFF
|
|
: cmpp_atpol_DEFAULT_FOR_FLAG);
|
|
}
|
|
}
|
|
ISFLAG("@policy"){
|
|
ARGVAL;
|
|
cmpp_atpol_from_str(pp, zVal);
|
|
}
|
|
ISFLAG("debug"){
|
|
NOVAL;
|
|
DOIT {
|
|
pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1;
|
|
}
|
|
}
|
|
ISFLAG2("u","undefined-policy"){
|
|
ARGVAL;
|
|
cmpp_unpol_from_str(pp, zVal);
|
|
}
|
|
ISFLAG("sql-trace"){
|
|
NOVAL;
|
|
/* Needs to be set before the start of the second pass, when
|
|
the db is inited. */
|
|
DOIT {
|
|
pp->pimpl->sqlTrace.expandSql = false;
|
|
do_trace_flag:
|
|
cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out);
|
|
if( isNoFlag ){
|
|
pp->pimpl->sqlTrace.out = cmpp_outputer_empty;
|
|
}else{
|
|
pp->pimpl->sqlTrace.out = cmpp_outputer_FILE;
|
|
pp->pimpl->sqlTrace.out.state = stderr;
|
|
}
|
|
}
|
|
}
|
|
ISFLAG("sql-trace-x"){
|
|
NOVAL;
|
|
DOIT {
|
|
pp->pimpl->sqlTrace.expandSql = true;
|
|
goto do_trace_flag;
|
|
}
|
|
}
|
|
ISFLAG("chomp-F"){
|
|
NOVAL;
|
|
DOIT pp->pimpl->flags.chompF = !isNoFlag;
|
|
}
|
|
ISFLAG2("d","delimiter"){
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp_delimiter_set(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG2("dd", "dump-defines"){
|
|
DOIT {
|
|
cmpp_FILE * const fp =
|
|
/* tcl's exec treats output to stderr as failure.
|
|
If we use [exec -ignorestderr] then it instead replaces
|
|
stderr's output with its own message, invalidating
|
|
test expectations. */
|
|
1 ? stdout : stderr;
|
|
fprintf(fp, "All %sdefine entries:\n",
|
|
cmpp__pp_zdelim(pp));
|
|
cmpp__dump_defines(pp, fp, 1);
|
|
}
|
|
}
|
|
#if !defined(CMPP_OMIT_D_DB)
|
|
ISFLAG2("db", "db-file"){
|
|
/* Undocumented flag used for testing purposes. */
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp_db_name_set(pp, zVal);
|
|
}
|
|
}
|
|
#endif
|
|
ISFLAG("version"){
|
|
NOVAL;
|
|
#if !defined(CMPP_OMIT_FILE_IO)
|
|
fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n",
|
|
cmpp_version(),
|
|
sqlite3_libversion(),
|
|
sqlite3_sourceid());
|
|
#endif
|
|
doIt = 100;
|
|
break;
|
|
}
|
|
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
|
|
ISFLAG("safe-mode"){
|
|
if( i>0 ){
|
|
cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"--%s, if used, must be the first argument.",
|
|
zArg);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
else{
|
|
cmpp__err(pp,CMPP_RC_MISUSE,
|
|
"Unhandled flag: %s", argv[i]);
|
|
}
|
|
}
|
|
DOIT {
|
|
if(!nFile){
|
|
/* We got no file arguments, so read from stdin. */
|
|
if(0
|
|
&& !pp->pimpl->flags.nIncludeDir
|
|
&& cmpp_include_dir_add(pp, ".") ){
|
|
break;
|
|
}
|
|
open_output_if_needed;
|
|
cmpp_process_file(pp, "-");
|
|
}
|
|
}
|
|
#undef DOIT
|
|
}
|
|
return ppCode;
|
|
#undef ARGVAL
|
|
#undef M
|
|
#undef ISFLAG
|
|
#undef ISFLAG2
|
|
#undef NOVAL
|
|
#undef open_output_if_needed
|
|
}
|
|
|
|
void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){
|
|
#if defined(CMPP_OMIT_FILE_IO)
|
|
(void)zAppName; (void)fOut;
|
|
#else
|
|
fprintf(fOut, "%s version %s\nwith SQLite %s %s\n",
|
|
zAppName ? zAppName : "c-pp",
|
|
cmpp_version(),
|
|
sqlite3_libversion(),
|
|
sqlite3_sourceid());
|
|
fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName);
|
|
fprintf(fOut,
|
|
"Flags and filenames may be in any order and "
|
|
"they are processed in that order.\n"
|
|
"\nFlags:\n");
|
|
#define GAP " "
|
|
#define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D)
|
|
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
|
|
arg("--safe-mode",
|
|
"Disables preprocessing directives which use the filesystem "
|
|
"or invoke external processes. If used, it must be the first "
|
|
"argument.");
|
|
#endif
|
|
|
|
arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
|
|
GAP "Because arguments are processed in order, this should\n"
|
|
GAP "normally be given before -f.");
|
|
arg("-f|--file FILE","Process FILE (default=- (stdin)).\n"
|
|
GAP "All non-flag arguments are assumed to be the input files.");
|
|
arg("-e SCRIPT",
|
|
"Treat SCRIPT as a complete c-pp input and process it.\n"
|
|
GAP "Doing anything marginally useful with this requires\n"
|
|
GAP "using it several times, once per directive. It will not\n"
|
|
GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for "
|
|
CMPP_DEFAULT_DELIM "expr, "
|
|
CMPP_DEFAULT_DELIM "assert, and "
|
|
CMPP_DEFAULT_DELIM "define.");
|
|
arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
|
|
arg("-UXYZ","Undefine all defines matching glob XYZ.");
|
|
arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
|
|
arg("-LXYZ","Add dir XYZ to the loadable module search path.");
|
|
arg("-FXYZ=filename",
|
|
"Define XYZ to the raw contents of the given file.\n"
|
|
GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n"
|
|
GAP "Maybe it should be. Or maybe we need a new flag for that.");
|
|
arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE "
|
|
"(default=" CMPP_DEFAULT_DELIM ").");
|
|
arg("--@policy retain|elide|error|off",
|
|
"Specifies how to handle @tokens@ (default=off).\n"
|
|
GAP "off = do not look for @tokens@\n"
|
|
GAP "retain = parse @tokens@ and retain any undefined ones\n"
|
|
GAP "elide = parse @tokens@ and elide any undefined ones\n"
|
|
GAP "error = parse @tokens@ and error out for any undefined ones"
|
|
);
|
|
arg("-u|--undefined-policy NAME",
|
|
"Sets the policy for how to handle references to undefined key:\n"
|
|
GAP "null = treat them as empty/falsy. This is the default.\n"
|
|
GAP "error = trigger an error. This should probably be "
|
|
"the default."
|
|
);
|
|
arg("-@", "Equivalent to --@policy=error.");
|
|
arg("-no-@", "Equivalent to --@policy=off (the default).");
|
|
arg("--sql-trace", "Send a trace of all SQL to stderr.");
|
|
arg("--sql-trace-x",
|
|
"Like --sql-trace but expand all bound values in the SQL.");
|
|
arg("--no-sql-trace", "Disable SQL tracing (default).");
|
|
arg("--chomp-F", "One trailing newline is trimmed from files "
|
|
"read via -FXYZ=filename.");
|
|
arg("--no-chomp-F", "Disable --chomp-F (default).");
|
|
#undef arg
|
|
#undef GAP
|
|
fputs("\nFlags which require a value accept either "
|
|
"--flag=value or --flag value. "
|
|
"The exceptions are that the -D... and -F... flags "
|
|
"require their '=' to be part of the flag (because they "
|
|
"are parsed elsewhere).\n\n",fOut);
|
|
#endif /*CMPP_OMIT_FILE_IO*/
|
|
}
|
|
|
|
#if defined(CMPP_MAIN) /* add main() */
|
|
int main(int argc, char const * const * argv){
|
|
int rc = 0;
|
|
cmpp * pp = 0;
|
|
cmpp_flag32_t newFlags = 0
|
|
#if defined(CMPP_MAIN_SAFEMODE)
|
|
| cmpp_ctor_F_SAFEMODE
|
|
#endif
|
|
;
|
|
cmpp_b bArgs = cmpp_b_empty;
|
|
sqlite3_config(SQLITE_CONFIG_URI,1);
|
|
{
|
|
/* Copy argv to a string so we can #define it. This has proven
|
|
helpful in testing, debugging, and output validation. */
|
|
for( int i = 0; i < argc; ++i ){
|
|
if( i ) cmpp_b_append_ch(&bArgs,' ');
|
|
cmpp_b_append(&bArgs, argv[i], strlen(argv[i]));
|
|
}
|
|
if( (rc = bArgs.errCode) ) goto end;
|
|
if( argc>1 && cmpp__arg_is_flag("--safe-mode", argv[1], NULL) ){
|
|
newFlags |= cmpp_ctor_F_SAFEMODE;
|
|
--argc;
|
|
++argv;
|
|
}
|
|
}
|
|
cmpp_ctor_cfg const cfg = {
|
|
.flags = newFlags
|
|
};
|
|
rc = cmpp_ctor(&pp, &cfg);
|
|
if( rc ) goto end;
|
|
/**
|
|
Define CMPP_MAIN_INIT to the name of a function with the signature
|
|
|
|
int (*)(cmpp*)
|
|
|
|
to have it called here. The intent is that custom directives can
|
|
be installed this way without having to edit this code.
|
|
*/
|
|
#if defined(CMPP_MAIN_INIT)
|
|
extern int CMPP_MAIN_INIT(cmpp*);
|
|
if( 0!=(rc = CMPP_MAIN_INIT(pp)) ){
|
|
g_warn0("Initialization via CMPP_MAIN_INIT() failed");
|
|
goto end;
|
|
}
|
|
#endif
|
|
#if defined(CMPP_MAIN_AUTOLOADER)
|
|
{
|
|
extern int CMPP_MAIN_AUTOLOADER(cmpp*,char const *,void*);
|
|
cmpp_d_autoloader al = cmpp_d_autoloader_empty;
|
|
al.f = CMPP_MAIN_AUTOLOADER;
|
|
cmpp_d_autoloader_set(pp, &al);
|
|
}
|
|
#endif
|
|
if( cmpp_define_v2(pp, "c-pp::argv", (char*)bArgs.z) ) goto end;
|
|
cmpp_b_clear(&bArgs);
|
|
rc = cmpp_process_argv(pp, argc-1, argv+1);
|
|
switch( rc ){
|
|
case 0: break;
|
|
case CMPP_RC_HELP:
|
|
rc = 0;
|
|
cmpp_process_argv_usage(argv[0], stdout);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
end:
|
|
cmpp_b_clear(&bArgs);
|
|
if( pp ){
|
|
char const *zErr = 0;
|
|
rc = cmpp_err_get(pp, &zErr);
|
|
if( rc && CMPP_RC_HELP!=rc ){
|
|
g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr);
|
|
}
|
|
cmpp_dtor(pp);
|
|
}else if( rc && CMPP_RC_HELP!=rc ){
|
|
g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc));
|
|
}
|
|
sqlite3_shutdown();
|
|
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|
|
#endif /* CMPP_MAIN */
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the cmpp_b-related parts of libcmpp.
|
|
*/
|
|
|
|
const cmpp_b cmpp_b_empty = cmpp_b_empty_m;
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp,
|
|
cmpp_b * const os,
|
|
void const * src,
|
|
cmpp_size_t n){
|
|
if( !ppCode && cmpp_b_append(os, src, n) ){
|
|
cmpp_check_oom(pp, 0);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp,
|
|
cmpp_b * const os,
|
|
cmpp_size_t n){
|
|
if( !ppCode && cmpp_b_reserve(os, n) ){
|
|
cmpp_check_oom(pp, 0);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){
|
|
if( s->z ) cmpp_mfree(s->z);
|
|
*s = cmpp_b_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){
|
|
if( s->z ){
|
|
#if 1
|
|
memset(s->z, 0, s->nAlloc)
|
|
/* valgrind pushes for this, which is curious because
|
|
cmpp_b_reserve[3]() memset()s new space to 0.
|
|
|
|
Try the following without this block using one commit after
|
|
[5f9c31d1da1d] (that'll be the commit that this comment and #if
|
|
block were added):
|
|
|
|
##define foo
|
|
##if not defined a
|
|
##/if
|
|
##query define {select ?1 a} bind [1]
|
|
|
|
There's a misuse complaint about a jump depending on
|
|
uninitialized memory deep under cmpp__is_int(), in strlen(), on
|
|
the "define" argument of the ##query. It does not appear if
|
|
the lines above it are removed, which indicates that it's at
|
|
least semi-genuine. gcc v13.3.0, if it matters.
|
|
*/;
|
|
#else
|
|
s->z[0] = 0;
|
|
#endif
|
|
s->n = 0;
|
|
}
|
|
s->errCode = 0;
|
|
return s;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){
|
|
if( l!=r ){
|
|
cmpp_b const x = *l;
|
|
*l = *r;
|
|
*r = x;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){
|
|
if( 0==s->errCode && s->nAlloc < n ){
|
|
void * const m = cmpp_mrealloc(s->z, s->nAlloc + n);
|
|
if( m ){
|
|
memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc))
|
|
/* valgrind convincingly recommends this. */;
|
|
s->z = m;
|
|
s->nAlloc += n;
|
|
}else{
|
|
s->errCode = CMPP_RC_OOM;
|
|
}
|
|
}
|
|
return s->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src,
|
|
cmpp_size_t n){
|
|
if(0==os->errCode){
|
|
cmpp_size_t const nNeeded = os->n + n + 1;
|
|
if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){
|
|
assert( CMPP_RC_OOM==os->errCode );
|
|
return os->errCode;
|
|
}
|
|
memcpy(os->z + os->n, src, n);
|
|
os->n += n;
|
|
os->z[os->n] = 0;
|
|
if( 0 ) {
|
|
g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n,
|
|
(char const*) src, (int)os->n);
|
|
}
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){
|
|
if( 0==os->errCode
|
|
&& (os->n+1<os->nAlloc
|
|
|| 0==cmpp_b_reserve(os, os->n+2)) ){
|
|
os->z[os->n++] = (unsigned char)ch;
|
|
os->z[os->n] = 0;
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_i32)(cmpp_b * os, int32_t d){
|
|
if( 0==os->errCode ){
|
|
char buf[16] = {0};
|
|
int const n = snprintf(buf, sizeof(buf), "%" PRIi32, d);
|
|
cmpp_b_append(os, buf, (unsigned)n);
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_i64)(cmpp_b * os, int64_t d){
|
|
if( 0==os->errCode ){
|
|
char buf[32] = {0};
|
|
int const n = snprintf(buf, sizeof(buf), "%" PRIi64, d);
|
|
cmpp_b_append(os, buf, (unsigned)n);
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_b_chomp)(cmpp_b * b){
|
|
return cmpp_chomp(b->z, &b->n);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_list_cleanup)(cmpp_b_list *li){
|
|
while( li->nAlloc ){
|
|
cmpp_b * const b = li->list[--li->nAlloc];
|
|
if(b){
|
|
cmpp_b_clear(b);
|
|
cmpp_mfree(b);
|
|
}
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = cmpp_b_list_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){
|
|
while( li->n ){
|
|
cmpp_b * const b = li->list[li->n--];
|
|
if(b) cmpp_b_reuse(b);
|
|
}
|
|
}
|
|
|
|
static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){
|
|
cmpp_b * p = 0;
|
|
assert( li->list ? li->nAlloc : 0==li->nAlloc );
|
|
if( !cmpp_b_list_reserve(NULL, li,
|
|
cmpp__li_reserve1_size(li, 20)) ){
|
|
p = li->list[li->n];
|
|
if( p ){
|
|
cmpp_b_reuse(p);
|
|
}else{
|
|
p = cmpp_malloc(sizeof(*p));
|
|
if( p ){
|
|
li->list[li->n++] = p;
|
|
*p = cmpp_b_empty;
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
|
|
largest first and empty slots last.
|
|
*/
|
|
static int cmpp_b__cmp_desc(const void *p1, const void *p2){
|
|
cmpp_b const * const eL = *(cmpp_b const **)p1;
|
|
cmpp_b const * const eR = *(cmpp_b const **)p2;
|
|
if( eL==eR ) return 0;
|
|
else if( !eL ) return 1;
|
|
else if (!eR ) return -1;
|
|
return (int)(/*largest first*/eL->nAlloc - eR->nAlloc);
|
|
}
|
|
|
|
/**
|
|
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
|
|
smallest first and empty slots last.
|
|
*/
|
|
static int cmpp_b__cmp_asc(const void *p1, const void *p2){
|
|
cmpp_b const * const eL = *(cmpp_b const **)p1;
|
|
cmpp_b const * const eR = *(cmpp_b const **)p2;
|
|
if( eL==eR ) return 0;
|
|
else if( !eL ) return 1;
|
|
else if (!eR ) return -1;
|
|
return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc);
|
|
}
|
|
|
|
/**
|
|
Sort li's buffer list using the given policy. NULL entries always
|
|
sort last. This is a no-op of how == cmpp_b_list_UNSORTED or
|
|
li->n<2.
|
|
*/
|
|
static void cmpp_b_list__sort(cmpp_b_list * const li,
|
|
enum cmpp_b_list_e how){
|
|
switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){
|
|
case cmpp_b_list_UNSORTED:
|
|
break;
|
|
case cmpp_b_list_DESC:
|
|
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc);
|
|
break;
|
|
case cmpp_b_list_ASC:
|
|
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
cmpp_b_list * const li = &pi->recycler.buf;
|
|
cmpp_b * b = 0;
|
|
if( cmpp_b_list_UNSORTED==pi->recycler.bufSort ){
|
|
pi->recycler.bufSort = cmpp_b_list_DESC;
|
|
cmpp_b_list__sort(li, pi->recycler.bufSort);
|
|
assert( cmpp_b_list_UNSORTED!=pi->recycler.bufSort
|
|
|| pi->recycler.buf.n<2 );
|
|
}
|
|
for( cmpp_size_t i = 0; i < li->n; ++i ){
|
|
b = li->list[i];
|
|
if( b ){
|
|
li->list[i] = 0;
|
|
assert( !b->n &&
|
|
"Someone wrote to a buffer after giving it back" );
|
|
if( i < li->n-1 ){
|
|
pi->recycler.bufSort = cmpp_b_list_UNSORTED;
|
|
}
|
|
return cmpp_b_reuse(b);
|
|
}
|
|
}
|
|
/**
|
|
Allocate the list entry now and then remove the buffer from it to
|
|
"borrow" it. We allocate now, instead of in cmpp_b_return(), so
|
|
that that function has no OOM condition (handling it properly in
|
|
higher-level code would be a mess).
|
|
*/
|
|
b = cmpp_b_list_push(li);
|
|
if( 0==cmpp_check_oom(pp, b) ) {
|
|
assert( b==li->list[li->n-1] );
|
|
li->list[li->n-1] = 0;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){
|
|
if( !b ) return;
|
|
cmpp__pi(pp);
|
|
cmpp_b_list * const li = &pi->recycler.buf;
|
|
for( cmpp_size_t i = 0; i < li->n; ++i ){
|
|
if( !li->list[i] ){
|
|
li->list[i] = cmpp_b_reuse(b);
|
|
pi->recycler.bufSort = cmpp_b_list_UNSORTED;
|
|
return;
|
|
}
|
|
}
|
|
assert( !"This shouldn't be possible - no slot in recycler.buf" );
|
|
cmpp_b_clear(b);
|
|
cmpp_mfree(b);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_output_f_b)(
|
|
void * state, void const * src, cmpp_size_t n
|
|
){
|
|
if( state ){
|
|
return cmpp_b_append(state, src, n);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if CMPP__OBUF
|
|
int cmpp__obuf_flush(cmpp__obuf * b);
|
|
int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n);
|
|
void cmpp__obuf_cleanup(cmpp__obuf * b);
|
|
int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n);
|
|
int cmpp_flush_f_obuf(void * state);
|
|
void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o);
|
|
const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m;
|
|
const cmpp_outputer cmpp_outputer_obuf = {
|
|
.out = cmpp_output_f_obuf,
|
|
.flush = cmpp_flush_f_obuf,
|
|
.cleanup = cmpp_outputer_cleanup_f_obuf
|
|
};
|
|
#endif
|
|
/*
|
|
** 2025-11-07:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the db-related pieces of libcmpp.
|
|
*/
|
|
|
|
/**
|
|
A proxy for sqlite3_prepare() which updates pp->pimpl->err on error.
|
|
*/
|
|
static int cmpp__prepare(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, ...){
|
|
/* We need for pp->pimpl->stmt.sp* to work regardless of pending errors so
|
|
that we can, when appropriate, create the rollback statements. */
|
|
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
|
|
char * z = 0;
|
|
int n = 0;
|
|
va_list va;
|
|
assert( pp->pimpl->db.dbh );
|
|
va_start(va, zSql);
|
|
sqlite3_str_vappendf(str, zSql, va);
|
|
va_end(va);
|
|
z = cmpp_str_finish(pp, str, &n);
|
|
if( z ){
|
|
int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, z, n, pStmt, 0);
|
|
cmpp__db_rc(pp, rc, z);
|
|
sqlite3_free(z);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
|
|
bool prepEvenIfErr){
|
|
if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL;
|
|
sqlite3_stmt ** q = 0;
|
|
char const * zSql = 0;
|
|
switch(which){
|
|
default:
|
|
cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which);
|
|
return NULL;
|
|
#define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break;
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
}
|
|
assert( q );
|
|
assert( zSql && *zSql );
|
|
if( !*q && (!ppCode || prepEvenIfErr) ){
|
|
cmpp__prepare(pp, q, "%s", zSql);
|
|
}
|
|
return *q;
|
|
}
|
|
|
|
void cmpp__stmt_reset(sqlite3_stmt * const q){
|
|
if( q ){
|
|
sqlite3_clear_bindings(q);
|
|
sqlite3_reset(q);
|
|
}
|
|
}
|
|
|
|
static inline int cmpp__stmt_is_sp(cmpp const * const pp,
|
|
sqlite3_stmt const * const q){
|
|
return q==pp->pimpl->stmt.spBegin
|
|
|| q==pp->pimpl->stmt.spRelease
|
|
|| q==pp->pimpl->stmt.spRollback;
|
|
}
|
|
|
|
int cmpp__step(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){
|
|
int rc = SQLITE_ERROR;
|
|
assert( q );
|
|
if( !ppCode || cmpp__stmt_is_sp(pp,q) ){
|
|
rc = sqlite3_step(q);
|
|
cmpp__db_rc(pp, rc, sqlite3_sql(q));
|
|
}
|
|
if( resetIt /* even if ppCode!=0 */ ) cmpp__stmt_reset(q);
|
|
assert( 0!=rc );
|
|
return rc;
|
|
}
|
|
|
|
|
|
/**
|
|
Expects an SQLITE_... result code and returns an approximate match
|
|
from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE
|
|
as non-errors, returning 0 for those.
|
|
*/
|
|
static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode);
|
|
int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){
|
|
(void)db;
|
|
int rc = 0;
|
|
switch(sqliteCode & 0xff){
|
|
case SQLITE_ROW:
|
|
case SQLITE_DONE:
|
|
case SQLITE_OK: rc = 0; break;
|
|
case SQLITE_NOMEM: rc = CMPP_RC_OOM; break;
|
|
case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break;
|
|
case SQLITE_TOOBIG:
|
|
case SQLITE_FULL:
|
|
case SQLITE_RANGE: rc = CMPP_RC_RANGE; break;
|
|
case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break;
|
|
case SQLITE_PERM:
|
|
case SQLITE_AUTH:
|
|
case SQLITE_BUSY:
|
|
case SQLITE_LOCKED:
|
|
case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break;
|
|
case SQLITE_CANTOPEN:
|
|
case SQLITE_IOERR: rc = CMPP_RC_IO; break;
|
|
case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break;
|
|
default:
|
|
//MARKER(("sqlite3_errcode()=0x%04x\n", rc));
|
|
rc = CMPP_RC_DB; break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__db_rc(cmpp *pp, int dbRc, char const *zMsg){
|
|
switch(dbRc){
|
|
case 0:
|
|
case SQLITE_DONE:
|
|
case SQLITE_ROW:
|
|
return 0;
|
|
default:
|
|
return cmpp_err_set(
|
|
pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
|
|
"SQLite error #%d: %s%s%s",
|
|
dbRc,
|
|
pp->pimpl->db.dbh
|
|
? sqlite3_errmsg(pp->pimpl->db.dbh)
|
|
: "<no db handle>",
|
|
zMsg ? ": " : "",
|
|
zMsg ? zMsg : ""
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
The base "define" impl. Requires q to be an INSERT for one of the
|
|
define tables and have the (t,k,v) columns set up to bind to ?1,
|
|
?2, and ?3.
|
|
*/
|
|
static
|
|
int cmpp__define_impl(cmpp * const pp,
|
|
sqlite3_stmt * const q,
|
|
unsigned char const * zKey,
|
|
cmpp_ssize_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_ssize_t nVal,
|
|
int tType,
|
|
bool resetStmt){
|
|
if( 0==ppCode){
|
|
assert( q );
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
nVal = cmpp__strlenu(zVal, nVal);
|
|
if( 0==cmpp__bind_textn(pp, q, 2, zKey, (int)nKey)
|
|
&& 0==cmpp__bind_int(pp, q, 1, tType) ){
|
|
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
|
|
/* TODO? if tType==cmpp_TT_Blob, bind it as a blob */
|
|
if( zVal ){
|
|
if( nVal ){
|
|
cmpp__bind_textn(pp, q, 3, zVal, (int)nVal);
|
|
}else{
|
|
/* Arguable */
|
|
cmpp__bind_null(pp, q, 3);
|
|
}
|
|
}else{
|
|
cmpp__bind_int(pp, q, 3, 1);
|
|
}
|
|
cmpp__step(pp, q, resetStmt);
|
|
g_debug(pp,2,("define: %s [%s]=[%.*s]\n",
|
|
cmpp_tt_cstr(tType), zKey, (int)nVal, zVal));
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__define2(cmpp *pp,
|
|
unsigned char const * zKey,
|
|
cmpp_ssize_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_ssize_t nVal,
|
|
cmpp_tt tType){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( q ){
|
|
cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
The legacy variant of define() which accepts X=Y in zKey. This
|
|
continues to exist because it's convenient for passing args from
|
|
main().
|
|
*/
|
|
static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal,
|
|
cmpp_tt ttype ){
|
|
|
|
if(ppCode) return ppCode;
|
|
CmppKvp kvp = CmppKvp_empty;
|
|
if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1,
|
|
zVal
|
|
? CmppKvp_op_none
|
|
: CmppKvp_op_eq1) ) {
|
|
return ppCode;
|
|
}
|
|
if( kvp.v.z ){
|
|
if( zVal ){
|
|
assert(!"cannot happen - CmppKvp_op_none will prevent it");
|
|
return cmpp_err_set(pp, CMPP_RC_MISUSE,
|
|
"Cannot assign two values to [%.*s] [%.*s] [%s]",
|
|
kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal);
|
|
}
|
|
}else{
|
|
kvp.v.z = (unsigned char const *)zVal;
|
|
kvp.v.n = zVal ? (int)strlen(zVal) : 0;
|
|
}
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( !q ) return ppCode;
|
|
int64_t intCheck = 0;
|
|
switch( ttype ){
|
|
case cmpp_TT_Unknown:
|
|
if(kvp.v.n){
|
|
if( cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
|
|
ttype = cmpp_TT_Int;
|
|
if( '+'==*kvp.v.z ){
|
|
++kvp.v.z;
|
|
--kvp.v.n;
|
|
}
|
|
}else{
|
|
ttype = cmpp_TT_String;
|
|
}
|
|
}else if( kvp.v.z ){
|
|
ttype = cmpp_TT_String;
|
|
}else{
|
|
ttype = cmpp_TT_Int;
|
|
intCheck = 1 /* No value ==> value of 1. */;
|
|
}
|
|
break;
|
|
case cmpp_TT_Int:
|
|
if( !cmpp__is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
|
|
ttype = cmpp_TT_String;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if( 0==cmpp__bind_textn(pp, q, 2, kvp.k.z, kvp.k.n)
|
|
&& 0==cmpp__bind_int(pp, q, 1, ttype) ){
|
|
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
|
|
switch( ttype ){
|
|
case cmpp_TT_Int:
|
|
cmpp__bind_int(pp, q, 3, intCheck);
|
|
break;
|
|
case cmpp_TT_Null:
|
|
cmpp__bind_null(pp, q, 3);
|
|
break;
|
|
default:
|
|
cmpp__bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n);
|
|
break;
|
|
}
|
|
cmpp__step(pp, q, true);
|
|
g_debug(pp,2,("define: [%.*s]=[%.*s]\n",
|
|
kvp.k.n, kvp.k.z,
|
|
kvp.v.n, kvp.v.z));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){
|
|
return cmpp__define_legacy(pp, zKey, zVal, cmpp_TT_Unknown);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){
|
|
return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1,
|
|
cmpp_TT_String);
|
|
}
|
|
|
|
static
|
|
int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_ssize_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_ssize_t nVal,
|
|
int ttype,
|
|
int64_t * pId){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false);
|
|
if( q ){
|
|
if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false)
|
|
&& pId ){
|
|
*pId = sqlite3_column_int64(q, 0);
|
|
assert( *pId );
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey,
|
|
char const *zVal, int64_t *pId){
|
|
assert( pId );
|
|
return cmpp__define_shadow(pp, ustr_c(zKey), -1,
|
|
ustr_c(zVal), -1, cmpp_TT_String, pId);
|
|
}
|
|
|
|
static
|
|
int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_ssize_t nKey, int64_t id){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false);
|
|
if( q ){
|
|
cmpp__bind_textn(pp, q, 1, zKey, (int)nKey);
|
|
cmpp__bind_int(pp, q, 2, id);
|
|
cmpp__step(pp, q, true);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){
|
|
return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id);
|
|
}
|
|
|
|
/*
|
|
** This sqlite3_trace_v2() callback outputs tracing info using
|
|
** ((cmpp*)c)->sqlTrace.pFile.
|
|
*/
|
|
static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){
|
|
switch(dx){
|
|
case SQLITE_TRACE_STMT:{
|
|
char const * const zSql = x;
|
|
cmpp * const pp = c;
|
|
cmpp__pi(pp);
|
|
if(pi->sqlTrace.out.out){
|
|
char * const zExp = pi->sqlTrace.expandSql
|
|
? sqlite3_expanded_sql((sqlite3_stmt*)p)
|
|
: 0;
|
|
sqlite3_str * const s = sqlite3_str_new(pi->db.dbh);
|
|
if( pi->dx ){
|
|
cmpp__dx_append_script_info(pi->dx, s);
|
|
sqlite3_str_appendchar(s, 1, ':');
|
|
sqlite3_str_appendchar(s, 1, ' ');
|
|
}
|
|
sqlite3_str_appendall(s, zExp ? zExp : zSql);
|
|
sqlite3_str_appendchar(s, 1, '\n');
|
|
int const n = sqlite3_str_length(s);
|
|
if( n ){
|
|
char * const z = sqlite3_str_finish(s);
|
|
if( z ){
|
|
cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n);
|
|
sqlite3_free(z);
|
|
}
|
|
}
|
|
sqlite3_free(zExp);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#include <sys/stat.h>
|
|
/*
|
|
** sqlite3 UDF which returns true if its argument refers to an
|
|
** accessible file, else false.
|
|
*/
|
|
static void cmpp__udf_file_exists(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
const char *zName;
|
|
(void)(argc); /* Unused parameter */
|
|
zName = (const char*)sqlite3_value_text(argv[0]);
|
|
if( 0!=zName ){
|
|
struct stat sb;
|
|
sqlite3_result_int(context, stat(zName, &sb)
|
|
? 0
|
|
: S_ISREG(sb.st_mode));
|
|
}
|
|
}
|
|
|
|
static void cmpp__udf_truthy(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
(void)(argc); /* Unused parameter */
|
|
assert(1==argc);
|
|
int buul = 0;
|
|
sqlite3_value * const sv = argv[0];
|
|
switch( sqlite3_value_type(sv) ){
|
|
case SQLITE_NULL:
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
buul = 0.0!=sqlite3_value_double(sv);
|
|
break;
|
|
case SQLITE_INTEGER:
|
|
buul = 0!=sqlite3_value_int(sv);
|
|
break;
|
|
case SQLITE_TEXT:
|
|
case SQLITE_BLOB:{
|
|
int const n = sqlite3_value_bytes(sv);
|
|
if( n>1 ) buul = 1;
|
|
else if( 1==n ){
|
|
const char *z =
|
|
(const char*)sqlite3_value_text(sv);
|
|
buul = z
|
|
? 0!=strcmp(z,"0")
|
|
: 0;
|
|
}
|
|
}
|
|
}
|
|
sqlite3_result_int(context, buul);
|
|
}
|
|
|
|
/**
|
|
SQLite3 UDF which compares its two arguments using memcmp()
|
|
semantics. NULL will compare equal to NULL, but less than anything
|
|
else.
|
|
*/
|
|
static void cmpp__udf_compare(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
(void)(argc); /* Unused parameter */
|
|
assert(2==argc);
|
|
sqlite3_value * const v1 = argv[0];
|
|
sqlite3_value * const v2 = argv[1];
|
|
unsigned char const * const z1 = sqlite3_value_text(v1);
|
|
unsigned char const * const z2 = sqlite3_value_text(v2);
|
|
int const n1 = sqlite3_value_bytes(v1);
|
|
int const n2 = sqlite3_value_bytes(v2);
|
|
int rv;
|
|
if( !z1 ){
|
|
rv = z2 ? -1 : 0;
|
|
}else if( !z2 ){
|
|
rv = 1;
|
|
}else{
|
|
rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2);
|
|
}
|
|
if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv);
|
|
sqlite3_result_int(context, rv);
|
|
}
|
|
|
|
int cmpp__db_init(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
if( pi->db.dbh || ppCode ) return ppCode;
|
|
int rc;
|
|
char * zErr = 0;
|
|
const char * zDrops =
|
|
"BEGIN EXCLUSIVE;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;"
|
|
"DROP VIEW IF EXISTS " CMPP__DB_MAIN_NAME ".vdef;"
|
|
"COMMIT;"
|
|
;
|
|
const char * zSchema =
|
|
"BEGIN EXCLUSIVE;"
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".def("
|
|
/* ^^^ defines */
|
|
"t INTEGER DEFAULT NULL,"
|
|
/*^^ type: cmpp_tt or NULL */
|
|
"k TEXT PRIMARY KEY NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
") WITHOUT ROWID;"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".incl("
|
|
/* ^^^ files currently being included */
|
|
"file TEXT PRIMARY KEY NOT NULL,"
|
|
"srcFile TEXT DEFAULT NULL,"
|
|
"srcLine INTEGER DEFAULT 0"
|
|
") WITHOUT ROWID;"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath("
|
|
/* ^^^ include path. We use (ORDER BY priority DESC, rowid) to
|
|
make their priority correct. priority should only be set by the
|
|
#include directive for its cwd entry. */
|
|
"priority INTEGER DEFAULT 0," /* higher sorts first */
|
|
"dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE"
|
|
");"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath("
|
|
/* ^^^ module path. We use ORDER BY ROWID to make their
|
|
priority correct. */
|
|
"dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
|
|
");"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".predef("
|
|
/* ^^^ pre-defines */
|
|
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
|
|
"k TEXT PRIMARY KEY NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
") WITHOUT ROWID;"
|
|
"INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)"
|
|
" VALUES(NULL,'cmpp::version','" CMPP_VERSION "')"
|
|
";"
|
|
|
|
/**
|
|
sdefs - "scoped defines" or "shadow defines". The problem these
|
|
solve is the one of supporting a __FILE__ define in cmpp input
|
|
sources, such that it remains valid both before and after an
|
|
#include, but has a new name in the scope of an #include. We
|
|
can't use savepoints for that because they're a nuclear option
|
|
affecting _all_ #defines in the #include'd file, whereas we
|
|
normally want #defines to stick around across files.
|
|
|
|
See cmpp_define_shadow() and cmpp_define_unshadow().
|
|
*/
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef("
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
|
|
"k TEXT NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
");"
|
|
|
|
/**
|
|
vdef is a view consolidating the various #define stores. It's
|
|
intended to be used for all general-purpose fetching of defines
|
|
and it orders the results such that the library's defines
|
|
supercede all others, then scoped keys, then client-level
|
|
defines.
|
|
|
|
To push a new sdef we simply insert into sdef. Then vdef will
|
|
order the newest sdef before any entry from the def table.
|
|
*/
|
|
"CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS"
|
|
" SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef"
|
|
/* ------^^^^ sorts before numbers */
|
|
" UNION ALL"
|
|
" SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef"
|
|
/* ^^^^ sorts newest of matching keys first */
|
|
" UNION ALL"
|
|
" SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def"
|
|
" ORDER BY 1, 3"
|
|
";"
|
|
|
|
#if 0
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype("
|
|
/* ^^^ token types */
|
|
"t INTEGER PRIMARY KEY NOT NULL,"
|
|
/*^^ type: cmpp_tt */
|
|
"n TEXT NOT NULL,"
|
|
/*^^ cmpp_TT_... name. */
|
|
"s TEXT DEFAULT NULL"
|
|
/* Symbolic or directive name, if any. */
|
|
");"
|
|
#endif
|
|
|
|
"COMMIT;"
|
|
"BEGIN EXCLUSIVE;"
|
|
;
|
|
cmpp__err_clear(pp);
|
|
int openFlags = SQLITE_OPEN_READWRITE;
|
|
if( pi->db.zName ){
|
|
openFlags |= SQLITE_OPEN_CREATE;
|
|
}
|
|
rc = sqlite3_open_v2(
|
|
pi->db.zName ? pi->db.zName : ":memory:",
|
|
&pi->db.dbh, openFlags, 0);
|
|
if(rc){
|
|
cmpp__db_rc(pp, rc, pi->db.zName
|
|
? pi->db.zName
|
|
: ":memory:");
|
|
sqlite3_close(pi->db.dbh);
|
|
pi->db.dbh = 0;
|
|
assert(ppCode);
|
|
return rc;
|
|
}
|
|
sqlite3_busy_timeout(pi->db.dbh, 5000);
|
|
sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME,
|
|
CMPP__DB_MAIN_NAME);
|
|
rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT,
|
|
cmpp__db_sq3TraceV2, pp);
|
|
if( cmpp__db_rc(pp, rc, "Installing tracer failed") ){
|
|
goto end;
|
|
}
|
|
//g_warn("Schema:\n%s\n",zSchema);
|
|
struct {
|
|
/* SQL UDFs */
|
|
char const * const zName;
|
|
void (*xUdf)(sqlite3_context *,int,sqlite3_value **);
|
|
int arity;
|
|
int flags;
|
|
} aFunc[] = {
|
|
{
|
|
.zName = "cmpp_file_exists",
|
|
.xUdf = cmpp__udf_file_exists,
|
|
.arity = 1,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
|
|
},
|
|
{
|
|
.zName = "cmpp_truthy",
|
|
.xUdf = cmpp__udf_truthy,
|
|
.arity = 1,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
|
|
},
|
|
{
|
|
.zName = "cmpp_compare",
|
|
.xUdf = cmpp__udf_compare,
|
|
.arity = 2,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
|
|
}
|
|
};
|
|
assert( 0==rc );
|
|
for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){
|
|
rc = sqlite3_create_function(
|
|
pi->db.dbh, aFunc[i].zName, aFunc[i].arity,
|
|
aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0
|
|
);
|
|
}
|
|
if( cmpp__db_rc(pp, rc, "UDF registration failed.") ){
|
|
return ppCode;
|
|
}
|
|
if( pi->db.zName ){
|
|
/* Drop all cmpp tables when using a persistent db so that we are
|
|
not beholden to a given structure. TODO: a config flag to
|
|
toggle this. */
|
|
rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr);
|
|
}
|
|
if( !rc ){
|
|
rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr);
|
|
}
|
|
|
|
if( !rc ){
|
|
extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *);
|
|
rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL);
|
|
}
|
|
|
|
if(rc){
|
|
if( zErr ){
|
|
cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc),
|
|
"SQLite error #%d initializing DB: %s", rc, zErr);
|
|
sqlite3_free(zErr);
|
|
}else{
|
|
cmpp_err_set(pp, cmpp__db_errcode(pi->db.dbh, rc),
|
|
"SQLite error #%d initializing DB", rc);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
while(0){
|
|
/* Insert the ttype mappings. We don't yet make use of this but
|
|
only for lack of a use case ;). */
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false);
|
|
if( !q ) goto end;
|
|
#define E(N,STR) \
|
|
cmpp__bind_int(pp, q, 1, cmpp_TT_ ## N); \
|
|
cmpp__bind_textn(pp, q, 2, \
|
|
ustr_c("cmpp_TT_ " # N), sizeof("cmpp_TT_" # N)-1); \
|
|
if( STR ) cmpp__bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \
|
|
else cmpp__bind_null(pp, q, 3); \
|
|
if( SQLITE_DONE!=cmpp__step(pp, q, true) ) return ppCode;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
sqlite3_finalize(q);
|
|
pi->stmt.insTtype = 0;
|
|
break;
|
|
}
|
|
|
|
end:
|
|
if( !ppCode ){
|
|
/*
|
|
** Keep us from getting in the situation later that delayed
|
|
** preparation if one of the savepoint statements fails (e.g. due
|
|
** to OOM or memory corruption).
|
|
*/
|
|
cmpp__stmt(pp, CmppStmt_spBegin, false);
|
|
cmpp__stmt(pp, CmppStmt_spRelease, false);
|
|
cmpp__stmt(pp, CmppStmt_spRollback, false);
|
|
cmpp__lazy_init(pp);
|
|
}
|
|
return ppCode;
|
|
}
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the core cmpp_dx_f() implementations of libcmpp.
|
|
*/
|
|
|
|
static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){
|
|
return cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "'%s' may only be used once.",
|
|
arg->z);
|
|
}
|
|
|
|
/* No-op cmpp_dx_f() impl. */
|
|
static void cmpp_dx_f_noop(cmpp_dx *dx){
|
|
(void)dx;
|
|
}
|
|
|
|
/**
|
|
cmpp_kav_each_f() impl for use by #define {k->v}.
|
|
*/
|
|
static int cmpp_kav_each_f_define__group(
|
|
cmpp_dx *dx,
|
|
unsigned char const *zKey, cmpp_size_t nKey,
|
|
unsigned char const *zVal, cmpp_size_t nVal,
|
|
void* callbackState
|
|
){
|
|
if( (callbackState==dx)
|
|
&& cmpp_has(dx->pp, (char const*)zKey, nKey) ){
|
|
return dxppCode;
|
|
}
|
|
return cmpp__define2(dx->pp, zKey, nKey, zVal, nVal, cmpp_TT_String);
|
|
}
|
|
|
|
/* #error impl. */
|
|
static void cmpp_dx_f_error(cmpp_dx *dx){
|
|
const char *zBegin = (char const *)dx->args.z;
|
|
unsigned n = (unsigned)dx->args.nz;
|
|
if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){
|
|
++zBegin;
|
|
n -= 2;
|
|
}
|
|
if( n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_ERROR, "%.*s", n, zBegin);
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_ERROR, "(no additional info)");
|
|
}
|
|
}
|
|
|
|
/* Impl. for #define. */
|
|
static void cmpp_dx_f_define(cmpp_dx *dx){
|
|
cmpp_d const * const d = dx->d;
|
|
assert(d);
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp_arg const * aKey = 0;
|
|
int argNdx = 0;
|
|
int nChomp = 0;
|
|
unsigned nHeredoc = 0;
|
|
unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */;
|
|
bool ifNotDefined = false /* true if '?' arg */;
|
|
cmpp_arg const *aAppend = 0;
|
|
#define checkIsDefined(ARG) \
|
|
if(ifNotDefined && (cmpp_has(dx->pp, (char const*)ARG->z, ARG->n) \
|
|
|| dxppCode)) break
|
|
|
|
for( cmpp_arg const * arg = dx->args.arg0;
|
|
0==dxppCode && arg;
|
|
arg = arg->next, ++argNdx ){
|
|
//g_warn("arg=%s", arg->z);
|
|
if( 0==argNdx && cmpp_arg_equals(arg, "?") ){
|
|
/* Only set the key if it's not already defined. */
|
|
ifNotDefined = true;
|
|
continue;
|
|
}
|
|
switch( arg->ttype ){
|
|
case cmpp_TT_ShiftL3:
|
|
++nChomp;
|
|
/* fall through */
|
|
case cmpp_TT_ShiftL:
|
|
if( arg->next || argNdx<1 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Ill-placed '%s'.", arg->z);
|
|
}else if( arg->n >= sizeof(acHeredoc)-1 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_RANGE,
|
|
"Heredoc name is too large.");
|
|
}else if( !aKey ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Missing key before %s.",
|
|
cmpp__tt_cstr(cmpp_TT_ShiftL, false));
|
|
}else{
|
|
assert( aKey );
|
|
nHeredoc = aKey->n;
|
|
memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/);
|
|
}
|
|
break;
|
|
case cmpp_TT_OpEq:
|
|
if( 1 /*seenEq || argNdx!=1 || !arg->next*/ ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Ill-placed '%s'.", arg->z);
|
|
break;
|
|
}
|
|
continue;
|
|
case cmpp_TT_StringAt:
|
|
if( cmpp__StringAtIsOk(dx->pp, cmpp_atpol_CURRENT) ){
|
|
break;
|
|
}
|
|
/* fall through */
|
|
case cmpp_TT_Int:
|
|
case cmpp_TT_String:
|
|
case cmpp_TT_Word:
|
|
if( cmpp_arg_isflag(arg,"-chomp") ){
|
|
++nChomp;
|
|
break;
|
|
}
|
|
if( cmpp_arg_isflag(arg,"-append")
|
|
|| cmpp_arg_isflag(arg,"-a") ){
|
|
aAppend = arg->next;
|
|
if( !aAppend ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting argument for %s",
|
|
arg->z);
|
|
}
|
|
arg = aAppend;
|
|
break;
|
|
}
|
|
if( aKey ){
|
|
/* This is the second arg - the value */
|
|
checkIsDefined(aKey);
|
|
cmpp_b * const os = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0;
|
|
while( os ){
|
|
if( ba ){
|
|
cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false);
|
|
if( dxppCode ) break;
|
|
if( 0 ){
|
|
g_warn("key=%s\n", aKey->z);
|
|
g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z);
|
|
}
|
|
}
|
|
if( cmpp_arg_to_b(dx, arg, os,
|
|
cmpp_arg_to_b_F_BRACE_CALL) ) break;
|
|
cmpp_b * const which = (ba && ba->n) ? ba : os;
|
|
if( which==ba && os->n ){
|
|
if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n);
|
|
cmpp_b_append4(dx->pp, ba, os->z, os->n);
|
|
}
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n,
|
|
arg->ttype);
|
|
if( 0 ){
|
|
g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z);
|
|
g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z);
|
|
}
|
|
break;
|
|
}
|
|
cmpp_b_return(dx->pp, os);
|
|
cmpp_b_return(dx->pp, ba);
|
|
aKey = 0;
|
|
}else if( cmpp_TT_Word!=arg->ttype ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_TYPE,
|
|
"Expecting a define-name token here.");
|
|
}else if( arg->next ){
|
|
aKey = arg;
|
|
}else{
|
|
/* No value = a value of 1. */
|
|
checkIsDefined(arg);
|
|
cmpp__define2(dx->pp, arg->z, arg->n,
|
|
ustr_c("1"), 1, cmpp_TT_Int);
|
|
}
|
|
break;
|
|
case cmpp_TT_GroupSquiggly:
|
|
assert( !acHeredoc[0] );
|
|
if( (ifNotDefined ? argNdx>1 : argNdx>0) || arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"{...} must be the only argument.")
|
|
/* This is for simplicity's sake. */;
|
|
}else{
|
|
cmpp_kav_each(dx, arg->z, arg->n,
|
|
cmpp_kav_each_f_define__group,
|
|
ifNotDefined ? dx : NULL,
|
|
cmpp_kav_each_F_NOT_EMPTY
|
|
| cmpp_kav_each_F_CALL_VAL
|
|
| cmpp_kav_each_F_PARENS_EXPR
|
|
//TODO cmpp_kav_each_F_IF_UNDEF
|
|
);
|
|
}
|
|
aKey = 0;
|
|
break;
|
|
case cmpp_TT_GroupParen:{
|
|
if( !aKey ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"(...) is not permitted as a key.");
|
|
break;
|
|
}
|
|
checkIsDefined(aKey);
|
|
int d = 0;
|
|
if( 0==cmpp__arg_evalSubToInt(dx, arg, &d) ){
|
|
char exprBuf[32] = {0};
|
|
cmpp_size_t nVal =
|
|
(cmpp_size_t)snprintf(&exprBuf[0],
|
|
sizeof(exprBuf), "%d", d);
|
|
assert(nVal>0);
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n,
|
|
ustr_c(&exprBuf[0]), nVal, cmpp_TT_Int);
|
|
}
|
|
break;
|
|
}
|
|
case cmpp_TT_GroupBrace:{
|
|
if( !aKey ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"[...] is not permitted as a key.");
|
|
break;
|
|
}
|
|
checkIsDefined(aKey);
|
|
cmpp_b * const b = cmpp_b_borrow(dx->pp);
|
|
if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n,
|
|
b->z, b->n, cmpp_TT_AnyType);
|
|
}
|
|
cmpp_b_return(dx->pp, b);
|
|
break;
|
|
}
|
|
default:
|
|
// TODO: treat (...) as an expression
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unhandled arg type %s: %s",
|
|
cmpp__tt_cstr(arg->ttype, true), arg->z);
|
|
break;
|
|
}
|
|
}
|
|
if( 0==nHeredoc && nChomp ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"-chomp can only be used with <<.");
|
|
}
|
|
if( 0==dxppCode && nHeredoc ){
|
|
// Process (#define KEY <<)
|
|
cmpp_b * const os = cmpp_b_borrow(dx->pp);
|
|
assert( dx->d->closer );
|
|
if( os &&
|
|
0==cmpp_dx_consume_b(dx, os, &dx->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
while( nChomp-- && cmpp_b_chomp(os) ){}
|
|
g_debug(dx->pp,2,("define heredoc: [%s]=[%.*s]\n",
|
|
acHeredoc, (int)os->n, os->z));
|
|
if( !ifNotDefined
|
|
|| !cmpp_has(dx->pp, (char const*)acHeredoc, nHeredoc) ){
|
|
cmpp__define2(
|
|
dx->pp, acHeredoc, nHeredoc, os->z, os->n, cmpp_TT_String
|
|
);
|
|
}
|
|
}
|
|
cmpp_b_return(dx->pp, os);
|
|
}
|
|
#undef checkIsDefined
|
|
return;
|
|
}
|
|
|
|
/* Impl. for #undef */
|
|
static void cmpp_dx_f_undef(cmpp_dx *dx){
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp_d const * const d = dx->d;
|
|
for( cmpp_arg const * arg = dx->args.arg0;
|
|
0==dxppCode && arg;
|
|
arg = arg->next ){
|
|
if( 0 ){
|
|
g_stderr(" %s: %s %p n=%d %.*s\n", d->name.z,
|
|
cmpp__tt_cstr(arg->ttype, true), arg->z,
|
|
(int)arg->n, (int)arg->n, arg->z);
|
|
}
|
|
if( cmpp_TT_Word==arg->ttype ){
|
|
#if 0
|
|
/* Too strict? */
|
|
if( 0==cmpp__legal_key_check(dx->pp, arg->z,
|
|
(cmpp_ssize_t)arg->n, false) ) {
|
|
cmpp_undef(dx->pp, (char const *)arg->z);
|
|
}
|
|
#else
|
|
cmpp_undef(dx->pp, (char const *)arg->z, NULL);
|
|
#endif
|
|
}else{
|
|
cmpp_err_set(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s",
|
|
d->name.z, arg->z);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Impl. for #once. */
|
|
static void cmpp_dx_f_once(cmpp_dx *dx){
|
|
cmpp_d const * const d = dx->d;
|
|
assert(d);
|
|
assert(d->closer);
|
|
if( dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting no arguments");
|
|
return;
|
|
}
|
|
cmpp_dx_pimpl * const dxp = dx->pimpl;
|
|
cmpp_b * const b = cmpp_b_borrow(dx->pp);
|
|
if( !b ) return;
|
|
cmpp_b_append_ch(b, '#');
|
|
cmpp_b_append4(dx->pp, b, d->name.z, d->name.n);
|
|
cmpp_b_append_ch(b, ':');
|
|
cmpp__get_b(dx->pp, ustr_c("__FILE__"), 8, b, true)
|
|
/* Wonky return semantics. */;
|
|
if( b->errCode
|
|
|| dxppCode
|
|
|| cmpp_b_append_ch(b, ':')
|
|
|| cmpp_b_append_i32(b, (int)dxp->pos.lineNo) ){
|
|
goto end;
|
|
}
|
|
//g_debug(dx->pp,1,("#once key: %s", b->z));
|
|
int const had = cmpp_has(dx->pp, (char const *)b->z, b->n);
|
|
if( dxppCode ) goto end;
|
|
else if( had ){
|
|
CmppLvl * const lvl = CmppLvl_push(dx);
|
|
if( lvl ){
|
|
CmppLvl_elide(lvl, true);
|
|
cmpp_outputer devNull = cmpp_outputer_empty;
|
|
cmpp_dx_consume(dx, &devNull, &d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
CmppLvl_pop(dx, lvl);
|
|
}
|
|
}else if( !cmpp_define_v2(dx->pp, (char const*)b->z, "1") ){
|
|
cmpp_dx_consume(dx, NULL, &d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
}
|
|
end:
|
|
cmpp_b_return(dx->pp, b);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Impl. for #/define, /#query, /#pipe. */
|
|
CMPP__EXPORT(void, cmpp_dx_f_dangling_closer)(cmpp_dx *dx){
|
|
cmpp_d const * const d = dx->d;
|
|
char const * const zD = cmpp_dx_delim(dx);
|
|
dxserr("%s%s used without its opening directive.",
|
|
zD, d->name.z);
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_INCLUDE
|
|
static int cmpp__including_has(cmpp *pp, unsigned const char * zName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false);
|
|
if( q && 0==cmpp__bind_text(pp, q, 1, zName) ){
|
|
if(SQLITE_ROW == cmpp__step(pp, q, true)){
|
|
rc = 1;
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
|
|
the `#include` dirs (cmpp_include_dir_add()). If no file match is
|
|
found, NULL is returned. Memory must eventually be passed to
|
|
cmpp_mfree() to free it.
|
|
*/
|
|
static char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
|
|
int * nVal){
|
|
char * zName = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false);
|
|
if( nVal ) *nVal = 0;
|
|
if( q && 0==cmpp__bind_text(pp, q, 1, zKey) ){
|
|
int const rc = cmpp__step(pp, q, false);
|
|
if(SQLITE_ROW==rc){
|
|
const unsigned char * z = sqlite3_column_text(q, 0);
|
|
int const n = sqlite3_column_bytes(q,0);
|
|
zName = n ? sqlite3_mprintf("%.*s", n, z) : 0;
|
|
if( n ) cmpp_check_oom(pp, zName);
|
|
if( nVal ) *nVal = n;
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
return zName;
|
|
}
|
|
|
|
/**
|
|
Removes zKey from the currently-being-`#include`d list
|
|
list.
|
|
*/
|
|
static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false);
|
|
if( q ){
|
|
cmpp__bind_text(pp, q, 1, ustr_c(zKey));
|
|
cmpp__step(pp, q, true);
|
|
g_debug(pp,2,("incl rm [%s]\n", zKey));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
** Sets pp's error state if the `#include` list contains the given
|
|
** key.
|
|
*/
|
|
static int cmpp__including_check(cmpp *pp, const char * zKey);
|
|
int cmpp__including_check(cmpp *pp, const char * zName){
|
|
if( !ppCode ){
|
|
if(cmpp__including_has(pp, zName)){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Recursive include detected: %s\n", zName);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
Adds the given filename to the list of being-`#include`d files,
|
|
using the given source file name and line number of error reporting
|
|
purposes. If recursion is later detected.
|
|
*/
|
|
static int cmpp__including_add(cmpp *pp, unsigned const char * zKey,
|
|
unsigned const char * zSrc, cmpp_size_t srcLine){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false);
|
|
if( q ){
|
|
cmpp__bind_text(pp, q, 1, zKey);
|
|
cmpp__bind_text(pp, q, 2, zSrc);
|
|
cmpp__bind_int(pp, q, 3, srcLine);
|
|
cmpp__step(pp, q, true);
|
|
g_debug(pp,2,("is-including-file add [%s] from [%s]:%"
|
|
CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/* Impl. for #include. */
|
|
static void cmpp_dx_f_include(cmpp_dx *dx){
|
|
char * zResolved = 0;
|
|
int nResolved = 0;
|
|
cmpp_b * const ob = cmpp_b_borrow(dx->pp);
|
|
bool raw = false;
|
|
cmpp_args args = cmpp_args_empty;
|
|
if( !ob || cmpp_dx_args_clone(dx, &args) ){
|
|
goto end;
|
|
}
|
|
assert(args.pimpl && args.pimpl->pp==dx->pp);
|
|
cmpp_arg const * arg = args.arg0;
|
|
for( ; arg; arg = arg->next){
|
|
#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
|
|
FLAG("-raw"){
|
|
raw = true;
|
|
continue;
|
|
}
|
|
break;
|
|
#undef FLAG
|
|
}
|
|
if( !arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
|
|
"Expecting at least one filename argument.");
|
|
}
|
|
for( ; !dxppCode && arg; arg = arg->next ){
|
|
cmpp_flag32_t a2bf = cmpp_arg_to_b_F_BRACE_CALL;
|
|
if( cmpp_TT_Word==arg->ttype && cmpp__arg_wordIsPathOrFlag(arg) ){
|
|
a2bf |= cmpp_arg_to_b_F_NO_DEFINES;
|
|
}
|
|
if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(ob), a2bf) ){
|
|
break;
|
|
}
|
|
//g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved);
|
|
if(!raw && cmpp__including_has(dx->pp, ob->z)){
|
|
/* Note that different spellings of the same filename
|
|
** will elude this check, but that seems okay, as different
|
|
** spellings means that we're not re-running the exact same
|
|
** invocation. We might want some other form of multi-include
|
|
** protection, rather than this, however. There may well be
|
|
** sensible uses for recursion. */
|
|
cmpp_dx_err_set(dx, CMPP_RC_RANGE, "Recursive include of file: %s",
|
|
ob->z);
|
|
break;
|
|
}
|
|
cmpp_mfree(zResolved);
|
|
nResolved = 0;
|
|
zResolved = cmpp__include_search(dx->pp, ob->z, &nResolved);
|
|
if(!zResolved){
|
|
if( !dxppCode ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "file not found: %s", ob->z);
|
|
}
|
|
break;
|
|
}
|
|
if( raw ){
|
|
if( !dx->pp->pimpl->out.out ) break;
|
|
FILE * const fp = cmpp_fopen(zResolved, "r");
|
|
if( fp ){
|
|
int const rc = cmpp_stream(cmpp_input_f_FILE, fp,
|
|
dx->pp->pimpl->out.out,
|
|
dx->pp->pimpl->out.state);
|
|
if( rc ){
|
|
cmpp_dx_err_set(dx, rc, "Unknown error streaming file %s.",
|
|
arg->z);
|
|
}
|
|
cmpp_fclose(fp);
|
|
}else{
|
|
cmpp_dx_err_set(dx, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Unknown error opening file %s.", arg->z);
|
|
}
|
|
}else{
|
|
cmpp__including_add(dx->pp, ob->z, ustr_c(dx->sourceName),
|
|
dx->pimpl->dline.lineNo);
|
|
cmpp_process_file(dx->pp, zResolved);
|
|
cmpp__include_rm(dx->pp, ob->z);
|
|
}
|
|
}
|
|
end:
|
|
cmpp_mfree(zResolved);
|
|
cmpp_args_cleanup(&args);
|
|
cmpp_b_return(dx->pp, ob);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_INCLUDE */
|
|
|
|
/**
|
|
cmpp_dx_f() callback state for cmpp_dx_f_if(): pointers to the
|
|
various directives of that family.
|
|
*/
|
|
struct CmppIfState {
|
|
cmpp_d * dIf;
|
|
cmpp_d * dElif;
|
|
cmpp_d * dElse;
|
|
cmpp_d const * dEndif;
|
|
};
|
|
typedef struct CmppIfState CmppIfState;
|
|
|
|
/* Version 2 of #if. */
|
|
static void cmpp_dx_f_if(cmpp_dx *dx){
|
|
/* Reminder to self:
|
|
|
|
We need to be able to recurse, even in skip mode, for #if nesting
|
|
to work. That's not great because it means we are evaluating
|
|
stuff we ideally should be skipping over, but it's keeping the
|
|
current tests working as-is. We can/do, however, avoid evaluating
|
|
expressions and such when recursing via skip mode. If we can
|
|
eliminate that here, by keeping track of the #if stack depth,
|
|
then we can possibly eliminate the whole CmppLvl_F_ELIDE
|
|
flag stuff.
|
|
|
|
The more convoluted version 1 #if (which this replaced not hours
|
|
ago) kept track of the skip state across a separate directive
|
|
function for #if and #/if. That was more complex but did avoid
|
|
having to recurse into #if in order to straighten out #elif and
|
|
#else. Update: tried a non-recursive variant involving moving
|
|
this function's gotTruth into the CmppLvl object() and
|
|
managing the CmppLvl stack here, but it just didn't want to
|
|
work for me and i was too tired to figure out why.
|
|
*/
|
|
int gotTruth = 0 /*expr result*/;
|
|
CmppIfState const * const cis = dx->d->impl.state;
|
|
cmpp_d const * dClosers[] = {
|
|
cis->dElif, cis->dElse, cis->dEndif
|
|
};
|
|
CmppLvl * lvl = 0;
|
|
CmppDLine const dline = dx->pimpl->dline;
|
|
cmpp_args args = cmpp_args_empty;
|
|
char delim[20] = {0};
|
|
#define skipOn CmppLvl_elide((lvl), true)
|
|
#define skipOff CmppLvl_elide((lvl), false)
|
|
|
|
assert( dx->d==cis->dIf );
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an expression.");
|
|
return;
|
|
}
|
|
snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx));
|
|
delim[sizeof(delim)-1] = 0;
|
|
lvl = CmppLvl_push(dx);
|
|
if( !lvl ) goto end;
|
|
if( cmpp_dx_is_eliding(dx) ){
|
|
gotTruth = 1;
|
|
}else if( cmpp__args_evalToInt(dx, &dx->pimpl->args, &gotTruth) ){
|
|
goto end;
|
|
}else if( !gotTruth ){
|
|
skipOn;
|
|
}
|
|
|
|
cmpp_d const * dPrev = dx->d;
|
|
cmpp_outputer devNull = cmpp_outputer_empty;
|
|
while( !dxppCode ){
|
|
dPrev = dx->d;
|
|
bool const isFinal = dPrev==cis->dElse
|
|
/* true if expecting an #/if. */;
|
|
if( cmpp_dx_consume(dx,
|
|
CmppLvl_is_eliding(lvl) ? &devNull : NULL,
|
|
isFinal ? &cis->dEndif : dClosers,
|
|
isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]),
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
break;
|
|
}
|
|
cmpp_d const * const d2 = dx->d;
|
|
if( !d2 ){
|
|
dxserr("Reached end of input in an untermined %s%s opened "
|
|
"at line %" CMPP_SIZE_T_PFMT ".",
|
|
delim, cis->dIf->name.z, dline.lineNo);
|
|
}
|
|
if( d2==cis->dEndif ){
|
|
break;
|
|
}else if( isFinal ){
|
|
assert(!"cannot happen - caught by consume()");
|
|
dxserr("Expecting %s%s to close %s%s.",
|
|
delim, cis->dEndif->name.z,
|
|
delim, dPrev->name.z);
|
|
break;
|
|
}else if( gotTruth ){
|
|
skipOn;
|
|
continue;
|
|
}else if( d2==cis->dElif ){
|
|
if( 0==cmpp_dx_args_parse(dx, &args)
|
|
&& 0==cmpp__args_evalToInt(dx, &args, &gotTruth) ){
|
|
if( gotTruth ) skipOff;
|
|
else skipOn;
|
|
}
|
|
continue;
|
|
}else{
|
|
assert( d2==cis->dElse
|
|
&& "Else (haha!) we cannot have gotten here" );
|
|
skipOff;
|
|
continue;
|
|
}
|
|
assert(!"unreachable");
|
|
}
|
|
|
|
#undef skipOff
|
|
#undef skipOn
|
|
end:
|
|
cmpp_args_cleanup(&args);
|
|
if( lvl ){
|
|
bool const lvlIsOk = CmppLvl_get(dx)==lvl;
|
|
CmppLvl_pop(dx, lvl);
|
|
if( !lvlIsOk && !dxppCode ){
|
|
assert(!"i naively believe that this is not possible");
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
|
|
"Mis-terminated %s%s opened at line "
|
|
"%" CMPP_SIZE_T_PFMT ".",
|
|
delim, cis->dIf->name.z, dline.lineNo);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Version 2 of #elif, #else, and #/if. */
|
|
static void cmpp_dx_f_if_dangler(cmpp_dx *dx){
|
|
CmppIfState const * const cis = dx->d->impl.state;
|
|
char const *zDelim = cmpp_dx_delim(dx);
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
|
|
"%s%s with no matching %s%s",
|
|
zDelim, dx->d->name.z,
|
|
zDelim, cis->dIf->name.z);
|
|
}
|
|
|
|
static void cmpp__dump_sizeofs(cmpp_dx*dx){
|
|
(void)dx;
|
|
#define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X))
|
|
SO(cmpp);
|
|
SO(cmpp_api_thunk);
|
|
SO(cmpp_arg);
|
|
SO(cmpp_args);
|
|
SO(cmpp_args_pimpl);
|
|
SO(cmpp_b);
|
|
SO(cmpp_d);
|
|
SO(cmpp_d_reg);
|
|
SO(cmpp__delim);
|
|
SO(cmpp__delim_list);
|
|
SO(cmpp_dx);
|
|
SO(cmpp_dx_pimpl);
|
|
SO(cmpp_outputer);
|
|
SO(cmpp_pimpl);
|
|
SO(((cmpp_pimpl*)0)->stmt);
|
|
SO(((cmpp_pimpl*)0)->policy);
|
|
SO(CmppArgList);
|
|
SO(CmppDLine);
|
|
SO(CmppDList);
|
|
SO(CmppDList_entry);
|
|
SO(CmppLvl);
|
|
SO(CmppSnippet);
|
|
SO(PodList__atpol);
|
|
printf("cmpp_TT__last = %d\n",
|
|
cmpp_TT__last);
|
|
#undef SO
|
|
}
|
|
|
|
|
|
/* Impl. for #pragma. */
|
|
static void cmpp_dx_f_pragma(cmpp_dx *dx){
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
if(!arg){
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting an argument");
|
|
return;
|
|
}else if(arg->next){
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Too many arguments");
|
|
return;
|
|
}
|
|
const char * const zArg = (char const *)arg->z;
|
|
#define M(X) 0==strcmp(zArg,X)
|
|
if(M("defines")){
|
|
cmpp__dump_defines(dx->pp, stderr, 1);
|
|
}else if(M("sizeof")){
|
|
cmpp__dump_sizeofs(dx);
|
|
}else if(M("chomp-F")){
|
|
dx->pp->pimpl->flags.chompF = 1;
|
|
}else if(M("no-chomp-F")){
|
|
dx->pp->pimpl->flags.chompF = 0;
|
|
}else if(M("api-thunk")){
|
|
/* Generate macros for CMPP_API_THUNK and friends from
|
|
cmpp_api_thunk_map. */
|
|
char const * zName = "CMPP_API_THUNK_NAME";
|
|
char buf[256];
|
|
#define out(FMT,...) snprintf(buf, sizeof(buf), FMT,__VA_ARGS__); \
|
|
cmpp_dx_out_raw(dx, buf, strlen(buf))
|
|
if( 0 ){
|
|
out("/* libcmpp API thunk. */\n"
|
|
"static cmpp_api_thunk const * %s = 0;\n"
|
|
"#define cmpp_api_init(PP) %s = (PP)->api\n", zName, zName);
|
|
}
|
|
#define A(V) \
|
|
if(V<=cmpp_api_thunk_version) { \
|
|
out("/* Thunk APIs which follow are available as of " \
|
|
"version %d... */\n",V); \
|
|
}
|
|
#define V(N,T,V)
|
|
#define F(N,T,P) out("#define cmpp_%s %s->%s\n", # N, zName, # N);
|
|
#define O(N,T) out("#define cmpp_%s (*%s->%s)\n", # N, zName, # N);
|
|
cmpp_api_thunk_map(A,V,F,O)
|
|
#undef V
|
|
#undef F
|
|
#undef O
|
|
#undef A
|
|
#undef out
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg);
|
|
}
|
|
#undef M
|
|
}
|
|
|
|
/* Impl. for #savepoint. */
|
|
static void cmpp_dx_f_savepoint(cmpp_dx *dx){
|
|
if(!dx->args.arg0 || dx->args.arg0->next){
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX, "Expecting one argument");
|
|
}else{
|
|
const char * const zArg = (const char *)dx->args.arg0->z;
|
|
#define M(X) else if( 0==strcmp(zArg,X) )
|
|
if( 0 ){}
|
|
M("begin"){
|
|
cmpp__dx_sp_begin(dx);
|
|
}
|
|
M("rollback"){
|
|
cmpp__dx_sp_rollback(dx);
|
|
}M("commit"){
|
|
cmpp__dx_sp_commit(dx);
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unknown savepoint option: %s", zArg);
|
|
}
|
|
}
|
|
#undef M
|
|
}
|
|
|
|
/* #stderr impl. */
|
|
static void cmpp_dx_f_stderr(cmpp_dx *dx){
|
|
if(dx->args.z){
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName,
|
|
dx->pimpl->dline.lineNo,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}else{
|
|
cmpp_d const * d = dx->d;
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n",
|
|
dx->sourceName, dx->pimpl->dline.lineNo,
|
|
cmpp_dx_delim(dx), d->name.z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Manages both the @token@ policy and the delimiters.
|
|
|
|
#@ ?push? policy NAME ?<<?
|
|
#@ ?push? delimiter OPEN CLOSE ?<<?
|
|
#@ ?push? policy NAME delimiter OPEN CLOSE ?<<?
|
|
#@ pop policy
|
|
#@ pop delimiter
|
|
#@ pop policy delimiter
|
|
#@ pop both
|
|
|
|
Function call forms:
|
|
|
|
[@ policy]
|
|
[@ delimiter]
|
|
*/
|
|
static void cmpp_dx_f_at(cmpp_dx *dx){
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
if( !arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting arguments.");
|
|
return;
|
|
}
|
|
enum ops { op_none, op_set, op_push, op_pop, op_heredoc };
|
|
enum popWhichE { pop_policy = 0x01, pop_delim = 0x02,
|
|
pop_both = pop_policy | pop_delim };
|
|
enum ops op = op_none /* what to do */;
|
|
int popWhich = 0 /* what to pop */;
|
|
bool gotPolicy = false;
|
|
bool checkedCallForm = !cmpp_dx_is_call(dx);
|
|
cmpp_arg const * argDelimO = 0 /* @token@ opener */;
|
|
cmpp_arg const * argDelimC = 0 /* @token@ closer */;
|
|
cmpp__pi(dx->pp);
|
|
cmpp_atpol_e polNew = cmpp_atpol_get(dx->pp);
|
|
for( ; arg; arg = arg ? arg->next : NULL ){
|
|
//g_warn("arg=%s", arg->z);
|
|
if( !checkedCallForm ){
|
|
assert( cmpp_dx_is_call(dx) );
|
|
checkedCallForm = true;
|
|
if( cmpp_arg_equals(arg, "policy") ){
|
|
char const * z =
|
|
cmpp__atpol_name(dx->pp, cmpp__policy(dx->pp,at));
|
|
if( z ){
|
|
cmpp_dx_out_raw(dx, z, strlen(z));
|
|
}
|
|
}else if( cmpp_arg_equals(arg, "delimiter") ){
|
|
char const * zO = 0;
|
|
char const * zC = 0;
|
|
cmpp_atdelim_get(dx->pp, &zO, &zC);
|
|
if( zC ){
|
|
cmpp_dx_out_raw(dx, zO, strlen(zO));
|
|
cmpp_dx_out_raw(dx, " ", 1);
|
|
cmpp_dx_out_raw(dx, zC, strlen(zC));
|
|
}
|
|
goto end;
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"In call form, '%s' expects one of "
|
|
"'policy' or delimiter'.");
|
|
}
|
|
goto end;
|
|
}/* checkedCallForm */
|
|
if( !argDelimC && op_none==op ){
|
|
/* Look for push|pop. */
|
|
if( cmpp_arg_equals(arg, "pop") ){
|
|
arg = arg->next;
|
|
if( !arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'pop' expects arguments of 'policy' "
|
|
"and/or 'delimiter' and/or 'both'.");
|
|
goto end;
|
|
}
|
|
for( ; arg; arg = arg->next ){
|
|
if( 0==(pop_policy & popWhich)
|
|
&& cmpp_arg_equals(arg, "policy") ){
|
|
popWhich |= pop_policy;
|
|
}else if( 0==(pop_delim & popWhich)
|
|
&& cmpp_arg_equals(arg, "delimiter") ){
|
|
popWhich |= pop_delim;
|
|
}else if( 0==(pop_both & popWhich)
|
|
&& cmpp_arg_equals(arg, "both") ){
|
|
popWhich |= pop_both;
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Invalid argument to 'pop': ", arg->z);
|
|
goto end;
|
|
}
|
|
}
|
|
assert( !arg );
|
|
op = op_pop;
|
|
break;
|
|
}/* pop */
|
|
if( cmpp_arg_equals(arg, "push") ){
|
|
op = op_push;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals(arg, "set") ){
|
|
/* set is implied if neither of push/pop are and we get
|
|
a policy name. */
|
|
op = op_set;
|
|
continue;
|
|
}
|
|
/* Fall through */
|
|
}/* !argDelimC && op_none==op */
|
|
if( !gotPolicy && cmpp_arg_equals(arg, "policy") ){
|
|
arg = arg->next;
|
|
if( !arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'policy' requires a policy name argument.");
|
|
goto end;
|
|
}
|
|
polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z);
|
|
if( cmpp_atpol_invalid==polNew ){
|
|
cmpp_atpol_from_str(dx->pp, (char const*)arg->z)
|
|
/* Will set the error state to something informative. */;
|
|
goto end;
|
|
}
|
|
if( op_none==op ) op = op_set;
|
|
gotPolicy = true;
|
|
continue;
|
|
}
|
|
if( !argDelimC && cmpp_arg_equals(arg, "delimiter") ){
|
|
assert( !argDelimO && !argDelimC );
|
|
argDelimO = arg->next;
|
|
argDelimC = argDelimO ? argDelimO->next : NULL;
|
|
if( !argDelimC ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'delimiter' requires two arguments.");
|
|
goto end;
|
|
}
|
|
arg = argDelimC->next;
|
|
continue;
|
|
}
|
|
if( op_pop!=op ){
|
|
if( cmpp_arg_equals(arg,"<<") ){
|
|
if( arg->next ) {
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'%s' must be the final argument.",
|
|
arg->z);
|
|
goto end;
|
|
}
|
|
op = op_heredoc;
|
|
break;
|
|
}
|
|
}
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z);
|
|
return;
|
|
}/*arg collection*/
|
|
|
|
assert( !dxppCode );
|
|
assert( cmpp_atpol_invalid!=polNew );
|
|
|
|
#define popcheck(LIST) \
|
|
if(dxppCode) goto end; \
|
|
if(!LIST.n) goto bad_pop
|
|
|
|
if( op_pop==op ){
|
|
assert( popWhich>0 && popWhich<=3 );
|
|
if( pop_policy & popWhich ){
|
|
popcheck(pi->policy.at);
|
|
cmpp_atpol_pop(dx->pp);
|
|
}
|
|
if( pop_delim & popWhich ){
|
|
popcheck(pi->delim.at);
|
|
cmpp_atdelim_pop(dx->pp);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
assert( op_set==op || op_push==op || op_heredoc==op );
|
|
if( argDelimC ){
|
|
/* Push or set the @token@ delimiters */
|
|
if( 0 ){
|
|
g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push",
|
|
argDelimO->z, argDelimC->z);
|
|
}
|
|
if( op_push==op || op_heredoc==op ){
|
|
if( cmpp_atdelim_push(dx->pp, (char const*)argDelimO->z,
|
|
(char const*)argDelimC->z) ){
|
|
goto end;
|
|
}
|
|
argDelimO = 0 /* Re-use argDelimC as a flag in case we need to
|
|
roll this back on an error below. */;
|
|
}else{
|
|
assert( op_set==op );
|
|
if( cmpp_atdelim_set(dx->pp, (char const*)argDelimO->z,
|
|
(char const*)argDelimC->z) ){
|
|
goto end;
|
|
}
|
|
argDelimO = argDelimC = 0;
|
|
}
|
|
}
|
|
|
|
assert( !dxppCode );
|
|
assert( !argDelimO );
|
|
if( op_heredoc==op ){
|
|
if( cmpp_atpol_push(dx->pp, polNew) ){
|
|
if( argDelimC ){
|
|
popcheck(pi->delim.at);
|
|
cmpp_atdelim_pop(dx->pp);
|
|
}
|
|
}else{
|
|
bool const pushedDelim = NULL!=argDelimC;
|
|
assert( dx->d->closer );
|
|
cmpp_dx_consume(dx, NULL, &dx->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D)
|
|
/* !Invalidates argDelimO and argDelimC! */;
|
|
popcheck(pi->policy.at);
|
|
cmpp_atpol_pop(dx->pp);
|
|
if( pushedDelim ) cmpp_atdelim_pop(dx->pp);
|
|
}
|
|
}else if( op_push==op ){
|
|
if( cmpp_atpol_push(dx->pp, polNew) && argDelimC ){
|
|
/* Roll back delimiter push */
|
|
cmpp_atdelim_pop(dx->pp);
|
|
}
|
|
}else{
|
|
assert( op_set==op );
|
|
if( cmpp__policy(dx->pp,at)!=polNew ){
|
|
cmpp_atpol_set(dx->pp, polNew);
|
|
}
|
|
}
|
|
end:
|
|
return;
|
|
bad_pop:
|
|
cmpp_dx_err_set(dx, CMPP_RC_RANGE,
|
|
"Cannot pop an empty stack.");
|
|
#undef popcheck
|
|
}
|
|
|
|
|
|
static void cmpp_dx_f_expr(cmpp_dx *dx){
|
|
int rv = 0;
|
|
assert( dx->args.z );
|
|
if( 0 ){
|
|
g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc,
|
|
dx->args.arg0->n, dx->args.arg0->z);
|
|
g_stderr("%s() dx->args.z [%.*s]\n", __func__,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}
|
|
if( !dx->args.argc ){
|
|
dxserr("An empty expression is not permitted.");
|
|
return;
|
|
}
|
|
#if 0
|
|
for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
|
|
g_stderr("got type=%s n=%u z=%.*s\n",
|
|
cmpp__tt_cstr(a->ttype, true),
|
|
(unsigned)a->n, (int)a->n, a->z);
|
|
}
|
|
#endif
|
|
if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){
|
|
if( 'a'==dx->d->name.z[0] ){
|
|
if( !rv ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_ASSERT, "Assertion failed: %s",
|
|
dx->pimpl->buf.argsRaw.z);
|
|
}
|
|
}else{
|
|
char buf[60];
|
|
snprintf(buf, sizeof(buf), "%d\n", rv);
|
|
cmpp_dx_out_raw(dx, buf, strlen(buf));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmpp_dx_f_undef_policy(cmpp_dx *dx){
|
|
cmpp_unpol_e up = cmpp_unpol_invalid;
|
|
int nSeen = 0;
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
enum ops { op_set, op_push, op_pop };
|
|
enum ops op = op_set;
|
|
if( !dx->args.argc ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting one of: error, null");
|
|
return;
|
|
}
|
|
again:
|
|
++nSeen;
|
|
if( cmpp_arg_equals(arg,"error") ) up = cmpp_unpol_ERROR;
|
|
else if( cmpp_arg_equals(arg,"null") ) up = cmpp_unpol_NULL;
|
|
else if( 1==nSeen ){
|
|
if( cmpp_arg_equals(arg, "push") ){
|
|
if( !arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting argument to 'push'.");
|
|
return;
|
|
}
|
|
op = op_push;
|
|
arg = arg->next;
|
|
goto again;
|
|
}else if( cmpp_arg_equals(arg, "pop") ){
|
|
if( arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Extra argument after 'pop': %s",
|
|
arg->next->z);
|
|
return;
|
|
}
|
|
op = op_pop;
|
|
}
|
|
}
|
|
if( op_pop!=op && cmpp_unpol_invalid==up ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unhandled undefined-policy '%s'."
|
|
" Try one of: error, null",
|
|
arg->z);
|
|
}else if( op_set==op ){
|
|
cmpp_unpol_set(dx->pp, up);
|
|
}else if( op_push==op ){
|
|
cmpp_unpol_push(dx->pp, up);
|
|
}else{
|
|
assert( op_pop==op );
|
|
if( !cmpp__epol(dx->pp,un).n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"No %s%s push is active.",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
}else{
|
|
cmpp_unpol_pop(dx->pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
/* Impl. for #attach. */
|
|
static void cmpp_dx_f_attach(cmpp_dx *dx){
|
|
if( 3!=dx->args.argc ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"%s expects: STRING as NAME", dx->d->name.z);
|
|
return;
|
|
}
|
|
cmpp_arg const * pNext = 0;
|
|
cmpp_b osDbFile = cmpp_b_empty;
|
|
cmpp_b osSchema = cmpp_b_empty;
|
|
for( cmpp_arg const * arg = dx->args.arg0;
|
|
0==dxppCode && arg;
|
|
arg = pNext ){
|
|
pNext = arg->next;
|
|
if( !osDbFile.n ){
|
|
if( 0==cmpp_arg_to_b(dx, arg, &osDbFile,
|
|
cmpp_arg_to_b_F_BRACE_CALL)
|
|
&& !osDbFile.n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Empty db file name is not permitted. "
|
|
"If '%s' is intended as a value, "
|
|
"it should be quoted.", arg->z);
|
|
break;
|
|
}
|
|
assert( pNext );
|
|
if( !pNext || !cmpp_arg_equals(pNext, "as") ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting 'as' after db file name.");
|
|
break;
|
|
}
|
|
pNext = pNext->next;
|
|
}else if( !osSchema.n ){
|
|
if( 0==cmpp_arg_to_b(dx, arg, &osSchema,
|
|
cmpp_arg_to_b_F_BRACE_CALL)
|
|
&& !osSchema.n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Empty db schema name is not permitted."
|
|
"If '%s' is intended as a value, "
|
|
"it should be quoted.",
|
|
arg->z);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( dxppCode ) goto end;
|
|
if( !osSchema.n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Missing schema name.");
|
|
goto end;
|
|
}
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(dx->pp, CmppStmt_dbAttach, false);
|
|
if( q ){
|
|
cmpp__bind_textn(dx->pp, q, 1, osDbFile.z, osDbFile.n);
|
|
cmpp__bind_textn(dx->pp, q, 2, osSchema.z, osSchema.n);
|
|
cmpp__step(dx->pp, q, true);
|
|
}
|
|
end:
|
|
cmpp_b_clear(&osDbFile);
|
|
cmpp_b_clear(&osSchema);
|
|
}
|
|
|
|
/* Impl. for #detach. */
|
|
static void cmpp_dx_f_detach(cmpp_dx *dx){
|
|
cmpp_d const * d = dx->d;
|
|
if( 1!=dx->args.argc ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"%s expects: NAME", d->name.z);
|
|
return;
|
|
}
|
|
cmpp_arg const * const arg = dx->args.arg0;
|
|
cmpp_b os = cmpp_b_empty;
|
|
if( cmpp_arg_to_b(dx, arg, &os, cmpp_arg_to_b_F_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
if( !os.n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Empty db schema name is not permitted.");
|
|
goto end;
|
|
}
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(dx->pp, CmppStmt_dbDetach, false);
|
|
if( q ){
|
|
cmpp__bind_textn(dx->pp, q, 1, os.z, os.n);
|
|
cmpp__step(dx->pp, q, true);
|
|
}
|
|
end:
|
|
cmpp_b_clear(&os);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_DB */
|
|
|
|
static void cmpp_dx_f_delimiter(cmpp_dx *dx){
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
enum ops { op_none, op_set, op_push, op_pop };
|
|
enum ops op = op_none;
|
|
cmpp_arg const * argD = 0;
|
|
bool doHeredoc = false;
|
|
bool const isCall = cmpp_dx_is_call(dx);
|
|
for( ; arg; arg = arg->next ){
|
|
if( op_none==op ){
|
|
/* Look for push|pop. */
|
|
if( cmpp_arg_equals(arg, "push") ){
|
|
op = op_push;
|
|
continue;
|
|
}else if( cmpp_arg_equals(arg, "pop") ){
|
|
if( arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'pop' expects no arguments.");
|
|
return;
|
|
}
|
|
op = op_pop;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
}
|
|
if( !argD ){
|
|
if( op_none==op ) op = op_set;
|
|
argD = arg;
|
|
continue;
|
|
}else if( !doHeredoc && cmpp_arg_equals(arg,"<<") ){
|
|
if( isCall ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'%s' is not legal in [call] form.", arg->z);
|
|
return;
|
|
}else if( arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"'%s' must be the final argument.", arg->z);
|
|
return;
|
|
}
|
|
op = op_push;
|
|
doHeredoc = true;
|
|
continue;
|
|
}
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
|
|
return;
|
|
}
|
|
if( op_pop==op ){
|
|
cmpp_delimiter_pop(dx->pp);
|
|
}else if( !argD ){
|
|
if( isCall ){
|
|
cmpp__delim const * const del = cmpp__dx_delim(dx);
|
|
if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n);
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "No delimiter specified.");
|
|
}
|
|
return;
|
|
}else{
|
|
char const * const z =
|
|
(0==strcmp("default",(char*)argD->z))
|
|
? NULL
|
|
: (char const*)argD->z;
|
|
if( op_push==op ){
|
|
cmpp_delimiter_push(dx->pp, (char const*)argD->z);
|
|
}else{
|
|
assert( op_set==op );
|
|
if( doHeredoc ) cmpp_delimiter_push(dx->pp, z);
|
|
else cmpp_delimiter_set(dx->pp, z);
|
|
}
|
|
}
|
|
if( !cmpp_dx_err_check(dx) ){
|
|
if( isCall ){
|
|
cmpp__delim const * const del = cmpp__dx_delim(dx);
|
|
if( del ) cmpp_dx_out_raw(dx, del->open.z, del->open.n);
|
|
}else if( doHeredoc ){
|
|
assert( op_push==op );
|
|
cmpp_dx_consume(dx, NULL, &dx->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
cmpp_delimiter_pop(dx->pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
/* Experimenting grounds. */
|
|
static void cmpp_dx_f_experiment(cmpp_dx *dx){
|
|
void * st = dx->d->impl.state;
|
|
(void)st;
|
|
g_warn("raw args: %s", dx->pimpl->buf.argsRaw.z);
|
|
g_warn("argc=%u", dx->args.argc);
|
|
g_warn("isCall=%d\n", cmpp_dx_is_call(dx));
|
|
if( 1 ){
|
|
for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
|
|
g_stderr("got type=%s n=%u z=%.*s\n",
|
|
cmpp__tt_cstr(a->ttype, true),
|
|
(unsigned)a->n, (int)a->n, a->z);
|
|
}
|
|
}
|
|
if( 0 ){
|
|
int rv = 0;
|
|
if( 0==cmpp__args_evalToInt(dx, &dx->pimpl->args, &rv) ){
|
|
g_stderr("expr result: %d\n", rv);
|
|
}
|
|
}
|
|
|
|
if( 0 ){
|
|
char const * zIn = "a strspn test @# and @";
|
|
g_stderr("strlen : %u\n", (unsigned)strlen(zIn));
|
|
g_stderr("strspn 1: %u, %u\n",
|
|
(unsigned)strspn(zIn, "#@"),
|
|
(unsigned)strspn(zIn, "@#"));
|
|
g_stderr("strcspn 2: %u, %u\n",
|
|
(unsigned)strcspn(zIn, "#@"),
|
|
(unsigned)strcspn(zIn, "@#"));
|
|
g_stderr("strcspn 3: %u, %u\n",
|
|
(unsigned)strcspn(zIn, "a strspn"),
|
|
(unsigned)strcspn(zIn, "nope"));
|
|
}
|
|
|
|
if( 1 ){
|
|
cmpp__dump_sizeofs(dx);
|
|
}
|
|
}
|
|
#endif /* #ifndef NDEBUG */
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
|
|
/**
|
|
Helper for #query and friends. Expects arg to be an SQL value. If
|
|
arg->next is "bind" then this consumes the following two arguments(
|
|
"bind" BIND_ARG), where BIND_ARG must be one of either
|
|
cmpp_TT_GroupSquiggly or cmpp_TT_GroupBrace.
|
|
|
|
If it returns 0 then:
|
|
|
|
- If "bind" was found then *pBind is set to the BIND_ARG argument
|
|
and *pNext is set to the one after that.
|
|
|
|
- Else *pBind is set to NULL and and *pNext is set to
|
|
arg->next.
|
|
|
|
In either case, *pNext may be set to NULL.
|
|
*/
|
|
static
|
|
int cmpp__consume_sql_args(cmpp *pp, cmpp_arg const *arg,
|
|
cmpp_arg const **pBind,
|
|
cmpp_arg const **pNext){
|
|
if( 0==ppCode ){
|
|
*pBind = 0;
|
|
cmpp_arg const *pN = arg->next;
|
|
if( pN && cmpp_arg_equals(pN, "bind") ){
|
|
pN = pN->next;
|
|
if( !pN || (
|
|
cmpp_TT_GroupSquiggly!=pN->ttype
|
|
&& cmpp_TT_GroupBrace!=pN->ttype
|
|
) ){
|
|
return serr("Expecting {...} or [...] after 'bind'.");
|
|
}
|
|
*pBind = pN;
|
|
*pNext = pN->next;
|
|
} else {
|
|
*pBind = 0;
|
|
*pNext = pN;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
cmpp_kav_each_f() impl for used by #query's `bind {...}` argument.
|
|
*/
|
|
static int cmpp_kav_each_f_query__bind(
|
|
cmpp_dx * const dx,
|
|
unsigned char const * const zKey, cmpp_size_t nKey,
|
|
unsigned char const * const zVal, cmpp_size_t nVal,
|
|
void * const callbackState
|
|
){
|
|
/* Expecting: :bindName -> bindValue */
|
|
if( ':'!=zKey[0] && '$'!=zKey[0] /*&& '@'!=zKey[0]*/ ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Bind keys must start with ':' or '$'.");
|
|
}else{
|
|
sqlite3_stmt * const q = callbackState;
|
|
assert( q );
|
|
int const bindNdx =
|
|
sqlite3_bind_parameter_index(q, (char const*)zKey);
|
|
if( bindNdx ){
|
|
cmpp__bind_textn(dx->pp, q, bindNdx, zVal, nVal);
|
|
}else{
|
|
cmpp_err_set(dx->pp, CMPP_RC_RANGE, "Invalid bind name: %.*s",
|
|
(int)nKey, zKey);
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
int cmpp__bind_group(cmpp_dx * const dx, sqlite3_stmt * const q,
|
|
cmpp_arg const * const aGroup){
|
|
if( dxppCode ) return dxppCode;
|
|
if( cmpp_TT_GroupSquiggly==aGroup->ttype ){
|
|
return cmpp_kav_each(
|
|
dx, aGroup->z, aGroup->n,
|
|
cmpp_kav_each_f_query__bind, q,
|
|
cmpp_kav_each_F_NOT_EMPTY
|
|
| cmpp_kav_each_F_CALL_VAL
|
|
| cmpp_kav_each_F_PARENS_EXPR
|
|
);
|
|
}
|
|
if( cmpp_TT_GroupBrace!=aGroup->ttype ){
|
|
return cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting {...} or [...] "
|
|
"for SQL binding list.");
|
|
}
|
|
int bindNdx = 0;
|
|
cmpp_args args = cmpp_args_empty;
|
|
cmpp_args_parse(dx, &args, aGroup->z, aGroup->n, 0);
|
|
if( !args.argc && !dxppCode ){
|
|
cmpp_err_set(dx->pp, CMPP_RC_RANGE,
|
|
"Empty SQL bind list is not permitted.");
|
|
/* Keep going so we can clean up a partially-parsed args. */
|
|
}
|
|
for( cmpp_arg const * aVal = args.arg0;
|
|
!dxppCode && aVal;
|
|
aVal = aVal->next ){
|
|
++bindNdx;
|
|
if( 0 ){
|
|
g_warn("bind #%d %s <<%s>>", bindNdx,
|
|
cmpp__tt_cstr(aVal->ttype, true), aVal->z);
|
|
}
|
|
cmpp__bind_arg(dx, q, bindNdx, aVal);
|
|
}
|
|
cmpp_args_cleanup(&args);
|
|
return dxppCode;
|
|
}
|
|
|
|
/** #query impl */
|
|
static void cmpp_dx_f_query(cmpp_dx *dx){
|
|
//cmpp_d const * d = cmpp_dx_d(dx);
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp * const pp = dx->pp;
|
|
sqlite3_stmt * q = 0;
|
|
cmpp_b * const obBody = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const sql = cmpp_b_borrow(dx->pp);
|
|
cmpp_outputer obNull = cmpp_outputer_empty;
|
|
//cmpp_b obBindArgs = cmpp_b_empty;
|
|
cmpp_args args = cmpp_args_empty
|
|
/* We need to copy the args or do some arg-type-specific work to
|
|
copy the memory for specific cases. */;
|
|
int nChomp = 0;
|
|
bool spStarted = false;
|
|
bool seenDefine = false;
|
|
bool batchMode = false;
|
|
cmpp_arg const * pNext = 0;
|
|
cmpp_arg const * aBind = 0;
|
|
cmpp_d const * const dNoRows = dx->d->impl.state;
|
|
cmpp_d const * const dClosers[2] = {dx->d->closer, dNoRows};
|
|
|
|
if( !obBody || !sql ) goto cleanup;
|
|
|
|
assert( dNoRows );
|
|
if( cmpp_dx_args_clone(dx, &args) ){
|
|
goto cleanup;
|
|
}
|
|
//g_warn("args.argc=%d", args.argc);
|
|
for( cmpp_arg const * arg = args.arg0;
|
|
0==dxppCode && arg;
|
|
arg = pNext ){
|
|
//g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z);
|
|
pNext = arg->next;
|
|
if( cmpp_arg_equals(arg, "define") ){
|
|
if( seenDefine ){
|
|
cmpp__dx_err_just_once(dx, arg);
|
|
goto cleanup;
|
|
}
|
|
seenDefine = true;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals(arg, "-chomp") ){
|
|
++nChomp;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals(arg, "-batch") ){
|
|
if( batchMode ){
|
|
cmpp__dx_err_just_once(dx, arg);
|
|
goto cleanup;
|
|
}
|
|
batchMode = true;
|
|
continue;
|
|
}
|
|
if( !sql->n ){
|
|
if( cmpp__consume_sql_args(pp, arg, &aBind, &pNext) ){
|
|
goto cleanup;
|
|
}
|
|
if( cmpp_arg_to_b(dx, arg, sql, cmpp_arg_to_b_F_BRACE_CALL) ){
|
|
goto cleanup;
|
|
}
|
|
//g_warn("SQL: <<%s>>", sql->z);
|
|
continue;
|
|
}
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
|
|
goto cleanup;
|
|
}
|
|
if( ppCode ) goto cleanup;
|
|
if( seenDefine ){
|
|
if( nChomp ){
|
|
serr("-chomp and define may not be used together.");
|
|
goto cleanup;
|
|
}else if( batchMode ){
|
|
serr("-batch and define may not be used together.");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if( !sql->n ){
|
|
serr("Expecting an SQL-string argument.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( batchMode ){
|
|
if( aBind ){
|
|
serr("Bindable values may not be used with -batch.");
|
|
goto cleanup;
|
|
}
|
|
char *zErr = 0;
|
|
cmpp__pi(dx->pp);
|
|
int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr);
|
|
rc = cmpp__db_rc(dx->pp, rc, zErr);
|
|
sqlite3_free(zErr);
|
|
goto cleanup;
|
|
}
|
|
|
|
if( cmpp__db_rc(pp, sqlite3_prepare_v2(
|
|
pp->pimpl->db.dbh, (char const *)sql->z,
|
|
(int)sql->n, &q, 0), 0) ){
|
|
goto cleanup;
|
|
}else if( !q ){
|
|
cmpp_err_set(pp, CMPP_RC_RANGE,
|
|
"Empty SQL is not permitted.");
|
|
goto cleanup;
|
|
}
|
|
//g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q));
|
|
int const nCol = sqlite3_column_count(q);
|
|
if( !nCol ){
|
|
cmpp_err_set(pp, CMPP_RC_RANGE,
|
|
"SQL does not have any result columns.");
|
|
goto cleanup;
|
|
}
|
|
if( !seenDefine ){
|
|
if( cmpp_sp_begin(pp) ) goto cleanup;
|
|
spStarted = true;
|
|
}
|
|
|
|
if( aBind && cmpp__bind_group(dx, q, aBind) ){
|
|
goto cleanup;
|
|
}
|
|
|
|
bool gotARow = false;
|
|
cmpp_dx_pos dxPosStart;
|
|
cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D;
|
|
cmpp_dx_pos_save(dx, &dxPosStart);
|
|
int const nChompOrig = nChomp;
|
|
while( 0==ppCode ){
|
|
int const dbrc = cmpp__step(pp, q, false);
|
|
if( SQLITE_ROW==dbrc ){
|
|
nChomp = nChompOrig;
|
|
gotARow = true;
|
|
if( cmpp__define_from_row(pp, q, false) ) break;
|
|
if( seenDefine ) break;
|
|
cmpp_dx_pos_restore(dx, &dxPosStart);
|
|
cmpp_b_reuse(obBody);
|
|
/* If it weren't for -chomp, we wouldn't need to
|
|
buffer this. */
|
|
if( cmpp_dx_consume_b(dx, obBody, dClosers,
|
|
sizeof(dClosers)/sizeof(dClosers[0]),
|
|
consumeFlags) ){
|
|
goto cleanup;
|
|
}
|
|
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
|
|
while( nChomp-- && cmpp_b_chomp(obBody) ){}
|
|
if( obBody->n && cmpp_dx_out_raw(dx, obBody->z, obBody->n) ) break;
|
|
if( dx->d == dNoRows ){
|
|
if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/,
|
|
consumeFlags) ){
|
|
goto cleanup;
|
|
}
|
|
assert( dx->d == dClosers[0] );
|
|
/* TODO? chomp? */
|
|
}
|
|
continue;
|
|
}
|
|
if( 0==ppCode && seenDefine ){
|
|
/* If we got here, there was no result row. */
|
|
cmpp__define_from_row(pp, q, true);
|
|
}
|
|
break;
|
|
}/*result row loop*/
|
|
cmpp__stmt_reset(q);
|
|
if( ppCode ) goto cleanup;
|
|
|
|
while( !seenDefine && !gotARow ){
|
|
/* No result rows. Skip past the body, emitting the #query:no-rows
|
|
content, if any. We disable @token processing for that first
|
|
step because (A) the output is not going anywhere, so no need
|
|
to expand it (noting that expanding may have side effects via
|
|
@[call...]@) and (B) the @tokens@ referring to this query's
|
|
results will not have been set because there was no row to set
|
|
them from, so @expanding@ them would fail. */
|
|
cmpp_atpol_e const atpol = cmpp_atpol_get(dx->pp);
|
|
if( cmpp_atpol_set(dx->pp, cmpp_atpol_OFF) ) break;
|
|
cmpp_dx_consume(dx, &obNull, dClosers,
|
|
sizeof(dClosers)/sizeof(dClosers[0]),
|
|
consumeFlags);
|
|
cmpp_atpol_set(dx->pp, atpol);
|
|
if( dxppCode ) break;
|
|
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
|
|
if( dx->d == dNoRows ){
|
|
if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/,
|
|
consumeFlags) ){
|
|
break;
|
|
}
|
|
assert( dx->d == dClosers[0] );
|
|
/* TODO? chomp? */
|
|
}
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
cmpp_args_cleanup(&args);
|
|
cmpp_b_return(dx->pp, obBody);
|
|
cmpp_b_return(dx->pp, sql);
|
|
sqlite3_finalize(q);
|
|
if( spStarted ) cmpp_sp_rollback(pp);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_DB */
|
|
|
|
#ifndef CMPP_OMIT_D_PIPE
|
|
/** #pipe impl. */
|
|
static void cmpp_dx_f_pipe(cmpp_dx *dx){
|
|
//cmpp_d const * d = cmpp_dx_d(dx);
|
|
unsigned char const * zArgs = dx->args.z;
|
|
assert( dx->args.arg0->n == dx->args.nz );
|
|
unsigned char const * const zArgsEnd = zArgs + dx->args.nz;
|
|
if( zArgs==zArgsEnd ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting a command and arguments to pipe.");
|
|
return;
|
|
}
|
|
cmpp_FILE * fpToChild = 0;
|
|
int nChompIn = 0, nChompOut = 0;
|
|
cmpp_b * const chout = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const cmd = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const body = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const bArg = cmpp_b_borrow(dx->pp)
|
|
/* arg parsing and the initial command name part of the
|
|
external command. */;
|
|
cmpp_args cmdArgs = cmpp_args_empty;
|
|
/* TODOs and FIXMEs:
|
|
|
|
We need flags to optionally @token@-parse before and/or after
|
|
filtering.
|
|
*/
|
|
bool seenDD = false /* true if seen "--" or [...] */;
|
|
bool doCapture = true /* true if we need a closing /pipe */;
|
|
bool argsAsGroup = false /* true if args is [...] */;
|
|
bool dumpDebug = false;
|
|
cmpp_flag32_t popenFlags = 0;
|
|
cmpp_popen_t po = cmpp_popen_t_empty;
|
|
if( cmpp_b_reserve3(dx->pp, cmd, zArgsEnd-zArgs + 1)
|
|
|| cmpp_b_reserve3(dx->pp, bArg, cmd->nAlloc) ){
|
|
goto cleanup;
|
|
}
|
|
|
|
unsigned char * zOut = bArg->z;
|
|
unsigned char const * const zOutEnd = bArg->z + bArg->nAlloc - 1;
|
|
while( 0==dxppCode ){
|
|
cmpp_arg arg = cmpp_arg_empty;
|
|
zOut = bArg->z;
|
|
if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd,
|
|
&zOut, zOutEnd) ){
|
|
goto cleanup;
|
|
}
|
|
if( cmpp_arg_equals(&arg, "--") ){
|
|
zOut = bArg->z;
|
|
if( cmpp_arg_parse(dx, &arg, &zArgs, zArgsEnd,
|
|
&zOut, zOutEnd) ){
|
|
goto cleanup;
|
|
}
|
|
if( !arg.n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting external command name "
|
|
"or [...] after --.");
|
|
goto cleanup;
|
|
}
|
|
do_arg_list:
|
|
seenDD = true;
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
if( cmpp_TT_GroupBrace==arg.ttype ){
|
|
argsAsGroup = true;
|
|
a2bFlags |= cmpp_arg_to_b_F_NO_BRACE_CALL;
|
|
}else if( cmpp__arg_wordIsPathOrFlag(&arg) ){
|
|
/* If it looks like it is a path, do not
|
|
expand it as a word. */
|
|
arg.ttype = cmpp_TT_String;
|
|
}
|
|
if( cmpp_arg_to_b(dx, &arg, cmd, a2bFlags)
|
|
|| (!argsAsGroup && cmpp_b_append_ch(cmd, ' ')) ){
|
|
goto cleanup;
|
|
}
|
|
//g_warn("command: [%s]=>%s", arg.z, cmd->z);
|
|
if( cmd->n<2 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_RANGE,
|
|
"Command name '%s' resolves to empty. "
|
|
"This is most commonly caused by not "
|
|
"quoting it but it can also mean that it "
|
|
"is an unknown define key.", arg.z);
|
|
goto cleanup;
|
|
}
|
|
//g_warn("arg=%s", arg.z);
|
|
//g_warn("cmd=%s", cmd->z);
|
|
break;
|
|
}
|
|
if( cmpp_TT_GroupBrace==arg.ttype ){
|
|
goto do_arg_list;
|
|
}
|
|
#define FLAG(X)if( cmpp_arg_isflag(&arg, X) )
|
|
FLAG("-no-input"){
|
|
doCapture = false;
|
|
continue;
|
|
}
|
|
FLAG("-chomp-output"){
|
|
++nChompOut;
|
|
continue;
|
|
}
|
|
FLAG("-chomp"){
|
|
++nChompIn;
|
|
continue;
|
|
}
|
|
FLAG("-exec-direct"){
|
|
popenFlags |= cmpp_popen_F_DIRECT;
|
|
continue;
|
|
}
|
|
FLAG("-path"){
|
|
popenFlags |= cmpp_popen_F_PATH;
|
|
continue;
|
|
}
|
|
FLAG("-debug"){
|
|
dumpDebug = true;
|
|
continue;
|
|
}
|
|
#undef FLAG
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s. %s%s requires -- "
|
|
"before its external command name.",
|
|
arg.z, cmpp_dx_delim(dx),
|
|
dx->d->name.z);
|
|
goto cleanup;
|
|
}
|
|
|
|
if( !seenDD ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"%s%s requires a -- before the name of "
|
|
"its external app.",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
goto cleanup;
|
|
}
|
|
|
|
//g_warn("zArgs n=%u zArgs=%s", (unsigned)(zArgsEnd-zArgs), zArgs);
|
|
/* dx->pimpl->args gets overwritten by cmpp_dx_consume(), so we have to copy
|
|
the args. */
|
|
if( argsAsGroup ){
|
|
assert( cmd->z );
|
|
if( cmpp_args_parse(dx, &cmdArgs, cmd->z, cmd->n, 0) ){
|
|
goto cleanup;
|
|
}
|
|
}else{
|
|
/* zArgs can have newlines in it. We need to strip those out
|
|
before passing it on. We elide them entirely, as opposed to
|
|
replacing them with a space. */
|
|
cmpp_skip_snl(&zArgs, zArgsEnd);
|
|
if( cmpp_b_reserve3(dx->pp, cmd, cmd->n + (zArgsEnd-zArgs) + 1) ){
|
|
goto cleanup;
|
|
}
|
|
unsigned char * zo = cmd->z + cmd->n;
|
|
unsigned char const *zi = zArgs;
|
|
#if !defined(NDEBUG)
|
|
unsigned char const * zoEnd = cmd->z + cmd->nAlloc;
|
|
#endif
|
|
for( ; zi<zArgsEnd; ++zi){
|
|
if( '\n'!=*zi && '\r'!=*zi ) *zo++ = *zi;
|
|
}
|
|
assert( zoEnd > zo );
|
|
*zo = 0;
|
|
cmd->n = zo - cmd->z;
|
|
}
|
|
assert( !dxppCode );
|
|
|
|
if( doCapture ){
|
|
assert( dx->d->closer );
|
|
if( cmpp_dx_consume_b(dx, body, &dx->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
goto cleanup;
|
|
}
|
|
while( nChompIn-- && cmpp_b_chomp(body) ){}
|
|
po.fpToChild = &fpToChild;
|
|
}
|
|
|
|
if( dumpDebug ){
|
|
g_warn("%s%s -debug: cmd argsAsGroup=%d n=%u z=%s",
|
|
cmpp_dx_delim(dx), dx->d->name.z,
|
|
(int)argsAsGroup,
|
|
(unsigned)cmd->n, cmd->z);
|
|
}
|
|
if( argsAsGroup ){
|
|
cmpp_popen_args(dx, &cmdArgs, &po);
|
|
}else{
|
|
unsigned char const * z = cmd->z;
|
|
//cmpp_skip_snl(&z, cmd->z + cmd->n);
|
|
cmpp_popen(dx->pp, z, popenFlags, &po);
|
|
}
|
|
if( dxppCode ) goto cleanup;
|
|
int rc = 0;
|
|
if( doCapture ){
|
|
/* Bug: if body is too bug (no idea how much that is), this will
|
|
block while waiting on input from the child. This can easily
|
|
happen with #include -raw. */
|
|
#if 0
|
|
/* Failed attempt to work around it. */
|
|
assert( fpToChild );
|
|
enum { BufSize = 128 };
|
|
unsigned char buf[BufSize];
|
|
cmpp_size_t nLeft = body->n;
|
|
unsigned char const * z = body->z;
|
|
while( nLeft>0 && !dxppCode ){
|
|
cmpp_size_t nWrite = nLeft < BufSize ? nLeft : BufSize;
|
|
g_warn("writing %u to child...", (unsigned)nWrite);
|
|
rc = cmpp_output_f_FILE(fpToChild, z, nWrite);
|
|
if( rc ){
|
|
cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process.");
|
|
break;
|
|
}
|
|
z += nWrite;
|
|
nLeft -= nWrite;
|
|
fflush(fpToChild);
|
|
cmpp_size_t nRead = BufSize;
|
|
rc = cmpp_input_f_fd(&po.fdFromChild, &buf[0], &nRead);
|
|
if( rc ) goto err_reading;
|
|
cmpp_b_append4(dx->pp, &chout, buf, nRead);\
|
|
}
|
|
if( !dxppCode ){
|
|
g_warn0("reading from child...");
|
|
rc = cmpp_stream( cmpp_input_f_fd, &po.fdFromChild,
|
|
cmpp_output_f_b, chout );
|
|
if( rc ) goto err_reading;
|
|
}
|
|
g_warn0("I/O done");
|
|
#else
|
|
//g_warn("writing %u bytes to child...", (unsigned)body->n);
|
|
rc = cmpp_output_f_FILE(fpToChild, body->z, body->n);
|
|
if( rc ){
|
|
cmpp_dx_err_set(dx, rc, "Error feeding stdin to piped process.");
|
|
goto cleanup;
|
|
}
|
|
//g_warn("wrote %u bytes to child.", (unsigned)body->n);
|
|
fclose(fpToChild);
|
|
fpToChild = 0;
|
|
if( dxppCode ) goto cleanup;
|
|
goto stream_chout;
|
|
#endif
|
|
}else{
|
|
stream_chout:
|
|
//g_warn0("waiting on child...");
|
|
rc = cmpp_stream(cmpp_input_f_fd, &po.fdFromChild,
|
|
cmpp_output_f_b, chout);
|
|
//g_warn0("I/O done");
|
|
if( rc ){
|
|
//err_reading:
|
|
cmpp_dx_err_set(dx, rc, "Error reading stdout from piped process.");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
while( nChompOut-- && cmpp_b_chomp(chout) ){}
|
|
//g_warn("Read in:\n%.*s", (int)chout->n, chout->z);
|
|
cmpp_dx_out_raw(dx, chout->z, chout->n);
|
|
|
|
cleanup:
|
|
cmpp_args_cleanup(&cmdArgs);
|
|
cmpp_b_return(dx->pp, chout);
|
|
cmpp_b_return(dx->pp, cmd);
|
|
cmpp_b_return(dx->pp, body);
|
|
cmpp_b_return(dx->pp, bArg);
|
|
cmpp_pclose(&po);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_PIPE */
|
|
|
|
/**
|
|
#sum ...args
|
|
|
|
Emits the sum of its arguments, treating each as an
|
|
integer. Non-integer arguments are silently skipped.
|
|
*/
|
|
static void cmpp_dx_f_sum(cmpp_dx *dx){
|
|
int64_t n = 0, i = 0;
|
|
cmpp_b b = cmpp_b_empty;
|
|
for( cmpp_arg const * arg = dx->args.arg0;
|
|
arg && !cmpp_dx_err_check(dx); arg = arg->next ){
|
|
if( 0==cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&b),
|
|
cmpp_arg_to_b_F_BRACE_CALL)
|
|
&& cmpp__is_int64(b.z, b.n, &i) ){
|
|
n += i;
|
|
}
|
|
}
|
|
cmpp_b_append_i64(cmpp_b_reuse(&b), n);
|
|
cmpp_dx_out_raw(dx, b.z, b.n);
|
|
cmpp_b_clear(&b);
|
|
}
|
|
|
|
/**
|
|
#arg ?flags? the-arg
|
|
|
|
-trim-left
|
|
-trim-right
|
|
-trim: trim both sides
|
|
|
|
It sends its arg to cmpp_arg_to_b() to expand it, optionally
|
|
trims the result, and emits that value.
|
|
|
|
This directive is not expected to be useful except, perhaps in
|
|
testing cmpp itself. Its trim flags, in particular, aren't commonly
|
|
useful because #arg is only useful in a function call context and
|
|
those unconditionally trim their output.
|
|
*/
|
|
static void cmpp_dx_f_arg(cmpp_dx *dx){
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
bool trimL = false, trimR = false;
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
for( ; arg && !cmpp_dx_err_check(dx); arg = arg->next ){
|
|
#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
|
|
FLAG("-raw") {
|
|
a2bFlags = cmpp_arg_to_b_F_FORCE_STRING;
|
|
continue;
|
|
}
|
|
FLAG("-trim-left") { trimL=true; continue; }
|
|
FLAG("-trim-right") { trimR=true; continue; }
|
|
FLAG("-trim") { trimL=trimR=true; continue; }
|
|
#undef FLAG
|
|
break;
|
|
}
|
|
if( arg ){
|
|
cmpp_b * const b = cmpp_b_borrow(dx->pp);
|
|
if( b && 0==cmpp_arg_to_b(dx, arg, b, a2bFlags) ){
|
|
unsigned char const * zz = b->z;
|
|
unsigned char const * zzEnd = b->z + b->n;
|
|
if( trimL ) cmpp_skip_snl(&zz, zzEnd);
|
|
if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd);
|
|
if( zzEnd-zz ){
|
|
cmpp_dx_out_raw(dx, zz, zzEnd-zz);
|
|
}
|
|
}
|
|
cmpp_b_return(dx->pp, b);
|
|
}else if( !cmpp_dx_err_check(dx) ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
#join ?flags? ...args
|
|
|
|
-s SEPARATOR: sets the separator for its RHS arguments. Default=space.
|
|
|
|
-nl: append a newline (will be stripped by [call]s!). This is the default
|
|
when !cmpp_dx_is_call(dx).
|
|
|
|
-nonl: do not append a newline. Default when dx->isCall.
|
|
*/
|
|
static void cmpp_dx_f_join(cmpp_dx *dx){
|
|
cmpp_b * const b = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const bSep = cmpp_b_borrow(dx->pp);
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
bool addNl = !cmpp_dx_is_call(dx);
|
|
int n = 0;
|
|
if( !b || !bSep ) goto end;
|
|
if( !dx->args.argc ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"%s%s expects ?flags? ...args",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
goto end;
|
|
}
|
|
cmpp_b_append_ch(bSep, ' ');
|
|
cmpp_check_oom(dx->pp, bSep->z);
|
|
for( cmpp_arg const * arg = dx->args.arg0; arg
|
|
&& !b->errCode
|
|
&& !bSep->errCode
|
|
&& !cmpp_dx_err_check(dx);
|
|
arg = arg->next ){
|
|
#define FLAG(X)if( cmpp_arg_isflag(arg, X) )
|
|
FLAG("-s"){
|
|
if( !arg->next ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Missing SEPARATOR argument to -s.");
|
|
break;
|
|
}
|
|
cmpp_arg_to_b(dx, arg->next,
|
|
cmpp_b_reuse(bSep),
|
|
cmpp_arg_to_b_F_BRACE_CALL);
|
|
arg = arg->next;
|
|
continue;
|
|
}
|
|
//FLAG("-nl"){ addNl=true; continue; }
|
|
FLAG("-nonl"){ addNl=false; continue; }
|
|
#undef FLAG
|
|
if( n++ && cmpp_dx_out_raw(dx, bSep->z, bSep->n) ){
|
|
break;
|
|
}
|
|
if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b), a2bFlags) ){
|
|
break;
|
|
}
|
|
cmpp_dx_out_raw(dx, b->z, b->n);
|
|
}
|
|
if( !cmpp_dx_err_check(dx) ){
|
|
if( !n ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting at least one argument.");
|
|
}else if( addNl ){
|
|
cmpp_dx_out_raw(dx, "\n", 1);
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(dx->pp, b);
|
|
cmpp_b_return(dx->pp, bSep);
|
|
}
|
|
|
|
|
|
/* Impl. for #file */
|
|
static void cmpp_dx_f_file(cmpp_dx *dx){
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp_d const * const d = dx->d;
|
|
enum e_op {
|
|
op_none, op_exists, op_join
|
|
};
|
|
cmpp_b * const b0 = cmpp_b_borrow(dx->pp);
|
|
if( !b0 ) goto end;
|
|
enum e_op op = op_none;
|
|
cmpp_arg const * opArg = 0;
|
|
cmpp_arg const * arg = 0;
|
|
for( arg = dx->args.arg0;
|
|
0==dxppCode && arg;
|
|
arg = arg->next ){
|
|
if( op_none==op ){
|
|
if( cmpp_arg_equals(arg, "exists") ){
|
|
op = op_exists;
|
|
opArg = arg->next;
|
|
arg = opArg->next;
|
|
if( arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"%s%s exists: too many arguments",
|
|
cmpp_dx_delim(dx), d->name.z);
|
|
goto end;
|
|
}
|
|
break;
|
|
}else if( cmpp_arg_equals(arg, "join") ){
|
|
op = op_join;
|
|
if( !arg->next ) goto missing_arg;
|
|
arg = arg->next;
|
|
break;
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unknown %s%s command: %s",
|
|
cmpp_dx_delim(dx), d->name.z, arg->z);
|
|
goto end;
|
|
}
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s unhandled argument: %s",
|
|
cmpp_dx_delim(dx), d->name.z, arg->z);
|
|
goto end;
|
|
}
|
|
}
|
|
switch( op ){
|
|
case op_none: goto missing_arg;
|
|
case op_join: {
|
|
int i = 0;
|
|
cmpp_flag32_t const bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
for( ; arg; arg = arg->next, ++i ){
|
|
if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(b0), bFlags)
|
|
|| (i && cmpp_dx_out_raw(dx, "/", 1))
|
|
|| (b0->n && cmpp_dx_out_raw(dx, b0->z, b0->n)) ){
|
|
break;
|
|
}
|
|
}
|
|
cmpp_dx_out_raw(dx, "\n", 1);
|
|
break;
|
|
}
|
|
case op_exists: {
|
|
assert( opArg );
|
|
bool const b = cmpp__file_is_readable((char const *)opArg->z);
|
|
cmpp_dx_out_raw(dx, b ? "1\n" : "0\n", 2);
|
|
break;
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(dx->pp, b0);
|
|
return;
|
|
missing_arg:
|
|
if( arg ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s %s: missing argument",
|
|
cmpp_dx_delim(dx), d->name.z, arg->z );
|
|
}else{
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "%s%s: missing subcommand",
|
|
cmpp_dx_delim(dx), d->name.z);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
|
|
/**
|
|
#cmp LHS op RHS
|
|
*/
|
|
static void cmpp_dx_f_cmp(cmpp_dx *dx){
|
|
cmpp_b * const bL = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const bR = cmpp_b_borrow(dx->pp);
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
if( !bL || !!bR ) goto end;
|
|
for( cmpp_arg const * arg = dx->args.arg0; arg
|
|
&& !cmpp_dx_err_check(dx);
|
|
arg = arg->next ){
|
|
if( !bL->z ){
|
|
cmpp_arg_to_b(dx, arg, bL, a2bFlags);
|
|
continue;
|
|
}
|
|
if( !bR->z ){
|
|
cmpp_arg_to_b(dx, arg, bR, a2bFlags);
|
|
continue;
|
|
}
|
|
goto usage;
|
|
}
|
|
|
|
if( cmpp_dx_err_check(dx) ) goto end;
|
|
if( !bL->z || !bR->z ){
|
|
usage:
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Usage: LHS RHS");
|
|
goto end;
|
|
}
|
|
assert( bL->z );
|
|
assert( bR->z );
|
|
char cbuf[20];
|
|
int const cmp = strcmp((char*)bL->z, (char*)bR->z);
|
|
int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp);
|
|
assert(n>0);
|
|
cmpp_dx_out_raw(dx, cbuf, (cmpp_size_t)n);
|
|
|
|
end:
|
|
cmpp_b_return(dx->pp, bL);
|
|
cmpp_b_return(dx->pp, bR);
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* Impl. for dummy placeholder. */
|
|
static void cmpp_dx_f_todo(cmpp_dx *dx){
|
|
cmpp_d const * d = cmpp_dx_d(dx);
|
|
g_warn("TODO: directive handler for %s", d->name.z);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
If zName matches one of the delayed-load directives, that directive
|
|
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
|
|
no match is found, but pp's error state is not updated in that
|
|
case. If a match is found and registration fails, that result code
|
|
will propagate via pp.
|
|
*/
|
|
int cmpp__d_delayed_load(cmpp *pp, char const *zName){
|
|
if( ppCode ) return ppCode;
|
|
int rc = CMPP_RC_NO_DIRECTIVE;
|
|
unsigned const nName = strlen(zName);
|
|
|
|
pp->pimpl->flags.isInternalDirectiveReg = true;
|
|
|
|
#define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME))
|
|
#define M_OC(NAME) (M(NAME) || M("/" NAME))
|
|
#define M_IF(NAME) if( M(NAME) )
|
|
#define CF(X) cmpp_d_F_ ## X
|
|
#define F_A_RAW CF(ARGS_RAW)
|
|
#define F_A_LIST CF(ARGS_LIST)
|
|
#define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY)
|
|
#define F_UNSAFE cmpp_d_F_NOT_IN_SAFEMODE
|
|
#define F_NC cmpp_d_F_NO_CALL
|
|
#define F_CALL cmpp_d_F_CALL_ONLY
|
|
#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \
|
|
cmpp_d_reg SYMNAME = { \
|
|
.name = NAME, \
|
|
.opener = { \
|
|
.f = OPENER, \
|
|
.flags = OFLAGS \
|
|
}, \
|
|
.closer = { \
|
|
.f = CLOSER, \
|
|
.flags = CFLAGS \
|
|
}, \
|
|
.dtor = 0, \
|
|
.state = 0 \
|
|
}
|
|
|
|
#define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS ) \
|
|
DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \
|
|
rc = cmpp_d_register(pp, &rReg, NULL); \
|
|
goto end
|
|
|
|
/* The #if family requires some hand-holding... */
|
|
if( M_OC("if") || M("elif") || M("else") ) {
|
|
DREG0(rIf, "if",
|
|
cmpp_dx_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL),
|
|
cmpp_dx_f_if_dangler, 0);
|
|
DREG0(rElif, "elif",
|
|
cmpp_dx_f_if_dangler, F_NC,
|
|
0, 0);
|
|
DREG0(rElse, "else",
|
|
cmpp_dx_f_if_dangler, F_NC,
|
|
0, 0);
|
|
CmppIfState * const cis = cmpp__malloc(pp, sizeof(*cis));
|
|
if( !cis ) goto end;
|
|
memset(cis, 0, sizeof(*cis));
|
|
rIf.state = cis;
|
|
rIf.dtor = cmpp_mfree;
|
|
if( cmpp_d_register(pp, &rIf, &cis->dIf)
|
|
/* rIf must be first to avoid leaking cis on error */
|
|
|| cmpp_d_register(pp, &rElif, &cis->dElif)
|
|
|| cmpp_d_register(pp, &rElse, &cis->dElse) ){
|
|
rc = ppCode;
|
|
}else{
|
|
assert( cis->dIf && cis->dElif && cis->dElse );
|
|
assert( !cis->dEndif );
|
|
assert( cis == cis->dIf->impl.state );
|
|
assert( cmpp_mfree==cis->dIf->impl.dtor );
|
|
cis->dElif->impl.state
|
|
= cis->dElse->impl.state
|
|
= cis;
|
|
cis->dElif->closer
|
|
= cis->dElse->closer
|
|
= cis->dEndif
|
|
= cis->dIf->closer;
|
|
rc = 0;
|
|
}
|
|
goto end;
|
|
}/* #if and friends */
|
|
|
|
/* Basic core directives... */
|
|
#define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS) \
|
|
if( M_OC(N) ){ \
|
|
DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS); \
|
|
} (void)0
|
|
|
|
M_IF_CORE("@", cmpp_dx_f_at, F_A_LIST,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
M_IF_CORE("arg", cmpp_dx_f_arg, F_A_LIST, 0, 0);
|
|
M_IF_CORE("assert", cmpp_dx_f_expr, F_EXPR, 0, 0);
|
|
M_IF_CORE("cmp", cmpp_dx_f_cmp, F_A_LIST, 0, 0);
|
|
M_IF_CORE("define", cmpp_dx_f_define, F_A_LIST,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
M_IF_CORE("delimiter", cmpp_dx_f_delimiter, F_A_LIST,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
M_IF_CORE("error", cmpp_dx_f_error, F_A_RAW, 0, 0);
|
|
M_IF_CORE("expr", cmpp_dx_f_expr, F_EXPR, 0, 0);
|
|
M_IF_CORE("join", cmpp_dx_f_join, F_A_LIST, 0, 0);
|
|
M_IF_CORE("once", cmpp_dx_f_once, F_A_LIST | F_NC,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
M_IF_CORE("pragma", cmpp_dx_f_pragma, F_A_LIST, 0, 0);
|
|
M_IF_CORE("savepoint", cmpp_dx_f_savepoint, F_A_LIST, 0, 0);
|
|
M_IF_CORE("stderr", cmpp_dx_f_stderr, F_A_RAW, 0, 0);
|
|
M_IF_CORE("sum", cmpp_dx_f_sum, F_A_LIST, 0, 0);
|
|
M_IF_CORE("undef", cmpp_dx_f_undef, F_A_LIST, 0, 0);
|
|
M_IF_CORE("undefined-policy", cmpp_dx_f_undef_policy, F_A_LIST, 0, 0);
|
|
M_IF_CORE("//", cmpp_dx_f_noop, F_A_RAW, 0, 0);
|
|
M_IF_CORE("file", cmpp_dx_f_file,
|
|
F_A_LIST | F_UNSAFE, 0, 0);
|
|
|
|
#undef M_IF_CORE
|
|
|
|
|
|
/* Directives which can be disabled via build flags or
|
|
flags to cmpp_ctor()... */
|
|
#define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \
|
|
M_IF(NAME) { \
|
|
if( 0==(FLAG & pp->pimpl->flags.newFlags) ) { \
|
|
DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \
|
|
} \
|
|
goto end; \
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_INCLUDE
|
|
M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE,
|
|
cmpp_dx_f_include, F_A_LIST | F_UNSAFE,
|
|
0, 0);
|
|
#endif
|
|
|
|
#ifndef CMPP_OMIT_D_PIPE
|
|
M_IF_FLAGGED("pipe", cmpp_ctor_F_NO_PIPE,
|
|
cmpp_dx_f_pipe, F_A_RAW | F_UNSAFE,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
#endif
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB,
|
|
cmpp_dx_f_attach, F_A_LIST | F_UNSAFE,
|
|
0, 0);
|
|
M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB,
|
|
cmpp_dx_f_detach, F_A_LIST | F_UNSAFE,
|
|
0, 0);
|
|
if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.newFlags)
|
|
&& (M_OC("query") || M("query:no-rows")) ){
|
|
DREG0(rQ, "query", cmpp_dx_f_query, F_A_LIST | F_UNSAFE,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
cmpp_d * dQ = 0;
|
|
rc = cmpp_d_register(pp, &rQ, &dQ);
|
|
if( 0==rc ){
|
|
/*
|
|
It would be preferable to delay registration of query:no-rows
|
|
until we need it, but doing so causes an error when:
|
|
|
|
|#if 0
|
|
|#query
|
|
|...
|
|
|#query:no-rows HERE
|
|
|...
|
|
|#/query
|
|
|#/if
|
|
|
|
Because query:no-rows won't have been registered, and unknown
|
|
directives are an error even in skip mode. Maybe they
|
|
shouldn't be. Maybe we should just skip them in skip mode.
|
|
That's only been an issue since doing delayed registration of
|
|
directives, so it's not come up until recently (as of
|
|
2025-10-27). i was so hoping to be able to get _rid_ of skip
|
|
mode at some point.
|
|
*/
|
|
cmpp_d * dNoRows = 0;
|
|
cmpp_d_reg const rNR = {
|
|
.name = "query:no-rows",
|
|
.opener = {
|
|
.f = cmpp_dx_f_dangling_closer,
|
|
.flags = F_NC
|
|
}
|
|
};
|
|
rc = cmpp_d_register(pp, &rNR, &dNoRows);
|
|
if( 0==rc ){
|
|
dNoRows->closer = dQ->closer;
|
|
assert( !dQ->impl.state );
|
|
dQ->impl.state = dNoRows;
|
|
}
|
|
}
|
|
goto end;
|
|
}
|
|
#endif /*CMPP_OMIT_D_DB*/
|
|
|
|
#if CMPP_D_MODULE
|
|
extern void cmpp_dx_f_module(cmpp_dx *);
|
|
M_IF_FLAGGED("module", cmpp_ctor_F_NO_MODULE,
|
|
cmpp_dx_f_module, F_A_LIST | F_UNSAFE,
|
|
0, 0);
|
|
#endif
|
|
|
|
#undef M_IF_FLAGGED
|
|
|
|
#ifndef NDEBUG
|
|
M_IF("experiment"){
|
|
DREG("experiment", cmpp_dx_f_experiment,
|
|
F_A_LIST | F_UNSAFE, 0, 0);
|
|
}
|
|
#endif
|
|
|
|
end:
|
|
#undef DREG
|
|
#undef DREG0
|
|
#undef F_EXPR
|
|
#undef F_A_RAW
|
|
#undef F_A_LIST
|
|
#undef F_UNSAFE
|
|
#undef F_NC
|
|
#undef F_CALL
|
|
#undef CF
|
|
#undef M
|
|
#undef M_OC
|
|
#undef M_IF
|
|
pp->pimpl->flags.isInternalDirectiveReg = false;
|
|
return ppCode ? ppCode : rc;
|
|
}
|
|
/*
|
|
** 2026-02-07:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses filesystem-related APIs libcmpp.
|
|
*/
|
|
|
|
#include <unistd.h>
|
|
|
|
/**
|
|
There are APIs i'd _like_ to have here, but the readily-available
|
|
code for them BSD license, so can't be pasted in here. Examples:
|
|
|
|
- Filename canonicalization.
|
|
|
|
- Cross-platform getcwd() (see below).
|
|
|
|
- Windows support. This requires, in addition to the different
|
|
filesystem APIs, converting strings into something it can use.
|
|
|
|
All of that adds up to infrastructure... which already exists
|
|
elsewhere but can't be copied here while retaining this project's
|
|
license.
|
|
*/
|
|
|
|
bool cmpp__file_is_readable(char const *zFile){
|
|
return 0==access(zFile, R_OK);
|
|
}
|
|
|
|
#if 0
|
|
FILE *cmpp__fopen(const char *zName, const char *zMode){
|
|
FILE *f;
|
|
if(zName && ('-'==*zName && !zName[1])){
|
|
f = (strchr(zMode, 'w') || strchr(zMode,'+'))
|
|
? stdout
|
|
: stdin
|
|
;
|
|
}else{
|
|
f = fopen(zName, zMode);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void cmpp__fclose( FILE * f ){
|
|
if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
|
|
fclose(f);
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
** 2025-11-07:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the arguments-handling-related pieces for libcmpp.
|
|
*/
|
|
|
|
const cmpp_args_pimpl cmpp_args_pimpl_empty =
|
|
cmpp_args_pimpl_empty_m;
|
|
const cmpp_args cmpp_args_empty = cmpp_args_empty_m;
|
|
const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m;
|
|
|
|
//just in case these ever get dynamic state
|
|
void cmpp_arg_cleanup(cmpp_arg *arg){
|
|
if( arg ) *arg = cmpp_arg_empty;
|
|
}
|
|
|
|
//just in case these ever get dynamic state
|
|
void cmpp_arg_reuse(cmpp_arg *arg){
|
|
if( arg ) *arg = cmpp_arg_empty;
|
|
}
|
|
|
|
/** Resets li's list for re-use but does not free it. Returns li. */
|
|
static CmppArgList * CmppArgList_reuse(CmppArgList *li){
|
|
for(cmpp_size_t n = li->nAlloc; n; ){
|
|
cmpp_arg_reuse( &li->list[--n] );
|
|
assert( !li->list[n].next );
|
|
}
|
|
li->n = 0;
|
|
return li;
|
|
}
|
|
|
|
/** Free all memory owned by li but does not free li. */
|
|
void CmppArgList_cleanup(CmppArgList *li){
|
|
const CmppArgList CmppArgList_empty = CmppArgList_empty_m;
|
|
while( li->nAlloc ){
|
|
cmpp_arg_cleanup( &li->list[--li->nAlloc] );
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = CmppArgList_empty;
|
|
}
|
|
|
|
/** Returns the most-recently-appended arg of li back to li's
|
|
free-list. */
|
|
static void CmppArgList_unappend(CmppArgList *li){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
cmpp_arg_reuse( &li->list[--li->n] );
|
|
}
|
|
}
|
|
|
|
cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){
|
|
cmpp_arg * p = 0;
|
|
assert( li->list ? li->nAlloc : 0==li->nAlloc );
|
|
if( 0==ppCode
|
|
&& 0==CmppArgList_reserve(pp, li,
|
|
cmpp__li_reserve1_size(li,10)) ){
|
|
p = &li->list[li->n++];
|
|
cmpp_arg_reuse( p );
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void cmpp_args_pimpl_cleanup(cmpp_args_pimpl *p){
|
|
assert( !p->nextFree );
|
|
cmpp_b_clear(&p->argOut);
|
|
CmppArgList_cleanup(&p->argli);
|
|
*p = cmpp_args_pimpl_empty;
|
|
}
|
|
|
|
static void cmpp_args_pimpl_reuse(cmpp_args_pimpl *p){
|
|
assert( !p->nextFree );
|
|
cmpp_b_reuse(&p->argOut);
|
|
CmppArgList_reuse(&p->argli);
|
|
assert( !p->argOut.n );
|
|
assert( !p->argli.n );
|
|
}
|
|
|
|
static void cmpp_args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){
|
|
if( p ){
|
|
assert( p->pp );
|
|
cmpp__pi(pp);
|
|
assert( !p->nextFree );
|
|
cmpp_args_pimpl_reuse(p);
|
|
p->nextFree = pi->recycler.argPimpl;
|
|
pi->recycler.argPimpl = p;
|
|
}
|
|
}
|
|
|
|
static cmpp_args_pimpl * cmpp_args_pimpl_borrow(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
cmpp_args_pimpl * p = 0;
|
|
if( pi->recycler.argPimpl ){
|
|
p = pi->recycler.argPimpl;
|
|
pi->recycler.argPimpl = p->nextFree;
|
|
p->nextFree = 0;
|
|
p->pp = pp;
|
|
assert( !p->argOut.n && "Buffer was used when not borrowed" );
|
|
}else{
|
|
p = cmpp__malloc(pp, sizeof(*p));
|
|
if( 0==cmpp_check_oom(pp, p) ) {
|
|
*p = cmpp_args_pimpl_empty;
|
|
p->pp = pp;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){
|
|
if( a ){
|
|
if( a->pimpl ){
|
|
cmpp * const pp = a->pimpl->pp;
|
|
assert( pp );
|
|
if( pp ){
|
|
cmpp_args_pimpl_return(pp, a->pimpl);
|
|
}else{
|
|
cmpp_args_pimpl_cleanup(a->pimpl);
|
|
cmpp_mfree(a->pimpl);
|
|
}
|
|
}
|
|
*a = cmpp_args_empty;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a){
|
|
cmpp_args_pimpl * const p = a->pimpl;
|
|
if( p ) cmpp_args_pimpl_reuse(p);
|
|
*a = cmpp_args_empty;
|
|
a->pimpl = p;
|
|
}
|
|
|
|
int cmpp_args__init(cmpp * pp, cmpp_args * a){
|
|
if( 0==ppCode ){
|
|
if( a->pimpl ){
|
|
assert( a->pimpl->pp == pp );
|
|
cmpp_args_reuse(a);
|
|
assert(! a->pimpl->argOut.n );
|
|
assert( a->pimpl->pp == pp );
|
|
}else{
|
|
a->pimpl = cmpp_args_pimpl_borrow(pp);
|
|
assert( !a->pimpl || a->pimpl->pp==pp );
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
Declare cmpp_argOp_f_NAME().
|
|
*/
|
|
#define cmpp_argOp_decl(NAME) \
|
|
static void cmpp_argOp_f_ ## NAME (cmpp_dx *dx, \
|
|
cmpp_argOp const *op, \
|
|
cmpp_arg const *vLhs, \
|
|
cmpp_arg const **pvRhs, \
|
|
int *pResult)
|
|
cmpp_argOp_decl(compare);
|
|
|
|
#if 0
|
|
cmpp_argOp_decl(logical1);
|
|
cmpp_argOp_decl(logical2);
|
|
cmpp_argOp_decl(defined);
|
|
#endif
|
|
|
|
static const struct {
|
|
const cmpp_argOp opAnd;
|
|
const cmpp_argOp opOr;
|
|
const cmpp_argOp opGlob;
|
|
const cmpp_argOp opNotGlob;
|
|
const cmpp_argOp opNot;
|
|
const cmpp_argOp opDefined;
|
|
#define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge)
|
|
#define E(NAME) const cmpp_argOp op ## NAME;
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
} cmpp_argOps = {
|
|
.opAnd = {
|
|
.ttype = cmpp_TT_OpAnd,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_logical2
|
|
},
|
|
.opOr = {
|
|
.ttype = cmpp_TT_OpOr,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_logical2
|
|
},
|
|
.opGlob = {
|
|
.ttype = cmpp_TT_OpGlob,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_glob
|
|
},
|
|
.opNotGlob = {
|
|
.ttype = cmpp_TT_OpNotGlob,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_glob
|
|
},
|
|
.opNot = {
|
|
.ttype = cmpp_TT_OpNot,
|
|
.arity = 1,
|
|
.assoc = 1,
|
|
.xCall = 0//cmpp_argOp_f_logical1
|
|
},
|
|
.opDefined = {
|
|
.ttype = cmpp_TT_OpDefined,
|
|
.arity = 1,
|
|
.assoc = 1,
|
|
.xCall = 0//cmpp_argOp_f_defined
|
|
},
|
|
/* Comparison ops... */
|
|
#define E(NAME) .op ## NAME = { \
|
|
.ttype = cmpp_TT_Op ## NAME, .arity = 2, .assoc = 0, \
|
|
.xCall = cmpp_argOp_f_compare },
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
};
|
|
|
|
cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt tt){
|
|
switch(tt){
|
|
case cmpp_TT_OpAnd: return &cmpp_argOps.opAnd;
|
|
case cmpp_TT_OpOr: return &cmpp_argOps.opOr;
|
|
case cmpp_TT_OpGlob: return &cmpp_argOps.opGlob;
|
|
case cmpp_TT_OpNot: return &cmpp_argOps.opNot;
|
|
case cmpp_TT_OpDefined: return &cmpp_argOps.opDefined;
|
|
#define E(NAME) case cmpp_TT_Op ## NAME: return &cmpp_argOps.op ## NAME;
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
default: return NULL;
|
|
}
|
|
}
|
|
#define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype)
|
|
|
|
#if 0
|
|
cmpp_argOp_decl(logical1){
|
|
assert( cmpp_TT_OpNot==op->ttype );
|
|
assert( !vRhs );
|
|
assert( vLhs );
|
|
if( 0==cmpp__arg_toBool(dx, vLhs, pResult) ){
|
|
*pResult = !*pResult;
|
|
}
|
|
}
|
|
|
|
cmpp_argOp_decl(logical2){
|
|
assert( vRhs );
|
|
assert( vLhs );
|
|
int vL = 0;
|
|
int vR = 0;
|
|
if( 0==cmpp__arg_toBool(dx, vLhs, &vL)
|
|
&& 0==cmpp__arg_toBool(dx, vRhs, &vR) ){
|
|
switch( op->ttype ){
|
|
case cmpp_TT_OpAnd: *pResult = vL && vR; break;
|
|
case cmpp_TT_OpOr: *pResult = vL || vR; break;
|
|
default:
|
|
cmpp__fatal("Cannot happen: illegal op mapping");
|
|
}
|
|
}
|
|
}
|
|
|
|
cmpp_argOp_decl(defined){
|
|
assert( cmpp_TT_OpDefined==op->ttype );
|
|
assert( !vRhs );
|
|
assert( vLhs );
|
|
if( cmpp_TT_Word==vLhs->ttype ){
|
|
*pResult = cmpp_has(pp, (char const *)vLhs->z, vLhs->n);
|
|
if( !*pResult && vLhs->n>1 && '#'==vLhs->z[0] ){
|
|
*pResult = !!cmpp__d_search3(pp, vLhs->z+1,
|
|
cmpp__d_search3_F_NO_DLL);
|
|
}
|
|
}else{
|
|
cmpp__err(pp, CMPP_RC_TYPE, "Invalid token type %s for %s",
|
|
cmpp__tt_cstr(vLhs->ttype, true),
|
|
cmpp__tt_cstr(op->ttype, false));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
static cmpp_argOp const * cmpp_argOp_isCompare(cmpp_tt tt){
|
|
cmpp_argOp const * const p = cmpp_argOp_for_tt(tt);
|
|
switch( p ? p->ttype : cmpp_TT_None ){
|
|
#define E(NAME) case cmpp_TT_Op ## NAME: return p;
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
return p;
|
|
case cmpp_TT_None:
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
An internal helper for cmpp_argOp_...(). It binds some value of
|
|
*paArg to column bindNdx of query q and sets *paArg to the next
|
|
argument to be consumed. This function expects that q is set up to
|
|
do the right thing when *paArg is a Word-type value (see
|
|
cmpp_argOp_f_compare()).
|
|
*/
|
|
static void cmpp_argOp__cmp_bind(cmpp_dx * const dx,
|
|
sqlite3_stmt * const q,
|
|
int bindNdx,
|
|
cmpp_arg const ** paArg){
|
|
cmpp_arg const * const arg = *paArg;
|
|
assert(arg);
|
|
switch( dxppCode ? 0 : arg->ttype ){
|
|
case 0: break;
|
|
case cmpp_TT_Word:
|
|
/* In this case, q is supposed to be set up to use
|
|
CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word
|
|
and performs the expansion to its value in the query. */
|
|
cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, arg->n);
|
|
*paArg = arg->next;
|
|
break;
|
|
case cmpp_TT_StringAt:
|
|
case cmpp_TT_String:
|
|
case cmpp_TT_Int:{
|
|
cmpp__bind_arg(dx, q, bindNdx, arg);
|
|
*paArg = arg->next;
|
|
break;
|
|
}
|
|
case cmpp_TT_OpNot:
|
|
case cmpp_TT_OpDefined:
|
|
case cmpp_TT_GroupParen:{
|
|
int rv = 0;
|
|
if( 0==cmpp__arg_toBool(dx, arg, &rv, paArg) ){
|
|
cmpp__bind_int(dx->pp, q, bindNdx, rv);
|
|
}
|
|
*paArg = arg->next;
|
|
break;
|
|
}
|
|
/* TODO? cmpp_TT_GroupParen */
|
|
default:
|
|
cmpp_dx_err_set(dx, CMPP_RC_TYPE,
|
|
"Invalid argument type (%s) for the comparison "
|
|
"queries: %s",
|
|
cmpp_tt_cstr(arg->ttype), arg->z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Internal helper for cmp_argOp_...().
|
|
|
|
Expects q to be a query with an integer in result column 0. This
|
|
steps/resets the query and applies the given comparison operator's
|
|
logic to column 0's value, placing the result of the operator in
|
|
*pResult.
|
|
|
|
If q has no result row, a default value of 0 is assumed.
|
|
*/
|
|
static void cmpp_argOp__cmp_apply(cmpp * const pp,
|
|
cmpp_argOp const * const op,
|
|
sqlite3_stmt * const q,
|
|
int * const pResult){
|
|
if( 0==ppCode ){
|
|
int rc = cmpp__step(pp, q, false);
|
|
assert( SQLITE_ROW==rc || ppCode );
|
|
if( SQLITE_ROW==rc ){
|
|
rc = sqlite3_column_int(q, 0);
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
switch( op->ttype ){
|
|
case 0: break;
|
|
case cmpp_TT_OpEq: *pResult = 0==rc; break;
|
|
case cmpp_TT_OpNeq: *pResult = 0!=rc; break;
|
|
case cmpp_TT_OpLt: *pResult = rc<0; break;
|
|
case cmpp_TT_OpLe: *pResult = rc<=0; break;
|
|
case cmpp_TT_OpGt: *pResult = rc>0; break;
|
|
case cmpp_TT_OpGe: *pResult = rc>=0; break;
|
|
default:
|
|
cmpp__fatal("Cannot happen: invalid arg mapping");
|
|
}
|
|
}
|
|
cmpp__stmt_reset(q);
|
|
}
|
|
|
|
/**
|
|
Applies *paRhs as the RHS of an integer binary operator, the LHS of
|
|
which is the lhs argument. The result is put in *pResult. On
|
|
success *paRhs is set to the next argument for the expression to
|
|
parse.
|
|
*/
|
|
static void cmpp_argOp_applyTo(cmpp_dx *dx,
|
|
cmpp_argOp const * const op,
|
|
int lhs,
|
|
cmpp_arg const ** paRhs,
|
|
int * pResult){
|
|
sqlite3_stmt * q = 0;
|
|
cmpp_arg const * aRhs = *paRhs;
|
|
assert(aRhs);
|
|
q = cmpp_TT_Word==aRhs->ttype
|
|
? cmpp__stmt(dx->pp, CmppStmt_cmpVD, false)
|
|
: cmpp__stmt(dx->pp, CmppStmt_cmpVV, false);
|
|
if( q ){
|
|
char numbuf[32];
|
|
int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs);
|
|
cmpp__bind_textn(dx->pp, q, 1, ustr_c(numbuf), nNum);
|
|
cmpp_argOp__cmp_bind(dx, q, 2, paRhs);
|
|
cmpp_argOp__cmp_apply(dx->pp, op, q, pResult);
|
|
}
|
|
}
|
|
|
|
cmpp_argOp_decl(compare){
|
|
cmpp_arg const * const vRhs = *pvRhs;
|
|
sqlite3_stmt * q = 0;
|
|
/* Select which query to use, depending on whether each
|
|
of the LHS/RHS are Word tokens. For Word tokens
|
|
the corresponding query columns get bound to
|
|
a subquery which resolves the word. Non-word
|
|
tokens get bound as-is. */
|
|
if( cmpp_TT_Word==vLhs->ttype ){
|
|
q = cmpp_TT_Word==vRhs->ttype
|
|
? cmpp__stmt(dx->pp, CmppStmt_cmpDD, false)
|
|
: cmpp__stmt(dx->pp, CmppStmt_cmpDV, false);
|
|
if(0){
|
|
g_warn("\nvLhs=%s %s\nvRhs=%s %s\n",
|
|
cmpp_tt_cstr(vLhs->ttype), vLhs->z,
|
|
cmpp_tt_cstr(vRhs->ttype), vRhs->z);
|
|
}
|
|
}else if( cmpp_TT_Word==vRhs->ttype ){
|
|
q = cmpp__stmt(dx->pp, CmppStmt_cmpVD, false);
|
|
}else{
|
|
q = cmpp__stmt(dx->pp, CmppStmt_cmpVV, false);
|
|
}
|
|
if( q ){
|
|
//cmpp__bind_textn(pp, q, 1, vLhs->z, vLhs->n);
|
|
cmpp_argOp__cmp_bind(dx, q, 1, &vLhs);
|
|
cmpp_argOp__cmp_bind(dx, q, 2, pvRhs);
|
|
cmpp_argOp__cmp_apply(dx->pp, op, q, pResult);
|
|
}
|
|
}
|
|
|
|
#undef cmpp_argOp_decl
|
|
|
|
#if 0
|
|
static inline int cmpp_dxt_isBinOp(cmpp_tt tt){
|
|
cmpp_argOp const * const a = cmpp_argOp_for_tt(tt);
|
|
return a ? 2==a->arity : 0;
|
|
}
|
|
|
|
static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){
|
|
return tt==cmpp_TT_OpNot || cmpp_TT_OpDefined;
|
|
}
|
|
|
|
static inline int cmpp_dxt_isGroup(cmpp_tt tt){
|
|
return tt==cmpp_TT_GroupParen || tt==cmpp_TT_GroupBrace || cmpp_TT_GroupSquiggly;
|
|
}
|
|
#endif
|
|
|
|
int cmpp__arg_evalSubToInt(cmpp_dx *dx,
|
|
cmpp_arg const *arg,
|
|
int * pResult){
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(dx, &sub, pResult);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
return dxppCode;
|
|
}
|
|
|
|
int cmpp__args_evalToInt(cmpp_dx * const dx,
|
|
cmpp_args const *pArgs,
|
|
int * pResult){
|
|
if( dxppCode ) return dxppCode;
|
|
|
|
cmpp_arg const * pNext = 0;
|
|
cmpp_arg const * pPrev = 0;
|
|
int result = *pResult;
|
|
cmpp_b osL = cmpp_b_empty;
|
|
cmpp_b osR = cmpp_b_empty;
|
|
static int level = 0;
|
|
++level;
|
|
|
|
#define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__)
|
|
|
|
//lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z);
|
|
for( cmpp_arg const *arg = pArgs->arg0;
|
|
arg && 0==dxppCode;
|
|
pPrev = arg, arg = pNext ){
|
|
pNext = arg->next;
|
|
if( cmpp_TT_Noop==arg->ttype ){
|
|
arg = pPrev /* help the following arg to DTRT */;
|
|
continue;
|
|
}
|
|
cmpp_argOp const * const thisOp = argOp(arg);
|
|
cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0;
|
|
if( 0 ){
|
|
lout("arg: %s @%p %s\n",
|
|
cmpp__tt_cstr(arg->ttype, true), arg, arg->z);
|
|
if(1){
|
|
if( pPrev ) lout(" prev arg: %s %s\n",
|
|
cmpp__tt_cstr(pPrev->ttype, true), pPrev->z);
|
|
if( pNext ) lout(" next arg: %s %s\n",
|
|
cmpp__tt_cstr(pNext->ttype, true), pNext->z);
|
|
}
|
|
}
|
|
if( thisOp ){ /* Basic validation */
|
|
if( !pNext ){
|
|
dxserr("Missing '%s' RHS.",
|
|
cmpp__tt_cstr(thisOp->ttype, false));
|
|
break;
|
|
}else if( !pPrev && 2==thisOp->arity ){
|
|
dxserr("Missing %s LHS.",
|
|
cmpp__tt_cstr(thisOp->ttype, false));
|
|
break;
|
|
}
|
|
if( nextOp && nextOp->arity>1 ){
|
|
dxserr("Invalid '%s' RHS: %s", arg->z, pNext->z);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch( arg->ttype ){
|
|
|
|
case cmpp_TT_OpNot:
|
|
case cmpp_TT_OpDefined:
|
|
if( pPrev && !argOp(pPrev) ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_CANNOT_HAPPEN,
|
|
"We expected to have consumed '%s' by "
|
|
"this point.",
|
|
pPrev->z);
|
|
}else{
|
|
cmpp__arg_toBool(dx, arg, &result, &pNext);
|
|
}
|
|
break;
|
|
|
|
case cmpp_TT_OpAnd:
|
|
case cmpp_TT_OpOr:{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
/* Reminder to self: we can't add short-circuiting of the RHS
|
|
right now because the handling of chained unary ops on the
|
|
RHS is handled via cmpp__arg_toBool(). */
|
|
int rv = 0;
|
|
if( 0==cmpp__arg_toBool(dx, pNext, &rv, &pNext) ){
|
|
if( cmpp_TT_OpAnd==arg->ttype ) result = result && rv;
|
|
else result = result || rv;
|
|
}
|
|
//g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0);
|
|
break;
|
|
}
|
|
|
|
case cmpp_TT_OpNotGlob:
|
|
case cmpp_TT_OpGlob:{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
assert( pNext!=arg );
|
|
assert( pPrev!=arg );
|
|
if( cmpp_arg_to_b(dx, pNext, &osL, 0) ){
|
|
break;
|
|
}
|
|
unsigned char const * const zGlob = osL.z;
|
|
if( 0==cmpp_arg_to_b(dx, pPrev, &osR, 0) ){
|
|
if( 0 ){
|
|
g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z);
|
|
}
|
|
result = 0==sqlite3_strglob((char const *)zGlob,
|
|
(char const *)osR.z);
|
|
if( cmpp_TT_OpNotGlob==arg->ttype ){
|
|
result = !result;
|
|
}
|
|
//g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result);
|
|
}
|
|
pNext = pNext->next;
|
|
break;
|
|
}
|
|
|
|
#define E(NAME) case cmpp_TT_Op ## NAME:
|
|
cmpp_argOps_cmp_map(E) {
|
|
cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0;
|
|
if( prevOp ){
|
|
/* Chained operators */
|
|
cmpp_argOp_applyTo(dx, thisOp, result, &pNext, &result);
|
|
}else{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
assert( thisOp );
|
|
assert( thisOp->xCall );
|
|
thisOp->xCall(dx, thisOp, pPrev, &pNext, &result);
|
|
}
|
|
break;
|
|
}
|
|
#undef E
|
|
|
|
#define checkConsecutiveNonOps \
|
|
if( pPrev && !argOp(pPrev) ){ \
|
|
dxserr("Illegal consecutive non-operators: %s %s", \
|
|
pPrev->z, arg->z); \
|
|
break; \
|
|
}(void)0
|
|
|
|
case cmpp_TT_Int:
|
|
case cmpp_TT_String:
|
|
checkConsecutiveNonOps;
|
|
if( !cmpp__is_int(arg->z, arg->n, &result) ){
|
|
/* This is mostly for and/or ops. glob will reach back and
|
|
grab arg->z. */
|
|
result = 0;
|
|
}
|
|
break;
|
|
case cmpp_TT_Word:
|
|
checkConsecutiveNonOps;
|
|
cmpp__get_int(dx->pp, arg->z, arg->n, &result);
|
|
break;
|
|
case cmpp_TT_GroupParen:{
|
|
checkConsecutiveNonOps;
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(dx, &sub, &result);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
case cmpp_TT_GroupBrace:{
|
|
checkConsecutiveNonOps;
|
|
cmpp_b b = cmpp_b_empty;
|
|
if( 0==cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0) ){
|
|
cmpp__is_int(b.z, b.n, &result);
|
|
}
|
|
cmpp_b_clear(&b);
|
|
break;
|
|
}
|
|
#undef checkConsecutiveNonOps
|
|
default:
|
|
assert( arg->z );
|
|
dxserr("Illegal expression token %s: %s",
|
|
cmpp__tt_cstr(arg->ttype, true), arg->z);
|
|
}/*switch(arg->ttype)*/
|
|
}/* foreach arg */
|
|
if( 0 ){
|
|
lout("END %s() result=%d\n", __func__, result);
|
|
}
|
|
--level;
|
|
if( !dxppCode ){
|
|
*pResult = result;
|
|
}
|
|
cmpp_b_clear(&osL);
|
|
cmpp_b_clear(&osR);
|
|
return dxppCode;
|
|
#undef lout
|
|
}
|
|
|
|
#undef argOp
|
|
#undef cmpp_argOp_decl
|
|
|
|
static inline cmpp_tt cmpp_dxt_is_group(cmpp_tt ttype){
|
|
switch(ttype){
|
|
case cmpp_TT_GroupParen:
|
|
case cmpp_TT_GroupBrace:
|
|
case cmpp_TT_GroupSquiggly:
|
|
return ttype;
|
|
default:
|
|
return cmpp_TT_None;
|
|
}
|
|
}
|
|
|
|
int cmpp_args_parse(cmpp_dx * const dx,
|
|
cmpp_args * const pArgs,
|
|
unsigned char const * const zInBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_flag32_t flags){
|
|
assert( zInBegin );
|
|
unsigned char const * const zInEnd =
|
|
zInBegin + cmpp__strlenu(zInBegin, nIn);
|
|
|
|
if( cmpp_args__init(dx->pp, pArgs) ) return dxppCode;
|
|
if( 0 ){
|
|
g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin),
|
|
zInBegin);
|
|
}
|
|
unsigned char const * zPos = zInBegin;
|
|
cmpp_size_t const nBuffer =
|
|
/* Buffer size for our copy of the args. We need to know the
|
|
size before we start so that we can have each arg reliably
|
|
point back into this without it being reallocated during
|
|
parsing. */
|
|
(cmpp_size_t)(zInEnd - zInBegin)
|
|
/* Plus we need one final NUL and one NUL byte per argument, but
|
|
we don't yet know how many arguments we will have, so let's
|
|
estimate... */
|
|
+ ((cmpp_size_t)(zInEnd - zInBegin))/3
|
|
+ 5/*fudge room*/;
|
|
cmpp_b * const buffer = &pArgs->pimpl->argOut;
|
|
assert( !buffer->n );
|
|
if( cmpp_b_reserve3(dx->pp, buffer, nBuffer) ){
|
|
return dxppCode;
|
|
}
|
|
unsigned char * zOut = buffer->z;
|
|
unsigned char const * const zOutEnd = zOut + buffer->nAlloc - 1;
|
|
cmpp_arg * prevArg = 0;
|
|
#if !defined(NDEBUG)
|
|
unsigned char const * const zReallocCheck = buffer->z;
|
|
#endif
|
|
|
|
if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin),
|
|
zInBegin);
|
|
pArgs->arg0 = NULL;
|
|
pArgs->argc = 0;
|
|
for( int i = 0; zPos<zInEnd; ++i){
|
|
//g_stderr("i=%d prevArg=%p\n",i, prevArg);
|
|
cmpp_arg * const arg =
|
|
CmppArgList_append(dx->pp, &pArgs->pimpl->argli);
|
|
if( !arg ) return dxppCode;
|
|
assert( pArgs->pimpl->argli.n );
|
|
if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos);
|
|
if( cmpp_arg_parse(dx, arg, &zPos, zInEnd, &zOut, zOutEnd) ){
|
|
if( 0 ) g_warn("zPos=<<%.*s>>", (int)(zInEnd-zPos), zPos);
|
|
break;
|
|
}
|
|
if( 0 ){
|
|
g_warn("#%d zPos=<<%.*s>>", i, (int)(zInEnd-zPos), zPos);
|
|
g_warn("#%d arg n=%u z=<<%.*s>> %s", i, (int)arg->n, (int)arg->n, arg->z, arg->z);
|
|
}
|
|
assert( zPos<=zInEnd );
|
|
if( 0 ){
|
|
g_stderr("ttype=%d %s n=%u z=%.*s\n", arg->ttype,
|
|
cmpp__tt_cstr(arg->ttype, true),
|
|
(unsigned)arg->n, (int)arg->n, arg->z);
|
|
}
|
|
if( cmpp_TT_Eof==arg->ttype ){
|
|
CmppArgList_unappend(&pArgs->pimpl->argli);
|
|
break;
|
|
}
|
|
switch( 0==(flags & cmpp_args_F_NO_PARENS)
|
|
? cmpp_dxt_is_group( arg->ttype )
|
|
: 0 ){
|
|
case cmpp_TT_GroupParen:{
|
|
/* Sub-expression. We tokenize it here just to ensure that we
|
|
can, so we can fail earlier rather than later. This is why
|
|
we need a recycler for the cmpp_args buffer memory. */
|
|
cmpp_args sub = cmpp_args_empty;
|
|
cmpp_args_parse(dx, &sub, arg->z, arg->n, flags);
|
|
//g_stderr("Parsed sub-expr: %s\n", sub.buffer.z);
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
case cmpp_TT_GroupBrace:
|
|
case cmpp_TT_GroupSquiggly:
|
|
default: break;
|
|
}
|
|
if( dxppCode ) break;
|
|
if( prevArg ){
|
|
assert( !prevArg->next );
|
|
prevArg->next = arg;
|
|
}
|
|
prevArg = arg;
|
|
}/*foreach input char*/
|
|
//g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(dxppCode), pArgs->args.n);
|
|
if( 0==dxppCode ){
|
|
pArgs->argc = pArgs->pimpl->argli.n;
|
|
assert( !pArgs->arg0 );
|
|
if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list;
|
|
if( zOut<zInEnd ) *zOut = 0;
|
|
if( 0 ){
|
|
for( cmpp_arg const * a = pArgs->arg0; a; a = a->next ){
|
|
g_stderr(" got: %s %.*s\n", cmpp__tt_cstr(a->ttype, true),
|
|
a->n, a->z);
|
|
}
|
|
}
|
|
}
|
|
assert(zReallocCheck==buffer->z
|
|
&& "Else buffer was reallocated, invalidating argN->z");
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0,
|
|
cmpp_args * const dest){
|
|
if( cmpp_args__init(pp, dest) || !a0 ) return ppCode;
|
|
cmpp_b * const ob = &dest->pimpl->argOut;
|
|
CmppArgList * const argli = &dest->pimpl->argli;
|
|
unsigned int i = 0;
|
|
cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */;
|
|
|
|
assert( !ob->n );
|
|
assert( !dest->arg0 );
|
|
assert( !dest->argc );
|
|
assert( !argli->n );
|
|
|
|
/* Preallocate ob->z to fit a copy of a0's args. */
|
|
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
|
|
nReserve += a->n + 1/*NUL byte*/;
|
|
}
|
|
if( cmpp_b_reserve3(pp, ob, nReserve+1)
|
|
|| CmppArgList_reserve(pp, argli, i) ){
|
|
goto end;
|
|
}
|
|
assert( argli->nAlloc>=i );
|
|
i = 0;
|
|
#ifndef NDEBUG
|
|
unsigned char const * const zReallocCheck = ob->z;
|
|
#endif
|
|
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
|
|
cmpp_arg * const aNew = &argli->list[i];
|
|
aNew->n = a->n;
|
|
aNew->z = ob->z + ob->n;
|
|
aNew->ttype = a->ttype;
|
|
if( i ) argli->list[i-1].next = aNew;
|
|
assert( !a->z[a->n] && "Expecting a NUL byte there" );
|
|
cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/);
|
|
if( 0 ){
|
|
g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype),
|
|
(int)a->n, a->z, a->z);
|
|
}
|
|
assert( zReallocCheck==ob->z
|
|
&& "This cannot fail: ob->z was pre-allocated" );
|
|
}
|
|
dest->argc = i;
|
|
dest->arg0 = i ? &argli->list[0] : 0;
|
|
end:
|
|
if( ppCode ){
|
|
cmpp_args_reuse(dest);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){
|
|
return cmpp_args_clone(dx->pp, dx->args.arg0, pOut);
|
|
}
|
|
|
|
char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg){
|
|
char * z = 0;
|
|
if( 0==ppCode ){
|
|
z = sqlite3_mprintf("%s",arg->z);
|
|
cmpp_check_oom(pp, z);
|
|
}
|
|
return z;
|
|
}
|
|
|
|
static cmpp_tt cmpp_tt_forWord(unsigned char const *z, unsigned n,
|
|
cmpp_tt dflt){
|
|
static const struct {
|
|
#define E(NAME,STR) struct CmppSnippet NAME;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
} ttStr = {
|
|
#define E(NAME,STR) \
|
|
.NAME = {(unsigned char const *)STR,sizeof(STR)-1},
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
};
|
|
#define CASE(NAME) if( 0==memcmp(ttStr.NAME.z, z, n) ) return cmpp_TT_ ## NAME
|
|
switch( n ){
|
|
case 1:
|
|
CASE(OpEq);
|
|
CASE(Plus);
|
|
CASE(Minus);
|
|
break;
|
|
case 2:
|
|
CASE(OpOr);
|
|
CASE(ShiftL);
|
|
//CASE(ShiftR);
|
|
//CASE(ArrowL);
|
|
CASE(ArrowR);
|
|
CASE(OpNeq);
|
|
CASE(OpLt);
|
|
CASE(OpLe);
|
|
CASE(OpGt);
|
|
CASE(OpGe);
|
|
break;
|
|
case 3:
|
|
CASE(OpAnd);
|
|
CASE(OpNot);
|
|
CASE(ShiftL3);
|
|
break;
|
|
case 4:
|
|
CASE(OpGlob);
|
|
break;
|
|
case 7:
|
|
CASE(OpDefined);
|
|
break;
|
|
#undef CASE
|
|
}
|
|
#if 0
|
|
bool b = cmpp__is_int(z, n, NULL);
|
|
if( 1|| !b ){
|
|
g_warn("is_int(%s)=%d", z, b);
|
|
}
|
|
return b ? cmpp_TT_Int : dflt;
|
|
#else
|
|
return cmpp__is_int(z, n, NULL) ? cmpp_TT_Int : dflt;
|
|
#endif
|
|
}
|
|
|
|
int cmpp_arg_parse(cmpp_dx * const dx, cmpp_arg *pOut,
|
|
unsigned char const **pzIn,
|
|
unsigned char const *zInEnd,
|
|
unsigned char ** pzOut,
|
|
unsigned char const * zOutEnd){
|
|
unsigned char const * zi = *pzIn;
|
|
unsigned char * zo = *pzOut;
|
|
cmpp_tt ttype = cmpp_TT_None;
|
|
|
|
#if 0
|
|
// trying to tickle valgrind
|
|
for(unsigned char const *x = zi; x < zInEnd; ++x ){
|
|
assert(*x);
|
|
}
|
|
#endif
|
|
cmpp_arg_reuse( pOut );
|
|
cmpp_skip_snl( &zi, zInEnd );
|
|
if( zi>=zInEnd ){
|
|
*pzIn = zi;
|
|
pOut->ttype = cmpp_TT_Eof;
|
|
return 0;
|
|
}
|
|
#define out(CH) if(zo>=zOutEnd) goto notEnoughOut; *zo++ = CH
|
|
#define eot_break if( cmpp_TT_None!=ttype ){ keepGoing = 0; break; } (void)0
|
|
pOut->z = zo;
|
|
bool keepGoing = true;
|
|
for( ; keepGoing
|
|
&& 0==dxppCode
|
|
&& zi<zInEnd
|
|
&& zo<zOutEnd; ){
|
|
cmpp_tt ttOverride = cmpp_TT_None;
|
|
switch( (int)*zi ){
|
|
case 0: keepGoing = false; break;
|
|
case ' ': case '\t': case '\n': case '\r':
|
|
eot_break;
|
|
cmpp_skip_snl( &zi, zInEnd );
|
|
break;
|
|
case '-':
|
|
if ('>'==zi[1] ){
|
|
ttype = cmpp_TT_ArrowR;
|
|
out(*zi++);
|
|
out(*zi++);
|
|
keepGoing = false;
|
|
}else{
|
|
goto do_word;
|
|
}
|
|
break;
|
|
case '=':
|
|
eot_break; keepGoing = false; ttype = cmpp_TT_OpEq; out(*zi++); break;
|
|
#define opcmp(CH,TT,TTEQ,TTSHIFT,TTARROW) \
|
|
case CH: eot_break; keepGoing = false; ttype = TT; out(*zi++); \
|
|
if( zi<zInEnd && '='==*zi ){ out(*zi++); ttype = TTEQ; } \
|
|
else if( zi<zInEnd && CH==*zi ){ out(*zi++); ttype = TTSHIFT; } \
|
|
else if( (int)TTARROW && zi<zInEnd && '-'==*zi ){ out(*zi++); ttype = TTARROW; }
|
|
|
|
opcmp('>',cmpp_TT_OpGt,cmpp_TT_OpGe,cmpp_TT_ShiftR,0) break;
|
|
opcmp('<',cmpp_TT_OpLt,cmpp_TT_OpLe,cmpp_TT_ShiftL,cmpp_TT_ArrowL)
|
|
if( cmpp_TT_ShiftL==ttype && zi<zInEnd && '<'==zi[0] ) {
|
|
out(*zi++);
|
|
ttype = cmpp_TT_ShiftL3;
|
|
}
|
|
break;
|
|
#undef opcmp
|
|
case '!':
|
|
eot_break;
|
|
keepGoing = false;
|
|
out(*zi++);
|
|
if( zi < zInEnd && '='==*zi ){
|
|
ttype = cmpp_TT_OpNeq;
|
|
out('=');
|
|
++zi;
|
|
}else{
|
|
while( zi < zInEnd && '!'==*zi ){
|
|
out(*zi++);
|
|
}
|
|
ttype = cmpp_TT_OpNot;
|
|
}
|
|
break;
|
|
case '@':
|
|
if( zi+2 >= zInEnd || ('"'!=zi[1] && '\''!=zi[1]) ){
|
|
goto do_word;
|
|
}
|
|
//if( cmpp__StringAtIsOk(dx->pp) ) break;
|
|
ttOverride = cmpp_TT_StringAt;
|
|
++zi /* consume opening '@' */;
|
|
//g_stderr("@-string override\n");
|
|
/* fall through */
|
|
case '"':
|
|
case '\'': {
|
|
/* Parse a string. We do not support backslash-escaping of any
|
|
sort here. Strings which themselves must contain quotes
|
|
should use the other quote type. */
|
|
keepGoing = false;
|
|
if( cmpp_TT_None!=ttype ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_SYNTAX,
|
|
"Misplaced quote character near: %.*s",
|
|
(int)(zi+1 - *pzIn), *pzIn);
|
|
break;
|
|
}
|
|
unsigned char const * zQuoteAt = zi;
|
|
if( cmpp__find_closing(dx->pp, &zQuoteAt, zInEnd) ){
|
|
break;
|
|
}
|
|
assert( zi+1 <= zQuoteAt );
|
|
assert( *zi == *zQuoteAt );
|
|
if( (zQuoteAt - zi - 2) >= (zOutEnd-zo) ){
|
|
goto notEnoughOut;
|
|
}
|
|
memcpy(zo, zi+1, zQuoteAt - zi - 1);
|
|
//g_warn("string=<<%.*s>>", (zQuoteAt-zi-1), zo);
|
|
zo += zQuoteAt - zi - 1;
|
|
zi = zQuoteAt + 1/* closing quote */;
|
|
ttype = (cmpp_TT_None==ttOverride ? cmpp_TT_String : ttOverride);
|
|
break;
|
|
}
|
|
case '[':
|
|
case '{':
|
|
case '(': {
|
|
/* Slurp these as a single token for later sub-parsing */
|
|
keepGoing = false;
|
|
unsigned char const * zAt = zi;
|
|
if( cmpp__find_closing(dx->pp, &zi, zInEnd) ) break;
|
|
/* Transform the output, eliding the open/close characters and
|
|
trimming spaces. We need to keep newlines intact, as the
|
|
content may be free-form, intended for other purposes, e.g.
|
|
the #pipe or #query directives. */
|
|
ttype = ('('==*zAt
|
|
? cmpp_TT_GroupParen
|
|
: ('['==*zAt
|
|
? cmpp_TT_GroupBrace
|
|
: cmpp_TT_GroupSquiggly));
|
|
++zAt /* consume opening brace */;
|
|
/* Trim leading and trailing space, but retain tabs and all but
|
|
the first and last newline. */
|
|
cmpp_skip_space(&zAt, zi);
|
|
if( zAt<zInEnd ){
|
|
if( '\n'==*zAt ) ++zAt;
|
|
else if(zAt+1<zInEnd && '\r'==*zAt && '\n'==zAt[1]) zAt+=2;
|
|
}
|
|
for( ; zAt<zi; ++zAt ){
|
|
out(*zAt);
|
|
}
|
|
if(0) g_warn("parse1: group n=%u [%.*s]\n",
|
|
(zi-zAt), (zi-zAt), zAt);
|
|
while( zo>*pzOut && ' '==zo[-1] ) *--zo = 0;
|
|
if( zo>*pzOut && '\n'==zo[-1] ){
|
|
*--zo = 0;
|
|
if( zo>*pzOut && '\r'==zo[-1] ){
|
|
*--zo = 0;
|
|
}
|
|
}
|
|
++zi /* consume the closer */;
|
|
break;
|
|
}
|
|
default:
|
|
; do_word:
|
|
out(*zi++);
|
|
ttype = cmpp_TT_Word;
|
|
break;
|
|
}
|
|
//g_stderr("kg=%d char=%d %c\n", keepGoing, (int)*zi, *zi);
|
|
}
|
|
if( dxppCode ){
|
|
/* problem already reported */
|
|
}else if( zo>=zOutEnd-1 ){
|
|
notEnoughOut:
|
|
cmpp_dx_err_set(dx, CMPP_RC_RANGE,
|
|
"Ran out of output space (%u bytes) while "
|
|
"parsing an argument", (unsigned)(zOutEnd-*pzOut));
|
|
}else{
|
|
pOut->n = (zo - *pzOut);
|
|
if( cmpp_TT_None==ttype ){
|
|
pOut->ttype = cmpp_TT_Eof;
|
|
}else if( cmpp_TT_Word==ttype && pOut->n ){
|
|
pOut->ttype = cmpp_tt_forWord(pOut->z, pOut->n, ttype);
|
|
}else{
|
|
pOut->ttype = ttype;
|
|
}
|
|
*zo++ = 0;
|
|
*pzIn = zi;
|
|
*pzOut = zo;
|
|
switch( pOut->ttype ){
|
|
case cmpp_TT_Int:
|
|
if( '+'==*pOut->z ){ /* strip leading + */
|
|
++pOut->z;
|
|
--pOut->n;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if(0){
|
|
g_stderr("parse1: %s n=%u <<%.*s>>",
|
|
cmpp__tt_cstr(pOut->ttype, true), pOut->n,
|
|
pOut->n, pOut->z);
|
|
}
|
|
}
|
|
#undef out
|
|
#undef eot_break
|
|
return dxppCode;
|
|
}
|
|
|
|
int cmpp__arg_toBool(cmpp_dx * const dx, cmpp_arg const *arg,
|
|
int * pResult, cmpp_arg const **pNext){
|
|
switch( dxppCode ? 0 : arg->ttype ){
|
|
case 0: break;
|
|
|
|
case cmpp_TT_Word:
|
|
*pNext = arg->next;
|
|
*pResult = cmpp__get_bool(dx->pp, arg->z, arg->n);
|
|
break;
|
|
|
|
case cmpp_TT_Int:
|
|
*pNext = arg->next;
|
|
cmpp__is_int(arg->z, arg->n, pResult)/*was already validated*/;
|
|
break;
|
|
|
|
case cmpp_TT_String:
|
|
case cmpp_TT_StringAt:{
|
|
unsigned char const * z = 0;
|
|
cmpp_size_t n = 0;
|
|
cmpp_b os = cmpp_b_empty;
|
|
if( 0==cmpp__arg_expand_ats(dx, &os, cmpp_atpol_CURRENT,
|
|
arg, cmpp_TT_StringAt, &z, &n) ){
|
|
*pNext = arg->next;
|
|
*pResult = n>0 && 0!=memcmp("0\0", z, 2);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
break;
|
|
}
|
|
|
|
case cmpp_TT_GroupParen:{
|
|
*pNext = arg->next;
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(dx, &sub, pResult);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
|
|
case cmpp_TT_OpDefined:
|
|
if( !arg->next ){
|
|
dxserr("Missing '%s' RHS.", arg->z);
|
|
}else if( cmpp_TT_Word!=arg->next->ttype ){
|
|
dxserr( "Invalid '%s' RHS: %s", arg->z, arg->next->z);
|
|
}else{
|
|
cmpp_arg const * aOperand = arg->next;
|
|
*pNext = aOperand->next;
|
|
if( aOperand->n>1
|
|
&& '#'==aOperand->z[0]
|
|
&& !!cmpp__d_search3(dx->pp, (char const*)aOperand->z+1,
|
|
cmpp__d_search3_F_NO_DLL) ){
|
|
*pResult = 1;
|
|
}else{
|
|
*pResult = cmpp_has(dx->pp, (char const *)aOperand->z,
|
|
aOperand->n);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case cmpp_TT_OpNot:{
|
|
assert( arg->next && "See cmpp_args__not_simplify()");
|
|
assert( cmpp_TT_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()");
|
|
if( 0==cmpp__arg_toBool(dx, arg->next, pResult, pNext) ){
|
|
*pResult = !*pResult;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
dxserr("Invalid token type %s for %s(): %s",
|
|
cmpp__tt_cstr(arg->ttype, true), __func__, arg->z);
|
|
break;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_arg_to_b)(cmpp_dx * const dx, cmpp_arg const *arg,
|
|
cmpp_b * ob, cmpp_flag32_t flags){
|
|
/**
|
|
Reminder to self: this function specifically does not do any
|
|
expression evaluation of its arguments. Please avoid the
|
|
temptation to make it do so. Unless it proves necessary. Or
|
|
useful. Even then, though, consider the implications deeply
|
|
before doing so.
|
|
*/
|
|
switch( dxppCode
|
|
? 0
|
|
: ((cmpp_arg_to_b_F_FORCE_STRING & flags)
|
|
? cmpp_TT_String : arg->ttype) ){
|
|
|
|
case 0:
|
|
break;
|
|
case cmpp_TT_Word:
|
|
if( 0==(flags & cmpp_arg_to_b_F_NO_DEFINES) ){
|
|
cmpp__get_b(dx->pp, arg->z, arg->n, ob, true);
|
|
break;
|
|
}
|
|
goto theDefault;
|
|
case cmpp_TT_StringAt:{
|
|
unsigned char const * z = 0;
|
|
cmpp_size_t n = 0;
|
|
if( 0 ){
|
|
g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z);
|
|
}
|
|
if( 0==cmpp__arg_expand_ats(dx, ob, cmpp_atpol_CURRENT, arg,
|
|
cmpp_TT_StringAt, &z, &n)
|
|
&& 0 ){
|
|
g_warn("expanded at [%.*s] [%s]", (int)n, z, z);
|
|
g_warn("ob->z [%.*s] [%s]", (int)ob->n, ob->z, ob->z);
|
|
}
|
|
break;
|
|
}
|
|
case cmpp_TT_GroupBrace:
|
|
if( !(cmpp_arg_to_b_F_NO_BRACE_CALL & flags)
|
|
&& (cmpp_arg_to_b_F_BRACE_CALL & flags) ){
|
|
cmpp_call_str(dx->pp, arg->z, arg->n, ob, 0);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default: {
|
|
theDefault: ;
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
oss.state = ob;//no: cmpp_b_reuse(ob); Append instead.
|
|
cmpp__out2(dx->pp, &oss, arg->z, arg->n);
|
|
break;
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
int cmpp__bind_arg(cmpp_dx * const dx, sqlite3_stmt * const q,
|
|
int bindNdx, cmpp_arg const * const arg){
|
|
|
|
if( 0 ){
|
|
g_warn("bind #%d %s <<%.*s>>", bindNdx,
|
|
cmpp__tt_cstr(arg->ttype, true),
|
|
(int)arg->n, arg->z);
|
|
}
|
|
switch( arg->ttype ){
|
|
default:
|
|
case cmpp_TT_Int:
|
|
case cmpp_TT_String:
|
|
cmpp__bind_textn(dx->pp, q, bindNdx, arg->z, (int)arg->n);
|
|
break;
|
|
|
|
case cmpp_TT_Word:
|
|
case cmpp_TT_StringAt:{
|
|
cmpp_b os = cmpp_b_empty;
|
|
if( 0==cmpp_arg_to_b(dx, arg, &os, 0) ){
|
|
if( 0 ){
|
|
g_warn("bind #%d <<%s>> => <<%.*s>>",
|
|
bindNdx, arg->z, (int)os.n, os.z);
|
|
}
|
|
cmpp__bind_textn(dx->pp, q, bindNdx, os.z, (int)os.n);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
break;
|
|
}
|
|
|
|
case cmpp_TT_GroupParen:{
|
|
cmpp_args sub = cmpp_args_empty;
|
|
int i = 0;
|
|
if( 0==cmpp_args_parse(dx, &sub, arg->z, arg->n, 0)
|
|
&& 0==cmpp__args_evalToInt(dx, &sub, &i) ){
|
|
/* See comment above about cmpp_TT_Int. */
|
|
cmpp__bind_int_text(dx->pp, q, bindNdx, i);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
|
|
case cmpp_TT_GroupBrace:{
|
|
cmpp_b b = cmpp_b_empty;
|
|
cmpp_call_str(dx->pp, arg->z, arg->n, &b, 0);
|
|
cmpp__bind_textn(dx->pp, q, bindNdx, b.z, b.n);
|
|
cmpp_b_clear(&b);
|
|
break;
|
|
}
|
|
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
/**
|
|
If a is in li->list, return its non-const pointer from li->list
|
|
(O(1)), else return NULL.
|
|
*/
|
|
static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){
|
|
if( li->nAlloc && a>=li->list && a<(li->list + li->nAlloc) ){
|
|
return li->list + (a - li->list);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
To be called only by cmpp_dx_args_parse() and only if the current
|
|
directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY.
|
|
|
|
Filter chains of "not" operators from pArgs, removing unnecessary
|
|
ones. Also collapse "not glob" into a single cmpp_TT_OpNotGlob argument.
|
|
Performs some basic validation as well to simplify downstream
|
|
operations. Returns p->err.code and is a no-op if that's set
|
|
before this is called.
|
|
*/
|
|
static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){
|
|
cmpp_arg * pPrev = 0;
|
|
cmpp_arg * pNext = 0;
|
|
CmppArgList * const ali = &pArgs->pimpl->argli;
|
|
pArgs->argc = 0;
|
|
for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL;
|
|
arg && !ppCode;
|
|
pPrev=arg, arg = pNext ){
|
|
pNext = CmppArgList_arg_nc(ali, arg->next);
|
|
assert( pNext || !arg->next );
|
|
if( cmpp_TT_OpNot==arg->ttype ){
|
|
if( !pNext ){
|
|
serr("Missing '%s' RHS", arg->z);
|
|
break;
|
|
}
|
|
cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype);
|
|
if( nop && nop->arity>1 && cmpp_TT_OpGlob!=nop->ttype ){
|
|
serr("Illegal '%s' RHS: binary '%s' operator",
|
|
arg->z, pNext->z);
|
|
break;
|
|
}
|
|
int bNeg = 1;
|
|
if( '!'==*arg->z ){
|
|
/* odd number of ! == negate */
|
|
bNeg = arg->n & 1;
|
|
}
|
|
while( pNext && cmpp_TT_OpNot==pNext->ttype ){
|
|
bNeg = !bNeg;
|
|
arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next);
|
|
}
|
|
if( pNext && cmpp_TT_OpGlob==pNext->ttype ){
|
|
/* Transform it to a cmpp_TT_OpNotGlob or cmpp_TT_OpGlob. */
|
|
assert( pNext->z > arg->z + arg->n );
|
|
arg->n = pNext->z + pNext->n - arg->z;
|
|
arg->next = pNext->next;
|
|
arg->ttype = bNeg
|
|
? cmpp_TT_OpNotGlob
|
|
: pNext->ttype;
|
|
++pArgs->argc;
|
|
}else if( pPrev ){
|
|
if( bNeg ){
|
|
++pArgs->argc;
|
|
}else{
|
|
/* Snip this node out. */
|
|
pPrev->next = pNext;
|
|
}
|
|
}else{
|
|
assert( 0==pArgs->argc );
|
|
++pArgs->argc;
|
|
if( !bNeg ){
|
|
arg->ttype = cmpp_TT_Noop;
|
|
}
|
|
}
|
|
/* Potential bug in waiting/fixme: by eliding all nots we are
|
|
** changing the behavior from forced coercion to bool to
|
|
** coercion to whatever the LHS wants. */
|
|
}else{
|
|
++pArgs->argc;
|
|
}
|
|
}
|
|
pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL;
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx,
|
|
cmpp_args *args){
|
|
if( !dxppCode
|
|
&& 0==cmpp_args_parse(dx, args, dx->args.z, dx->args.nz,
|
|
cmpp_args_F_NO_PARENS)
|
|
&& (cmpp_d_F_NOT_SIMPLIFY & dx->d->flags) ){
|
|
cmpp_args__not_simplify(dx->pp, args);
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
/* Helper for cmpp_kav_each() and friends. */
|
|
static
|
|
int cmpp__each_parse_args(cmpp_dx *dx,
|
|
cmpp_args *args,
|
|
unsigned char const *zBegin,
|
|
cmpp_ssize_t nz,
|
|
cmpp_flag32_t flags){
|
|
if( 0==cmpp_args_parse(dx, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){
|
|
if( !args->argc
|
|
&& (cmpp_kav_each_F_NOT_EMPTY & flags) ){
|
|
cmpp_err_set(dx->pp, CMPP_RC_RANGE,
|
|
"Empty list is not permitted here.");
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
/* Helper for cmpp_kav_each() and friends. */
|
|
static
|
|
int cmpp__each_paren_expr(cmpp_dx *dx, cmpp_arg const * arg,
|
|
unsigned char * pOut, size_t nOut){
|
|
cmpp_args sub = cmpp_args_empty;
|
|
int rc = cmpp_args_parse(dx, &sub, arg->z, arg->n, 0);
|
|
if( 0==rc ){
|
|
int d = 0;
|
|
rc = cmpp__args_evalToInt(dx, &sub, &d);
|
|
if( 0==rc ){
|
|
snprintf((char *)pOut, nOut, "%d", d);
|
|
}
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_kav_each)(cmpp_dx *dx,
|
|
unsigned char const *zBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_kav_each_f callback,
|
|
void *callbackState,
|
|
cmpp_flag32_t flags){
|
|
if( dxppCode ) return dxppCode;
|
|
/* Reminder to self: we cannot reuse internal buffers here because a
|
|
callback could recurse into this or otherwise use APIs which use
|
|
those same buffers. */
|
|
cmpp_b bKey = cmpp_b_empty;
|
|
cmpp_b bVal = cmpp_b_empty;
|
|
bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags);
|
|
cmpp_args args = cmpp_args_empty;
|
|
unsigned char exprBuf[32] = {0};
|
|
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
|
|
unsigned char const * const zEnd = zBegin + nz;
|
|
cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_to_b() flags*/;
|
|
assert( zBegin );
|
|
assert( zEnd );
|
|
assert( zEnd>=zBegin );
|
|
|
|
if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){
|
|
goto cleanup;
|
|
}else if( reqArrow && 0!=args.argc%3 ){
|
|
cmpp_err_set(dx->pp, CMPP_RC_RANGE,
|
|
"Expecting a list of 3 tokens per entry: "
|
|
"KEY -> VALUE");
|
|
}else if( !reqArrow && 0!=args.argc%2 ){
|
|
cmpp_err_set(dx->pp, CMPP_RC_RANGE,
|
|
"Expecting a list of 2 tokens per entry: "
|
|
"KEY VALUE");
|
|
}
|
|
if( cmpp_kav_each_F_CALL_KEY & flags ){
|
|
a2bK |= cmpp_arg_to_b_F_BRACE_CALL;
|
|
flags |= cmpp_kav_each_F_EXPAND_KEY;
|
|
}
|
|
if( cmpp_kav_each_F_CALL_VAL & flags ){
|
|
a2bV |= cmpp_arg_to_b_F_BRACE_CALL;
|
|
flags |= cmpp_kav_each_F_EXPAND_VAL;
|
|
}
|
|
cmpp_arg const * aNext = 0;
|
|
for( cmpp_arg const * aKey = args.arg0;
|
|
!dxppCode && aKey;
|
|
aKey = aNext ){
|
|
aNext = aKey->next;
|
|
cmpp_arg const * aVal = aKey->next;
|
|
if( !aVal ){
|
|
dxserr("Expecting %s after key '%s'.",
|
|
(reqArrow ? "->" : "a value"),
|
|
aKey->z);
|
|
break;
|
|
}
|
|
if( reqArrow ){
|
|
if( cmpp_TT_ArrowR!=aVal->ttype ){
|
|
dxserr("Expecting -> after key '%s'.", aKey->z);
|
|
break;
|
|
}
|
|
aVal = aVal->next;
|
|
if( !aVal ){
|
|
dxserr("Expecting a value after '%s' ->.", aKey->z);
|
|
break;
|
|
}
|
|
}
|
|
//g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z);
|
|
/* Expand the key/value parts if needed... */
|
|
unsigned char const *zKey;
|
|
unsigned char const *zVal;
|
|
cmpp_size_t nKey, nVal;
|
|
if( cmpp_kav_each_F_EXPAND_KEY & flags ){
|
|
if( cmpp_arg_to_b(dx, aKey, cmpp_b_reuse(&bKey),
|
|
a2bK) ){
|
|
break;
|
|
}
|
|
zKey = bKey.z;
|
|
nKey = bKey.n;
|
|
}else{
|
|
zKey = aKey->z;
|
|
nKey = aKey->n;
|
|
}
|
|
if( cmpp_TT_GroupParen==aVal->ttype
|
|
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
|
|
if( cmpp__each_paren_expr(dx, aVal, &exprBuf[0],
|
|
sizeof(exprBuf)-1) ){
|
|
break;
|
|
}
|
|
zVal = &exprBuf[0];
|
|
nVal = cmpp__strlenu(zVal, -1);
|
|
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
|
|
if( cmpp_arg_to_b(dx, aVal, cmpp_b_reuse(&bVal),
|
|
a2bV) ){
|
|
break;
|
|
}
|
|
zVal = bVal.z;
|
|
nVal = bVal.n;
|
|
}else{
|
|
zVal = aVal->z;
|
|
nVal = aVal->n;
|
|
}
|
|
aNext = aVal->next;
|
|
if( 0!=callback(dx, zKey, nKey, zVal, nVal, callbackState) ){
|
|
break;
|
|
}
|
|
}
|
|
cleanup:
|
|
cmpp_b_clear(&bKey);
|
|
cmpp_b_clear(&bVal);
|
|
cmpp_args_cleanup(&args);
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_str_each)(cmpp_dx *dx,
|
|
unsigned char const *zBegin,
|
|
cmpp_ssize_t nIn,
|
|
cmpp_kav_each_f callback, void *callbackState,
|
|
cmpp_flag32_t flags){
|
|
g_warn0("UNTESTED!");
|
|
if( dxppCode ) return dxppCode;
|
|
/* Reminder to self: we cannot reuse internal buffers here because a
|
|
callback could recurse into this or otherwise use APIs which use
|
|
those same buffers. */
|
|
cmpp_b ob = cmpp_b_empty;
|
|
cmpp_args args = cmpp_args_empty;
|
|
unsigned char exprBuf[32] = {0};
|
|
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
|
|
unsigned char const * const zEnd = zBegin + nz;
|
|
assert( zBegin );
|
|
assert( zEnd );
|
|
assert( zEnd>=zBegin );
|
|
|
|
if( cmpp__each_parse_args(dx, &args, zBegin, nz, flags) ){
|
|
goto cleanup;
|
|
}
|
|
cmpp_arg const * aNext = 0;
|
|
for( cmpp_arg const * arg = args.arg0;
|
|
!dxppCode && arg;
|
|
arg = aNext ){
|
|
aNext = arg->next;
|
|
//g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z);
|
|
/* Expand the key/value parts if needed... */
|
|
unsigned char const *zVal;
|
|
cmpp_size_t nVal;
|
|
if( cmpp_TT_GroupParen==arg->ttype
|
|
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
|
|
if( cmpp__each_paren_expr(dx, arg, &exprBuf[0],
|
|
sizeof(exprBuf)-1) ){
|
|
break;
|
|
}
|
|
zVal = &exprBuf[0];
|
|
nVal = cmpp__strlenu(zVal, -1);
|
|
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
|
|
if( cmpp_arg_to_b(dx, arg, cmpp_b_reuse(&ob), 0) ){
|
|
break;
|
|
}
|
|
zVal = ob.z;
|
|
nVal = ob.n;
|
|
}else{
|
|
zVal = arg->z;
|
|
nVal = arg->n;
|
|
}
|
|
if( 0!=callback(dx, arg->z, arg->n, zVal, nVal, callbackState) ){
|
|
break;
|
|
}
|
|
}
|
|
cleanup:
|
|
cmpp_b_clear(&ob);
|
|
cmpp_args_cleanup(&args);
|
|
return dxppCode;
|
|
}
|
|
|
|
/**
|
|
Returns true if z _might_ be a cmpp_TT_StringAt, else false. It may have
|
|
false positives but won't have false negatives.
|
|
|
|
This is only intended to be used on NUL-terminated strings, not a
|
|
pointer into a cmpp input source.
|
|
*/
|
|
static bool cmpp__might_be_atstring(unsigned char const *z){
|
|
char const * const x = strchr((char const *)z, '@');
|
|
return x && !!strchr(x+1, '@');
|
|
}
|
|
|
|
int cmpp__arg_expand_ats(cmpp_dx const * const dx,
|
|
cmpp_b * os,
|
|
cmpp_atpol_e atPolicy,
|
|
cmpp_arg const * const arg,
|
|
cmpp_tt thisTtype,
|
|
unsigned char const **pExp,
|
|
cmpp_size_t * nExp){
|
|
assert( os );
|
|
cmpp_b_reuse(os);
|
|
if( 0==dxppCode
|
|
&& (cmpp_TT_AnyType==thisTtype || thisTtype==arg->ttype)
|
|
&& cmpp__might_be_atstring(arg->z)
|
|
&& 0==cmpp__StringAtIsOk(dx->pp, atPolicy) ){
|
|
#if 0
|
|
if( !os->nAlloc ){
|
|
cmpp_b_reserve3(os, 128);
|
|
}
|
|
#endif
|
|
cmpp_outputer oos = cmpp_outputer_b;
|
|
oos.state = os;
|
|
assert( !os->n );
|
|
if( !cmpp_dx_out_expand(dx, &oos, arg->z, arg->n,
|
|
atPolicy ) ){
|
|
*pExp = os->z;
|
|
if( nExp ) *nExp = os->n;
|
|
if( 0 ){
|
|
g_warn("os->n=%u os->z=[%.*s]\n", os->n, (int)os->n,
|
|
os->z);
|
|
}
|
|
|
|
}
|
|
}else if( !dxppCode ){
|
|
*pExp = arg->z;
|
|
if( nExp ) *nExp = arg->n;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
bool cmpp__arg_wordIsPathOrFlag(
|
|
cmpp_arg const * const arg
|
|
){
|
|
return cmpp_TT_Word==arg->ttype
|
|
&& ('-'==(char)arg->z[0]
|
|
|| strchr((char*)arg->z, '.')
|
|
|| strchr((char*)arg->z, '-')
|
|
|| strchr((char*)arg->z, '/')
|
|
|| strchr((char*)arg->z, '\\'));
|
|
}
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the cmpp_popen() pieces.
|
|
*/
|
|
#if !defined(_POSIX_C_SOURCE)
|
|
# define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
|
|
#endif
|
|
#include <signal.h>
|
|
|
|
const cmpp_popen_t cmpp_popen_t_empty = cmpp_popen_t_empty_m;
|
|
|
|
#if CMPP_PLATFORM_IS_UNIX
|
|
#include <signal.h>
|
|
static int cmpp__err_errno(cmpp *pp, int errNo, char const *zContext){
|
|
return cmpp_err_set(pp, cmpp_errno_rc(errNo, CMPP_RC_ERROR),
|
|
"errno #%d: %s", errNo, zContext);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Uses fork()/exec() to run a command in a separate process and open
|
|
a two-way stream to it.
|
|
|
|
If azCmd is NULL then zCmd must contain the command to run and
|
|
any flags. It is passed as the 4th argument to
|
|
execl("/bin/sh", "/bin/sh", "-c", zCmd, NULL).
|
|
|
|
If azCmd is not NULL then it must be suitable for use as the 2nd
|
|
argument to execv(2). execv(X, azCmd) is used in this case, where
|
|
X is (zCmd ? zCmd : azCmd[0]).
|
|
|
|
Flags:
|
|
|
|
- cmpp_popen_F_DIRECT: if azCmd is NULL and flags has this bit set then
|
|
zCmd is instead passed to execl(zCmd, zCmd, NULL). That can only
|
|
work if zCmd is a single command without arguments.
|
|
cmpp_popen_F_DIRECT has no effect if azCmd is not NULL.
|
|
|
|
- cmpp_popen_F_PATH: tells it to use execlp() or execvp(), which
|
|
performs path lookup of its initial argument.
|
|
|
|
On success:
|
|
|
|
- po->fdFromChild is the child's stdout. Read from it to read from
|
|
the child.
|
|
|
|
- If po->fpToChild is not NULL then *po->fpToChild is set to the
|
|
child's stdin. Write to it to send the child stuff. Be sure to
|
|
flush() and/or close() it to keep it from hanging forever. If
|
|
po->fpToChild is NULL then the stdin of the child is closed.
|
|
|
|
- po->childPid will be set to the PID of the child process.
|
|
|
|
On error: you know the drill.
|
|
|
|
After calling this, the caller is obligated to pass po to
|
|
cmpp_pclose(). If the caller fcloses() *po->fpToChild then they
|
|
must set it to NULL so that passing it to cmpp_pclose() knows not
|
|
to close it.
|
|
|
|
Bugs: because the command is run via /bin/sh -c ... we cannot tell
|
|
if it's actually found. All we can tell is that /bin/sh ran.
|
|
|
|
Also: this doesn't capture stderr, so commands should redirect
|
|
stderr to stdout. Adding the child's stderr handle to cmpp_popen_t is
|
|
a potential TODO without a current use case.
|
|
*/
|
|
static
|
|
int cmpp__popen_impl(cmpp *pp, unsigned char const *zCmd,
|
|
char * const * azCmd, cmpp_flag32_t flags,
|
|
cmpp_popen_t *po){
|
|
#if !CMPP_PLATFORM_IS_UNIX
|
|
return cmpp__err(pp, CMPP_RC_UNSUPPORTED,
|
|
"Piping is not supported in this build.");
|
|
#else
|
|
if( ppCode ) return ppCode;
|
|
#define shut(P,N) close(P[N])
|
|
/** Attribution: this impl is derived from one found in
|
|
the Fossil SCM. */
|
|
int pin[2];
|
|
int pout[2];
|
|
|
|
po->fdFromChild = -1;
|
|
if( po->fpToChild ) *po->fpToChild = 0;
|
|
if( pipe(pin)<0 ){
|
|
return cmpp__err_errno(pp, errno, "pipe(in) failed");
|
|
}
|
|
if( pipe(pout)<0 ){
|
|
int const rc = cmpp__err_errno(pp, errno,
|
|
"pipe(out) failed");
|
|
shut(pin,0);
|
|
shut(pin,1);
|
|
return rc;
|
|
}
|
|
po->childPid = fork();
|
|
if( po->childPid<0 ){
|
|
int const rc = cmpp__err_errno(pp, errno, "fork() failed");
|
|
shut(pin,0);
|
|
shut(pin,1);
|
|
shut(pout,0);
|
|
shut(pout,1);
|
|
return rc;
|
|
}
|
|
signal(SIGPIPE,SIG_IGN);
|
|
if( po->childPid==0 ){
|
|
/* The child process. */
|
|
int fd;
|
|
close(0);
|
|
fd = dup(pout[0]);
|
|
if( fd!=0 ) {
|
|
cmpp__fatal("Error opening file descriptor 0.");
|
|
};
|
|
shut(pout,0);
|
|
shut(pout,1);
|
|
close(1);
|
|
fd = dup(pin[1]);
|
|
if(fd!=1) {
|
|
cmpp__fatal("Error opening file descriptor 1.");
|
|
};
|
|
shut(pin,0);
|
|
shut(pin,1);
|
|
if( azCmd ){
|
|
if( pp->pimpl->flags.doDebug>1 ){
|
|
for( int i = 0; azCmd[i]; ++i ){
|
|
g_warn("execv arg[%d]=%s", i, azCmd[i]);
|
|
}
|
|
}
|
|
int (*exc)(const char *, char *const []) =
|
|
(cmpp_popen_F_PATH & flags) ? execvp : execv;
|
|
exc(zCmd ? (char*)zCmd : azCmd[0], azCmd);
|
|
cmpp__fatal("execv() failed");
|
|
}else{
|
|
g_debug(pp,2,("zCmd=%s\n", zCmd));
|
|
int (*exc)(const char *, char const *, ...) =
|
|
(cmpp_popen_F_PATH & flags) ? execlp : execl;
|
|
if( cmpp_popen_F_DIRECT & flags ){
|
|
exc((char*)zCmd, (char*)zCmd, (char*)0);
|
|
}else{
|
|
exc("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0);
|
|
}
|
|
cmpp__fatal("execl() failed");
|
|
}
|
|
/* not reached */
|
|
}else{
|
|
/* The parent process. */
|
|
//cmpp_outputer_flush(&pp->pimpl->out.ch);
|
|
po->fdFromChild = pin[0];
|
|
shut(pin,1);
|
|
shut(pout,0);
|
|
if( po->fpToChild ){
|
|
*po->fpToChild = fdopen(pout[1], "w");
|
|
if( !*po->fpToChild ){
|
|
shut(pin,0);
|
|
shut(pout,1);
|
|
po->fdFromChild = -1;
|
|
cmpp__err_errno(pp, errno,
|
|
"Error opening child process's stdin "
|
|
"FILE handle from its descriptor.");
|
|
}
|
|
}else{
|
|
shut(pout,1);
|
|
}
|
|
return ppCode;
|
|
}
|
|
#undef shut
|
|
#endif
|
|
}
|
|
|
|
int cmpp_popen(cmpp *pp, unsigned char const *zCmd,
|
|
cmpp_flag32_t flags, cmpp_popen_t *po){
|
|
return cmpp__popen_impl(pp, zCmd, NULL, flags, po);
|
|
}
|
|
|
|
int cmpp_popenv(cmpp *pp, char * const * azCmd,
|
|
cmpp_flag32_t flags, cmpp_popen_t *po){
|
|
return cmpp__popen_impl(pp, NULL, azCmd, flags, po);
|
|
}
|
|
|
|
int cmpp_popen_args(cmpp_dx *dx, cmpp_args const * args,
|
|
cmpp_popen_t *po){
|
|
#if !CMPP_PLATFORM_IS_UNIX
|
|
return cmpp__popen_impl(dx->pp, NULL, 0, po) /* will fail */;
|
|
#else
|
|
if( dxppCode ) return dxppCode;
|
|
enum { MaxArgs = 128 };
|
|
char * argv[MaxArgs] = {0};
|
|
cmpp_size_t offsets[MaxArgs] = {0};
|
|
cmpp_b osAll = cmpp_b_empty;
|
|
cmpp_b os1 = cmpp_b_empty;
|
|
if( args->argc >= MaxArgs ){
|
|
return cmpp_dx_err_set(dx, CMPP_RC_RANGE,
|
|
"Too many arguments (%d). Max is %d.",
|
|
args->argc, (int)MaxArgs);
|
|
}
|
|
int i = 0;
|
|
for(cmpp_arg const * a = args->arg0;
|
|
a; ++i, a = a->next ){
|
|
offsets[i] = osAll.n;
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_to_b_F_BRACE_CALL;
|
|
if( cmpp__arg_wordIsPathOrFlag(a) ){
|
|
a2bFlags |= cmpp_arg_to_b_F_FORCE_STRING;
|
|
}
|
|
if( cmpp_arg_to_b(dx, a, cmpp_b_reuse(&os1), a2bFlags)
|
|
|| cmpp_b_append4(dx->pp, &osAll, os1.z, os1.n+1/*NUL*/) ){
|
|
goto end;
|
|
}
|
|
assert( osAll.n > offsets[i] );
|
|
if( 0 ){
|
|
g_warn("execv arg[%d] = %s => %s", i, a->z,
|
|
osAll.z+offsets[i]);
|
|
}
|
|
}
|
|
argv[i] = 0;
|
|
for( --i; i >= 0; --i ){
|
|
argv[i] = (char*)(osAll.z + offsets[i]);
|
|
if( 0 ){
|
|
g_warn("execv arg[%d] = %s", i, argv[i]);
|
|
}
|
|
}
|
|
end:
|
|
if( 0==dxppCode ){
|
|
cmpp__popen_impl(dx->pp, NULL, argv, 0, po);
|
|
}
|
|
cmpp_b_clear(&osAll);
|
|
cmpp_b_clear(&os1);
|
|
return dxppCode;
|
|
#endif
|
|
}
|
|
|
|
int cmpp_pclose(cmpp_popen_t *po){
|
|
#if CMPP_PLATFORM_IS_UNIX
|
|
if( po->fdFromChild>=0 ) close(po->fdFromChild);
|
|
if( po->fpToChild && *po->fpToChild ) fclose(*po->fpToChild);
|
|
int const childPid = po->childPid;
|
|
*po = cmpp_popen_t_empty;
|
|
#if 1
|
|
int wp, rc = 0;
|
|
if( childPid>0 ){
|
|
//kill(childPid, SIGINT); // really needed?
|
|
do{
|
|
wp = waitpid(childPid, &rc, WNOHANG);
|
|
if( wp>0 ){
|
|
if( WIFEXITED(rc) ){
|
|
rc = WEXITSTATUS(rc);
|
|
}else if( WIFSIGNALED(rc) ){
|
|
rc = WTERMSIG(rc);
|
|
}else{
|
|
rc = 0/*???*/;
|
|
}
|
|
}
|
|
} while( wp>0 );
|
|
}
|
|
return rc;
|
|
#elif 0
|
|
while( waitpid(childPid, NULL, WNOHANG)>0 ){}
|
|
#else
|
|
if( childPid>0 ){
|
|
kill(childPid, SIGINT); // really needed?
|
|
waitpid((pid_t)childPid, NULL, WNOHANG);
|
|
}else{
|
|
while( waitpid( (pid_t)0, NULL, WNOHANG)>0 ){}
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the module-loading pieces libcmpp.
|
|
*/
|
|
|
|
#if CMPP_ENABLE_DLLS
|
|
static const CmppSohList CmppSohList_empty =
|
|
CmppSohList_empty_m;
|
|
#endif
|
|
|
|
#if CMPP_ENABLE_DLLS
|
|
/**
|
|
If compiled without CMPP_ENABLE_DLLS defined to a true value
|
|
then this function always returns CMPP_RC_UNSUPPORTED and updates
|
|
the error state of its first argument with information about that
|
|
code.
|
|
|
|
Its first argument is the controlling cmpp. It can actually be
|
|
NULL - it's only used for reporting error details.
|
|
|
|
Its second argument is the name of a DLL file.
|
|
|
|
Its third argument is the name of a symbol in the given DLL which
|
|
resolves to a cmpp_module pointer. This name may be NULL,
|
|
in which case a default symbol name of "cmpp_module1" is used
|
|
(which is only useful when plugins are built one per DLL).
|
|
|
|
The fourth argument is the output pointer to store the
|
|
resulting module handle in.
|
|
|
|
The fifth argument is an optional list to append the DLL's
|
|
native handle to. It may be NULL.
|
|
|
|
This function tries to open a DLL named fname using the system's
|
|
DLL loader. If none is found, CMPP_RC_NOT_FOUND is returned and the
|
|
cmpp's error state is populated with info about the error. If
|
|
one is found, it looks for a symbol in the DLL: if symName is not
|
|
NULL and is not empty then the symbol "cmpp_module_symName" is
|
|
sought, else "cmpp_module". (e.g. if symName is "foo" then it
|
|
searches for a symbol names "cmpp_module_foo".) If no such symbol is
|
|
found then CMPP_RC_NOT_FOUND (again) is returned and the
|
|
cmpp's error state is populated, else the symbol is assumed to
|
|
be a (cmpp_module*) and *mod is assigned to it.
|
|
|
|
All errors update pp's error state but all are recoverable.
|
|
|
|
Returns 0 on success.
|
|
|
|
On success:
|
|
|
|
- `*mod` is set to the module object. Its ownship is kinda murky: it
|
|
lives in memory made available via the module loader. It remains
|
|
valid memory until the DLL is closed. The module might also
|
|
actually be statically linked with the application, in which case
|
|
it will live as long as the app.
|
|
|
|
- If soli is not NULL then the native DLL handle is appended to it.
|
|
Allocation errors when appending the DLL handle to the target list
|
|
are ignored - failure to retain a DLL handle for closing later is
|
|
not considered critical (and it would be extraordinarily rare (and
|
|
closing them outside of late-/post-main() cleanup is ill-advised,
|
|
anyway)).
|
|
|
|
@see cmpp_module_load()
|
|
@see CMPP_MODULE_DECL
|
|
@see CMPP_MODULE_IMPL2
|
|
@see CMPP_MODULE_IMPL3
|
|
@see CMPP_MODULE_IMPL_SOLO
|
|
@see CMPP_MODULE_REGISTER2
|
|
@see CMPP_MODULE_REGISTER3
|
|
*/
|
|
static
|
|
int cmpp__module_extract(cmpp * pp,
|
|
char const * dllFileName,
|
|
char const * symName,
|
|
cmpp_module const ** mod);
|
|
#endif
|
|
|
|
#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
|
|
# define CMPP_D_MODULE 1
|
|
#else
|
|
# define CMPP_D_MODULE 0
|
|
#endif
|
|
|
|
#if CMPP_D_MODULE
|
|
/**
|
|
The #module directive:
|
|
|
|
#module dll ?moduleName?
|
|
|
|
Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a
|
|
directive module.
|
|
*/
|
|
//static
|
|
void cmpp_dx_f_module(cmpp_dx *dx) {
|
|
cmpp_arg const * aName = 0;
|
|
cmpp_b obDll = cmpp_b_empty;
|
|
for( cmpp_arg const *arg = dx->args.arg0;
|
|
arg; arg = arg->next ){
|
|
//MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z));
|
|
if( cmpp_dx_err_check(dx) ) goto end;
|
|
else if( !obDll.z ){
|
|
cmpp_arg_to_b(
|
|
dx, arg, &obDll,
|
|
0//cmpp_arg_to_b_F_NO_DEFINES
|
|
);
|
|
continue;
|
|
}else if( !aName ){
|
|
aName = arg;
|
|
continue;
|
|
}
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s", arg->z);
|
|
goto end;
|
|
}
|
|
if( !obDll.z ){
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE,
|
|
"Expecting a DLL name argument.");
|
|
goto end;
|
|
}
|
|
cmpp_module_load(dx->pp, (char const *)obDll.z,
|
|
aName ? (char const *)aName->z : NULL);
|
|
end:
|
|
cmpp_b_clear(&obDll);
|
|
return;
|
|
#if 0
|
|
missing_arg:
|
|
cmpp_dx_err_set(dx, CMPP_RC_MISUSE, "Expecting an argument after %s.",
|
|
arg->z);
|
|
return;
|
|
#endif
|
|
}
|
|
#endif /* #module */
|
|
|
|
/**
|
|
Module loader pedantic licensing note: Most of cmpp's
|
|
module-loading code was copied verbatim from another project[^1],
|
|
but was written by the same author who relicenses it in
|
|
cmpp.
|
|
|
|
[^1]: https://fossil.wanderinghorse.net/r/cwal
|
|
*/
|
|
#if CMPP_ENABLE_DLLS
|
|
#if CMPP_HAVE_DLOPEN
|
|
typedef void * cmpp_soh;
|
|
# include <dlfcn.h> /* this actually has a different name on some platforms! */
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
# include <ltdl.h>
|
|
typedef lt_dlhandle cmpp_soh;
|
|
#elif CMPP_ENABLE_DLLS
|
|
# error "We have no dlopen() impl for this configuration."
|
|
#endif
|
|
|
|
static cmpp_soh cmpp__dlopen(char const * fname,
|
|
char const **errMsg){
|
|
static int once = 0;
|
|
cmpp_soh soh = 0;
|
|
if(!once && ++once){
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlopen( 0, RTLD_NOW | RTLD_GLOBAL );
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlinit();
|
|
lt_dlopen( 0 );
|
|
#endif
|
|
}
|
|
#if CMPP_HAVE_DLOPEN
|
|
soh = dlopen(fname, RTLD_NOW | RTLD_GLOBAL);
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
soh = lt_dlopen(fname);
|
|
#endif
|
|
if(!soh && errMsg){
|
|
#if CMPP_HAVE_DLOPEN
|
|
*errMsg = dlerror();
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
*errMsg = lt_dlerror();
|
|
#endif
|
|
}
|
|
return soh;
|
|
}
|
|
|
|
static
|
|
cmpp_module const * cmpp__dlsym(cmpp_soh soh,
|
|
char const * mname){
|
|
cmpp_module const ** sym =
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlsym(soh, mname)
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlsym(soh, mname)
|
|
#else
|
|
NULL
|
|
#endif
|
|
;
|
|
return sym ? *sym : NULL;
|
|
}
|
|
|
|
static void cmpp__dlclose(cmpp_soh soh){
|
|
if( soh ) {
|
|
#if CMPP_CLOSE_DLLS
|
|
/* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlclose(soh);
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlclose(soh);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
#endif /* CMPP_ENABLE_DLLS */
|
|
|
|
#define CmppSohList_works (CMPP_ENABLE_DLLS && CMPP_CLOSE_DLLS)
|
|
|
|
int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){
|
|
#if CmppSohList_works
|
|
int const rc = cmpp_array_reserve(pp, (void**)&soli->list,
|
|
soli->n
|
|
? (soli->n==soli->nAlloc
|
|
? soli->nAlloc*2
|
|
: soli->n+1)
|
|
: 8,
|
|
&soli->nAlloc, sizeof(void*));
|
|
if( 0==rc ){
|
|
soli->list[soli->n++] = soh;
|
|
}
|
|
return rc;
|
|
#else
|
|
(void)pp; (void)soli; (void)soh;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void CmppSohList_close(CmppSohList *s){
|
|
#if CmppSohList_works
|
|
while( s->nAlloc ){
|
|
if( s->list[--s->nAlloc] ){
|
|
//MARKER(("closing soh %p\n", s->list[s->nAlloc]));
|
|
cmpp__dlclose(s->list[s->nAlloc]);
|
|
s->list[s->nAlloc] = 0;
|
|
}
|
|
}
|
|
cmpp_mfree(s->list);
|
|
*s = CmppSohList_empty;
|
|
#else
|
|
(void)s;
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
Passes soli to CmppSohList_close() then frees soli. Results are
|
|
undefined if soli is not NULL but was not returned from
|
|
CmppSohList_new().
|
|
|
|
Special case: if built without DLL-closing support, this is a no-op.
|
|
*/
|
|
//static void CmppSohList_free(CmppSohList *soli);
|
|
void CmppSohList_free(CmppSohList *s){
|
|
if( s ){
|
|
#if CmppSohList_works
|
|
CmppSohList_close(s);
|
|
cmpp_mfree(s);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns a new, cleanly-initialized CmppSohList or NULL
|
|
on allocation error. The returned instance must eventually be
|
|
passed to CmppSohList_free().
|
|
|
|
Special case: if built without DLL-closing support, this returns a
|
|
no-op singleton instance.
|
|
*/
|
|
//static CmppSohList * CmppSohList_new(void);
|
|
CmppSohList * CmppSohList_new(void){
|
|
#if CmppSohList_works
|
|
CmppSohList * s = cmpp_malloc(sizeof(*s));
|
|
if( s ) *s = CmppSohList_empty;
|
|
return s;
|
|
#else
|
|
static CmppSohList soli = CmppSohList_empty;
|
|
return &soli;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#undef CmppSohList_works
|
|
|
|
#if CMPP_ENABLE_DLLS
|
|
/**
|
|
Default entry point symbol name for loadable modules. This must
|
|
match the symbolic name defined by CMPP_MODULE_IMPL_SOLO().
|
|
*/
|
|
static char const * const cmppModDfltSym = "cmpp_module1";
|
|
|
|
/**
|
|
Looks for a symbol in the given DLL handle. If symName is NULL or
|
|
empty, the symbol "cmpp_module" is used, else the symbols
|
|
("cmpp_module__" + symName) is used. If it finds one, it casts it to
|
|
cmpp_module and returns it. On error it may update pp's
|
|
error state with the error information if pp is not NULL.
|
|
|
|
Errors:
|
|
|
|
- symName is too long.
|
|
|
|
- cmpp__dlsym() lookup failure.
|
|
*/
|
|
static cmpp_module const *
|
|
cmpp__module_fish_out_entry_pt(cmpp * pp,
|
|
cmpp_soh soh,
|
|
char const * symName){
|
|
enum { MaxLen = 128 };
|
|
char buf[MaxLen] = {0};
|
|
cmpp_size_t const slen = symName ? strlen(symName) : 0;
|
|
cmpp_module const * mod = 0;
|
|
if(slen > (MaxLen-20)){
|
|
cmpp_err_set(pp, CMPP_RC_RANGE,
|
|
"DLL symbol name '%.*s' is too long. Max is %d.",
|
|
(int)slen, symName, (int)MaxLen-20);
|
|
}else{
|
|
if(symName && *symName){
|
|
snprintf(buf, MaxLen,"cmpp_module__%s", symName);
|
|
symName = &buf[0];
|
|
}else{
|
|
symName = cmppModDfltSym;
|
|
}
|
|
mod = cmpp__dlsym(soh, symName);
|
|
}
|
|
/*MARKER(("%s() [%s] ==> %p\n",__func__, symName,
|
|
(void const *)mod));*/
|
|
return mod;
|
|
}
|
|
#endif/*CMPP_ENABLE_DLLS*/
|
|
|
|
#if CMPP_ENABLE_DLLS
|
|
/**
|
|
Tries to dlsym() the given cmpp_module symbol from the given
|
|
DLL handle. On success, 0 is returned and *mod is assigned to the
|
|
memory. On error, non-0 is returned and pp's error state may be
|
|
updated.
|
|
|
|
Ownership of the returned module ostensibly lies with the first
|
|
argument, but that's not entirely true. If CMPP_CLOSE_DLLS is true
|
|
then a copy of the module's pointer is stored in the engine for
|
|
later closing. The memory itself is owned by the module loader, and
|
|
"should" stay valid until the DLL is closed.
|
|
*/
|
|
static int cmpp__module_get_sym(cmpp * pp,
|
|
cmpp_soh soh,
|
|
char const * symName,
|
|
cmpp_module const ** mod){
|
|
|
|
cmpp_module const * lm = 0;
|
|
int rc = cmpp_err_has(pp);
|
|
if( 0==rc ){
|
|
lm = cmpp__module_fish_out_entry_pt(pp, soh, symName);
|
|
rc = cmpp_err_has(pp);
|
|
}
|
|
if(0==rc){
|
|
if(lm){
|
|
*mod = lm;
|
|
}else{
|
|
cmpp__dlclose(soh);
|
|
rc = cmpp_err_set(pp, CMPP_RC_NOT_FOUND,
|
|
"Did not find module entry point symbol '%s'.",
|
|
symName ? symName : cmppModDfltSym);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
#endif/*CMPP_ENABLE_DLLS*/
|
|
|
|
#if !CMPP_ENABLE_DLLS
|
|
static int cmpp__err_no_dlls(cmpp * const pp){
|
|
return cmpp_err_set(pp, CMPP_RC_UNSUPPORTED,
|
|
"No dlopen() equivalent is installed "
|
|
"for this build configuration.");
|
|
}
|
|
#endif
|
|
|
|
#if CMPP_ENABLE_DLLS
|
|
//no: CMPP_WASM_EXPORT
|
|
int cmpp__module_extract(cmpp * pp,
|
|
char const * fname,
|
|
char const * symName,
|
|
cmpp_module const ** mod){
|
|
int rc = cmpp_err_has(pp);
|
|
if( rc ) return rc;
|
|
else if( cmpp_is_safemode(pp) ){
|
|
return cmpp_err_set(pp, CMPP_RC_ACCESS,
|
|
"Cannot use DLLs in safe mode.");
|
|
}else{
|
|
cmpp_soh soh;
|
|
char const * errMsg = 0;
|
|
soh = cmpp__dlopen(fname, &errMsg);
|
|
if(soh){
|
|
if( pp ){
|
|
CmppSohList_append(NULL/*alloc error here can be ignored*/,
|
|
&pp->pimpl->mod.sohList, soh);
|
|
}
|
|
cmpp_module const * x = 0;
|
|
rc = cmpp__module_get_sym(pp, soh, symName, &x);
|
|
if(!rc && mod) *mod = x;
|
|
return rc;
|
|
}else{
|
|
return errMsg
|
|
? cmpp_err_set(pp, CMPP_RC_ERROR, "DLL open failed: %s",
|
|
errMsg)
|
|
: cmpp_err_set(pp, CMPP_RC_ERROR,
|
|
"DLL open failed for unknown reason.");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//no: CMPP_WASM_EXPORT
|
|
int cmpp_module_load(cmpp * pp, char const * fname,
|
|
char const * symName){
|
|
#if CMPP_ENABLE_DLLS
|
|
if( ppCode ){
|
|
/* fall through */
|
|
}else if( cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.newFlags ){
|
|
cmpp_err_set(pp, CMPP_RC_ACCESS,
|
|
"%s() is disallowed in safe-mode.");
|
|
}else{
|
|
cmpp__pi(pp);
|
|
char * zName = 0;
|
|
if( fname ){
|
|
zName = cmpp_path_search(pp, (char const *)pi->mod.path.z,
|
|
pi->mod.pathSep, fname,
|
|
pi->mod.soExt);
|
|
if( !zName ){
|
|
return cmpp_err_set(pp, CMPP_RC_NOT_FOUND,
|
|
"Did not find [%s] or [%s%s] "
|
|
"in search path [%s].",
|
|
fname, fname, pi->mod.soExt,
|
|
pi->mod.path.z);
|
|
}
|
|
}
|
|
cmpp_module const * mod = 0;
|
|
if( 0==cmpp__module_extract(pp, zName, symName, &mod) ){
|
|
assert(mod);
|
|
assert(mod->init);
|
|
int const rc = mod->init(pp);
|
|
if( rc && !ppCode ){
|
|
cmpp_err_set(pp, CMPP_RC_ERROR,
|
|
"Module %s::init() failed with code #%d/%s "
|
|
"without providing additional info.",
|
|
symName ? symName : "cmpp_module",
|
|
rc, cmpp_rc_cstr(rc));
|
|
}
|
|
cmpp_mfree(zName);
|
|
}
|
|
}
|
|
return ppCode;
|
|
#else
|
|
(void)fname; (void)symName;
|
|
return cmpp__err_no_dlls(pp);
|
|
#endif
|
|
}
|
|
/*
|
|
** 2015-08-18, 2023-04-28
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** May you do good and not evil.
|
|
** May you find forgiveness for yourself and forgive others.
|
|
** May you share freely, never taking more than you give.
|
|
**
|
|
*************************************************************************
|
|
**
|
|
** This file demonstrates how to create a table-valued-function using
|
|
** a virtual table. This demo implements the generate_series() function
|
|
** which gives the same results as the eponymous function in PostgreSQL,
|
|
** within the limitation that its arguments are signed 64-bit integers.
|
|
**
|
|
** Considering its equivalents to generate_series(start,stop,step): A
|
|
** value V[n] sequence is produced for integer n ascending from 0 where
|
|
** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
|
|
** for each produced value (independent of production time ordering.)
|
|
**
|
|
** All parameters must be either integer or convertable to integer.
|
|
** The start parameter is required.
|
|
** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
|
|
** The step parameter defaults to 1 and 0 is treated as 1.
|
|
**
|
|
** Examples:
|
|
**
|
|
** SELECT * FROM generate_series(0,100,5);
|
|
**
|
|
** The query above returns integers from 0 through 100 counting by steps
|
|
** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total
|
|
** of 21 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,100);
|
|
**
|
|
** Integers from 0 through 100 with a step size of 1. 101 rows.
|
|
**
|
|
** SELECT * FROM generate_series(20) LIMIT 10;
|
|
**
|
|
** Integers 20 through 29. 10 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,-100,-5);
|
|
**
|
|
** Integers 0 -5 -10 ... -100. 21 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,-1);
|
|
**
|
|
** Empty sequence.
|
|
**
|
|
** HOW IT WORKS
|
|
**
|
|
** The generate_series "function" is really a virtual table with the
|
|
** following schema:
|
|
**
|
|
** CREATE TABLE generate_series(
|
|
** value,
|
|
** start HIDDEN,
|
|
** stop HIDDEN,
|
|
** step HIDDEN
|
|
** );
|
|
**
|
|
** The virtual table also has a rowid which is an alias for the value.
|
|
**
|
|
** Function arguments in queries against this virtual table are translated
|
|
** into equality constraints against successive hidden columns. In other
|
|
** words, the following pairs of queries are equivalent to each other:
|
|
**
|
|
** SELECT * FROM generate_series(0,100,5);
|
|
** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5;
|
|
**
|
|
** SELECT * FROM generate_series(0,100);
|
|
** SELECT * FROM generate_series WHERE start=0 AND stop=100;
|
|
**
|
|
** SELECT * FROM generate_series(20) LIMIT 10;
|
|
** SELECT * FROM generate_series WHERE start=20 LIMIT 10;
|
|
**
|
|
** The generate_series virtual table implementation leaves the xCreate method
|
|
** set to NULL. This means that it is not possible to do a CREATE VIRTUAL
|
|
** TABLE command with "generate_series" as the USING argument. Instead, there
|
|
** is a single generate_series virtual table that is always available without
|
|
** having to be created first.
|
|
**
|
|
** The xBestIndex method looks for equality constraints against the hidden
|
|
** start, stop, and step columns, and if present, it uses those constraints
|
|
** to bound the sequence of generated values. If the equality constraints
|
|
** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step.
|
|
** xBestIndex returns a small cost when both start and stop are available,
|
|
** and a very large cost if either start or stop are unavailable. This
|
|
** encourages the query planner to order joins such that the bounds of the
|
|
** series are well-defined.
|
|
**
|
|
** Update on 2024-08-22:
|
|
** xBestIndex now also looks for equality and inequality constraints against
|
|
** the value column and uses those constraints as additional bounds against
|
|
** the sequence range. Thus, a query like this:
|
|
**
|
|
** SELECT value FROM generate_series($SA,$EA)
|
|
** WHERE value BETWEEN $SB AND $EB;
|
|
**
|
|
** Is logically the same as:
|
|
**
|
|
** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB));
|
|
**
|
|
** Constraints on the value column can server as substitutes for constraints
|
|
** on the hidden start and stop columns. So, the following two queries
|
|
** are equivalent:
|
|
**
|
|
** SELECT value FROM generate_series($S,$E);
|
|
** SELECT value FROM generate_series WHERE value BETWEEN $S and $E;
|
|
**
|
|
*/
|
|
#if 0
|
|
#include "sqlite3ext.h"
|
|
#else
|
|
#include "sqlite3.h"
|
|
#endif
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
|
|
/* series_cursor is a subclass of sqlite3_vtab_cursor which will
|
|
** serve as the underlying representation of a cursor that scans
|
|
** over rows of the result.
|
|
**
|
|
** iOBase, iOTerm, and iOStep are the original values of the
|
|
** start=, stop=, and step= constraints on the query. These are
|
|
** the values reported by the start, stop, and step columns of the
|
|
** virtual table.
|
|
**
|
|
** iBase, iTerm, iStep, and bDescp are the actual values used to generate
|
|
** the sequence. These might be different from the iOxxxx values.
|
|
** For example in
|
|
**
|
|
** SELECT value FROM generate_series(1,11,2)
|
|
** WHERE value BETWEEN 4 AND 8;
|
|
**
|
|
** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7.
|
|
** Another example:
|
|
**
|
|
** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC;
|
|
**
|
|
** The cursor initialization for the above query is:
|
|
**
|
|
** iOBase = 1 iBase = 13
|
|
** iOTerm = 15 iTerm = 1
|
|
** iOStep = 3 iStep = 3 bDesc = 1
|
|
**
|
|
** The actual step size is unsigned so that can have a value of
|
|
** +9223372036854775808 which is needed for querys like this:
|
|
**
|
|
** SELECT value
|
|
** FROM generate_series(9223372036854775807,
|
|
** -9223372036854775808,
|
|
** -9223372036854775808)
|
|
** ORDER BY value ASC;
|
|
**
|
|
** The setup for the previous query will be:
|
|
**
|
|
** iOBase = 9223372036854775807 iBase = -1
|
|
** iOTerm = -9223372036854775808 iTerm = 9223372036854775807
|
|
** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0
|
|
*/
|
|
typedef unsigned char u8;
|
|
typedef struct series_cursor series_cursor;
|
|
struct series_cursor {
|
|
sqlite3_vtab_cursor base; /* Base class - must be first */
|
|
sqlite3_int64 iOBase; /* Original starting value ("start") */
|
|
sqlite3_int64 iOTerm; /* Original terminal value ("stop") */
|
|
sqlite3_int64 iOStep; /* Original step value */
|
|
sqlite3_int64 iBase; /* Starting value to actually use */
|
|
sqlite3_int64 iTerm; /* Terminal value to actually use */
|
|
sqlite3_uint64 iStep; /* The step size */
|
|
sqlite3_int64 iValue; /* Current value */
|
|
u8 bDesc; /* iStep is really negative */
|
|
u8 bDone; /* True if stepped past last element */
|
|
};
|
|
|
|
/*
|
|
** Computed the difference between two 64-bit signed integers using a
|
|
** convoluted computation designed to work around the silly restriction
|
|
** against signed integer overflow in C.
|
|
*/
|
|
static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){
|
|
assert( a>=b );
|
|
return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b);
|
|
}
|
|
|
|
/*
|
|
** Add or substract an unsigned 64-bit integer from a signed 64-bit integer
|
|
** and return the new signed 64-bit integer.
|
|
*/
|
|
static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){
|
|
sqlite3_uint64 x = *(sqlite3_uint64*)&a;
|
|
x += b;
|
|
return *(sqlite3_int64*)&x;
|
|
}
|
|
static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){
|
|
sqlite3_uint64 x = *(sqlite3_uint64*)&a;
|
|
x -= b;
|
|
return *(sqlite3_int64*)&x;
|
|
}
|
|
|
|
/*
|
|
** The seriesConnect() method is invoked to create a new
|
|
** series_vtab that describes the generate_series virtual table.
|
|
**
|
|
** Think of this routine as the constructor for series_vtab objects.
|
|
**
|
|
** All this routine needs to do is:
|
|
**
|
|
** (1) Allocate the series_vtab object and initialize all fields.
|
|
**
|
|
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
|
|
** result set of queries against generate_series will look like.
|
|
*/
|
|
static int seriesConnect(
|
|
sqlite3 *db,
|
|
void *pUnused,
|
|
int argcUnused, const char *const*argvUnused,
|
|
sqlite3_vtab **ppVtab,
|
|
char **pzErrUnused
|
|
){
|
|
sqlite3_vtab *pNew;
|
|
int rc;
|
|
|
|
/* Column numbers */
|
|
#define SERIES_COLUMN_ROWID (-1)
|
|
#define SERIES_COLUMN_VALUE 0
|
|
#define SERIES_COLUMN_START 1
|
|
#define SERIES_COLUMN_STOP 2
|
|
#define SERIES_COLUMN_STEP 3
|
|
|
|
(void)pUnused;
|
|
(void)argcUnused;
|
|
(void)argvUnused;
|
|
(void)pzErrUnused;
|
|
rc = sqlite3_declare_vtab(db,
|
|
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
|
|
if( rc==SQLITE_OK ){
|
|
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
|
if( pNew==0 ) return SQLITE_NOMEM;
|
|
memset(pNew, 0, sizeof(*pNew));
|
|
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** This method is the destructor for series_cursor objects.
|
|
*/
|
|
static int seriesDisconnect(sqlite3_vtab *pVtab){
|
|
sqlite3_free(pVtab);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Constructor for a new series_cursor object.
|
|
*/
|
|
static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){
|
|
series_cursor *pCur;
|
|
(void)pUnused;
|
|
pCur = sqlite3_malloc( sizeof(*pCur) );
|
|
if( pCur==0 ) return SQLITE_NOMEM;
|
|
memset(pCur, 0, sizeof(*pCur));
|
|
*ppCursor = &pCur->base;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Destructor for a series_cursor.
|
|
*/
|
|
static int seriesClose(sqlite3_vtab_cursor *cur){
|
|
sqlite3_free(cur);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
** Advance a series_cursor to its next row of output.
|
|
*/
|
|
static int seriesNext(sqlite3_vtab_cursor *cur){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
if( pCur->iValue==pCur->iTerm ){
|
|
pCur->bDone = 1;
|
|
}else if( pCur->bDesc ){
|
|
pCur->iValue = sub64(pCur->iValue, pCur->iStep);
|
|
assert( pCur->iValue>=pCur->iTerm );
|
|
}else{
|
|
pCur->iValue = add64(pCur->iValue, pCur->iStep);
|
|
assert( pCur->iValue<=pCur->iTerm );
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return values of columns for the row at which the series_cursor
|
|
** is currently pointing.
|
|
*/
|
|
static int seriesColumn(
|
|
sqlite3_vtab_cursor *cur, /* The cursor */
|
|
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
|
int i /* Which column to return */
|
|
){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
sqlite3_int64 x = 0;
|
|
switch( i ){
|
|
case SERIES_COLUMN_START: x = pCur->iOBase; break;
|
|
case SERIES_COLUMN_STOP: x = pCur->iOTerm; break;
|
|
case SERIES_COLUMN_STEP: x = pCur->iOStep; break;
|
|
default: x = pCur->iValue; break;
|
|
}
|
|
sqlite3_result_int64(ctx, x);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
#ifndef LARGEST_UINT64
|
|
#define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL)
|
|
#define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL)
|
|
#define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL)
|
|
#endif
|
|
|
|
/*
|
|
** The rowid is the same as the value.
|
|
*/
|
|
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
*pRowid = pCur->iValue;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return TRUE if the cursor has been moved off of the last
|
|
** row of output.
|
|
*/
|
|
static int seriesEof(sqlite3_vtab_cursor *cur){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
return pCur->bDone;
|
|
}
|
|
|
|
/* True to cause run-time checking of the start=, stop=, and/or step=
|
|
** parameters. The only reason to do this is for testing the
|
|
** constraint checking logic for virtual tables in the SQLite core.
|
|
*/
|
|
#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
|
|
# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
|
|
#endif
|
|
|
|
/*
|
|
** Return the number of steps between pCur->iBase and pCur->iTerm if
|
|
** the step width is pCur->iStep.
|
|
*/
|
|
static sqlite3_uint64 seriesSteps(series_cursor *pCur){
|
|
if( pCur->bDesc ){
|
|
assert( pCur->iBase >= pCur->iTerm );
|
|
return span64(pCur->iBase, pCur->iTerm)/pCur->iStep;
|
|
}else{
|
|
assert( pCur->iBase <= pCur->iTerm );
|
|
return span64(pCur->iTerm, pCur->iBase)/pCur->iStep;
|
|
}
|
|
}
|
|
|
|
#if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32)
|
|
/*
|
|
** Case 1 (the most common case):
|
|
** The standard math library is available so use ceil() and floor() from there.
|
|
*/
|
|
static double seriesCeil(double r){ return ceil(r); }
|
|
static double seriesFloor(double r){ return floor(r); }
|
|
#elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
|
|
/*
|
|
** Case 2 (2nd most common): Use GCC/Clang builtins
|
|
*/
|
|
static double seriesCeil(double r){ return __builtin_ceil(r); }
|
|
static double seriesFloor(double r){ return __builtin_floor(r); }
|
|
#else
|
|
/*
|
|
** Case 3 (rarely happens): Use home-grown ceil() and floor() routines.
|
|
*/
|
|
static double seriesCeil(double r){
|
|
sqlite3_int64 x;
|
|
if( r!=r ) return r;
|
|
if( r<=(-4503599627370496.0) ) return r;
|
|
if( r>=(+4503599627370496.0) ) return r;
|
|
x = (sqlite3_int64)r;
|
|
if( r==(double)x ) return r;
|
|
if( r>(double)x ) x++;
|
|
return (double)x;
|
|
}
|
|
static double seriesFloor(double r){
|
|
sqlite3_int64 x;
|
|
if( r!=r ) return r;
|
|
if( r<=(-4503599627370496.0) ) return r;
|
|
if( r>=(+4503599627370496.0) ) return r;
|
|
x = (sqlite3_int64)r;
|
|
if( r==(double)x ) return r;
|
|
if( r<(double)x ) x--;
|
|
return (double)x;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
** This method is called to "rewind" the series_cursor object back
|
|
** to the first row of output. This method is always called at least
|
|
** once prior to any call to seriesColumn() or seriesRowid() or
|
|
** seriesEof().
|
|
**
|
|
** The query plan selected by seriesBestIndex is passed in the idxNum
|
|
** parameter. (idxStr is not used in this implementation.) idxNum
|
|
** is a bitmask showing which constraints are available:
|
|
**
|
|
** 0x0001: start=VALUE
|
|
** 0x0002: stop=VALUE
|
|
** 0x0004: step=VALUE
|
|
** 0x0008: descending order
|
|
** 0x0010: ascending order
|
|
** 0x0020: LIMIT VALUE
|
|
** 0x0040: OFFSET VALUE
|
|
** 0x0080: value=VALUE
|
|
** 0x0100: value>=VALUE
|
|
** 0x0200: value>VALUE
|
|
** 0x1000: value<=VALUE
|
|
** 0x2000: value<VALUE
|
|
**
|
|
** This routine should initialize the cursor and position it so that it
|
|
** is pointing at the first row, or pointing off the end of the table
|
|
** (so that seriesEof() will return true) if the table is empty.
|
|
*/
|
|
static int seriesFilter(
|
|
sqlite3_vtab_cursor *pVtabCursor,
|
|
int idxNum, const char *idxStrUnused,
|
|
int argc, sqlite3_value **argv
|
|
){
|
|
series_cursor *pCur = (series_cursor *)pVtabCursor;
|
|
int iArg = 0; /* Arguments used so far */
|
|
int i; /* Loop counter */
|
|
sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */
|
|
sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */
|
|
sqlite3_int64 iLimit = 0; /* if >0, the value of the LIMIT */
|
|
sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */
|
|
|
|
(void)idxStrUnused;
|
|
|
|
/* If any constraints have a NULL value, then return no rows.
|
|
** See ticket https://sqlite.org/src/info/fac496b61722daf2
|
|
*/
|
|
for(i=0; i<argc; i++){
|
|
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
|
|
goto series_no_rows;
|
|
}
|
|
}
|
|
|
|
/* Capture the three HIDDEN parameters to the virtual table and insert
|
|
** default values for any parameters that are omitted.
|
|
*/
|
|
if( idxNum & 0x01 ){
|
|
pCur->iOBase = sqlite3_value_int64(argv[iArg++]);
|
|
}else{
|
|
pCur->iOBase = 0;
|
|
}
|
|
if( idxNum & 0x02 ){
|
|
pCur->iOTerm = sqlite3_value_int64(argv[iArg++]);
|
|
}else{
|
|
pCur->iOTerm = 0xffffffff;
|
|
}
|
|
if( idxNum & 0x04 ){
|
|
pCur->iOStep = sqlite3_value_int64(argv[iArg++]);
|
|
if( pCur->iOStep==0 ) pCur->iOStep = 1;
|
|
}else{
|
|
pCur->iOStep = 1;
|
|
}
|
|
|
|
/* If there are constraints on the value column but there are
|
|
** no constraints on the start, stop, and step columns, then
|
|
** initialize the default range to be the entire range of 64-bit signed
|
|
** integers. This range will contracted by the value column constraints
|
|
** further below.
|
|
*/
|
|
if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){
|
|
pCur->iOBase = SMALLEST_INT64;
|
|
}
|
|
if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){
|
|
pCur->iOTerm = LARGEST_INT64;
|
|
}
|
|
pCur->iBase = pCur->iOBase;
|
|
pCur->iTerm = pCur->iOTerm;
|
|
if( pCur->iOStep>0 ){
|
|
pCur->iStep = pCur->iOStep;
|
|
}else if( pCur->iOStep>SMALLEST_INT64 ){
|
|
pCur->iStep = -pCur->iOStep;
|
|
}else{
|
|
pCur->iStep = LARGEST_INT64;
|
|
pCur->iStep++;
|
|
}
|
|
pCur->bDesc = pCur->iOStep<0;
|
|
if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
|
|
/* Extract the LIMIT and OFFSET values, but do not apply them yet.
|
|
** The range must first be constrained by the limits on value.
|
|
*/
|
|
if( idxNum & 0x20 ){
|
|
iLimit = sqlite3_value_int64(argv[iArg++]);
|
|
if( idxNum & 0x40 ){
|
|
iOffset = sqlite3_value_int64(argv[iArg++]);
|
|
}
|
|
}
|
|
|
|
/* Narrow the range of iMin and iMax (the minimum and maximum outputs)
|
|
** based on equality and inequality constraints on the "value" column.
|
|
*/
|
|
if( idxNum & 0x3380 ){
|
|
if( idxNum & 0x0080 ){ /* value=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r==seriesCeil(r)
|
|
&& r>=(double)SMALLEST_INT64
|
|
&& r<=(double)LARGEST_INT64
|
|
){
|
|
iMin = iMax = (sqlite3_int64)r;
|
|
}else{
|
|
goto series_no_rows;
|
|
}
|
|
}else{
|
|
iMin = iMax = sqlite3_value_int64(argv[iArg++]);
|
|
}
|
|
}else{
|
|
if( idxNum & 0x0300 ){ /* value>X or value>=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r<(double)SMALLEST_INT64 ){
|
|
iMin = SMALLEST_INT64;
|
|
}else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){
|
|
iMin = (sqlite3_int64)seriesCeil(r+1.0);
|
|
}else{
|
|
iMin = (sqlite3_int64)seriesCeil(r);
|
|
}
|
|
}else{
|
|
iMin = sqlite3_value_int64(argv[iArg++]);
|
|
if( (idxNum & 0x0200)!=0 ){
|
|
if( iMin==LARGEST_INT64 ){
|
|
goto series_no_rows;
|
|
}else{
|
|
iMin++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( idxNum & 0x3000 ){ /* value<X or value<=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r>(double)LARGEST_INT64 ){
|
|
iMax = LARGEST_INT64;
|
|
}else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){
|
|
iMax = (sqlite3_int64)(r-1.0);
|
|
}else{
|
|
iMax = (sqlite3_int64)seriesFloor(r);
|
|
}
|
|
}else{
|
|
iMax = sqlite3_value_int64(argv[iArg++]);
|
|
if( idxNum & 0x2000 ){
|
|
if( iMax==SMALLEST_INT64 ){
|
|
goto series_no_rows;
|
|
}else{
|
|
iMax--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( iMin>iMax ){
|
|
goto series_no_rows;
|
|
}
|
|
}
|
|
|
|
/* Try to reduce the range of values to be generated based on
|
|
** constraints on the "value" column.
|
|
*/
|
|
if( pCur->bDesc==0 ){
|
|
if( pCur->iBase<iMin ){
|
|
sqlite3_uint64 span = span64(iMin,pCur->iBase);
|
|
pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
|
|
if( pCur->iBase<iMin ){
|
|
if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iBase = add64(pCur->iBase, pCur->iStep);
|
|
}
|
|
}
|
|
if( pCur->iTerm>iMax ){
|
|
pCur->iTerm = iMax;
|
|
}
|
|
}else{
|
|
if( pCur->iBase>iMax ){
|
|
sqlite3_uint64 span = span64(pCur->iBase,iMax);
|
|
pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
|
|
if( pCur->iBase>iMax ){
|
|
if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iBase = sub64(pCur->iBase, pCur->iStep);
|
|
}
|
|
}
|
|
if( pCur->iTerm<iMin ){
|
|
pCur->iTerm = iMin;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Adjust iTerm so that it is exactly the last value of the series.
|
|
*/
|
|
if( pCur->bDesc==0 ){
|
|
if( pCur->iBase>pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iTerm = sub64(pCur->iTerm,
|
|
span64(pCur->iTerm,pCur->iBase) % pCur->iStep);
|
|
}else{
|
|
if( pCur->iBase<pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iTerm = add64(pCur->iTerm,
|
|
span64(pCur->iBase,pCur->iTerm) % pCur->iStep);
|
|
}
|
|
|
|
/* Transform the series generator to output values in the requested
|
|
** order.
|
|
*/
|
|
if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0)
|
|
|| ((idxNum & 0x0010)!=0 && pCur->bDesc!=0)
|
|
){
|
|
sqlite3_int64 tmp = pCur->iBase;
|
|
pCur->iBase = pCur->iTerm;
|
|
pCur->iTerm = tmp;
|
|
pCur->bDesc = !pCur->bDesc;
|
|
}
|
|
|
|
/* Apply LIMIT and OFFSET constraints, if any */
|
|
assert( pCur->iStep!=0 );
|
|
if( idxNum & 0x20 ){
|
|
if( iOffset>0 ){
|
|
if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){
|
|
goto series_no_rows;
|
|
}else if( pCur->bDesc ){
|
|
pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset);
|
|
}else{
|
|
pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset);
|
|
}
|
|
}
|
|
if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){
|
|
pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep);
|
|
}
|
|
}
|
|
pCur->iValue = pCur->iBase;
|
|
pCur->bDone = 0;
|
|
return SQLITE_OK;
|
|
|
|
series_no_rows:
|
|
pCur->iBase = 0;
|
|
pCur->iTerm = 0;
|
|
pCur->iStep = 1;
|
|
pCur->bDesc = 0;
|
|
pCur->bDone = 1;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** SQLite will invoke this method one or more times while planning a query
|
|
** that uses the generate_series virtual table. This routine needs to create
|
|
** a query plan for each invocation and compute an estimated cost for that
|
|
** plan.
|
|
**
|
|
** In this implementation idxNum is used to represent the
|
|
** query plan. idxStr is unused.
|
|
**
|
|
** The query plan is represented by bits in idxNum:
|
|
**
|
|
** 0x0001 start = $num
|
|
** 0x0002 stop = $num
|
|
** 0x0004 step = $num
|
|
** 0x0008 output is in descending order
|
|
** 0x0010 output is in ascending order
|
|
** 0x0020 LIMIT $num
|
|
** 0x0040 OFFSET $num
|
|
** 0x0080 value = $num
|
|
** 0x0100 value >= $num
|
|
** 0x0200 value > $num
|
|
** 0x1000 value <= $num
|
|
** 0x2000 value < $num
|
|
**
|
|
** Only one of 0x0100 or 0x0200 will be returned. Similarly, only
|
|
** one of 0x1000 or 0x2000 will be returned. If the 0x0080 is set, then
|
|
** none of the 0xff00 bits will be set.
|
|
**
|
|
** The order of parameters passed to xFilter is as follows:
|
|
**
|
|
** * The argument to start= if bit 0x0001 is in the idxNum mask
|
|
** * The argument to stop= if bit 0x0002 is in the idxNum mask
|
|
** * The argument to step= if bit 0x0004 is in the idxNum mask
|
|
** * The argument to LIMIT if bit 0x0020 is in the idxNum mask
|
|
** * The argument to OFFSET if bit 0x0040 is in the idxNum mask
|
|
** * The argument to value=, or value>= or value> if any of
|
|
** bits 0x0380 are in the idxNum mask
|
|
** * The argument to value<= or value< if either of bits 0x3000
|
|
** are in the mask
|
|
**
|
|
*/
|
|
static int seriesBestIndex(
|
|
sqlite3_vtab *pVTab,
|
|
sqlite3_index_info *pIdxInfo
|
|
){
|
|
int i, j; /* Loop over constraints */
|
|
int idxNum = 0; /* The query plan bitmask */
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
int bStartSeen = 0; /* EQ constraint seen on the START column */
|
|
#endif
|
|
int unusableMask = 0; /* Mask of unusable constraints */
|
|
int nArg = 0; /* Number of arguments that seriesFilter() expects */
|
|
int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET,
|
|
** and value. aIdx[5] covers value=, value>=, and
|
|
** value>, aIdx[6] covers value<= and value< */
|
|
const struct sqlite3_index_constraint *pConstraint;
|
|
|
|
/* This implementation assumes that the start, stop, and step columns
|
|
** are the last three columns in the virtual table. */
|
|
assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
|
|
assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
|
|
|
|
aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1;
|
|
pConstraint = pIdxInfo->aConstraint;
|
|
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
|
int iCol; /* 0 for start, 1 for stop, 2 for step */
|
|
int iMask; /* bitmask for those column */
|
|
int op = pConstraint->op;
|
|
if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT
|
|
&& op<=SQLITE_INDEX_CONSTRAINT_OFFSET
|
|
){
|
|
if( pConstraint->usable==0 ){
|
|
/* do nothing */
|
|
}else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){
|
|
aIdx[3] = i;
|
|
idxNum |= 0x20;
|
|
}else{
|
|
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
|
|
aIdx[4] = i;
|
|
idxNum |= 0x40;
|
|
}
|
|
continue;
|
|
}
|
|
if( pConstraint->iColumn<SERIES_COLUMN_START ){
|
|
if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
|
|
pConstraint->iColumn==SERIES_COLUMN_ROWID)
|
|
&& pConstraint->usable
|
|
){
|
|
switch( op ){
|
|
case SQLITE_INDEX_CONSTRAINT_EQ:
|
|
case SQLITE_INDEX_CONSTRAINT_IS: {
|
|
idxNum |= 0x0080;
|
|
idxNum &= ~0x3300;
|
|
aIdx[5] = i;
|
|
aIdx[6] = -1;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_GE: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x0100;
|
|
idxNum &= ~0x0200;
|
|
aIdx[5] = i;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_GT: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x0200;
|
|
idxNum &= ~0x0100;
|
|
aIdx[5] = i;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_LE: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x1000;
|
|
idxNum &= ~0x2000;
|
|
aIdx[6] = i;
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_LT: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x2000;
|
|
idxNum &= ~0x1000;
|
|
aIdx[6] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
iCol = pConstraint->iColumn - SERIES_COLUMN_START;
|
|
assert( iCol>=0 && iCol<=2 );
|
|
iMask = 1 << iCol;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
|
bStartSeen = 1;
|
|
}
|
|
#endif
|
|
if( pConstraint->usable==0 ){
|
|
unusableMask |= iMask;
|
|
continue;
|
|
}else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
|
idxNum |= iMask;
|
|
aIdx[iCol] = i;
|
|
}
|
|
}
|
|
if( aIdx[3]==0 ){
|
|
/* Ignore OFFSET if LIMIT is omitted */
|
|
idxNum &= ~0x60;
|
|
aIdx[4] = 0;
|
|
}
|
|
for(i=0; i<7; i++){
|
|
if( (j = aIdx[i])>=0 ){
|
|
pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
|
|
pIdxInfo->aConstraintUsage[j].omit =
|
|
!SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3;
|
|
}
|
|
}
|
|
/* The current generate_column() implementation requires at least one
|
|
** argument (the START value). Legacy versions assumed START=0 if the
|
|
** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES
|
|
** to obtain the legacy behavior */
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
if( !bStartSeen ){
|
|
sqlite3_free(pVTab->zErrMsg);
|
|
pVTab->zErrMsg = sqlite3_mprintf(
|
|
"first argument to \"generate_series()\" missing or unusable");
|
|
return SQLITE_ERROR;
|
|
}
|
|
#endif
|
|
if( (unusableMask & ~idxNum)!=0 ){
|
|
/* The start, stop, and step columns are inputs. Therefore if there
|
|
** are unusable constraints on any of start, stop, or step then
|
|
** this plan is unusable */
|
|
return SQLITE_CONSTRAINT;
|
|
}
|
|
if( (idxNum & 0x03)==0x03 ){
|
|
/* Both start= and stop= boundaries are available. This is the
|
|
** the preferred case */
|
|
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
|
|
pIdxInfo->estimatedRows = 1000;
|
|
if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){
|
|
if( pIdxInfo->aOrderBy[0].desc ){
|
|
idxNum |= 0x08;
|
|
}else{
|
|
idxNum |= 0x10;
|
|
}
|
|
pIdxInfo->orderByConsumed = 1;
|
|
}
|
|
}else if( (idxNum & 0x21)==0x21 ){
|
|
/* We have start= and LIMIT */
|
|
pIdxInfo->estimatedRows = 2500;
|
|
}else{
|
|
/* If either boundary is missing, we have to generate a huge span
|
|
** of numbers. Make this case very expensive so that the query
|
|
** planner will work hard to avoid it. */
|
|
pIdxInfo->estimatedRows = 2147483647;
|
|
}
|
|
pIdxInfo->idxNum = idxNum;
|
|
#ifdef SQLITE_INDEX_SCAN_HEX
|
|
pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX;
|
|
#endif
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** This following structure defines all the methods for the
|
|
** generate_series virtual table.
|
|
*/
|
|
static sqlite3_module seriesModule = {
|
|
0, /* iVersion */
|
|
0, /* xCreate */
|
|
seriesConnect, /* xConnect */
|
|
seriesBestIndex, /* xBestIndex */
|
|
seriesDisconnect, /* xDisconnect */
|
|
0, /* xDestroy */
|
|
seriesOpen, /* xOpen - open a cursor */
|
|
seriesClose, /* xClose - close a cursor */
|
|
seriesFilter, /* xFilter - configure scan constraints */
|
|
seriesNext, /* xNext - advance a cursor */
|
|
seriesEof, /* xEof - check for end of scan */
|
|
seriesColumn, /* xColumn - read data */
|
|
seriesRowid, /* xRowid - read data */
|
|
0, /* xUpdate */
|
|
0, /* xBegin */
|
|
0, /* xSync */
|
|
0, /* xCommit */
|
|
0, /* xRollback */
|
|
0, /* xFindMethod */
|
|
0, /* xRename */
|
|
0, /* xSavepoint */
|
|
0, /* xRelease */
|
|
0, /* xRollbackTo */
|
|
0, /* xShadowName */
|
|
0 /* xIntegrity */
|
|
};
|
|
|
|
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
|
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
int sqlite3_series_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
int rc = SQLITE_OK;
|
|
(void)pApi;
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){
|
|
*pzErrMsg = sqlite3_mprintf(
|
|
"generate_series() requires SQLite 3.8.12 or later");
|
|
return SQLITE_ERROR;
|
|
}
|
|
rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
|
|
#endif
|
|
return rc;
|
|
}
|
|
#define CMPP_D_DEMO
|
|
/*
|
|
** 2025-10-18:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
**
|
|
** This file contains demonstration client-side directives for the
|
|
** c-pp API.
|
|
*/
|
|
#if defined(CMPP_D_DEMO)
|
|
/* Only when building with the main c-pp app. */
|
|
#elif !defined(CMPP_MODULE_REGISTER1)
|
|
/**
|
|
Assume a standalone module build. Arrange for the default module
|
|
entry point to be installed so that cmpp_module_load() does not
|
|
require that the user know the entry point name.
|
|
*/
|
|
#define CMPP_MODULE_STANDALONE
|
|
#define CMPP_API_THUNK
|
|
#include "libcmpp.h"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
/**
|
|
cmpp_d_autoload_f() impl for this file's directives and its close
|
|
friends.
|
|
*/
|
|
int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state);
|
|
/**
|
|
Registers demo and utility directives with pp.
|
|
*/
|
|
int cmpp_module__demo_register(cmpp *pp);
|
|
|
|
/**
|
|
Simply says hello and emits info about its arguments.
|
|
*/
|
|
static void cmpp_dx_f_demo1(cmpp_dx *dx){
|
|
cmpp_dx_outf(dx, "Hello from %s%s\n",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
for( cmpp_arg const * a = dx->args.arg0;
|
|
0==cmpp_dx_err_check(dx) && a;
|
|
a = a->next ){
|
|
cmpp_dx_outf(dx, "arg type=%s n=%u z=%.*s\n",
|
|
cmpp_tt_cstr(a->ttype),
|
|
(unsigned)a->n, (int)a->n, a->z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Internal helper for other directives. Emits an HTML <div> tag. If
|
|
passed any arguments, each is assumed to be a CSS class name and is
|
|
applied to the DIV. This does _not_ emit the lcosing DIV
|
|
tag. Returns 0 on success.
|
|
*/
|
|
static int divOpener(cmpp_dx *dx){
|
|
cmpp_dx_out_raw(dx, "<div", 4);
|
|
int nClass = 0;
|
|
for( cmpp_arg const * a = dx->args.arg0;
|
|
0==cmpp_dx_err_check(dx) && a;
|
|
a = a->next ){
|
|
if( 1==++nClass ){
|
|
cmpp_dx_out_raw(dx, " class='", 8);
|
|
}else{
|
|
cmpp_dx_out_raw(dx, " ", 1);
|
|
}
|
|
cmpp_dx_out_raw(dx, a->z, a->n);
|
|
}
|
|
return cmpp_dx_out_raw(dx, nClass ? "'>" : ">", 1 + !!nClass);
|
|
}
|
|
|
|
/**
|
|
Opens an HTML DIV tag, as per divOpener().
|
|
*/
|
|
static void cmpp_dx_f_divOpen(cmpp_dx *dx){
|
|
if( 0==divOpener(dx) ){
|
|
int * const nDiv = dx->d->impl.state;
|
|
assert( nDiv );
|
|
++*nDiv;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Closes an HTML DIV tag which was opened by cmpp_dx_f_divOpen().
|
|
*/
|
|
static void cmpp_dx_f_divClose(cmpp_dx *dx){
|
|
int * const nDiv = dx->d->impl.state;
|
|
assert( nDiv );
|
|
if( *nDiv > 0 ){
|
|
--*nDiv;
|
|
}else{
|
|
char const * const zDelim = cmpp_dx_delim(dx);
|
|
cmpp_dx_err_set(
|
|
dx, CMPP_RC_MISUSE,
|
|
"%s/%s was used without an opening %s%s directive",
|
|
zDelim, dx->d->name.z, zDelim, dx->d->name.z
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Another HTML DIV-inspired wrapper which consumes a block of input
|
|
and wraps it in a DIV. This is functionally the same as the
|
|
divOpen/divClose examples but demonstrates how to slurp up the
|
|
content between the open/close directives from within the opening
|
|
directive's callback.
|
|
*/
|
|
static void cmpp_dx_f_divWrapper(cmpp_dx *dx){
|
|
if( divOpener(dx) ) return;
|
|
cmpp_b os = {0};
|
|
if( 0==cmpp_dx_consume_b(
|
|
dx, &os, &dx->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D
|
|
)
|
|
/* ^^^ says "read to the matching #/div" accounting for, and
|
|
processing, nested directives). The #/div closing tag is
|
|
identified by dx->d->closer. */
|
|
){
|
|
cmpp_b_chomp( &os );
|
|
/* Recall that most cmpp APIs become no-ops if dx->pp has an error
|
|
set, so we don't strictly need to error-check these calls: */
|
|
cmpp_dx_out_raw(dx, os.z, os.n);
|
|
cmpp_dx_out_raw(dx, "</div>\n", 7);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
}
|
|
|
|
/**
|
|
A cmpp_d_autoload_f() impl for testing and demonstration
|
|
purposes.
|
|
*/
|
|
int cmpp_d_autoload_f_demos(cmpp *pp, char const *dname, void *state){
|
|
(void)state;
|
|
cmpp_api_init(pp);
|
|
|
|
#define M(NAME) (0==strcmp(NAME,dname))
|
|
#define MOC(NAME) (M(NAME) || M("/"NAME))
|
|
|
|
#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \
|
|
cmpp_d_reg SYMNAME = { \
|
|
.name = NAME, \
|
|
.opener = { \
|
|
.f = OPENER, \
|
|
.flags = OFLAGS \
|
|
}, \
|
|
.closer = { \
|
|
.f = CLOSER, \
|
|
.flags = CFLAGS \
|
|
}, \
|
|
.dtor = 0, \
|
|
.state = 0 \
|
|
}
|
|
|
|
#define CHECK(NAME,CHECKCLOSER,OPENER,OFLAGS,CLOSER,CFLAGS) \
|
|
if( M(NAME) || (CHECKCLOSER && M("/"NAME)) ){ \
|
|
DREG0(reg,NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \
|
|
return cmpp_d_register(pp, ®, NULL); \
|
|
} (void)0
|
|
|
|
|
|
CHECK("demo1", 0, cmpp_dx_f_demo1, cmpp_d_F_ARGS_LIST, 0, 0);
|
|
|
|
CHECK("demo-div-wrapper", 1, cmpp_dx_f_divWrapper,
|
|
cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL,
|
|
cmpp_dx_f_dangling_closer, 0);
|
|
|
|
if( MOC("demo-div") ){
|
|
cmpp_d_reg const r = {
|
|
.name = "demo-div",
|
|
.opener = {
|
|
.f = cmpp_dx_f_divOpen,
|
|
.flags = cmpp_d_F_ARGS_LIST | cmpp_d_F_NO_CALL
|
|
},
|
|
.closer = {
|
|
.f = cmpp_dx_f_divClose
|
|
},
|
|
.state = cmpp_malloc(sizeof(int)),
|
|
.dtor = cmpp_mfree
|
|
};
|
|
/* State for one of the custom directives. */;
|
|
int const rc = cmpp_d_register(pp, &r, NULL);
|
|
if( 0==rc ){
|
|
*((int*)r.state) = 0
|
|
/* else reg.state was freed by cmpp_d_register() */;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#undef M
|
|
#undef MOC
|
|
#undef CHECK
|
|
#undef DREG0
|
|
return CMPP_RC_NO_DIRECTIVE;
|
|
}
|
|
|
|
int cmpp_module__demo_register(cmpp *pp){
|
|
cmpp_api_init(pp);
|
|
int rc;
|
|
#define X(D) \
|
|
rc = cmpp_d_autoload_f_demos(pp, D, NULL); \
|
|
if( rc && CMPP_RC_NO_DIRECTIVE!=rc ) goto end
|
|
X("demo1");
|
|
X("demo-div");
|
|
X("demo-div-wrapper");
|
|
#undef X
|
|
end:
|
|
return cmpp_err_get(pp, NULL);
|
|
}
|
|
CMPP_MODULE_REGISTER1(demo);
|
|
#endif /* include guard */
|