mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-23 21:49:02 +03:00
19025 lines
610 KiB
C
19025 lines
610 KiB
C
/**
|
|
This is the auto-generated "amalgamation build" of libcmpp. This C
|
|
file contains both the header and source file for c-pp,
|
|
a.k.a. libcmpp. It was amalgamated using:
|
|
|
|
./c-pp -I. -I./src -Dsrcdir=./src -o libcmpp.c ./tool/libcmpp.c-pp.c
|
|
|
|
with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC
|
|
*/
|
|
#if !defined(NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED)
|
|
#define NET_WANDERINGHORSE_LIBCMPP_C_INCLUDED
|
|
#if !defined(_POSIX_C_SOURCE)
|
|
# define _POSIX_C_SOURCE 200809L /* for fdopen() in stdio.h */
|
|
#endif
|
|
#define CMPP_AMALGAMATION
|
|
#if !defined(NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED)
|
|
/**
|
|
This is the auto-generated "amalgamation build" of libcmpp. It was amalgamated
|
|
using:
|
|
|
|
./c-pp -I. -I./src -Dsrcdir=./src -o libcmpp.h ./tool/libcmpp.c-pp.h
|
|
|
|
with libcmpp 2.0.x 4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46 @ 2026-09-30 09:12:44.230 UTC
|
|
*/
|
|
#define CMPP_PACKAGE_NAME "libcmpp"
|
|
#define CMPP_LIB_VERSION "2.0.x"
|
|
#define CMPP_LIB_VERSION_HASH "4539e17f451054d2aeb6a5ffe3891af087fd8fa6cfae4e31d02e1fddfec69d46"
|
|
#define CMPP_LIB_VERSION_TIMESTAMP "2026-09-30 09:12:44.230 UTC"
|
|
#define CMPP_LIB_CONFIG_TIMESTAMP "2026-09-30 09:14 GMT"
|
|
#define CMPP_VERSION CMPP_LIB_VERSION " " CMPP_LIB_VERSION_HASH " @ " CMPP_LIB_VERSION_TIMESTAMP
|
|
#define CMPP_PLATFORM_EXT_DLL ".so"
|
|
#define CMPP_MODULE_PATH ".:/usr/local/lib/cmpp"
|
|
|
|
#if !defined(NET_WANDERINGHORSE_CMPP_H_INCLUDED_)
|
|
#define NET_WANDERINGHORSE_CMPP_H_INCLUDED_
|
|
/*
|
|
** 2022-11-12:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
**
|
|
** The C-minus Preprocessor: C-like preprocessor. Why? Because C
|
|
** preprocessors _can_ process non-C code but generally make quite a
|
|
** mess of it. The purpose of this library is a customizable
|
|
** preprocessor suitable for use with arbitrary UTF-8-encoded text.
|
|
**
|
|
** The supported preprocessor directives are documented in the
|
|
** README.md hosted with this file (or see the link below).
|
|
**
|
|
** Any mention of "#" in the docs, e.g. "#if", is symbolic. The
|
|
** directive delimiter is configurable and defaults to "##". Define
|
|
** CMPP_DEFAULT_DELIM to a string when compiling to define the default
|
|
** at build-time.
|
|
**
|
|
** This API is presented as a library but was evolved from a
|
|
** monolithic app. Thus the library interface is likely still missing
|
|
** some pieces needed to make it more readily usable as a library.
|
|
**
|
|
** Author(s):
|
|
**
|
|
** - Stephan Beal <https://wanderinghorse.net/home/stephan/>
|
|
**
|
|
** Canonical home:
|
|
**
|
|
** - <https://fossil.wanderinghorse.net/r/c-pp>
|
|
**
|
|
** One may see mentions of %amalgamation and %lite in some comments in
|
|
** this file and its implementation files. Those are preprocessor
|
|
** directives for this very preprocessor, used by the build process to
|
|
** filter out certain pieces to produce its amalgamation build
|
|
** and (derived from that) the "lite" build. Their placement is
|
|
** significant, especially when they're embedded within a multi-line C
|
|
** macro or at the end of the line with a C directive. In normal
|
|
** builds they're innocuous, but when passed through the "lightener"
|
|
** process, they activate (see Makefile.in keyword "%lite" and
|
|
** tool/libcmpp.c-pp.[ch]).
|
|
*/
|
|
/**
|
|
License note: the license headers have been stripped from other
|
|
files in this amalgamation build in order to save space. They all
|
|
have the same license as the one above but may have different
|
|
dates. The canonical sources are authoritative with regards to the
|
|
licenses of the code in each segment of this amalgamation.
|
|
*/
|
|
|
|
#if defined(CMPP_HAVE_AUTOCONFIG_H)
|
|
#include "libcmpp-autoconfig.h"
|
|
#endif
|
|
#if defined(HAVE_AUTOCONFIG_H)
|
|
#include "autoconfig.h"
|
|
#endif
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
# if defined(BUILD_libcmpp_static) || defined(CMPP_AMALGAMATION_BUILD)
|
|
# define CMPP_EXPORT extern
|
|
# elif defined(BUILD_libcmpp)
|
|
# define CMPP_EXPORT extern __declspec(dllexport)
|
|
# else
|
|
# define CMPP_EXPORT extern __declspec(dllimport)
|
|
# endif
|
|
#else
|
|
# define CMPP_EXPORT extern
|
|
#endif
|
|
|
|
/**
|
|
cmpp_FILE is a portability hack for WASM builds, where we want to
|
|
elide the (FILE*)-using pieces to avoid having a dependency on
|
|
Emscripten's POSIX I/O proxies. In all non-WASM builds it is
|
|
guaranteed to be an alias for FILE. On WASM builds it is guaranteed
|
|
to be an alias for void and the cmpp APIs which use it become
|
|
inoperative in WASM builds.
|
|
|
|
That said: the code does not yet support completely compiling out
|
|
(FILE*) dependencies, and may not be able to because canonical
|
|
sqlite3 (upon which it is based) depends heavily on file
|
|
descriptors and slightly on FILE handles.
|
|
*/
|
|
#if defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasi__)
|
|
typedef void cmpp_FILE;
|
|
# define CMPP_PLATFORM_IS_WASM 1
|
|
#else
|
|
#include <stdio.h>
|
|
typedef FILE cmpp_FILE;
|
|
# define CMPP_PLATFORM_IS_WASM 0
|
|
#endif
|
|
|
|
#include <stdint.h>
|
|
#include <inttypes.h> /* PRIu32 and friends */
|
|
#include <stdbool.h>
|
|
#include "sqlite3.h" /* sqlite3_str */
|
|
|
|
/**
|
|
For loadable modules to be able to portably access the cmpp API,
|
|
without requiring that their loading binary be linked with
|
|
-rdynamic, we need a "thunk". The API exposes cmpp_api_thunk for
|
|
that purpose. The following macros set up the thunk for a given
|
|
compilation unit. They are intended to only be used by loadable
|
|
modules, not generic client code.
|
|
|
|
Before including this header, define CMPP_API_THUNK with no value
|
|
and/or define CMPP_API_THUNK_NAME to a C symbol name. The latter
|
|
macro implies the former and defines the name of the static symbol
|
|
to be the local cmpp_api_thunk instance, defaulting to cmppApi.
|
|
|
|
The first line of a module's registration function should then be:
|
|
|
|
cmpp_api_init(pp);
|
|
|
|
where pp is the name of the sole argument to the registration
|
|
callback. After that is done, the cmpp_...() APIs may be used via
|
|
the macros defined elsewhere in this header, all of which route
|
|
through the thunk object.
|
|
*/
|
|
#if defined(CMPP_API_THUNK) || defined(CMPP_API_THUNK_NAME)
|
|
# if !defined(CMPP_API_THUNK)
|
|
# define CMPP_API_THUNK
|
|
# endif
|
|
# if !defined(CMPP_API_THUNK_NAME)
|
|
# define CMPP_API_THUNK_NAME cmppApi
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct cmpp__pimpl cmpp__pimpl;
|
|
typedef struct cmpp_api_thunk cmpp_api_thunk;
|
|
typedef struct cmpp_outputer cmpp_outputer;
|
|
typedef struct cmpp_b cmpp_b;
|
|
typedef struct cmpp_arg cmpp_arg;
|
|
typedef void cmpp_itch;
|
|
|
|
/**
|
|
32-bit flag bitmask type. This typedef exists primarily to improve
|
|
legibility of function signatures and member structs by conveying
|
|
their intent for use as flags instead of result codes or lengths.
|
|
*/
|
|
typedef uint32_t cmpp_flag32_t;
|
|
|
|
/**
|
|
An X-macro which invokes its argument (a macro name) to expand to
|
|
all possible values of cmpp_rc_e entries. The macro name passed to
|
|
it is invoked once for each entry and passed 3 arguments: the enum
|
|
entry's full name (CMPP_RC_...), its integer value, and a terse
|
|
informational string.
|
|
*/
|
|
#define cmpp_rc_e_map(E) \
|
|
E(CMPP_RC_OK, 0, \
|
|
"The quintessential not-an-error value.") \
|
|
E(CMPP_RC_ERROR, 100, \
|
|
"Generic/unknown error.") \
|
|
E(CMPP_RC_NYI, 101, \
|
|
"A placeholder return value for not yet implemented functions.") \
|
|
E(CMPP_RC_OOM, 102, \
|
|
"Out of memory.") \
|
|
E(CMPP_RC_MISUSE, 103, \
|
|
"API misuse (invalid args)") \
|
|
E(CMPP_RC_RANGE, 104, \
|
|
"A range was violated.") \
|
|
E(CMPP_RC_ACCESS, 105, \
|
|
"Access to or locking of a resource was denied " \
|
|
"by some security mechanism or other.") \
|
|
E(CMPP_RC_IO, 106, \
|
|
"Indicates an I/O error. Whether it was reading or " \
|
|
"writing is context-dependent.") \
|
|
E(CMPP_RC_NOT_FOUND, 107, \
|
|
"Requested resource not found.") \
|
|
E(CMPP_RC_ALREADY_EXISTS, 108, \
|
|
"Indicates that a to-be-created resource already exists.") \
|
|
E(CMPP_RC_CORRUPT, 109, \
|
|
"Data consistency problem.") \
|
|
E(CMPP_RC_SYNTAX, 110, \
|
|
"Some sort of syntax error.") \
|
|
E(CMPP_RC_NOOP, 111, \
|
|
"Special sentinel value for some APIs.") \
|
|
E(CMPP_RC_UNSUPPORTED, 112, \
|
|
"An unsupported operation was request.") \
|
|
E(CMPP_RC_DB, 113, \
|
|
"Indicates db-level error (e.g. statement prep failed). In such " \
|
|
"cases, the error state of the related db handle (cmpp_db) " \
|
|
"will be updated to contain more information directly from the " \
|
|
"db driver.") \
|
|
E(CMPP_RC_NOT_DEFINED, 114, \
|
|
"Failed to expand an undefined value.") \
|
|
E(CMPP_RC_ASSERT, 116, "An #assert failed.") \
|
|
E(CMPP_RC_TYPE, 118, \
|
|
"Indicates that some data type or logical type is incorrect.") \
|
|
E(CMPP_RC_CANNOT_HAPPEN, 140, \
|
|
"This is intended only for internal use, to " \
|
|
"report conditions which \"cannot possibly happen\".") \
|
|
E(CMPP_RC_HELP, 141, \
|
|
"--help was used in the arguments to cmpp_process_argv()") \
|
|
E(CMPP_RC_NO_DIRECTIVE, 142, \
|
|
"A special case of CMPP_RC_NOT_FOUND needed to disambiguate.") \
|
|
E(CMPP_RC_MODULE_VERSION, 143, \
|
|
"API version mismatch when loading a module.") \
|
|
E(CMPP_RC_end,200, \
|
|
"Must be the final entry in the enum. Used for creating " \
|
|
"client-side result codes which are guaranteed to live outside " \
|
|
"of this one's range.")
|
|
|
|
/**
|
|
Most functions in this library which return an int return result
|
|
codes from the cmpp_rc_e enum. None of these entries are
|
|
guaranteed to have a specific value across library versions except
|
|
for CMPP_RC_OK, which is guaranteed to always be 0 (and the API
|
|
guarantees that no other code shall have a value of zero).
|
|
|
|
The only reasons numbers are hard-coded to the values is to
|
|
simplify debugging during development. Clients may use
|
|
cmpp_rc_cstr() to get some human-readable (or programmer-readable)
|
|
form for any given value in this enum.
|
|
*/
|
|
enum cmpp_rc_e {
|
|
#define E(N,V,H) N = V,
|
|
cmpp_rc_e_map(E)
|
|
#undef E
|
|
};
|
|
typedef enum cmpp_rc_e cmpp_rc_e;
|
|
|
|
/**
|
|
Returns the string form of the given cmpp_rc_e value or NULL if
|
|
it's not a member of that enum.
|
|
*/
|
|
char const * cmpp_rc_cstr(int rc);
|
|
#define cmpp_rc_ucstr(RC) (unsigned char const*)cmpp_rc_cstr(RC)
|
|
|
|
/**
|
|
CMPP_BITNESS specifies whether the library should use 32- or 64-bit
|
|
integer types for its size/length measurements. It's difficult to
|
|
envision use cases for a preprocessor which would require counters
|
|
or ranges larger than 32 bits provide for, so the default is 32
|
|
bits. Builds created with different CMPP_BITNESS values are not
|
|
binary-compatible.
|
|
*/
|
|
#define CMPP_BITNESS 32
|
|
#if 32==CMPP_BITNESS
|
|
/**
|
|
Unsigned integer type for string/stream lengths. 32 bits is
|
|
sufficient for all but the weirdest of inputs and outputs.
|
|
*/
|
|
typedef uint32_t cmpp_size_t;
|
|
|
|
/**
|
|
A signed integer type indicating the maximum length of strings or
|
|
byte ranges in a stream. It is most frequently used in API
|
|
signatures where "if this value is negative then use
|
|
strlen(someOtherArg) to count it".
|
|
|
|
Maintenance reminder: this type must remain 32-bits or risks
|
|
breaking countless printf-style formatting uses, where it frequently
|
|
ends up being used with "%.*s".
|
|
*/
|
|
typedef int32_t cmpp_strlen_t;
|
|
|
|
/**
|
|
The printf-format-compatible format letter (or group of letters)
|
|
appropriate for use with cmpp_size_t. Contrary to popular usage,
|
|
size_t cannot be portably used with printf(), without careful
|
|
casting, because it has neither a fixed size nor a standardized
|
|
printf/scanf format specifier (like the stdint.h types do).
|
|
*/
|
|
#define CMPP_SIZE_T_PFMT PRIu32
|
|
#elif 64==CMPP_BITNESS
|
|
typedef uint64_t cmpp_size_t;
|
|
typedef int64_t cmpp_strlen_t;
|
|
#define CMPP_SIZE_T_PFMT PRIu64
|
|
#else
|
|
#error "Invalid CMPP_BITNESS value. Expecting 32 or 64."
|
|
#endif
|
|
|
|
/**
|
|
Semantically like cmpp_strlen_t but guranteed to be 32 bits. This
|
|
is primarily provided as a reminder that some sources to which we
|
|
pass negative values as a length are limited to 32-bit signed
|
|
ranges.
|
|
*/
|
|
typedef int32_t cmpp_ssize32_t;
|
|
|
|
/**
|
|
Generic interface for streaming in data. Implementations must read
|
|
(at most) *n bytes from their input, copy it to dest, assign *n to
|
|
the number of bytes actually read, return 0 on success, and return
|
|
non-0 cmpp_rc_e value on error (e.g. CMPP_RC_IO).
|
|
|
|
When called, *n is the max length to read. On return, *n must be
|
|
set to the amount actually read. Implementations may need to
|
|
internally distinguish a short read due to EOF from a short read
|
|
due to an I/O error, e.g. using feof() and/or ferror(). A short
|
|
read for EOF is not an error but a short read for input failure is.
|
|
This library invariably treats a short read as EOF unless the
|
|
cmpp_input_f() implementation says otherwise.
|
|
|
|
The state parameter is the implementation-specified input
|
|
file/buffer/whatever channel.
|
|
*/
|
|
typedef int (*cmpp_input_f)(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
Generic interface for streaming out data. Implementations must
|
|
write n bytes from src to their destination channel and return 0 on
|
|
success, or a value from the cmpp_rc_e enum on error
|
|
(e.g. CMPP_RC_IO). The state parameter is the
|
|
implementation-specified output channel.
|
|
|
|
It is implementation-defined whether an n of 0 is legal. This
|
|
library refrains from passing 0 to these functions.
|
|
|
|
In the context of cmpp, the library makes no guarantees that output
|
|
will always end at a character boundary. It may send any given
|
|
multibyte character as the end resp. start of two calls to this
|
|
function. If that is of a concern for implementors of these
|
|
functions (e.g. because they're appending the output to a UI
|
|
widget), they may need to buffer all of the output before applying
|
|
it (see cmpp_b), or otherwise account for partial characters.
|
|
|
|
That said: the core library, by an accident of design, will always
|
|
emit data at character boundaries, assuming that its input is
|
|
well-formed UTF-8 text (which cmpp does not validate to be the
|
|
case). Custom cmpp_f() implementations are not strictly
|
|
required to do so but, because of how cmpp is used, almost
|
|
certainly will. But relying on that is ill-advised.
|
|
*/
|
|
typedef int (*cmpp_output_f)(void * state, void const * src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Generic interface for flushing arbitrary output streams. Must
|
|
return 0 on success, a non-0 cmpp_rc_e value on error. When in
|
|
doubt, return CMPP_RC_IO on error. The interpretation of the state
|
|
parameter is implementation-specific.
|
|
*/
|
|
typedef int (*cmpp_flush_f)(void * state);
|
|
|
|
/**
|
|
The library's primary class. Each one of these represents a
|
|
separate preprocessor instance.
|
|
|
|
See also: cmpp_dx (the class which client-side extensions interact
|
|
with the most).
|
|
*/
|
|
struct cmpp {
|
|
/**
|
|
API thunk object to support use via loadable modules. Client code
|
|
does not normally need to access this member, but it's exposed
|
|
here to give loadable modules more flexibility in how they use
|
|
the thunk.
|
|
|
|
This pointer is _always_ the same singleton object and the
|
|
library never exposes a cmpp object with a NULL api member.
|
|
*/
|
|
cmpp_api_thunk const * const api;
|
|
|
|
/**
|
|
Private internal state. Owned by this object.
|
|
*/
|
|
cmpp__pimpl * const pimpl;
|
|
};
|
|
typedef struct cmpp cmpp;
|
|
|
|
/**
|
|
Flags for use with cmpp_ctor_opt::flags.
|
|
*/
|
|
enum cmpp_ctor_e {
|
|
/* Sentinel value. */
|
|
cmpp_ctor_F_none = 0,
|
|
/* Disables #include. */
|
|
cmpp_ctor_F_NO_INCLUDE = 0x01,
|
|
/* Disables #pipe. */
|
|
cmpp_ctor_F_NO_PIPE = 0x02,
|
|
/* Disables #attach, #detach, and #query. */
|
|
cmpp_ctor_F_NO_DB = 0x04,
|
|
/* Disables #module. */
|
|
cmpp_ctor_F_NO_MODULE = 0x08,
|
|
/**
|
|
Disable all built-in directives which may work with the filesystem
|
|
or invoke external processes. Client-defined directives with the
|
|
cmpp_f_F_NOT_IN_SAFEMODE flag are also disabled. Directives
|
|
disabled via the cmpp_ctor_F_NO_... flags (or equivalent library
|
|
built-time options) do not get registered, so will trigger
|
|
"unknown directive" errors rather than safe-mode violation errors.
|
|
*/
|
|
cmpp_ctor_F_SAFEMODE = 0x10,
|
|
|
|
|
|
cmpp_ctor_F_DUMMY_TRAILING_COMMA_OCD
|
|
};
|
|
|
|
/**
|
|
A configuration object for cmpp_ctor(). This type may be extended
|
|
as new construction-time customization opportunities are
|
|
discovered.
|
|
*/
|
|
struct cmpp_ctor_opt {
|
|
/**
|
|
Bitmask from the cmpp_ctor_e enum.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
/**
|
|
If not NULL then this must name either an existing SQLite3 db
|
|
file or the name of one which can be created on demand. If NULL
|
|
then an in-memory or temporary database is used (which one is
|
|
unspecified). The library copies these bytes, so they need not be
|
|
valid after a call to cmpp_ctor().
|
|
*/
|
|
char const * dbFile;
|
|
};
|
|
typedef struct cmpp_ctor_opt cmpp_ctor_opt;
|
|
|
|
/**
|
|
Assigns *pp to a new cmpp or NULL on OOM. Any non-NULL return
|
|
value must eventually be passed to cmpp_dtor() to free it.
|
|
|
|
The cfg argument, if not NULL, holds config info for the new
|
|
instance. If NULL, an instance with unspecified defaults is
|
|
used. These configuration pieces may not be modified after the
|
|
instance is created.
|
|
|
|
It returns 0 if *pp is ready to use and non-0 if either allocation
|
|
fails (in which case *pp will be set to 0) or initialization of *pp
|
|
failed (in which case cmpp_err_get() can be used to determine why
|
|
it failed). In either case, the caller must eventually pass *pp to
|
|
cmpp_dtor() to free it.
|
|
|
|
If the library is built with the preprocessor symbol
|
|
CMPP_CTOR_INSTANCE_INIT defined, it must refer to a function with
|
|
this signature:
|
|
|
|
int CMPP_CTOR_INSTANCE_INIT(cmpp *);
|
|
|
|
The library calls this before returning and arranges to call it
|
|
lazily if pp gets reset. The intent is that the init function
|
|
installs custom directives using cmpp_d_register(). That
|
|
initialization, on error, is expected to set its argument's error
|
|
state with cmpp_errf().
|
|
*/
|
|
CMPP_EXPORT int cmpp_ctor(cmpp **pp, cmpp_ctor_opt const * cfg);
|
|
|
|
/**
|
|
If pp is not NULL, it is passed to cmpp_reset() and then freed.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dtor(cmpp *pp);
|
|
|
|
|
|
/**
|
|
realloc(3)-compatible allocator used by the library.
|
|
|
|
This API very specifically uses sqlite3_realloc() as its basis.
|
|
*/
|
|
CMPP_EXPORT void * cmpp_mrealloc(void * p, size_t n);
|
|
|
|
/**
|
|
malloc(3)-compatible allocator used by the library.
|
|
|
|
This API very specifically uses sqlite3_malloc() as its basis.
|
|
*/
|
|
CMPP_EXPORT void * cmpp_malloc(size_t n);
|
|
|
|
/**
|
|
Proxy for cmpp_malloc() which (A) is a no-op returning NULL pp has
|
|
error state and (B) sets pp's error state on OOM.
|
|
*/
|
|
void * cmpp_malloc2(cmpp* pp, cmpp_size_t n);
|
|
|
|
/**
|
|
free(3)-style deallocator. It can also be used as a destructor for
|
|
cmpp_d_register() _if_ the memory in question is allocated by
|
|
cmpp_malloc(), cmpp_realloc(), or the sqlite3_malloc() family of
|
|
APIs.
|
|
|
|
This is not called cmpp_free() to try to avoid any confusion with
|
|
cmpp_dtor().
|
|
*/
|
|
CMPP_EXPORT void cmpp_mfree(void *);
|
|
|
|
/**
|
|
If m is NULL then pp's persistent error code is set to CMPP_RC_OOM,
|
|
else this is a no-op. Returns pp's error code.
|
|
|
|
To simplify certain uses, pp may be NULL, in which case this
|
|
function returns CMPP_RC_OOM if m is NULL and 0 if it's not.
|
|
*/
|
|
CMPP_EXPORT int cmpp_check_oom(cmpp * const pp, void const * m );
|
|
|
|
/**
|
|
Re-initializes all state of pp. This saves some memory for reuse
|
|
but resets it all to default states. This closes the database and
|
|
will also reset any autoloader, policies, or delimiter
|
|
configurations to their compile-time defaults. It retains only a
|
|
small amount of state, like any configuration which was passed to
|
|
cmpp_ctor().
|
|
|
|
After calling this, pp is in a cleanly-initialized state and may be
|
|
re-used with the cmpp API. Its database will not be initialized
|
|
until an API which needs it is called, so pp can be used with
|
|
functions which may otherwise be prohibited after the db is
|
|
opened. (Do we still have any?)
|
|
|
|
As of this writing, this is the only way to reliably recover a cmpp
|
|
instance from any significant errors. Errors may do things like
|
|
leave savepoints out of balance, and this cleanup step resets all
|
|
of that state. However, it also loses state like the autoloader.
|
|
|
|
TODO?: we need(?) a partial-clear operation which keeps some of the
|
|
instance's state, most notably custom directives, the db handle,
|
|
and any cached prepared statements. See cmpp_errf() for the
|
|
distinction between recoverable and non-recoverable errors.
|
|
*/
|
|
CMPP_EXPORT void cmpp_reset(cmpp *pp);
|
|
|
|
#if 0
|
|
Not yet;
|
|
/**
|
|
If called before pp has initialized its database, this sets the
|
|
file name used for that database. If called afterwards, pp's error
|
|
state is updated and CMPP_RC_MISUSE is returned. If called while
|
|
pp has error state set, that code is returned without side-effects.
|
|
|
|
This does not open the database. It is opened on demand when
|
|
processing starts.
|
|
|
|
On success it returns 0 and this function makes a copy of zName.
|
|
|
|
As a special case, zName may be NULL to use the default name, but
|
|
there is little reason to do so unless one changes their mind after
|
|
setting it to non-NULL.
|
|
*/
|
|
//CMPP_EXPORT int cmpp_db_name_set(cmpp *pp, const char * zName);
|
|
#endif
|
|
|
|
/**
|
|
Returns true if the bytes in the range [zName, zName+n) comprise a
|
|
legal name for a directive or a define.
|
|
|
|
It disallows any control characters, spaces, and most punctuation,
|
|
but allows alphanumeric (but must not start with a number) as well
|
|
as any of: -./:_ (but it may not start with '-'). Any characters
|
|
with a high bit set are assumed to be UTF-8 and are permitted as
|
|
well.
|
|
|
|
The name's length is limited, rather arbitrarily, to 64 bytes.
|
|
|
|
If the key is not legal then false is returned and if zErrPos is
|
|
not NULL then *zErrPos is set to the position in zName of the first
|
|
offending character. If validation fails because n is too long then
|
|
*zErrPos (if zErrPos is not NULL) will be set to 0.
|
|
|
|
Design note: this takes unsigned characters because it most
|
|
commonly takes input from cmpp_arg::z strings.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos);
|
|
|
|
/**
|
|
Adds the given `#define` name to the list of defines, overwriting
|
|
any previous value (scoped to the current savepoint, if any).
|
|
|
|
zKey must be NUL-terminated and legal as a key. The rules are the
|
|
same as for cmpp_is_legal_key() except that a '=' is also permitted
|
|
if it's not at the start of the string because...
|
|
|
|
If zVal is NULL then zKey may contain an '=', from which the value
|
|
will be extracted. If zVal is not NULL then zKey may _not_ contain
|
|
an '='.
|
|
|
|
The ability for zKey to contain a key=val was initially to
|
|
facilitate input from the CLI (e.g. -Dfoo=bar) because cmpp was
|
|
initially a CLI app (as opposed to a library). It's considered a
|
|
"legacy" feature, not recommended for most purposes, but it _is_
|
|
convenient for that particular purpose.
|
|
|
|
Returns 0 on success and updates pp's error state on error.
|
|
|
|
See: cmpp_define_v2()
|
|
See: cmpp_undefine()
|
|
See: cmpp_undef_legacy()
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_legacy(cmpp *pp, const char * zKey,
|
|
char const *zVal);
|
|
|
|
/**
|
|
Works like cmpp_define_legacy() except that it does not examine zKey to
|
|
see if it contains an '='.
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_v2(cmpp *pp, const char * zKey, char const *zVal);
|
|
|
|
/**
|
|
Removes the given `#define` name from the list of defines. zKey is,
|
|
in this case, treated as a GLOB pattern, and all matching defines
|
|
are deleted. (This ugly historical behavior will be removed at some
|
|
point, along with that argument!)
|
|
|
|
If nRemoved is not NULL then, on success, it is set to the number
|
|
of entries removed by this call.
|
|
|
|
Returns 0 on success and updates pp's error state on error. It is not
|
|
an error if no value was undefined.
|
|
|
|
This does _not_ affect defines made using cmpp_define_shadow().
|
|
*/
|
|
CMPP_EXPORT int cmpp_undef_legacy(cmpp *pp, const char * zKey,
|
|
unsigned int *nRemoved);
|
|
/**
|
|
Undefines the define with the given name (an exact match). Returns
|
|
0 on success. If pp
|
|
has error state, this is a no-op returning the current error code,
|
|
otherwise it returns non-0 only on allocation error or db-related
|
|
errors (which, in practice, do not happen for in-memory databases
|
|
unless they've run out of memory).
|
|
*/
|
|
CMPP_EXPORT int cmpp_undefine(cmpp *pp, const char * zKey,
|
|
cmpp_strlen_t n);
|
|
|
|
/**
|
|
This works similarly to cmpp_define_v2() except that the new define
|
|
"shadows", rather than overwrites, an existing define with the same
|
|
name.
|
|
|
|
All APIs which look up define keys will get the value of the shadow
|
|
define. The shadow can be uninstalled with cmpp_define_unshadow(),
|
|
effectively restoring its previous value (if any). That function
|
|
should be called one time for each call to this one, passing the
|
|
same key to each call. A given key may be shadowed any number of
|
|
times by this routine. Each one saves the internal ID of the shadow
|
|
into *pId (which must not be NULL). That value must be passed to
|
|
cmpp_define_unshadow() to ensure that the "shadow stack" stays
|
|
balanced.
|
|
|
|
cmpp_undef() will _not_ undefine an entry added through this
|
|
interface.
|
|
|
|
Returns pp's persistent error code (0 on success).
|
|
|
|
Design note: this function was added to support adding a define
|
|
named __FILE__ to input scripts which works like it does in a C
|
|
preprocessor. Alas, supporting __LINE__ would be much more costly,
|
|
as it would have to be updated in the db from several places, so
|
|
its cost would outweigh its meager benefits.
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_shadow(cmpp *pp, char const *zKey,
|
|
char const *zVal,
|
|
int64_t * pId);
|
|
|
|
/**
|
|
Removes the shadow define matching the zKey and id values which
|
|
where previously passed to cmpp_define_shadow(). It is not an
|
|
error if no match is found, in which case this function has no
|
|
visible side-effects.
|
|
|
|
Unlike cmpp_undef(), zKey is matched precisely, not against a glob.
|
|
|
|
In order to keep the "shadow stack" properly balanced, this will
|
|
delete any shadow entries for the given key which have the same id
|
|
or a newer one (i.e. they were left over from a missed call to
|
|
cmpp_define_unshadow()).
|
|
|
|
Returns pp's persistent error code (0 on success).
|
|
*/
|
|
CMPP_EXPORT int cmpp_define_unshadow(cmpp *pp, char const *zKey,
|
|
int64_t id);
|
|
|
|
/**
|
|
Adds the given dir to the list of includes. They are checked in the
|
|
order they are added.
|
|
*/
|
|
CMPP_EXPORT int cmpp_include_dir_add(cmpp *pp, const char * zKey);
|
|
|
|
/**
|
|
Sets pp's default output channel. If pp already has a channel, it
|
|
is closed[^1].
|
|
|
|
The second argument, if not NULL, is _bitwise copied_, which has
|
|
implications for the ownership of out->state (see below). If it is
|
|
is NULL, cmpp_outputer_empty is copied in its place, which makes
|
|
further output a no-op.
|
|
|
|
The third argument is a symbolic name for the channel (perhaps its
|
|
file name). It is used in debugging and error messages. cmpp does
|
|
_not_ copy it, so its bytes must outlive the cmpp instance. (In
|
|
practice, the byte names come from main()'s argv or scope-local
|
|
strings in the same scope as the cmpp instance.) This argument
|
|
should only be NULL if the second argument is.
|
|
|
|
cmpp_reset(), or opening another channel, will end up calling
|
|
out->cleanup() (if it's not NULL) and passing it a pointer to a
|
|
_different_ cmpp_outputer object, but with the _same_
|
|
cmpp_outputer::state pointer, which may invalidate out->state.
|
|
|
|
To keep cmpp from doing that, make a copy of the output object, set
|
|
the cleanup member of that copy to NULL, then pass that copy to this
|
|
function. It is then up to the client to call out->cleanup(out) when
|
|
the time is right.
|
|
|
|
For example:
|
|
|
|
```
|
|
cmpp_outputer my = cmpp_outputer_FILE;
|
|
my.state = cmpp_fopen("/some/file", "wb");
|
|
cmpp_outputer tmp = my;
|
|
tmp.cleanup = NULL;
|
|
cmpp_outputer_set( pp, &tmp, "my file");
|
|
...
|
|
my.cleanup(&my); // will cmpp_fclose(my.state)
|
|
```
|
|
|
|
Potential TODO: internally store the output channel as a pointer.
|
|
It's not clear whether that would resolve the above grief or
|
|
compound it.
|
|
|
|
[^1]: depending on the output channel, it might not _actually_ be
|
|
closed, but pp is disassociated from it, in any case.
|
|
*/
|
|
CMPP_EXPORT
|
|
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out, char const *zName);
|
|
|
|
/**
|
|
Treats the range [zIn,zIn+nIn) as a complete cmpp input and process
|
|
it appropriately. zName is the name of the input for purposes of
|
|
error messages. If nIn is negative, strlen() is used to calculate
|
|
it.
|
|
|
|
This is a no-op if pp has any error state set. It returns pp's
|
|
persistent error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_string(cmpp *pp, const char * zName,
|
|
unsigned char const * zIn,
|
|
cmpp_strlen_t nIn);
|
|
|
|
/**
|
|
A thin proxy for cmpp_process_string() which reads its input from
|
|
the given file. Returns 0 on success, else returns pp's persistent
|
|
error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_file(cmpp *pp, const char * zName);
|
|
|
|
/**
|
|
A thin proxy for cmpp_process_string() which reads its input from
|
|
the given input source, consuming it all before passing it
|
|
on. Returns 0 on success, else returns pp's persistent error code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_stream(cmpp *pp, const char * zName,
|
|
cmpp_input_f src, void * srcState);
|
|
|
|
/**
|
|
Process the given main()-style arguments list. When calling from
|
|
main(), be sure to pass it main()'s (argc+1, argv+1) to skip argv[0]
|
|
(the binary's name).
|
|
|
|
Each argument is expected to be one of the following:
|
|
|
|
1) One of --help or -?: causes this function to return CMPP_RC_HELP
|
|
without emitting any output.
|
|
|
|
2) -DX or -DX=Y: sets define X to 1 (if no "=" is used and no Y given) or to
|
|
Y.
|
|
|
|
3) -UX: unsets all defines matching glob X.
|
|
|
|
4) -FX=Y: works like -DX=Y but treats Y as a filename and sets X to
|
|
the contents of that file.
|
|
|
|
5) -IX: adds X to the "include path". If _no_ include path is
|
|
provided then cmpp assumes a path of ".", but if _any_ paths are
|
|
provided then it does not assume that "." is in the path.
|
|
|
|
6) --chomp-F: specifies whether subsequent -F flags should "chomp"
|
|
one trailing newline from their input files.
|
|
|
|
7) --delimiter|-d=X sets the directive delimiter to X. Its default
|
|
is a compile-time constant.
|
|
|
|
8) --output|-o=filename: sets the output channel to the given file.
|
|
A value of "-" means stdout. If no output channel is opened when
|
|
this is called, and files are to be processed, stdout is
|
|
assumed. (That's a historical artifact from earlier evolutions.)
|
|
To override that behavior use cmpp_outputer_set().
|
|
|
|
9) --file|-f=filename: sets the input channel to the given file.
|
|
A value of "-" means stdin.
|
|
|
|
10) -e=SCRIPT Treat SCRIPT as a complete c-pp input and process it.
|
|
Because it's difficult to pack multiple lines of text into this,
|
|
it's really of use for testing #expr and #assert.
|
|
|
|
11) --@policy=X sets the @token@ parsing policy. X must be
|
|
one of (retain, elide, error, off) and defaults to off.
|
|
|
|
12) -@: shorthand for --@policy=error.
|
|
|
|
13) --sql-trace: enables tracing of all SQL statements to stderr.
|
|
This is useful for seeing how a script interacts with the
|
|
database. Use --no-sql-trace to disable it.
|
|
|
|
14) --sql-trace-x: like --sql-trace but replaces bound parameter
|
|
placeholders with their SQL values. Use --no-sql-trace to disable
|
|
it.
|
|
|
|
15) --dump-defines: emit all defines to stdout. They should
|
|
arguably go to stderr but that interferes with automated testing.
|
|
|
|
Any argument which does not match one of the above flags, and does
|
|
not start with a "-", is treated as if it were passed to the --file
|
|
flag.
|
|
|
|
Flags may start with either 1 or 2 dashes - they are equivalent.
|
|
|
|
Flags which take a value may either be in the form X=Y or X Y, i.e.
|
|
may be a single argv entry or a pair of them.
|
|
|
|
It performs two passes on the arguments: the first is for validation
|
|
checking for --help/-?. No processing of the input(s) and output(s)
|
|
happens unless the first pass completes. Similarly, no validation of
|
|
whether any provided filename are actually readable is performed
|
|
until the second pass.
|
|
|
|
Arguments are processed in the order they are given. Thus the following
|
|
have completely different meanings:
|
|
|
|
1) -f foo.in -Dfoo
|
|
2) -Dfoo -f foo.in
|
|
|
|
The former will process foo.in before defining foo.
|
|
|
|
This behavior makes it possible to process multiple input files in
|
|
a single go:
|
|
|
|
--output foo.out foo.in -Dfoo foo.in -Ufoo --output bar.out -Dbar foo.in
|
|
|
|
It returns 0 on success. cmpp_err_get() can be used to fetch any
|
|
error message.
|
|
*/
|
|
CMPP_EXPORT int cmpp_process_argv(cmpp *pp, int argc, char const * const * argv);
|
|
|
|
/**
|
|
Intended to be called if cmpp_process_argv() returns CMPP_RC_HELP.
|
|
It emits --help-style text to the given output stream.
|
|
As the first argument pass it either argv[0] or NULL. The second
|
|
should normally be stdout or stderr.
|
|
|
|
Reminder to self: this could take a (cmpp_output_f,void*) pair
|
|
instead, and should do so for the sake of WASI builds, but its impl
|
|
currently relies heavily on fprintf() formatting.
|
|
*/
|
|
CMPP_EXPORT void cmpp_process_argv_usage(char const *zAppName,
|
|
cmpp_FILE *os);
|
|
|
|
/**
|
|
Returns pp's current error number (from the cmpp_rc_e enum) and
|
|
sets *zMsg (if zMsg is not NULL) to the error string or NULL if no
|
|
error is set. The bytes are owned by pp and may be invalidated by
|
|
any functions which take pp as an argument.
|
|
|
|
See cmpp_err_get() for more information.
|
|
|
|
Naming note: this function predates the cmpp_errinfo class by a
|
|
donkey's age, thus this apparent name collision. If it weren't for
|
|
this function, that class would be called cmpp_err.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_get(cmpp *pp, char const **zMsg);
|
|
|
|
/**
|
|
Sets or clears (if 0==rc) pp's persistent error state. zFmt may be
|
|
NULL or a format string compatible with sqlite3_mprintf().
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
rc without other side effects.
|
|
|
|
Returns rc with one exception: if allocation of a copy of the error
|
|
string fails then CMPP_RC_OOM will be returned (and pp will be
|
|
updated appropriately).
|
|
|
|
If rc is non-0 and pp is currently processing a script, the
|
|
resulting error string will be prefixed with error location
|
|
information.
|
|
|
|
It is legal for zFmt to be NULL or an empty string, in which case a
|
|
default, vague error message is used (without requiring allocation
|
|
of a new string).
|
|
|
|
Recoverable vs. unrecoverable errors:
|
|
|
|
Most cmpp APIs become no-ops if their cmpp object has error state
|
|
set, treating any error as unrecoverable. That approach simplifies
|
|
writing code for it by allowing multiple calls to be chained
|
|
without concern for whether the previous one succeeded.
|
|
|
|
ACHTUNG: simply clearing the error state by passing 0 as the 2nd
|
|
argument to this function is _not_ enough to recover from certain
|
|
errors. e.g. an error in the middle of a script may leave db
|
|
savepoints imbalanced. The only way to _fully_ recover from any
|
|
significant failures is to use cmpp_reset(), which resets all of
|
|
pp's state.
|
|
|
|
APIs which may set the error state but are recoverable by simply
|
|
clearing that state will document that. Errors from APIs which do
|
|
not claim to be recoverable in error cases must be treated as
|
|
unrecoverable.
|
|
|
|
See cmpp_err_get() for more information.
|
|
|
|
Potential TODO: change the error-reporting interface to support
|
|
distinguishing from recoverable and non-recoverable errors. "The
|
|
problem" is that no current uses need that - they simply quit and
|
|
free up the cmpp instance on error. Maybe that's the way it
|
|
_should_ be.
|
|
*/
|
|
CMPP_EXPORT int cmpp_errf(cmpp *pp, int rc, char const *zFmt, ...);
|
|
|
|
/** va_list counterpart of cmpp_errf(). */
|
|
CMPP_EXPORT int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list vars);
|
|
|
|
/**
|
|
A variant of cmpp_errf() which is not variadic, primarily as a
|
|
consolation for WASM builds. zMsg may be NULL. The second, third,
|
|
and fourth parameters behave as documented for cmpp_errinfo_set().
|
|
|
|
If pp is not NULL, this returns either rc or CMPP_RC_OOM, as per
|
|
cmpp_errinfo_set().
|
|
|
|
This permits a NULL pp to simplify certain uses but is a no-op in
|
|
that case, returning rc.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err(cmpp *pp, int rc, char const *zMsg,
|
|
cmpp_strlen_t nMsg);
|
|
|
|
#define cmpp_err_c(PP,RC,MSG) cmpp_err(PP, RC, "" MSG, sizeof(MSG)-1)
|
|
|
|
static inline void cmpp_err_reset(cmpp *pp){ cmpp_err(pp,0,0,0); }
|
|
|
|
#if 0
|
|
/**
|
|
Clears any error state in pp. Most cmpp APIs become no-ops if their
|
|
cmpp instance has its error flag set.
|
|
|
|
See cmpp_err_get() for important details about doing this.
|
|
*/
|
|
//CMPP_EXPORT void cmpp_err_clear(cmpp *pp);
|
|
|
|
/**
|
|
This works like a combination of cmpp_err_get() and
|
|
cmpp_err_clear(), in that it clears pp's error state by transferring
|
|
ownership of it to the caller. If pp has any error state, *zMsg is
|
|
set to the error string and the error code is returned, else 0 is
|
|
returned and *zMsg is set to 0.
|
|
|
|
The string returned via *zMsg must eventually be passed to
|
|
cmpp_mfree() to free it.
|
|
|
|
This function is provided simply as an optimization to avoid
|
|
having to copy the error string in some cases.
|
|
|
|
ACHTUNG: see the ACHTUNG in cmpp_err_clear().
|
|
*/
|
|
//CMPP_EXPORT int cmpp_err_take(cmpp *pp, char **zMsg);
|
|
#endif
|
|
|
|
/**
|
|
Returns pp's current error code, which will be 0 if it currently
|
|
has no error state.
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
0.
|
|
*/
|
|
CMPP_EXPORT int cmpp_err_has(cmpp const * pp);
|
|
|
|
/**
|
|
Returns true if pp was initialized in "safe mode". That is: if the
|
|
cmpp_ctor_F_SAFEMODE flag was passed to cmpp_ctor().
|
|
|
|
To simplify certain uses, this is a no-op if pp is NULL, returning
|
|
false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_safemode(cmpp const * pp);
|
|
|
|
/**
|
|
Starts a new SAVEPOINT in the database. Returns non-0, and updates
|
|
pp's persistent error state, on failure.
|
|
|
|
If this returns 0, the caller is obligated to later pass pp to one
|
|
of cmpp_sp_commit() or cmpp_sp_rollback().
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_begin(cmpp *pp);
|
|
|
|
/**
|
|
Commits the most recently-opened savepoint UNLESS pp's error state
|
|
is set, in which case this behaves like cmpp_sp_rollback().
|
|
Returns 0 on success.
|
|
|
|
A call to cmpp_sp_begin() which returns 0 obligates the caller to
|
|
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
|
|
for either to be called in any other context.
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_commit(cmpp *pp);
|
|
|
|
/**
|
|
Rolls back the most recently-opened savepoint. Returns 0 on
|
|
success.
|
|
|
|
A call to cmpp_sp_begin() which returns 0 obligates the caller to
|
|
call either cmpp_sp_rollback() or cmpp_sp_commit(). It is illegal
|
|
for either to be called in any other context.
|
|
*/
|
|
CMPP_EXPORT int cmpp_sp_rollback(cmpp *pp);
|
|
|
|
|
|
/**
|
|
_Almost_ equivalent to fopen(3) but:
|
|
|
|
- If name=="-", it returns one of stdin or stdout, depending on the
|
|
mode string: stdout is returned if 'w' or '+' appear, otherwise
|
|
stdin.
|
|
|
|
If it returns NULL, the global errno "should" contain a description
|
|
of the problem unless the problem was argument validation.
|
|
|
|
If at all possible, use cmpp_fclose() (as opposed to fclose()) to
|
|
close these handles, as it has logic to skip closing the three
|
|
standard streams.
|
|
*/
|
|
CMPP_EXPORT cmpp_FILE * cmpp_fopen(char const * name, char const *mode);
|
|
|
|
/**
|
|
Passes f to fclose(3) unless f is NULL or one of the C-standard
|
|
handles (stdin, stdout, stderr), in which cases it does nothing at
|
|
all.
|
|
*/
|
|
CMPP_EXPORT void cmpp_fclose(cmpp_FILE * f);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires state to be a (FILE*), which
|
|
this function passes the call on to fwrite(). Returns 0 on
|
|
success, CMPP_RC_IO on error.
|
|
|
|
If state is NULL then stdout is used.
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_FILE(void * state, void const * src, cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires state to be a ([const] int*)
|
|
referring to a writable file descriptor, which this function
|
|
dereferences and passes to write(2).
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_fd(void * state, void const * src, cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_input_f() implementation which requires that state be
|
|
a readable (FILE*) handle, which it passes to fread(3).
|
|
*/
|
|
CMPP_EXPORT int cmpp_input_f_FILE(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
A cmpp_input_f() implementation which requires that state be a
|
|
readable file descriptor, in the form of an ([const] int*), which
|
|
this function passes to write(2).
|
|
*/
|
|
CMPP_EXPORT int cmpp_input_f_fd(void * state, void * dest, cmpp_size_t * n);
|
|
|
|
/**
|
|
A cmpp_flush_f() impl which expects pFile to be-a (FILE*) opened
|
|
for writing, which this function passes the call on to
|
|
fflush(). If fflush() returns 0, so does this function, else it
|
|
returns non-0.
|
|
*/
|
|
CMPP_EXPORT int cmpp_flush_f_FILE(void * pFile);
|
|
|
|
/**
|
|
A generic streaming routine which copies data from an
|
|
cmpp_input_f() to an cmpp_outpuf_f().
|
|
|
|
Reads all data from inF(inState,...) in chunks of an unspecified
|
|
size and passes them on to outF(outState,...). It reads until inF()
|
|
returns fewer bytes than requested or returns non-0. Returns the
|
|
result of the last call to outF() or (if reading fails) inF().
|
|
Results are undefined if either of inState or outState arguments
|
|
are NULL and their callbacks require non-NULL. (This function
|
|
cannot know whether a NULL state argument is legal for the given
|
|
callbacks.)
|
|
|
|
Here is an example which basically does the same thing as the
|
|
cat(1) command on Unix systems:
|
|
|
|
```
|
|
cmpp_stream(cmpp_input_f_FILE, stdin, cmpp_output_f_FILE, stdout);
|
|
```
|
|
|
|
Or copy a FILE to a string buffer:
|
|
|
|
```
|
|
cmpp_b os = cmpp_b_empty;
|
|
cmpp_FILE * f = cmpp_fopen(...);
|
|
rc = cmpp_stream(cmpp_input_f_FILE, f, cmpp_output_f_b, &os);
|
|
// On error os might be partially populated.
|
|
// Eventually clean up the buffer:
|
|
cmpp_b_clear(&os);
|
|
```
|
|
*/
|
|
CMPP_EXPORT int cmpp_stream(cmpp_input_f inF, void * inState,
|
|
cmpp_output_f outF, void * outState);
|
|
|
|
/**
|
|
Reads the entire contents of the given input stream, allocating it
|
|
in a buffer. On success, returns 0, assigns *pOut to the buffer,
|
|
and *nOut to the number of bytes read (which will be fewer than are
|
|
allocated). It guarantees that on success it NUL-terminates the
|
|
buffer at one byte after the returned size, with one exception: if
|
|
the stream has no input, both *pOut and *nOut will be set to 0.
|
|
|
|
On error it returns whatever code xIn() returns.
|
|
*/
|
|
CMPP_EXPORT int cmpp_slurp(cmpp_input_f xIn, void *stateIn,
|
|
unsigned char **pOut, cmpp_size_t * nOut);
|
|
|
|
/**
|
|
UNTESTED!
|
|
|
|
Reads the entire contents of the given file, appending it to b.
|
|
|
|
Returns 0 on success.
|
|
|
|
If pp is not NULL then:
|
|
|
|
1) This is a no-op if pp has an error set, returning that code.
|
|
|
|
2) Any error triggered by this function will be set in its
|
|
persistent error state.
|
|
|
|
If pp is NULL then errors are considered transient.
|
|
|
|
On error b may be partially populated.
|
|
*/
|
|
CMPP_EXPORT int cmpp_slurp_file(cmpp *pp, char const *fn,
|
|
cmpp_b * b);
|
|
|
|
/**
|
|
A cleanup callback interface for use with cmpp_outputer::cleanup().
|
|
Implementations must handle self->state appropriately for its type,
|
|
and clear self->state if appropriate, but must not free the self
|
|
object. It is implementation-specified whether self->state and/or
|
|
self->name are set to NULL by this function. Whether they should be
|
|
often depends on how they're used.
|
|
*/
|
|
typedef void (*cmpp_outputer_cleanup_f)(cmpp_outputer *self);
|
|
|
|
/**
|
|
An interface which encapsulates data for managing a streaming
|
|
output destination, primarily intended for use with cmpp_stream()
|
|
but also used internally by cmpp for directing output to a buffer.
|
|
*/
|
|
struct cmpp_outputer {
|
|
/**
|
|
An optional descriptive name for the channel. The bytes
|
|
are owned elsewhere and are typically static or similarly
|
|
long-lived.
|
|
*/
|
|
char const * name;
|
|
|
|
/**
|
|
Output channel.
|
|
*/
|
|
cmpp_output_f out;
|
|
|
|
/**
|
|
flush() implementation. This may be NULL for most uses of this
|
|
class. Cases which specifically require it must document that
|
|
requirement so.
|
|
*/
|
|
cmpp_flush_f flush;
|
|
|
|
/**
|
|
Optional: if not NULL, it must behave appropriately for its state
|
|
type, cleaning up any memory it owns.
|
|
*/
|
|
cmpp_outputer_cleanup_f cleanup;
|
|
|
|
/**
|
|
State to be used when calling this->out() and this->flush(),
|
|
namely: this->out(this->state, ... ) and
|
|
this->flush(this->state).
|
|
|
|
Whether or not any given instance of this class owns the memory
|
|
pointed to by this member must be documented for their cleanup()
|
|
method.
|
|
|
|
Because cmpp_outputer instances frequently need to be stashed and
|
|
unstashed via bitwise copying, it is illegal to replace this
|
|
pointer after its initial assignment. The object it points to may
|
|
be mutated freely, but this pointer must stay stable for the life
|
|
of this object.
|
|
*/
|
|
void * state;
|
|
};
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
const-copy initialization.
|
|
*/
|
|
#define cmpp_outputer_empty_m \
|
|
{.name=NULL, .out = NULL,.flush = NULL, .cleanup = NULL, .state =NULL}
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
non-const-copy initialization. These copies can, for purposes of
|
|
cmpp's output API, be used as-is to have cmpp process its inputs
|
|
but generate no output.
|
|
*/
|
|
CMPP_EXPORT const cmpp_outputer cmpp_outputer_empty;
|
|
|
|
/**
|
|
If o->out is not NULL, the result of o->out(o->state,p,n) is
|
|
returned, else 0 is returned.
|
|
*/
|
|
CMPP_EXPORT int cmpp_outputer_out(cmpp_outputer *o, void const *p, cmpp_size_t n);
|
|
|
|
/**
|
|
If o->flush is not NULL, the result of o->flush(o->state) is
|
|
returned, else 0 is returned.
|
|
*/
|
|
CMPP_EXPORT int cmpp_outputer_flush(cmpp_outputer *o);
|
|
|
|
/**
|
|
If o->cleanup is not NULL, it is called, otherwise this is a no-op.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup(cmpp_outputer *o);
|
|
|
|
/**
|
|
A cmpp_outputer initializer which uses cmpp_flush_f_FILE(),
|
|
cmpp_output_f_FILE(), and cmpp_outputer_cleanup_f_FILE() for its
|
|
implementation. After copying this, the state member must be
|
|
pointed to an opened-for-writing (FILE*).
|
|
*/
|
|
CMPP_EXPORT const cmpp_outputer cmpp_outputer_FILE;
|
|
|
|
/**
|
|
The cmpp_outputer_cleanup_f() impl used by cmpp_outputer_FILE. If
|
|
self->state is not NULL then it is passed to fclose() (_unless_ it
|
|
is stdin, stdout, or stderr) and set to NULL. self->name is
|
|
also set to NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self);
|
|
|
|
/**
|
|
Sets pp's current directive delimiter to a copy of the
|
|
NUL-terminated zDelim. The delimiter is the sequence which starts
|
|
lines and distinguishes cmpp directives from other input, in the
|
|
same way that C preprocessors use '#' as a delimiter.
|
|
|
|
If zDelim is NULL then the default delimiter is used. The default
|
|
delimiter can be set when compiling the library by defining
|
|
CMPP_DEFAULT_DELIM to a quoted string value.
|
|
|
|
zDelim is assumed to be in UTF-8 encoding. If any bytes in the
|
|
range (0,32) are found, CMPP_RC_MISUSE is returned and pp's
|
|
persistent error state is set.
|
|
|
|
The delimiter must be short and syntactically unambiguous for the
|
|
intended inputs. It has a rather arbitrary maximum length of
|
|
32[^1], but it's difficult to envision it being remotely
|
|
human-friendly with a delimiter longer than 3 bytes. It's
|
|
conceivable, but seemingly far-fetched, that longer delimiters
|
|
might be interesting in some machine-generated cases, e.g. using a
|
|
random sequence as the delimiter.
|
|
|
|
Returns 0 on success. Returns non-0 if called when the delimiter
|
|
stack is empty, if it cannot copy the string, or zDelim is deemed
|
|
unsuitable for use as a delimiter. Calling this when the stack is
|
|
empty represents a serious API misuse (possibly indicating that
|
|
cmpp_delimiter_pop() was used out of scope) and will trigger an
|
|
assert() in debug builds. Except for that last case, errors from
|
|
this function are recoverable (see cmpp_errf()).
|
|
|
|
[^1]: the historical length limit was 12 but we actually hit that
|
|
in this very tree when the "//%amalgamation" pieces were added to
|
|
replace having to shell out to /usr/bin/sed.
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_set(cmpp *pp, char const *zDelim);
|
|
|
|
/**
|
|
Fetches pp's current delimiter string, assigning it to *zDelim.
|
|
The string is owned by pp and will be invalidated by any call to
|
|
cmpp_delimiter_set() or the #delimiter script directive.
|
|
|
|
If, by some odd usage constellation, this is called after an
|
|
allocation of the delimiter stack has failed, this will set *zDelim
|
|
to the compile-time-default delimiter. That "cannot happen" in
|
|
normal use because such a failure would have been reacted to and
|
|
this would not be called.
|
|
*/
|
|
CMPP_EXPORT void cmpp_delimiter_get(cmpp const *pp, char const **zDelim);
|
|
|
|
/**
|
|
Pushes zDelim as the current directive delimiter. Returns 0 on
|
|
success and non-zero on error (invalid zDelim value or allocation
|
|
error). If this returns 0 then the caller is obligated to
|
|
eventually call cmpp_delimiter_pop() one time. If it returns non-0
|
|
then they must _not_ call that function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_push(cmpp *pp, char const *zDelim);
|
|
|
|
/**
|
|
Must be called one time for each successful call to
|
|
cmpp_delimiter_push(). It restores the directive delimimter to the
|
|
value it has when cmpp_delimiter_push() was last called.
|
|
|
|
Returns pp's current error code, and will set it to non-0 if called
|
|
when no cmpp_delimiter_push() is active. Popping an empty stack
|
|
represents a serious API misuse and may fail an assert() in debug
|
|
builds.
|
|
*/
|
|
CMPP_EXPORT int cmpp_delimiter_pop(cmpp *pp);
|
|
|
|
/**
|
|
If z[*n] ends on a \n or \r\n pair, it/they are stripped,
|
|
*z is NUL-terminated there, and *n is adjusted downwards
|
|
by 1 or 2. Returns true if it chomped, else false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_chomp(unsigned char * z, cmpp_size_t * n);
|
|
|
|
/**
|
|
A basic memory buffer class. This is primarily used with for
|
|
creating dynamic strings and as a target for cmpp_outputer_b to
|
|
capture arbitrary output for later use.
|
|
|
|
TODO: add the heuristic that an nAlloc of 0 with a non-NULL z
|
|
refers to externally-owned memory. This would change the
|
|
buffer-write APIs to automatically copy it before making any
|
|
changes. We have code for this in the trees this class derives
|
|
from, it just needs to be ported over. It would allow us to avoid
|
|
allocating in some cases where we need a buffer but it will always
|
|
(or commonly) be a copy of a static string, like a single space.
|
|
*/
|
|
struct cmpp_b {
|
|
/**
|
|
This buffer's memory, owned by this object. This library
|
|
exclusively uses sqlite3_realloc() and friends for memory
|
|
management.
|
|
|
|
If this pointer is taken away from this object then it must
|
|
eventually be passed to cmpp_mfree() or sqlite3_free().
|
|
|
|
This class's APIs strive to keep valid buffers NUL-terminated at
|
|
byte this->z[this->n]. Any deviation from that is (A) unknown and
|
|
(B) to be considered a bug.
|
|
*/
|
|
unsigned char * z;
|
|
/**
|
|
Number of bytes of this->z which are in use, not counting any
|
|
automatic NUL terminator which this class's APIs may add.
|
|
*/
|
|
cmpp_size_t n;
|
|
/**
|
|
Number of bytes allocated in this->z.
|
|
|
|
Potential TODO: use a value of zero here, with a non-zero this->z
|
|
or this->n, to mean that this->z is owned elsewhere. This would
|
|
cause cmpp_b_append() to copy its original source before
|
|
appending. Similarly, cmpp_b_clear() would necessarily _not_ free
|
|
this->z. We've used that heuristic in a predecessor of this class
|
|
in another tree to good effect for years, but it's not certain
|
|
that we'd get the same level of utility out of that capability as
|
|
we do in that other project.
|
|
*/
|
|
cmpp_size_t nAlloc;
|
|
|
|
/**
|
|
cmpp_b APIs which may fail will set this. Similarly, most
|
|
of the cmpp_b APIs become no-ops if this is non-0.
|
|
*/
|
|
int errCode;
|
|
};
|
|
|
|
/**
|
|
An empty-initialized cmpp_b struct for use in const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_b_empty_m {.z=0,.n=0,.nAlloc=0,.errCode=0}
|
|
|
|
/**
|
|
An empty-initialized cmpp_b struct for use in non-const copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_b cmpp_b_empty;
|
|
|
|
/**
|
|
A proxy for sqlite3_str_new() which returns NULL on OOM instead of
|
|
returning a dummy object. If it returns NULL then pp's error state
|
|
will be update with CMPP_RC_OOM.
|
|
*/
|
|
CMPP_EXPORT sqlite3_str * cmpp_sqlite3_str_new(cmpp *pp);
|
|
|
|
/**
|
|
Proxy for sqlite3_str_finish() which updates pp's error state if s
|
|
has error state. Returns s's string on success and NULL on
|
|
error. The returned string must eventualy be passed to
|
|
cmpp_mfree(). It also, it turns out, returns NULL if s is empty, so
|
|
callers must check pp->err to see if NULL is an error.
|
|
|
|
If n is not NULL then on success it is set to the byte length of
|
|
the returned string, not including its NUL terminator.
|
|
|
|
If this is called while pp has error state, this does not override
|
|
that error state but will return 0.
|
|
*/
|
|
CMPP_EXPORT char * cmpp_sqlite3_str_finish(cmpp *pp, sqlite3_str *s, int * n);
|
|
|
|
|
|
/**
|
|
A class for holding error state.
|
|
|
|
This class _should_ be called cmpp_err, but by the time it was
|
|
added we already had a family of cmpp_err-named APIs and no good
|
|
new names for them. Naming it cmpp_e would cause me confusion
|
|
because `_e` is the conventional suffix for enums in this library.
|
|
*/
|
|
struct cmpp_errinfo {
|
|
/** Error code. 0 = no error. */
|
|
int code;
|
|
/**
|
|
Buffer memory owned by this object. Updated by many different
|
|
cmpp_errinfo APIs and freed by cmpp_errinfo_dtor().
|
|
*/
|
|
cmpp_b b;
|
|
/**
|
|
|
|
Might point to this->b.z or it might point to a static/const
|
|
error strings, in particular for use with OOM.
|
|
|
|
Except on allocation errors, the cmpp_errinfo APIs which update
|
|
this->b point this to this->b.z.
|
|
*/
|
|
char const *zStatic;
|
|
};
|
|
typedef struct cmpp_errinfo cmpp_errinfo;
|
|
#define cmpp_errinfo_empty_m {.code=0,.b=cmpp_b_empty_m,.zStatic=0}
|
|
extern const cmpp_errinfo cmpp_errinfo_empty;
|
|
|
|
/**
|
|
Sets err's state to the given code and string and returns either rc
|
|
or, if an allocation error happens while setting the message,
|
|
CMPP_RC_OOM.
|
|
|
|
If rc is 0 then zMsg is ignored, else the first nMsg bytes of zMsg
|
|
are copied into err->b, or through zMsg's first NUL byte if nMsg is
|
|
negative.
|
|
|
|
To simplify certain uses, err may be NULL, in which case this simply
|
|
returns rc.
|
|
*/
|
|
CMPP_EXPORT int cmpp_errinfo_set(cmpp_errinfo *err, int rc, char const *zMsg,
|
|
cmpp_strlen_t nMsg);
|
|
|
|
/** va_list form of cmpp_errinfo_set(), supporting sqlite3_str_vappendf()'s
|
|
formatting options. */
|
|
CMPP_EXPORT int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt,
|
|
va_list va);
|
|
|
|
/** Variadic form of cmpp_errinfo_setf(). */
|
|
CMPP_EXPORT int cmpp_errinfo_setf(cmpp_errinfo *err, int rc,
|
|
char const *zFmt, ...);
|
|
|
|
/**
|
|
Clears err and frees all memory it owns but does not free err.
|
|
*/
|
|
CMPP_EXPORT void cmpp_errinfo_dtor(cmpp_errinfo *err);
|
|
|
|
/**
|
|
Resets err's state a non-error state but retains any
|
|
memory it has buffered. Returns err.
|
|
*/
|
|
CMPP_EXPORT cmpp_errinfo * cmpp_errinfo_reuse(cmpp_errinfo *err);
|
|
|
|
/**
|
|
Frees b->z and zeroes out s but does not free s.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_clear(cmpp_b *b);
|
|
|
|
/**
|
|
If b has content, b->nUsed is set to 0 and b->z is NUL-terminated
|
|
at its first byte, else this is a no-op. b->errCode is set to
|
|
0. Returns s.
|
|
*/
|
|
CMPP_EXPORT cmpp_b * cmpp_b_reuse(cmpp_b *b);
|
|
|
|
/**
|
|
Swaps all contents of the given buffers, including their persistent
|
|
error code.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_swap(cmpp_b * l, cmpp_b * r);
|
|
|
|
/**
|
|
If b->errCode is 0 and b->nAlloc is less than n, b->z is
|
|
reallocated to have at least n bytes, else this is a no-op. Returns
|
|
0 on success, CMPP_RC_OOM on error.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_reserve(cmpp_b *b, cmpp_size_t n);
|
|
|
|
/**
|
|
Works just like cmpp_b_reserve() but on allocation error it updates
|
|
pp's error state. If called when pp has error state, that code is
|
|
returned and this function has no side-effects.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_reserve3(cmpp * pp, cmpp_b * os, cmpp_size_t n);
|
|
|
|
/**
|
|
Appends n bytes from src to b, reallocating os as necessary.
|
|
Returns 0 on succes, CMPP_RC_OOM on allocation error.
|
|
|
|
This may modify b even if n is 0, to ensure that it always has a
|
|
NUL terminator. src may only be NULL if n is 0.
|
|
|
|
Errors from this function, and the other cmpp_b_append...()
|
|
variants, are recoverable (see cmpp_errf()).
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append(cmpp_b * b, void const *src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Works like cmpp_b_append() except that if n is negative,
|
|
strlen() is used to calculate it.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_str(cmpp_b * b, char const * str,
|
|
cmpp_strlen_t n);
|
|
/**
|
|
Convenience form of cmpp_b_append_str() which requires a string
|
|
literal.
|
|
*/
|
|
#define cmpp_b_append_c(B,STR) cmpp_b_append(B, STR "",sizeof(STR)-1)
|
|
|
|
/**
|
|
Works just like cmpp_b_append() but on allocation error it updates
|
|
pp's error state. If called when pp has error state, that code is
|
|
returned and this function has no side-effects.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append4(cmpp * pp, cmpp_b * b,
|
|
void const * src, cmpp_size_t n);
|
|
|
|
/**
|
|
Works like cmpp_b_append4() except that if n is negative,
|
|
strlen() is used to calculate it.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append4_str(cmpp * pp, cmpp_b * b,
|
|
char const *str, cmpp_strlen_t n);
|
|
/**
|
|
Convenience form of cmpp_b_append4_str() which requires a string
|
|
literal.
|
|
*/
|
|
#define cmpp_b_append4_c(PP,B,STR) \
|
|
cmpp_b_append4(PP, B, STR "", sizeof(STR)-1)
|
|
|
|
/**
|
|
Appends ch to the end of os->z, expanding as necessary, and
|
|
NUL-terminates os. Returns os->errCode and is a no-op if that is
|
|
non-0 when this is called. This is slightly more efficient than
|
|
passing length-1 strings to cmpp_b_append() _if_ os's memory
|
|
is pre-allocated with cmpp_b_reserve(), otherwise it may be
|
|
less efficient because it may need to allocate frequently if used
|
|
repeatedly.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_ch(cmpp_b * os, char ch);
|
|
|
|
/**
|
|
Appends a decimal string representation of d to os. Returns
|
|
os->errCode and is a no-op if that is non-0 when this is called.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_i32(cmpp_b * os, int32_t d);
|
|
|
|
/** int64_t counterpart of cmpp_b_append_i32(). */
|
|
CMPP_EXPORT int cmpp_b_append_i64(cmpp_b * os, int64_t d);
|
|
|
|
/**
|
|
Compares b to [m,m+n) using memcmp() semantics as if b were the
|
|
LHS. That is: returns 0 if b and z contain equivalent content,
|
|
negative if b's content is "less than" z's, and positive if b's
|
|
content is "greater than" z.
|
|
|
|
If n<0 then m is assumed to be a NUL-terminated string and
|
|
strlen() is used to calculate it.
|
|
|
|
This function will misbehave with inputs having more than 31 bits
|
|
of length.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_cmp(cmpp_b const * b, void const *m,
|
|
cmpp_ssize32_t n);
|
|
|
|
/**
|
|
Unescapes b's contents (if any) inline, adjusting b->n accordingly
|
|
(it will never grow but may stay the same size or shrink).
|
|
|
|
Any sequences of (`\\`, `\b`, `\n`, `\t`, `\r`, `\"`, `\'`, `\``)
|
|
are replaced with their traditional single-byte counterparts. Other
|
|
bytes are passed through as-is. Other backslash sequences are left
|
|
as-is.
|
|
|
|
Similarly, `\uNNNN` and `\UNNNNNNNN`, where N is runs of hex
|
|
digits, unescape to their corresponding Unicode code point, or are
|
|
left as-is if they're invalid.
|
|
|
|
We don't do `\f` or `\v` because they're historically only used in
|
|
ugly hacks, like separators for bespoke formats, not "real
|
|
inputs". We don't do `\0` because embedded nuls via unescaping are
|
|
simply inappropriate in the context of this library.
|
|
|
|
Potential TODO: add an argument for configuring unescaping or maybe
|
|
erroring for unknown escape sequences (pass a cmpp_errinfo object).
|
|
|
|
Potential TODO: an argument to tell it the byte offset to start at,
|
|
to allow us to unescape being-appended-to buffers without
|
|
re-unescaping the existing body.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_unescape(cmpp_b * b);
|
|
|
|
/**
|
|
A thin wrapper around cmpp_chomp() which chomps b->z.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_b_chomp(cmpp_b * b);
|
|
|
|
/**
|
|
Counts the number of newlines between [zBegin,zPos). zBegin must
|
|
be <= zEnd. zPos must be >= zBegin and <= zEnd. This function may
|
|
assert() that the preconditions are met.
|
|
|
|
The range need not be NUL-terminated and embedded NULs are counted
|
|
as for other bytes.
|
|
|
|
Returns the number of newlines and, if pCol is not NULL, sets the
|
|
0- and byte-based column number of zPos relative to the most recent
|
|
newline or (if no newlines were found) zBegin.
|
|
*/
|
|
CMPP_EXPORT cmpp_size_t cmpp_count_nl(unsigned char const *zBegin,
|
|
unsigned char const * const zEnd,
|
|
unsigned char const * const zPos,
|
|
cmpp_size_t *pCol);
|
|
|
|
/**
|
|
A cmpp_output_f() impl which requires that its first argument be a
|
|
(cmpp_b*) or be NULL. If buffer is not NULL then it appends n bytes
|
|
of src to buffer, reallocating as needed. Returns CMPP_RC_OOM in
|
|
reallocation error. On success it always NUL-terminates buffer->z.
|
|
A NULL buffer is treated as success but has no side effects.
|
|
|
|
Example usage:
|
|
|
|
```
|
|
cmpp_b b = cmpp_b_empty;
|
|
int rc = cmpp_stream(cmpp_input_f_FILE, stdin,
|
|
cmpp_output_f_b, &b);
|
|
...
|
|
cmpp_b_clear(&b);
|
|
```
|
|
*/
|
|
CMPP_EXPORT int cmpp_output_f_b(void * buffer, void const * src,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
A cmpp_outputer_cleanup_f() implementation which requires that
|
|
self->state be either NULL or a cmpp_b pointer. This function
|
|
passes it to cmpp_b_clear(). It does _not_ set self->state or
|
|
self->name to NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_outputer_cleanup_f_b(cmpp_outputer *self);
|
|
|
|
/**
|
|
A cmpp_outputer prototype which can be copied to use a dynamic
|
|
string buffer as an output source. Its state member must be set (by
|
|
the client) to a cmpp_b instance. Its out() method is
|
|
cmpp_output_f_b(). Its cleanup() method is
|
|
cmpp_outputer_cleanup_f_b(). It has no flush() method.
|
|
*/
|
|
extern const cmpp_outputer cmpp_outputer_b;
|
|
|
|
/**
|
|
Returns a string containing version information in an unspecified
|
|
format.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_version(void);
|
|
|
|
/**
|
|
Policies for how to handle undefined @tokens@ when performing
|
|
content filtering.
|
|
*/
|
|
enum cmpp_atpol_e {
|
|
/** Sentinel value. */
|
|
cmpp_atpol_invalid = -1,
|
|
/** Turn off @token@ parsing. */
|
|
cmpp_atpol_OFF = 0,
|
|
/** Retain undefined @token@ - emit it as-is. */
|
|
cmpp_atpol_RETAIN,
|
|
/** Elide undefined @token@. */
|
|
cmpp_atpol_ELIDE,
|
|
/** Error for undefined @token@. */
|
|
cmpp_atpol_ERROR,
|
|
/** A sentinel value for use with cmpp_out_expand(). */
|
|
cmpp_atpol_CURRENT,
|
|
/**
|
|
This isn't _really_ the default. It's the default for the --@ CLI
|
|
flag.
|
|
*/
|
|
cmpp_atpol_DEFAULT_FOR_FLAG = cmpp_atpol_ERROR,
|
|
/**
|
|
The compile-time default for all cmpp instances.
|
|
*/
|
|
cmpp_atpol_DEFAULT = cmpp_atpol_OFF
|
|
|
|
};
|
|
typedef enum cmpp_atpol_e cmpp_atpol_e;
|
|
|
|
/**
|
|
Policies describing how cmpp should react to attempts to use
|
|
undefined keys.
|
|
*/
|
|
enum cmpp_unpol_e {
|
|
/* Sentinel. */
|
|
cmpp_unpol_invalid,
|
|
/** Treat undefined keys as NULL/falsy. This is the default. */
|
|
cmpp_unpol_NULL,
|
|
/** Trigger an error for undefined keys. This should probably be the
|
|
default. */
|
|
cmpp_unpol_ERROR,
|
|
/**
|
|
The compile-time default for all cmpp instances.
|
|
*/
|
|
cmpp_unpol_DEFAULT = cmpp_unpol_NULL
|
|
};
|
|
typedef enum cmpp_unpol_e cmpp_unpol_e;
|
|
|
|
/**
|
|
Masks to embed some metadata into cmpp_tt_e token
|
|
type IDs.
|
|
*/
|
|
enum cmpp_tt_mask_e {
|
|
/** Mask of the "id" bits for a token ID. */
|
|
cmpp_tt_mask_id = 0x00fff,
|
|
/** Mask of the "shape" bits for a token ID, describing the general
|
|
data type so that we can group certain constructs together under
|
|
a single mask for switch/case dispatching purposes. */
|
|
cmpp_tt_mask_shape = 0x0f000,
|
|
/** Anything which does not have a closer match
|
|
is a Word. */
|
|
cmpp_tt_shape_word = 0x01000,
|
|
/** For numbers, optionally signed. Currently only decimal
|
|
integers. */
|
|
cmpp_tt_shape_number = 0x02000,
|
|
/** For "", '', and `` strings. */
|
|
cmpp_tt_shape_string = 0x04000,
|
|
/** For {}, [], and () macro-tokens. */
|
|
cmpp_tt_shape_group = 0x08000,
|
|
/** For expression operators. */
|
|
cmpp_tt_shape_op = 0x10000,
|
|
/** For EOL, EOC, and EOF. */
|
|
cmpp_tt_shape_flow = 0x20000
|
|
};
|
|
|
|
/**
|
|
cmpp token types, currently distinct from the public API's cmpp_tt_map
|
|
as we consolidate the two.
|
|
|
|
The first argument to E is the tail of the cmpp_tt_ ## TAIL part
|
|
of the enum entry's name. The second is the cmpp_tt_mask_id part
|
|
of its enum value (integer). The third is the mask/non-ID part of
|
|
its value. The enum's value is the logical OR of arguments 2 and 3.
|
|
|
|
Maintenance reminder: we need these even in %lite builds for use by
|
|
cmpp_tizer.
|
|
*/
|
|
#define cmpp_tt_map(E) \
|
|
E(None, 0, 0) \
|
|
E(Error, 1, cmpp_tt_shape_flow) \
|
|
/** Anything with no more specific type. */ \
|
|
E(Word, 2, cmpp_tt_shape_word) \
|
|
E(StringSQ, 3, cmpp_tt_shape_string) \
|
|
E(StringDQ, 4, cmpp_tt_shape_string) \
|
|
/** Backtick string. */ \
|
|
E(StringBT, 5, cmpp_tt_shape_string) \
|
|
/** Decimal int, optionally signed but some
|
|
contexts will elide a leading + when
|
|
rendering then token. */ \
|
|
E(IntDec, 6, cmpp_tt_shape_number) \
|
|
E(RawLine, 7, 0) \
|
|
E(OpPlus, 100, cmpp_tt_shape_op) \
|
|
E(OpMinus, 101, cmpp_tt_shape_op) \
|
|
E(OpNot, 102, cmpp_tt_shape_op) \
|
|
E(OpAnd, 103, cmpp_tt_shape_op) \
|
|
E(OpOr, 104, cmpp_tt_shape_op) \
|
|
E(OpGlob, 105, cmpp_tt_shape_op) \
|
|
E(OpCmpEq, 106, cmpp_tt_shape_op) \
|
|
E(OpCmpNeq, 107, cmpp_tt_shape_op) \
|
|
E(OpCmpLt, 108, cmpp_tt_shape_op) \
|
|
E(OpCmpLe, 109, cmpp_tt_shape_op) \
|
|
E(OpCmpGt, 110, cmpp_tt_shape_op) \
|
|
E(OpCmpGe, 111, cmpp_tt_shape_op) \
|
|
E(OpShiftL, 112, cmpp_tt_shape_op) \
|
|
E(OpShiftL3, 113, cmpp_tt_shape_op) \
|
|
E(OpShiftR, 114, cmpp_tt_shape_op) \
|
|
E(OpBitwiseAnd, 115, cmpp_tt_shape_op) \
|
|
E(OpBitwiseOr, 116, cmpp_tt_shape_op) \
|
|
E(OpBitwiseNegate,117, cmpp_tt_shape_op) \
|
|
/*SQLite does have xor
|
|
E(OpXOr, 118, cmpp_tt_shape_op)*/ \
|
|
E(OpMultiply, 119, cmpp_tt_shape_op) \
|
|
E(OpDivide, 120, cmpp_tt_shape_op) \
|
|
E(OpModulo, 121, cmpp_tt_shape_op) \
|
|
E(GroupBrace, 200, cmpp_tt_shape_group) \
|
|
E(GroupParen, 201, cmpp_tt_shape_group) \
|
|
E(GroupSquiggly, 202, cmpp_tt_shape_group) \
|
|
E(EOL, 253, cmpp_tt_shape_flow) \
|
|
E(EOC, 254, cmpp_tt_shape_flow) \
|
|
E(EOF, 255, cmpp_tt_shape_flow) \
|
|
/* Arg 2 MUST fit in cmpp_tt_mask_id. */ \
|
|
/* Refactoring crutches for legacy
|
|
token type compatibility: */ \
|
|
E(OpDefined, 300, cmpp_tt_shape_op) \
|
|
E(OpNotGlob, 301, cmpp_tt_shape_op) \
|
|
E(Noop, 302, cmpp_tt_shape_op) \
|
|
E(Null, 303, cmpp_tt_shape_op) \
|
|
E(DbString, 304, cmpp_tt_shape_word) \
|
|
E(ThinArrowR, 305, cmpp_tt_shape_op) \
|
|
E(ThinArrowL, 306, cmpp_tt_shape_op) \
|
|
E(AnyType, 307, 0)
|
|
|
|
/**
|
|
Token type IDs and their metadata for use with cmpp_itch, namely
|
|
cmpp__token::ttype. No entries have well-defined values except
|
|
that cmpp_tt_None is guaranteed to be 0.
|
|
|
|
cmpp_tt_EOF represents a _logical_ EOF: the end of the tokenizer's
|
|
input. For sub-parsing this represent the end of a grouping token.
|
|
*/
|
|
enum cmpp_tt_e {
|
|
#define E(NAME,ID,MASK) cmpp_tt_ ## NAME = ID | MASK,
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
cmpp_tt__last
|
|
};
|
|
typedef enum cmpp_tt_e cmpp_tt_e;
|
|
|
|
/**
|
|
If e is a cmpp_tt_e entry then a debugging-friendly string form of
|
|
it is returned, else 0 is returned. The returned bytes are static
|
|
and NUL-terminated.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_tt_cstr(int e);
|
|
|
|
/**
|
|
A snippet from a parent string, usually a parsed token and almost
|
|
never NUL-terminated. This is the fundamental atomic unit for
|
|
directive and command argument tokenization.
|
|
*/
|
|
struct cmpp_token {
|
|
/** Start of the range. */
|
|
unsigned char const *z;
|
|
/** Number of bytes in this->z. */
|
|
unsigned int n;
|
|
/**
|
|
Start of the inner range (for group tokens), else the same as
|
|
this->z.
|
|
|
|
Legacy compatibility quirk: see cmpp_args_F_NO_SQUIGGLY_HACK.
|
|
*/
|
|
unsigned char const *zInner;
|
|
/** Number of bytes in this->zInner. */
|
|
unsigned int nInner;
|
|
/** From enum cmpp_tt_e. */
|
|
int ttype;
|
|
/*
|
|
Potential TODO: add:
|
|
cmpp_tizer const * tz;
|
|
|
|
So that we can derive source location info given only a
|
|
token. "The problem" with that is that it indirectly adds
|
|
sizeof(void*) to each cmpp_arg object (via each embedding one of
|
|
these).
|
|
*/
|
|
};
|
|
typedef struct cmpp_token cmpp_token;
|
|
#define cmpp_token_empty_m {.z=0,.n=0,.zInner=0,.nInner=0,.ttype=0}
|
|
extern const cmpp_token cmpp_token_empty;
|
|
|
|
#define cmpp_token_isa(TOKPTR,TTYPE) ((TTYPE)==(TOKPTR)->ttype)
|
|
typedef struct cmpp_tizer cmpp_tizer;
|
|
|
|
/**
|
|
Returns a pointer to t's string and assigns *n to its byte length.
|
|
If inner is true and t->zInner differs from t->z then the range
|
|
[t->zInner,t->nInner) is used, else [t->z,t->n) is used.
|
|
|
|
Achtung: tokens are rarely NUL-terminated.
|
|
*/
|
|
char const * cmpp_token_cstr3(cmpp_token const * t, cmpp_size_t * n, bool inner);
|
|
|
|
int cmpp_token_strcmp(cmpp_token const * t, char const *z, cmpp_strlen_t n);
|
|
#define cmpp_token_ustrcmp(T,Z,N) \
|
|
cmpp_token_strcmp((T),(unsigned char const *)(Z),(N))
|
|
|
|
/** Convenience form of cmpp_token_cstr3() which casts the result to
|
|
(unsigned char const *). */
|
|
#define cmpp_token_ucstr3(T,NP,I) \
|
|
(unsigned char const *)cmpp_token_cstr3((T),(NP),(I))
|
|
|
|
/**
|
|
A basic tokenizer for argument/script-like tokens.
|
|
*/
|
|
struct cmpp_tizer {
|
|
/**
|
|
Parent of a sub-tokenizer. (To be) Used for error reporting: by
|
|
knowing the whole input range we can provide better line/column
|
|
info.
|
|
*/
|
|
cmpp_tizer const *parent;
|
|
/** Optional error object through which to report detailed error
|
|
info. It is owned by whoever sets it. */
|
|
cmpp_errinfo * err;
|
|
/** Starting point. This memory must stay valid for the life
|
|
of the tokenizer. */
|
|
unsigned char const * zBegin;
|
|
/** One-after-the-end position. */
|
|
unsigned char const * zEnd;
|
|
/** Initialize == zBegin. zPos is never > zEnd. */
|
|
unsigned char const * zPos;
|
|
/** Bytes owned elsewhere. If NULL, inherit from this->parent
|
|
(see cmpp_tizer_name()). */
|
|
char const * name;
|
|
/**
|
|
Info for current token. Only valid after a call to
|
|
cmpp_tizer_next().
|
|
*/
|
|
cmpp_token token;
|
|
/**
|
|
Put-back token. We probably don't need this, as any back-tracking
|
|
would be done with call-local copies of tokens. It's overwritten
|
|
at the start of cmpp_tizer_next() unless tz is at EOF at the
|
|
time it's called (as opposed to hitting EOF in that call).
|
|
*/
|
|
cmpp_token pbToken;
|
|
/**
|
|
A place to mark location when an error is expected. If one does
|
|
happen, and it channels through the proper magic, the location of
|
|
this token will be included in the error message. The intent is
|
|
not so much that this be set immediately before each potential
|
|
error, but that it be set whenever a token is about to be
|
|
processed, so that exceptions through that handling can have a
|
|
sensible error position. It's not an exact science.
|
|
|
|
This token's size is not currently used, only its z member, but
|
|
we use a full token here for the sake of future experimentation.
|
|
Some cases don't have one, in which case they can set
|
|
this->errToken.z to a pointer _if_ it's known to live within
|
|
[this->zBegin,this->zEnd).
|
|
*/
|
|
cmpp_token errToken;
|
|
/**
|
|
True when we're at the "begin-of-expression". i.e. we're looking
|
|
for a command to run. This is true only when looking for the
|
|
initial token in a given Itch command line (where a line ends an
|
|
EOL or ';'). For subsequent searches in the same command line it
|
|
must be false. This is generally managed by cmpp_tizer_next() but
|
|
it wouldn't hurt for code which is responsible for finding
|
|
command lines to set it at the start of that process.
|
|
*/
|
|
bool atBOX;
|
|
};
|
|
|
|
/**
|
|
An empty-initialized cmpp_tizer struct for use in const copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_tizer_empty_m { \
|
|
.parent=0, .err=0, \
|
|
.zBegin=0, .zEnd=0, .zPos=0, \
|
|
.token=cmpp_token_empty_m, \
|
|
.pbToken=cmpp_token_empty_m, \
|
|
.errToken=cmpp_token_empty_m, \
|
|
.atBOX=true \
|
|
}
|
|
|
|
/**
|
|
An empty-initialized cmpp_tizer struct for use in non-const copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_tizer cmpp_tizer_empty;
|
|
|
|
/**
|
|
Sets up tz to tokenize the given half-open input range (zBegin,zBegin+nBytes),
|
|
except that if nBytes is negative then strlen() is used to calculate it.
|
|
|
|
The err arg may be NULL but it won't be able to report precise
|
|
error location info via cmpp_tizer_next().
|
|
|
|
It is safe to pass the same tz instance multiple times. The
|
|
instances own no dynamic state and this function completely
|
|
overwrites tz.
|
|
|
|
Results are specifically undefined if:
|
|
|
|
- zBegin is NULL.
|
|
- zBegin is invalidated during the lifetime of tz.
|
|
- The contents of zBegin are modified during the lifetime of tz.
|
|
|
|
i.e. don't do that.
|
|
|
|
This function may assert() that its inputs look sane because this
|
|
is a column upon which many internals are based and it need not
|
|
tolerate bad inputs.
|
|
|
|
If a call succeeds for any given instance, the caller is obligated
|
|
to eventually pass pass tz to cmpp_tizer_dtor(), even if later
|
|
calls to this fail. It is safe to pass it to cmpp_tizer_dtor()
|
|
regardless of whether this fails, but an initial call is guaranteed
|
|
not to leak resources, whereas a follow-up call without an
|
|
intervening cmpp_tizer_dtor() call may leak resources if cmpp_tizer
|
|
every gains any. The destructor requirement is to leave us the
|
|
option of eventually giving cmpp_tizer dynamic state (plus, it
|
|
zeroes out the object to help avoid misuse after its usable
|
|
lifetime).
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_init(cmpp_tizer * tz, cmpp_errinfo *err,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nBytes);
|
|
|
|
/**
|
|
Copies "inherited" info from tzParent to tz, e.g. sets tz->parent
|
|
to tzParent. If tz->err is NULL then it sets tz->err to
|
|
tzParent->err, else tz->err is retained. Likewise for tz->name
|
|
but uses cmpp_tizer_name(tzParent) to get it.
|
|
|
|
Results are undefined if tz's input range is not fully contained
|
|
within tzParent's, and this function may assert, or otherwise fail
|
|
fatally, if that is the case (because it represents a serious
|
|
footgun for any downstream code which relies on those semantics).
|
|
i.e. don't do that.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_inherit(cmpp_tizer const * tzParent,
|
|
cmpp_tizer * tz);
|
|
|
|
/**
|
|
Initializes a sub-tokenizer which is limited to the contents of
|
|
tzParent->token. The token's inner range, if any, is used for
|
|
the input, so the delimiters for strings and grouping tokens
|
|
are not part of the input.
|
|
|
|
If tok is NULL then tzParent->token is used.
|
|
|
|
This also sets tzSub->parent to tzParent.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_init_sub(cmpp_tizer const *tzParent,
|
|
cmpp_token const * tok,
|
|
cmpp_tizer * tzSub);
|
|
|
|
/**
|
|
Returns the first name found in tz or one of tz->parent
|
|
(recursively).
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_tizer_name(cmpp_tizer const *tz);
|
|
|
|
/**
|
|
If tz holds any state, this relenquishes that state, then tz is
|
|
zeroed out, available for discarding or passing it to
|
|
cmpp_tizer_init() again. It is illegal to pass an uninitialized
|
|
object to this function.
|
|
|
|
Achtung: tz->err is owned by whoever put it there. This function
|
|
zeroes out that member but does not modify that object.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_dtor(cmpp_tizer * tz);
|
|
|
|
/**
|
|
Calculates the whole input range of tz and its parents (if they
|
|
share an encompassing byte range), for purposes of calculating line
|
|
counts. Sets *zBegin and *zEnd to the half-open range.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_full_range(
|
|
cmpp_tizer const * tz, unsigned char const **zBegin,
|
|
unsigned char const **zEnd
|
|
);
|
|
|
|
/**
|
|
Returns true if tz->token.zPos is either at/past tz->zEnd or
|
|
cmpp_tt_EOF==tz->token.ttype.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_tizer_eof(cmpp_tizer const *tz);
|
|
|
|
/**
|
|
Returns true if z lies within, or at the end of, tz's complete
|
|
input range, as per cmpp_tizer_full_range().
|
|
*/
|
|
CMPP_EXPORT bool cmpp_tizer_contains(cmpp_tizer const * tz,
|
|
unsigned char const *z);
|
|
|
|
/**
|
|
Rewinds the tokenizer to its initial tokenization position, such
|
|
that the next call tp cmpp_tizer_next() will start at that
|
|
position. This clears all cmpp_token-type members of tz and sets
|
|
tz->atBOX to true.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_rewind(cmpp_tizer *tz);
|
|
|
|
/**
|
|
Flags for use with cmpp_tizer_next().
|
|
*/
|
|
enum cmpp_tizer_next_e {
|
|
/** Sentinel entry. */
|
|
cmpp_tizer_next_NONE = 0,
|
|
/**
|
|
Tells cmpp_tizer_next() to skip newlines, treating them as
|
|
whitespace or comments. By default newlines are end-of-command
|
|
distinct tokens reported with the type cmpp_tt_EOL. Sometimes
|
|
newlines are EOL tokens, sometimes they're spaces, and it's up to
|
|
the caller to specify which is which.
|
|
*/
|
|
cmpp_tizer_next_SKIP_NL = 0x01,
|
|
/**
|
|
Tells cmpp_tizer_next() to skip over C-style comments. They may
|
|
span lines. For contexts where newlines end tokenization, the
|
|
newlines within a comment are not considered newlines.
|
|
*/
|
|
cmpp_tizer_next_SKIP_C_COMMENTS = 0x02
|
|
|
|
/**
|
|
TODO: a flag which triggers the workaround
|
|
described arg.c:cmpp__squiggly_strip_lead().
|
|
*/
|
|
};
|
|
|
|
/**
|
|
Fetches the next token and updates tz->token.
|
|
|
|
On success, returns 0, and tz->token contain the relevant
|
|
information. On error tz->token.type==cmpp_tt_Error, tz->token.n
|
|
will be 0, and tz->token.z will point to the best approximation of
|
|
where the error is.
|
|
|
|
At EOF, 0 is returned, tz->token.z points to the one-after-the-end
|
|
position, tz->n==0, and tz->token.type==cmpp_tt_EOF.
|
|
*/
|
|
CMPP_EXPORT int cmpp_tizer_next(cmpp_tizer *tz, cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Flags for use with cmpp_token_content().
|
|
*/
|
|
enum cmpp_token_content_e {
|
|
/** Sentinel entry. */
|
|
cmpp_token_content_NONE = 0,
|
|
/**
|
|
This returns the whole content of the token, regardless of its
|
|
type and with no grouping wrappers or + signs removes.
|
|
*/
|
|
cmpp_token_content_WHOLE = 0x01,
|
|
/**
|
|
Do not pass the destination buffer to cmpp_b_reuse().
|
|
*/
|
|
cmpp_token_content_APPEND = 0x02,
|
|
/**
|
|
If the token is a string or a group token, trim the content of
|
|
leading and trailing spaces.
|
|
*/
|
|
cmpp_token_content_TRIM = 0x04,
|
|
/**
|
|
By default it will apply unescaping to string-type tokens unless
|
|
this flag is set. It does not unescape squiggly groups, despite
|
|
them being a type of string.
|
|
*/
|
|
cmpp_token_content_NO_UNESCAPE = 0x08
|
|
};
|
|
|
|
/**
|
|
Returns the content part of the given token by appending to b or
|
|
(depending on the flags) replacing b's content with a copy of tok's
|
|
contents, possibly a transformed form of them (depending on the
|
|
tok->ttype and flags).
|
|
|
|
If tok is NULL then tz->token is used.
|
|
|
|
The target buffer is passed to cmpp_b_reuse() unless flags contains
|
|
the cmpp_token_content_APPEND flag.
|
|
|
|
Returns 0 on success. Errors are propagated via tz->pp.
|
|
|
|
The following tok->ttype values do not produce any output and cause
|
|
0 to be returned:
|
|
|
|
cmpp_tt_EOF, cmpp_tt_Error, cmpp_tt_None
|
|
*/
|
|
CMPP_EXPORT int cmpp_token_content(cmpp_tizer const * tz,
|
|
cmpp_token const * tok, cmpp_b * b,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Calculates the 1-based line and 0-based column of the given token
|
|
(or tz->token if tok is NULL) and writes them *l and *c.
|
|
*/
|
|
CMPP_EXPORT void cmpp_token_lc(cmpp_tizer const * tz,
|
|
cmpp_token const * tok,
|
|
cmpp_size_t *l, cmpp_size_t *c);
|
|
|
|
|
|
/**
|
|
True if (cmpp_token const *)TOKEN's contents match the string literal
|
|
STR, else false.
|
|
*/
|
|
#define cmpp_token_equals_c(TOKEN,STR) \
|
|
(sizeof("" STR)-1==(TOKEN)->n && 0==memcmp(STR,(TOKEN)->z,sizeof(STR)-1))
|
|
|
|
/**
|
|
True if (cmpp_token const *)TOKEN's contents match the string literal
|
|
STR or ("-" STR), else false. The intent is that "-flag" be passed
|
|
here to tolerantly accept either "-flag" or "--flag".
|
|
*/
|
|
#define cmpp_token_isflag_c(TOKEN,STR) \
|
|
(cmpp_token_equals_c(TOKEN,STR) || cmpp_token_equals_c(TOKEN, "-" STR))
|
|
|
|
|
|
/**
|
|
Returns true if (t->id & cmpp_tt_mask_shape)
|
|
is cmpp_tt_shape_group, else false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_token_is_group(cmpp_token const * t);
|
|
|
|
/**
|
|
Returns true if (t->id & cmpp_tt_mask_shape)
|
|
is cmpp_tt_shape_string, else false.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_token_is_string(cmpp_token const * t);
|
|
|
|
/**
|
|
If z is 0 then the error position is cleared. Else if z is within
|
|
tz's full input range (including parents), this sets tz's error
|
|
position to z. Else this has no side effect.
|
|
|
|
To be clear: this does not put the tokenizer into an error
|
|
state. This records the given position for the sake of any
|
|
potential following error. APIs which use cmpp_tizer frequently set
|
|
this for each being-processed token, such that that position will
|
|
be used for error reporting if something goes wrong in a following
|
|
step. The overwhelming majority of the time, this will precisely
|
|
pinpoint the location of an error.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_errpos_set(cmpp_tizer *tz, unsigned char const *z);
|
|
|
|
/**
|
|
Like cmpp_tizer_errpos_set() but requires non-NULL arguments and it
|
|
uses the position tok->z.
|
|
*/
|
|
CMPP_EXPORT void cmpp_tizer_errpos_set_t(cmpp_tizer *tz, cmpp_token const * tok);
|
|
|
|
/**
|
|
If tz is not NULL and has any token set which could potentially be
|
|
used for an error position, its address is returned, else 0 is
|
|
returned. This prefers tok->errToken, then tz->token, then
|
|
tz->zPos, in that order.
|
|
|
|
This does _not_ imply that there is an error at that position: this
|
|
is used for trying to guess a position for a pending error report
|
|
when one is otherwise not provided. (The internals which make use
|
|
of cmpp_tizer make an honest effort to update the _potential_ error
|
|
position as they work so that any errors they trigger will have
|
|
accurate location information.)
|
|
*/
|
|
CMPP_EXPORT unsigned char const * cmpp_tizer_errpos(cmpp_tizer const * tz);
|
|
|
|
/**
|
|
Returns a new sqlite3_str which is prefixed (ideally) with error
|
|
location information for tz, specifically for position z. Ownership
|
|
of the object is transferred to the caller, who is generally
|
|
expected to append to it as if nothing were there and eventually
|
|
pass it to sqlite3_str_finish() to finalize it and take over its
|
|
C-string result. Returns NULL on OOM[^1].
|
|
|
|
If z is NULL then cmpp_tizer_errpos() is used.
|
|
|
|
This function may assert() that z lives in tz's range - any value
|
|
out of that range represents a serious misuse of the API. In
|
|
non-debug builds it will return a new, but empty, string in that
|
|
case.
|
|
|
|
Very minor caveat: the returned object has no access to an sqlite3
|
|
DB handle, which means that it is not subject to the size limits
|
|
configured for the DB owned by any given cmpp instance. Thus it is
|
|
possible, but only through what may fairly be characterized as
|
|
blatant malicious misuse, that this string can grow larger than the
|
|
underlying sqlite3 db is configured to accept, leading to
|
|
downstream errors if, e.g., the string is used as a the value for a
|
|
db column. That said: this function is invariably used for error
|
|
output, not db data, so this caveat is largely moot.
|
|
|
|
[^1]: sqlite3_str_new() _never_ returns NULL. On OOM it returns a
|
|
dummy object which is in a perpetual error state. This API,
|
|
however, checks for that and will return NULL on OOM.
|
|
*/
|
|
CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix(cmpp_tizer const *tz,
|
|
unsigned char const *z);
|
|
|
|
/**
|
|
A proxy for cmpp_tizer_err_prefix() which takes a token as
|
|
input. If it's null then tz->token is used.
|
|
*/
|
|
CMPP_EXPORT sqlite3_str * cmpp_tizer_err_prefix_t(cmpp_tizer const *tz,
|
|
cmpp_token const *tok);
|
|
|
|
/**
|
|
Convenience helper to loop over a cmpp_tizer until it reaches EOF
|
|
on an error. This can be used within Itch commands to iterate over
|
|
script-containing strings and token lists.
|
|
|
|
TZP = a cmpp_tizer_init()'d (cmpp_tizer*).
|
|
|
|
RC = the name of an int-type var which will be set to the
|
|
cmpp_tizer_next() loop on each iteration. It is set to 0 before
|
|
starting the loop. If it's 0 after the loop, the tokenizer has
|
|
reached its EOF. It it's not 0 then it hit an error and will have
|
|
reported it via TZP->err if that's not NULL. The condition (0==RC)
|
|
is checked before each step and the loop is aborted if it's not
|
|
0. i.e. setting it to non-0 will stop the loop on the next
|
|
iteration without fetching a following token.
|
|
|
|
FLAGS = the second argument to cmpp_tizer_next(). If it's a
|
|
variable name instead of a literal, the loop body may change the
|
|
flags during the loop to affect the next interation.
|
|
|
|
POST is the optional Z part of for(X;Y;Z). It may be empty.
|
|
|
|
Usage:
|
|
|
|
```
|
|
int rc;
|
|
cmpp_tizer tz = cmpp_tizer_empty;
|
|
cmpp_token const * tok = &tz->token;
|
|
cmpp_tizer_init(tz, 0, "a b c", -1);
|
|
cmpp_tizer_foreach4(tz, rc, 0,) {
|
|
... use tok->ttype and friends ...
|
|
}
|
|
if( rc ){error;}
|
|
else {reached EOF or break'd out of the loop;}
|
|
```
|
|
|
|
Credits: this trick was gleaned from the ParTcl sources.
|
|
*/
|
|
#define cmpp_tizer_foreach4(TZP,RC,FLAGS,POST) \
|
|
RC = 0; \
|
|
for( ; (TZP)->zPos<(TZP)->zEnd \
|
|
&& 0==RC \
|
|
&& (0==(RC=cmpp_tizer_next(TZP,FLAGS))) \
|
|
&& cmpp_tt_EOF!=(TZP)->token.ttype; \
|
|
POST )
|
|
|
|
/** Slightly-simplified form of cmpp_size_foreach4() which elides that
|
|
one's fourth argument. */
|
|
#define cmpp_tizer_foreach3(TZP,RC,FLAGS) \
|
|
cmpp_tizer_foreach4(TZP, RC, FLAGS,)
|
|
|
|
/**
|
|
A single argument for a cmpp_f() implementation. When a
|
|
cmpp_d::flags does not have the cmpp_f_F_ARGS_RAW bit set then the
|
|
part of the input immediately following the directive (and on the
|
|
same logical line).
|
|
|
|
These objects are created by cmpp instances as they parse directive
|
|
lines in an input stream, and they pass them, using the cmpp_f()
|
|
interface, to the native implementations for each
|
|
directive. Similarly, cmpp_itch uses this for command arguments
|
|
(which, when it comes down to it, are the same thing except for the
|
|
small detail of string interpolation performed by the script
|
|
environment).
|
|
|
|
Lifetime:
|
|
|
|
For the _most_ part:
|
|
|
|
Each of these is valid for the duration of the cmpp_f() call into
|
|
which the argument was initially passed. Their memory may be freed,
|
|
recycled, or otherwise vanish, the moment the cmpp_f() impl
|
|
returns.
|
|
|
|
Caveat:
|
|
|
|
cmpp_f() impls which do _not_ use the cmpp_f_F_ARGS_V4 flag may,
|
|
depending on what type of work they do, be subject to lifetime
|
|
invalidation of these objects during the call to
|
|
cmpp_f(). Specifically, that happens in impls which themselves
|
|
consume from the input stream (e.g. using cmpp_dx_consume()).
|
|
Implementations which do not do that have the same lifetime
|
|
guarantees as V4-using implementations. The workaround for such
|
|
cases is to use cmpp_dx_args_clone() but (A) the V4 argument
|
|
handling obviates the need for it and (B) that option will be
|
|
removed once all built-in directives are ported to V4.
|
|
|
|
Historically they have been used together with cmpp_args, a
|
|
container for these, but as of 2026-09 the cmpp_args (plural) class
|
|
is effectively deprecated and not needed for directives which use
|
|
the cmpp_f_F_ARGS_V4 interface (which all new directives "should"
|
|
use).
|
|
*/
|
|
struct cmpp_arg {
|
|
/**
|
|
Token type from the cmpp_tt_e enum.
|
|
*/
|
|
int ttype;
|
|
/**
|
|
The arg's string value, shorn of any opening/closing quotes or ()
|
|
or {} or []. The args-parsing process guarantees to NUL-terminate
|
|
this. The bytes are typically owned by a cmpp_args object, but
|
|
clients may direct them wherever the need to, so long as the
|
|
bytes are valid longer than this object is.
|
|
|
|
The library guarantees that this is NUL-terminated in the
|
|
following contexts:
|
|
|
|
- When this object is passed to a cmpp_f() impl.
|
|
- When this object was initialized from a public cmpp_args
|
|
API, e.g. cmpp_args_parse() or cmpp_args_clone().
|
|
|
|
It does not gurantee so in other contexts.
|
|
|
|
This pointer memory is owned by the API which set it up and it
|
|
may be invalidated by any modifications to, e.g., a cmpp_args
|
|
object which initialized it.
|
|
*/
|
|
unsigned char const * z;
|
|
|
|
/**
|
|
The arg's effective length, in bytes, after opening/closing chars
|
|
are stripped. That is, its string form is the range
|
|
[this->z,this->z+n).
|
|
*/
|
|
unsigned n;
|
|
|
|
/**
|
|
DEPRECATED: this is part of the pre-V4 args handling and
|
|
must not be used in newer versions.
|
|
|
|
The next argument in the list. It is owned by whatever code set
|
|
it up (typically cmpp_args_parse()). The long-term plan is to get
|
|
rid of this in favor of argv-style traversal but current
|
|
cmpp_args infrastructure requires a linked list.
|
|
|
|
This will go away or will eventually only be set for directives
|
|
which do not opt in to the Newer Ways.
|
|
|
|
DO NOT use this in cmpp_f() impls tagged with cmpp_f_F_ARGS_V4:
|
|
it is unconditionally set to NULL for those.
|
|
*/
|
|
cmpp_arg const * next;
|
|
|
|
/**
|
|
If this argument came from a cmpp_itch interpreter, this will be
|
|
set to a buffer which contains the interpolated form of the
|
|
argument. That is, this->z might say "$x" and this->b might say
|
|
"3".
|
|
|
|
In non-cmpp_itch invocations of cmpp_f() this is currently always
|
|
0 but The Plan is to eventually offer the ability for directives
|
|
to opt-in to interpolations which most directives already
|
|
currently use. That would save significant amounts of code for
|
|
some directives.
|
|
|
|
This buffer is owned by this object but the very simple cmpp_arg
|
|
lifetime model makes it perfectly legal do one of the following
|
|
from cmpp_f() implementations:
|
|
|
|
- Pass this to cmpp_b_swap(), giving its memory to a buffer
|
|
they control.
|
|
|
|
- Pass the buffer to cmpp_itch_result_take(), which is
|
|
basically a proxy for cmpp_b_swap(). In this way a value can
|
|
be returned as-is from the command without any allocation.
|
|
|
|
- Take over ownership of b->z and wipe b's contents by copying
|
|
over it with cmpp_b_empty.
|
|
|
|
- Take over b, assign this->b to 0, and then eventually pass be
|
|
to cmpp_b_return(theOwningCmppInstance, b) to relinquish
|
|
it. DO NOT pass b to cmpp_free(). This of course requires a
|
|
non-const instance, and the ones passed to cmpp_f()
|
|
implementations are const.
|
|
*/
|
|
cmpp_b * interp;
|
|
|
|
/**
|
|
The raw argument token, referring to memory owned by whoever set
|
|
this object up. It is intended only for use by low-level
|
|
directives where the pointer relationships and memory lifetimes
|
|
are well-understood.
|
|
|
|
Depending on the args-parsing mode, token.z and token.zInner may
|
|
point into the containing script's memory, so will only be
|
|
NUL-terminated when they're actually at the right-most edge of
|
|
their input range (and only if _that_ is the whole input range
|
|
rather than from a sub-tokenizer). token.n and token.nInner MUST
|
|
be used to constrain the input range when token.z or token.zInner
|
|
are emitted or copied.
|
|
*/
|
|
cmpp_token token;
|
|
};
|
|
|
|
/**
|
|
Empty-initialized cmpp_arg instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_arg_empty_m { \
|
|
.ttype=cmpp_tt_None, \
|
|
.z=0,.n=0,.next=0, \
|
|
.interp=0, \
|
|
.token=cmpp_token_empty_m \
|
|
}
|
|
|
|
/* Forward decls. */
|
|
typedef struct cmpp_dx cmpp_dx;
|
|
typedef struct cmpp__dx_pimpl cmpp__dx_pimpl;
|
|
typedef struct cmpp_d cmpp_d;
|
|
|
|
/**
|
|
Flags for use with cmpp_d::flags.
|
|
*/
|
|
enum cmpp_f_e {
|
|
/** Sentinel value. */
|
|
cmpp_f_F_none = 0,
|
|
/**
|
|
cmpp_dx_next() will not parse the directive's arguments. Instead,
|
|
it makes cmpp_dx::arg0 encapsulate the whole line of the
|
|
directive (sans the directive's name) as a single argument. The
|
|
only transformation which is performed is the removal of
|
|
backslashes from backslash-escaped newlines. It is up to the
|
|
directive's callback to handle (or not) the arguments.
|
|
|
|
As of early 2026-08, the memory set up for cmpp_f()
|
|
implementations for this argument style is stable. That is, it
|
|
remains valid until the cmpp_f() returns, rather than being
|
|
invalidated by recursing into the library from the cmpp_f() impl.
|
|
Some docs may mention such invalidation because it was real for a
|
|
long time, but that is now thankfully resolved.
|
|
|
|
This flag may be used together with cmpp_f_F_ARGS_V4 but
|
|
only exotic use cases would want to do so.
|
|
*/
|
|
cmpp_f_F_ARGS_RAW = 0x01,
|
|
|
|
/**
|
|
cmpp_dx_next() will parse the directive's arguments.
|
|
cmpp_dx::arg0 will point to the first argument in the list, or
|
|
NULL if there are no arguments.
|
|
|
|
If both cmpp_f_F_ARGS_LIST and cmpp_f_F_ARGS_RAW are specified,
|
|
it is unspecified which one applies. i.e. don't do that.
|
|
|
|
See also (and prefer): cmpp_f_F_ARGS_V4.
|
|
*/
|
|
cmpp_f_F_ARGS_LIST = 0x02,
|
|
|
|
/**
|
|
Specifies that the directive is using "version 4" of the
|
|
argument-passing mechanism, in which it populates
|
|
cmpp_f_args::argc and cmpp_f_args::argv with the count of and
|
|
pointers to the arguments. This mode always includes the
|
|
directive or command name being invoked as the first argument, so
|
|
argc is always at least 1 and argv is non-NULL unless this flag
|
|
is combined with cmpp_f_F_ARGS_RAW (see below).
|
|
|
|
The advantage to this flag over the other ARGS_... variants is
|
|
that the argument-related memory is not invalidated if the
|
|
callback recurses into the library. i.e. the memory in
|
|
cmpp_f_args::argv and cmpp_f_args::raw are stable for the
|
|
duration of the cmpp_f() call. The earlier modes now gurantee
|
|
that for cmpp_f_args::raw but not cmpp_dx::args (and the earlier
|
|
modes to not set cmpp_f_args::argv).
|
|
|
|
This is the argument-handling flag which All New Code Should
|
|
Use. Lots of old code still doesn't, which is sad, but we're
|
|
slowly getting them ported to v4.
|
|
|
|
Using this flag together with cmpp_f_F_ARGS_LIST is redundant and
|
|
harmless but the V4 semantics will be applied.
|
|
|
|
If cmpp_f_F_ARGS_RAW is used together with this flag then
|
|
it works a bit differently:
|
|
|
|
- cmpp_f_args::argc will be set to the number of arguments
|
|
tokenized by the preprocessor (it has to do so to be able to
|
|
get to the point of calling the cmpp_f()). Because V4 includes
|
|
the name of the being-invoked directive or command as argument
|
|
0, argc will always be at least one.
|
|
|
|
- cmpp_f_args::argv will be NULL. Such cmpp_f() impls are
|
|
expected to parse the arguments using (e.g.) cmpp_tizer with
|
|
the input range defined by cmpp_f_args::raw.z and
|
|
cmpp_f_args::raw.n (noting that it is generally NOT
|
|
nul-terminated). That range contains the directive's raw
|
|
argument line, up to (but not including) its final newline or
|
|
EOF. It is guaranteed to be non-NULL and have a non-0 length
|
|
because the directive or command name being invoked is the
|
|
first argument in the list. That range is guaranteed to point
|
|
back into the original source bytes, backslash-escaped newlines
|
|
and all.
|
|
*/
|
|
cmpp_f_F_ARGS_V4 = 0x40,
|
|
|
|
/**
|
|
Indicates that the cmpp_f() should not be available if the cmpp
|
|
instance is configured with the cmpp_ctor_F_SAFEMODE flag. All
|
|
directives which do any of the following are obligated to set
|
|
this flag:
|
|
|
|
- Filesystem or network access.
|
|
- Invoking external processes.
|
|
- Anything else which might be deamed "security-relevant".
|
|
|
|
When registering a directive which has both opener and closer
|
|
implementations, it is sufficient to set this only on the opener.
|
|
|
|
The library imposes this flag in the following places:
|
|
|
|
- Registration of a directive (cmpp_d_register()) or cmpp_itch
|
|
command (cmpp_itch_cmd_add()) with this flag will fail if
|
|
cmpp_is_safemode() is true for that cmpp instance.
|
|
|
|
- cmpp_dx_process() will refuse to invoke a directive with this
|
|
flag when cmpp_is_safemode() is true.
|
|
|
|
- cmpp_itch refuses to invoke commands with this flag when
|
|
cmpp_is_safemode() is true.
|
|
|
|
The latter two are also protected at registration-time, with the
|
|
above behaviors representing "defense in depth" rather than
|
|
resolving a known deficiency.
|
|
*/
|
|
cmpp_f_F_NOT_IN_SAFEMODE = 0x008,
|
|
|
|
/**
|
|
Call-only directives are only usable in [directive ...] "call"
|
|
contexts. They are not permitted to have a closing directive.
|
|
This flag has no effect on cmpp_itch commands.
|
|
*/
|
|
cmpp_f_F_CALL_ONLY = 0x010,
|
|
|
|
/**
|
|
Indicates that the directive is incapable of working in a [call]
|
|
context and an error should be triggered if it is. _Most_
|
|
directives which have a closing directive should have this
|
|
flag. The exceptions are directives which only conditionally use
|
|
a closing directive, like #query. This flag has no effect on
|
|
cmpp_itch commands.
|
|
*/
|
|
cmpp_f_F_NO_CALL = 0x020,
|
|
|
|
/**
|
|
Tells the framework that the directive's arity should be enforced
|
|
by the tokenizer (so that the directive implementation does not
|
|
have to deal with it).
|
|
|
|
A cmpp_d_reg::arity value of 0 or higher causes the framework,
|
|
when this flag is set, to enforce precisely that many arguments.
|
|
A negative value means to not enforce any argument count. This
|
|
capability is generally intended for script bindings (even then,
|
|
most of them need a negative arity).
|
|
|
|
This only really works for very simple cases, as most directives
|
|
require variadic flag and arg handling. This also does not
|
|
(cannot) enforce argument type restrictions - that has to be done
|
|
in the directive.
|
|
*/
|
|
cmpp_f_F_ENFORCE_ARITY = 0x040,
|
|
|
|
/**
|
|
Indicates that the directive requires an active cmpp_dx in order
|
|
to work (e.g. because it reads from the input stream). The
|
|
framework will not invoke such directives or cmpp_itch functions
|
|
if cmpp_dx_current() returns NULL, generating an error instead.
|
|
All commands which consume from a being-preprocessed input stream
|
|
require an active cmpp_dx.
|
|
|
|
Commands which convey all output using cmpp_itch_result_set() and
|
|
friends do not require a generally require a cmpp_dx. Those which
|
|
consume preprocessor input or emit output to the cmpp output
|
|
stream do.
|
|
*/
|
|
cmpp_f_F_NEEDS_DX = 0x080,
|
|
|
|
/**
|
|
See cmpp_args_F_NO_SQUIGGLY_HACK.
|
|
*/
|
|
cmpp_f_F_NO_SQUIGGLY_HACK = 0x100,
|
|
|
|
|
|
/**
|
|
Mask of the client-usable range for this enum. Values outside of
|
|
this mask are reserved for internal use and will be stripped from
|
|
registrations made with cmpp_d_register() or cmpp_itch_cmd_add().
|
|
*/
|
|
cmpp_f_F_MASK = 0x0ffff
|
|
|
|
};
|
|
|
|
/**
|
|
State related to invocation of a cmpp_f() function.
|
|
*/
|
|
struct cmpp_f_args {
|
|
/**
|
|
The preprocessor context.
|
|
*/
|
|
cmpp * const pp;
|
|
|
|
/**
|
|
The current directive context.
|
|
*/
|
|
cmpp_dx * const dx;
|
|
|
|
/**
|
|
Number of arguments passed to the directive. This is ONLY SET to
|
|
non-0 if this->d->flags contains cmpp_f_F_ARGS_V4. See that
|
|
flag's docs for details.
|
|
*/
|
|
unsigned const argc;
|
|
|
|
/**
|
|
Array of arguments passed to the directive. This is ONLY SET to
|
|
non-NULL if this->d->flags contains cmpp_f_F_ARGS_V4. See that
|
|
flag's docs for details.
|
|
*/
|
|
cmpp_arg const * const * const argv;
|
|
|
|
/**
|
|
The calling directive, if any. This is meant to be used in place
|
|
of the historical cmpp_dx::d member because this pointer does not
|
|
change when recursing into the library (whereas that one does).
|
|
We cannot eliminate that one from the API just yet but we are
|
|
headed there.
|
|
|
|
This is currently NULL when the cmpp_f() is called from a
|
|
cmpp_itch interpreter instead of the preprocessor. It's not clear
|
|
whether we want them to have access to this->dx and this->d, but
|
|
they can get either via cmpp_dx_current() anyway, noting that
|
|
this->dx->d may change when calling back into the library
|
|
to consume input.
|
|
*/
|
|
cmpp_d const * const d;
|
|
|
|
/** Brief usage string, intended for use in error messages and
|
|
--help functionality. */
|
|
char const * usage;
|
|
|
|
/** Opaque state defined by whoever created the associated
|
|
cmpp_f() impl, upon which interpretation depends. */
|
|
void * state;
|
|
|
|
/**
|
|
Flags from the cmpp_f_e enum.
|
|
*/
|
|
cmpp_flag32_t const flags;
|
|
|
|
/**
|
|
The raw arguments memory, for directives which want to use an
|
|
alternative tokenizer, emit debug info, or whatever it is what
|
|
one wants to do with it. cmpp_itch, for example, uses this to
|
|
ensure that its functions can map error location info to the
|
|
original source location (which we cannot do if we copy it
|
|
unless we add pieces to record the containing script's
|
|
position and account for it in error reporting).
|
|
|
|
Achtungs:
|
|
|
|
- For cmpp_itch commands (not cmpp_d callbacks, despite having
|
|
this same signature), this points directly back into the source
|
|
script. That _should_ be the case for non-Itch impls with the
|
|
cmpp_f_F_ARGS_V4 flag as well but cannot be without a
|
|
considerably compatibility break which is not worth reworking at
|
|
this point.
|
|
|
|
- For all other cases, this pointer lives in dynamic memory which
|
|
is edited very slightly from the original script, e.g. to remove
|
|
backslash-escaped newlines. (Itch scripts don't need that because
|
|
their tokenizer does it.) That memory stays valid for the life of
|
|
the cmpp_f() call and is invalidated the moment it
|
|
returns. (Directives use the same tokenizer but have backwards
|
|
compatibility constraints on how their implementations receive
|
|
the arguments until/unless they're ported to the V4 argument
|
|
handling.)
|
|
|
|
As of early 2026-09 the memory pointed to here remains valid
|
|
through the duration of the cmpp_f() call even if it recurses
|
|
into the library's content-consumption APIs. Before that change,
|
|
it would be invalidated if the impl called certain library APIs.
|
|
|
|
Design note: the ability to get the raw arguments was primarily
|
|
added so that directives like #pipe have something they can pass
|
|
as-is to external apps or APIs. That said: the arguments must be
|
|
able to at least pass through cmpp's argument tokenizer to get to
|
|
that point, and there are constructs which will not work well
|
|
with that. (We've yet to hit this limitation in any no small
|
|
amount of downstream inputs but it can certainly be contrived.)
|
|
*/
|
|
struct {
|
|
/** Start of the args memory. */
|
|
unsigned char const * const z;
|
|
/** Byte length of this->z. */
|
|
cmpp_size_t const n;
|
|
} raw;
|
|
/**
|
|
True if this invocation is a [call...] from preprocessor input,
|
|
as distinct from an cmpp_itch interpreter call.
|
|
*/
|
|
bool const isDxCall;
|
|
};
|
|
typedef struct cmpp_f_args cmpp_f_args;
|
|
|
|
/**
|
|
A helper for cmpp_f() implementations which use the
|
|
cmpp_f_F_ARGS_V4 flag: it uses cmpp_errf() to write a usage-style
|
|
error.
|
|
|
|
This currently sets the result code to CMPP_RC_ERROR, for
|
|
lack of a more specific one (we can't use CMPP_RC_ITCH_F_USAGE
|
|
because that one is stripped out of %lite builds).
|
|
|
|
Returns its error code.
|
|
|
|
This function relies on the fact that args->argc>0 for v4 arguments
|
|
and it dereferences args->argv[0] and its contents.
|
|
*/
|
|
CMPP_EXPORT int cmpp_f_v4_err_usage(cmpp_f_args const *args);
|
|
|
|
/**
|
|
Callback type for cmpp_d::impl::f() and cmpp_itch commands. cmpp
|
|
directives and cmpp_itch commands are all implemented as functions
|
|
with this signature. Implementations are called by the library as
|
|
the preprocessor consumes input and encounters directives, or when
|
|
its cmpp_itch interpreter calls one of its registered commands.
|
|
|
|
The first rule of callback is: to report errors (all of which end
|
|
processing of the current input) call one of the cmpp_err() family
|
|
of functions, then clean up any local resources, then return. The
|
|
library will recognize the error and propagate it. (cmpp_itch
|
|
callbacks should prefer cmpp_itch_err() and friends, as that one
|
|
distinguishes between script-only and non-script errors and will
|
|
not propagate the latter through to the owning cmpp context. We
|
|
cannot safely make that distinction in cmpp_err() because there
|
|
might, wildly hypothetically, be any number of cmpp_itch instances
|
|
and it would have to be told which, which is exactly what
|
|
cmpp_itch_err() and friends do.)
|
|
|
|
See the cmpp_f_args docs regarding memory lifetime of the argument
|
|
and its contents. They differ depending on the cmpp_f_e flags used
|
|
for registration.
|
|
|
|
Sidebar: despite cmpp_d and and cmpp_itch both using this
|
|
interface, the two things live in separate namespaces. That is,
|
|
internally there is no confusion about which is which, and trying
|
|
to invoke #someItchCommand as a directive won't work (even if
|
|
someItchCommand is a valid Itch command).
|
|
*/
|
|
typedef void (*cmpp_f)(cmpp_f_args const *args);
|
|
|
|
/**
|
|
A typedef for generic deallocation routines.
|
|
*/
|
|
typedef void (*cmpp_finalizer_f)(void *);
|
|
|
|
/**
|
|
State specific to concrete cmpp_d implementations.
|
|
*/
|
|
struct cmpp_d_impl {
|
|
/**
|
|
Callback func. If any API other than cmpp_dx_process() invokes
|
|
this, behavior is undefined.
|
|
*/
|
|
cmpp_f f;
|
|
|
|
/**
|
|
For custom directives with a non-NULL this->state, this will be
|
|
called, and passed that object, when the directive is cleaned
|
|
up. For directives with both an opening and a closing tag, this
|
|
destructor is only attached to the opening tag.
|
|
|
|
If any API other othan cmpp's internal cleanup routines invoke
|
|
this, behavior is undefined.
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
|
|
/**
|
|
State for the directive's callback. It is accessible in
|
|
cmpp_f() impls via theDx->d->impl.state. For custom
|
|
directives with both an opening and closing directive, this
|
|
same state object gets assigned to both.
|
|
*/
|
|
void * state;
|
|
};
|
|
typedef struct cmpp_d_impl cmpp_d_impl;
|
|
#define cmpp_d_impl_empty_m {.f=0,.dtor=0}
|
|
|
|
/**
|
|
Each c-pp "directive" is modeled by one of these.
|
|
*/
|
|
struct cmpp_d {
|
|
|
|
/**
|
|
The directive's name. It is stored along with its length to speed
|
|
up searches.
|
|
*/
|
|
struct {
|
|
/**
|
|
The directive's name, as it must appear after the directive
|
|
delimiter. Its bytes are assumed to be static or otherwise
|
|
outlive this object.
|
|
*/
|
|
const char *z;
|
|
/** Byte length of this->z. We record this to speed up searches. */
|
|
unsigned n;
|
|
} name;
|
|
|
|
/**
|
|
Brief help text. Its memory must outlive the directive.
|
|
|
|
This member was introduced for the sake of cmpp_itch callbacks,
|
|
where it has demonstrable utility. It unfortunately does not
|
|
serve the purpose well for directives, which commonly have
|
|
multiple call forms, both with and without a closing
|
|
tag. Historically (pre-Itch), directives did all of their own
|
|
validation error reporting so did not have a use for this member.
|
|
|
|
As of this writing (2026-09-07), this value is non-NULL but also
|
|
not terribly helpful for the library's builtin directives because
|
|
the internal setup makes it difficult to wedge it it. Itch
|
|
callbacks, on the other hand, tend to set it to something useful.
|
|
*/
|
|
char const * usage;
|
|
|
|
/**
|
|
The number of arguments this directive requires, or -1
|
|
for any number. A value of 0 means it does not accept
|
|
arguments.
|
|
|
|
Enforcement of the arity is opt-in, only enforced if
|
|
cmpp_f_F_ENFORCE_ARITY is in this->flags and if this->flags has
|
|
cmpp_f_F_ARGS_V4. Before that addition we did not have
|
|
any enforcement.
|
|
|
|
Achtung: v4 arguments add the directive/command name as the first
|
|
argument, so the callback always receives an argc of at least 1
|
|
and the arguments start at index 1. That "off by one" is _not_
|
|
accounted for in this artity value: it counts only the arguments,
|
|
not the commeand/directive name.
|
|
*/
|
|
int arity;
|
|
|
|
/**
|
|
Bitmask of flags from cmpp_f_e plus possibly internal flags.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
|
|
#if 0
|
|
/**
|
|
TODO: historically directives have all done their own
|
|
interpolation, but the internal APIs eventually reached a stage
|
|
where the directives were all repeating the same interpolation.
|
|
This flag is to provide interpolation options, initially only for
|
|
directives, but potentially also for use with (not cmpp_itch
|
|
callbacks (same interface).
|
|
*/
|
|
cmpp_flag32_t interpolationFlags;
|
|
#endif
|
|
/**
|
|
The directive which acts as this directive's closing element
|
|
element, or 0 if it has none. Directives may have multiple
|
|
closers but they are managed internally, not via this interface.
|
|
*/
|
|
cmpp_d const * closer;
|
|
|
|
/**
|
|
State specific to concrete implementations.
|
|
*/
|
|
cmpp_d_impl impl;
|
|
};
|
|
|
|
/**
|
|
Each instance of the cmpp_dx class (a.k.a. "directive context")
|
|
manages a single input source. It's responsible for the
|
|
tokenization of all input, locating directives, and processing
|
|
("running") directives. The directive-specific work happens in
|
|
cmpp_f() implementations, and this class internally manages the
|
|
setup, input traversal, and teardown.
|
|
|
|
These objects only exist while cmpp is actively processing
|
|
input. Client code interacts with them only through cmpp_f()
|
|
implementations which the library invokes.
|
|
|
|
The process of filtering input to look for directives is to call
|
|
cmpp_dx_next() until it indicates either an error or that a
|
|
directive was found. In the latter case, the cmpp_dx object is
|
|
populated with info about the current directive. cmpp_dx_process()
|
|
will run that directive, but cmpp_f() implementations sometimes
|
|
need to make decisions based on the located directive before doing
|
|
so (and sometimes they need to skip running it).
|
|
|
|
If cmpp_dx_next() finds no directive, the end of the input has been
|
|
reached and there is no further output to generate.
|
|
|
|
Content encountered before a directive is found is passed on to the
|
|
output stream via cmpp_out_raw() or cmpp_out_expand().
|
|
*/
|
|
struct cmpp_dx {
|
|
/**
|
|
The cmpp object which owns this context.
|
|
*/
|
|
cmpp * const pp;
|
|
|
|
/**
|
|
The directive on whose behalf this context is active. See
|
|
this->args for notes about invalidation of this value.
|
|
|
|
ACHTUNG: this may be modified by any APIs which recurse into the
|
|
library. cmpp_f() impls should generaly use cmpp_f_args::d
|
|
instead of this, but impls which have multiple closers are known
|
|
to need to be able to see changes to this in order to behave
|
|
properly.
|
|
*/
|
|
cmpp_d const *d;
|
|
|
|
/**
|
|
Name of the input for error reporting. Typically an input script
|
|
file name, but it need not refer to a file.
|
|
*/
|
|
unsigned const char * const sourceName;
|
|
|
|
/**
|
|
State related to arguments passed to the current directive.
|
|
|
|
DEPRECATED 2026-09: this member not needed for directives which
|
|
use cmpp_f_F_ARGS_V4, as that flag uses an approach which (A) is
|
|
easier to use in practice and (B) does not invalidate argument
|
|
memory when recursing into the library. Once/if older directive
|
|
code is updated, this member will go away.
|
|
|
|
This particular struct is LEGACY and DEPRECATED. It will not be
|
|
removed until all delimiter handlers are adapted to the
|
|
cmpp_f_F_ARGS_V4 model but it should not be used in any new
|
|
code. cmpp_f() impls in that mode should use
|
|
cmpp_f_args::argc/argv for a more conventional experience and
|
|
none of the inherent memory-invalidation problems which plague
|
|
the older model.
|
|
|
|
It is important to keep in mind that the memory for the members
|
|
of this sub-struct may be modified or reallocated
|
|
(i.e. invalidated) by any APIs which call in to cmpp_dx_next().
|
|
cmpp_f() implementations must take care not to use any of this
|
|
memory after calling into that function, cmpp_dx_consume(), or
|
|
similar. If needed, it must be copied (e.g. using
|
|
cmpp_dx_args_clone() to create a local copy of the parsed
|
|
arguments).
|
|
*/
|
|
struct {
|
|
/**
|
|
Starting byte of unparsed arguments. This is for cmpp_d_f()
|
|
implementations which need custom argument parsing.
|
|
|
|
The contents differ from the raw, unparsed source file in that
|
|
backslash-escaped newlines are replaced with newlines. The API
|
|
does not offer access to the raw underlying strings. Maybe it
|
|
should.
|
|
|
|
This member is set regardless of the argument parsing mode
|
|
for the directive.
|
|
*/
|
|
unsigned const char * z;
|
|
|
|
/**
|
|
The byte length of z. It is not called "n", like its
|
|
counterpart in other structs, so as to avoid confusion between
|
|
dx->args->n and dx->args->argc (see, that was confusing,
|
|
right?).
|
|
|
|
This member is set regardless of the argument parsing mode for
|
|
the directive.
|
|
*/
|
|
cmpp_size_t nz;
|
|
|
|
/**
|
|
The parsed arg count for the this->arg0 list.
|
|
|
|
ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode.
|
|
Use cmpp_f_args::argc instead.
|
|
*/
|
|
unsigned argc;
|
|
|
|
/**
|
|
The first parsed arg or NULL. How this is set up is affected by
|
|
cmpp_d::flags.
|
|
|
|
ACHTUNG: do not use this when using cmpp_f_F_ARGS_V4 mode.
|
|
Use cmpp_f_args::argv instead.
|
|
|
|
This is specifically _NOT_ defined as a sequential array and
|
|
using pointer math to traverse it invokes undefined behavior.
|
|
|
|
To traverse the list:
|
|
|
|
for( cmpp_arg const *a = dx->args.arg0; a; a=a->next ){
|
|
...
|
|
}
|
|
|
|
Directives registered which cmpp_f_F_ARGS_V4 are instead
|
|
expected to use cmpp_f_args::argv, and this member may be
|
|
NULL for them.
|
|
*/
|
|
cmpp_arg const * arg0;
|
|
} args;
|
|
|
|
/**
|
|
Private impl details.
|
|
*/
|
|
cmpp__dx_pimpl * const pimpl;
|
|
};
|
|
|
|
|
|
/**
|
|
Returns pp's current directive context. When processing
|
|
directives, this is the context which is active during that
|
|
time. When not processing directives, this is NULL.
|
|
|
|
Lifetime: the returned object is managed by pp and it will be
|
|
destroyed as soon as it reaches the end of the dx's input or an
|
|
error pre-empts that and propgates back up to that point. Callers
|
|
must not hold onto this pointer for longer than the current
|
|
function, nor must they expect this function to return the same
|
|
value before and after calling into APIs which traverse into new
|
|
input scripts (e.g. via the #include directive). In a case like
|
|
#include or cmpp_process_string(), this function will return the
|
|
same thing immediately before and after that operation, but during
|
|
processing of that source it will have a different value which
|
|
expires as soon as the source is consumed or errors out.
|
|
*/
|
|
CMPP_EXPORT cmpp_dx * cmpp_dx_current(cmpp *pp);
|
|
|
|
/**
|
|
Thin proxy for cmpp_errf(), replacing only the first argument.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_errf(cmpp_dx *dx, int rc,
|
|
char const *zFmt, ...);
|
|
/**
|
|
va_list counterpart of cmpp_dx_errf().
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt,
|
|
va_list vargs);
|
|
|
|
/**
|
|
Scans dx to the next directive line, emitting all input before that
|
|
which is _not_ a directive line to dx->pp's output channel unless
|
|
it's elided due to being inside a block which elides its content
|
|
(e.g. #if).
|
|
|
|
Returns 0 if no errors were triggered, else a cmpp_rc_e code. This
|
|
is a no-op if dx->pp has persistent error state set, and that error
|
|
code is returned.
|
|
|
|
If it returns 0 then it sets *pGotOne to true if a directive was
|
|
found and false if not (in which case the end of the input has
|
|
been reached and further calls to this function for the same input
|
|
source will be no-ops). If it sets *pGotOne to true then it also
|
|
sets up dx's state for use with cmpp_dx_process(), which should
|
|
(normally) then be called.
|
|
|
|
ACHTUNG: calling this resets any argument-handling-related state of
|
|
dx. That is important for cmpp_f() implemenations, which _must
|
|
not_ hold copies of any pointers from dx->args.arg0 or dx->args.z
|
|
beyond a call to this function. Any state they need must be
|
|
evaluated, potentially copied, before calling this function().
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_next(cmpp_dx * dx, bool * pGotOne);
|
|
|
|
/**
|
|
This is only legal to call immediately after a successful call to
|
|
cmpp_dx_next(). It requires that cmpp_dx_next() has just located
|
|
the next directive. This function runs that directive. Returns 0
|
|
on success and all that.
|
|
|
|
Design note: directive-Search and directive-process are two
|
|
distinctly separate steps because directives which have both
|
|
open/closing tags frequently discard the closing directive without
|
|
running it (it exists to tell the directive how far to read). Those
|
|
closing directives exist independently, though, and will trigger
|
|
errors when encountered outside of the context of their opening
|
|
directive tag (e.g. an "#/if" without an "#if").
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_process(cmpp_dx * dx);
|
|
|
|
/**
|
|
A bitmask of flags for use with cmpp_dx_consume()
|
|
*/
|
|
enum cmpp_dx_consume_e {
|
|
/**
|
|
Tells cmpp_dx_consume() to process any directives it encounters
|
|
which are not in the specified set of closing directives. Its
|
|
default is to fail if another directive is seen.
|
|
*/
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D = 0x01,
|
|
/**
|
|
Tells cmpp_dx_consume() that non-directive content encountered
|
|
before the designated closing directive(s) must use an at-policy
|
|
of cmpp_atpol_OFF. That is: the output target of that function will
|
|
get the raw, unfiltered content. This is for cases where the
|
|
consumer will later re-emit that content, delaying @token@
|
|
parsing until a later step (e.g. #query does this).
|
|
|
|
This may misinteract in unpredictable ways when used with
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D. Please report them as bugs.
|
|
*/
|
|
cmpp_dx_consume_F_ATPOL_OFF = 0x02
|
|
};
|
|
|
|
/**
|
|
A helper for cmpp_f() implementations which read in their
|
|
blocked-off content instead of passing it through the normal output
|
|
channel. e.g. `#define x <<` stores that content in a define named
|
|
"x".
|
|
|
|
This function runs a cmpp_dx_next() loop which does the following:
|
|
|
|
If the given output channel is not NULL then it first replaces the
|
|
output channel with the given one, such that all output which would
|
|
normally be produced will be sent there until this function
|
|
returns, at which point the output channel is restored. If the
|
|
given channel is NULL then output is not captured - it instead goes
|
|
dx's current output channel.
|
|
|
|
dClosers must be a list of nClosers legal closing tags
|
|
entries. Typically this is the single closing directive/tag of the
|
|
current directive, available to the opening directive's cmpp_f()
|
|
impl via dx->d->closer. Some special cases require multiple
|
|
candidates, however (examples: #query and the #if family).
|
|
|
|
The flags argument may be 0 or a bitmask of values from the
|
|
cmpp_dx_consume_e enum.
|
|
|
|
If flags does not have the cmpp_dx_consume_F_PROCESS_OTHER_D bit set
|
|
then this function requires that the next directive in the input be
|
|
one specified by dClosers. If the next directive is not one of
|
|
those, it will fail with code CMPP_RC_SYNTAX.
|
|
|
|
If flags has the cmpp_dx_consume_F_PROCESS_OTHER_D bit set then it
|
|
will continue to search for and process directives until the
|
|
dCloser directive is found. Calling into other directives will
|
|
invalidate certain state that a cmpp_f() has access to - see
|
|
below for details. If dCloser is not found before EOF, a
|
|
CMPP_RC_SYNTAX error is triggered.
|
|
|
|
Once one of dCloser is found, this function returns with dx->d
|
|
referring to the that directive. In practice, the caller should
|
|
_not_ call cmpp_dx_process() at that point - the closing directive
|
|
is typically a no-op placeholder which exists only to mark the end
|
|
of the block. If the closer has work to do, however, the caller of
|
|
this function should call cmpp_dx_process() at that point.
|
|
|
|
On success it returns 0, the input stream will have been consumed
|
|
between the directive dx and its closing tag, and dx->d will point
|
|
to the new directive. If os is not NULL then os will have been
|
|
sent any content.
|
|
|
|
On error, processing of the directive must end immediately,
|
|
returning from the cmpp_f() impl after cleaning up any local
|
|
resources.
|
|
|
|
ACHTUNG: since this invokes cmpp_dx_next(), it invalidates
|
|
dx->args.arg0. Its dx->d is also replaced but the previous value
|
|
remains valid until the cmpp instance is cleaned up.
|
|
|
|
Example from the context of a cmpp_f() implementation
|
|
|
|
```
|
|
// "dx" is the cmpp_dx arg to this function
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
cmpp_b os = cmpp_b_empty;
|
|
oss.state = &os;
|
|
if( 0==cmpp_dx_consume(dx, &oss, dx->d->closer, 1, 0) ){
|
|
cmpp_b_chomp( &os );
|
|
... maybe modify the buffer or decorate the output in some way...
|
|
cmpp_out_raw(dx->pp, os.z, os.n);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
```
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_consume(cmpp_dx * dx, cmpp_outputer * os,
|
|
cmpp_d const *const * dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Output state for use with cmpp_dx_consume_v2().
|
|
|
|
Memory lifetimes:
|
|
|
|
- All string pointers in this class point directly back into the
|
|
source script so have its lifetime. From this library's
|
|
perspective that means until the cmpp_process_string() call which
|
|
started the process finishes, at which point those pointers leave
|
|
the library's site and are owned by their caller. That caller
|
|
might be cmpp_process_stream() or cmpp_process_file(), in which
|
|
case such pointers are invalidated before those functions return.
|
|
|
|
- The cmpp_d pointers are owned by the underlying cmpp instance.
|
|
As of this writing the API provides no means to remove
|
|
directives, nor are there any plans to introduce any. Thus it is
|
|
currently "impossible" that those pointers become invalidated
|
|
before the cmpp instance is passed to cmpp_reset() or
|
|
cmpp_dtor(). However, users of this API "should" treat them as if
|
|
they are invalidated when the function set them up for passing to
|
|
cmpp_dx_consume_v2() returns.
|
|
*/
|
|
struct cmpp_dx_consumed {
|
|
/**
|
|
Start of the consumed range, starting at the first byte of the
|
|
directive's line.
|
|
*/
|
|
unsigned char const * beginDirective;
|
|
/**
|
|
Start of the content part of the consumed range, starting at
|
|
the byte after the directive's opening line.
|
|
*/
|
|
unsigned char const * beginContent;
|
|
/** One-after-the-end of the content part of the consumed range. */
|
|
unsigned char const * endContent;
|
|
/** One-after-the-end of the full consumed range, including the
|
|
closing directive. */
|
|
unsigned char const * endDirective;
|
|
/** The opening directive. */
|
|
cmpp_d const * dOpen;
|
|
/** The closing directive. */
|
|
cmpp_d const * dClose;
|
|
};
|
|
typedef struct cmpp_dx_consumed cmpp_dx_consumed;
|
|
#define cmpp_dx_consumed_empty_m {0,0,0,0,0,0}
|
|
|
|
/**
|
|
Works exactly like cmpp_dx_consume() except for the final
|
|
argument:
|
|
|
|
When this is called, con is unconditionally cleared and then
|
|
partially re-populated (see cmpp_dx_consumed docs for the exact
|
|
semantics of each field):
|
|
|
|
- con->dOpen is set to dx->d
|
|
- con->beginContent is set
|
|
- con->beginContent is set
|
|
|
|
It then calls cmpp_dx_consume(). Regardless of success or error, it
|
|
finishes populating con before returning the result of that call:
|
|
|
|
- con->dClosed = the closing directive (the new dx->d)
|
|
- con->endContent
|
|
- con->endDirective
|
|
|
|
The problem this solves:
|
|
|
|
The #itch directive, in order to be able to provide accurate script
|
|
error location information, needs to be able to locate it within
|
|
the original source. It uses this function to consume to its #/itch
|
|
tag and then access the script from the data stored in con. This
|
|
allows it to provide byte-accurate line/column info. If it instead
|
|
copied the input, it could provide location info only relative to
|
|
the directive's contents (which is not horrible but it simplifies
|
|
the internals if they can access the outer directive's memory
|
|
directly, as internal buffers, e.g. for string interpolation,
|
|
otherwise build walls between individual tokens and their original
|
|
source, making them unable to be mapped to an input source
|
|
location. Similarly, if #itch were to allow other directives
|
|
embedded within a script, it would be forced to make a copy
|
|
(preprocessing produces different content than the source,
|
|
of course) and it would be literally impossible to map any
|
|
code location within that into a value which would make sense
|
|
to any user.
|
|
|
|
Consider the following itch script, which is invalid because
|
|
Itch does not permit embedded directives (for the reason given
|
|
above):
|
|
|
|
```
|
|
#define foo "this is foo"
|
|
#@ policy error
|
|
#itch
|
|
#if 1
|
|
eval {
|
|
foo bar baz
|
|
bar baz barre
|
|
error "@foo@"
|
|
}
|
|
#/if
|
|
#/itch
|
|
```
|
|
|
|
If sub-directives were permitted, the result would come out looking
|
|
like:
|
|
|
|
```
|
|
eval {
|
|
foo bar baz
|
|
bar baz barre
|
|
error "this is foo"
|
|
}
|
|
```
|
|
|
|
Now, when "error" triggers, it would report line 4, column 3. In
|
|
this trivial example that's only off by 1 line, but the distance
|
|
grows quickly and eventually spans into evals which run
|
|
dyamically-generated code via var- and command-call interpolation,
|
|
at which point the line/column values become meaningless and
|
|
therefore not only useless but also misleading, causing wasted
|
|
developer time as they track numbers which point to either nothing
|
|
at all or something completely different.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_consume_v2(cmpp_dx * dx, cmpp_outputer * os,
|
|
cmpp_d const *const * dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags,
|
|
cmpp_dx_consumed * con);
|
|
|
|
/**
|
|
Equivalent to cmpp_dx_consume(), capturing to the given buffer
|
|
instead of a cmpp_outputer object.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_consume_b(cmpp_dx * dx, cmpp_b * b,
|
|
cmpp_d const * const * dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
If arg is not NULL, cleans up any resources owned by
|
|
arg but does not free arg.
|
|
|
|
As of this writing, they own none and some code still requires
|
|
that. That is Olde Thynking, though, and due to change.
|
|
*/
|
|
CMPP_EXPORT void cmpp_arg_cleanup(cmpp *pp, cmpp_arg *arg);
|
|
|
|
/**
|
|
If arg is not NULL resets arg to be re-used. arg must have
|
|
initially been cleanly initialized by copying cmpp_arg_empty (or
|
|
equivalent, i.e. zeroing it out).
|
|
*/
|
|
CMPP_EXPORT void cmpp_arg_reuse(cmpp_arg *arg);
|
|
|
|
/**
|
|
Returns the C-string representation of arg. If arg->interp is set
|
|
then that string is returned, else arg->z is returned.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_arg_cstr(cmpp_arg const *arg);
|
|
|
|
/**
|
|
Works like cmpp_arg_cstr() but also writes the byte length
|
|
of the resulting string into *n (n may not e NULL).
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_arg_cstr2(cmpp_arg const *arg, cmpp_size_t *n);
|
|
|
|
#define cmpp_arg_ucstr(ARG) (unsigned char const*)cmpp_arg_cstr(ARG)
|
|
#define cmpp_arg_ucstr2(ARG,N) (unsigned char const*)cmpp_arg_cstr2((ARG),(N))
|
|
|
|
/**
|
|
This is the core argument-parsing function used by the library's
|
|
provided directives. Its is available in the public API as a
|
|
convenience for custom cmpp_f() implementations, but custom
|
|
implementations are not required to make use of it.
|
|
|
|
Populates a cmpp_arg object by parsing the next token from its
|
|
input source.
|
|
|
|
Expects *pzIn to point to the start of input for parsing arguments
|
|
and zInEnd to be the logical EOF of that range. This function
|
|
populates pOut with the info of the parse. Returns 0 on success,
|
|
non-0 (and updates pp's error state) on error.
|
|
|
|
Output (the parsed token) is written to *pzOut. zOutEnd must be the
|
|
logical EOF of *pzOut. *pzOut needs to be, at most,
|
|
(zInEnd-*pzIn)+1 bytes long. This function range checks the output
|
|
and will not write to or past zOutEnd, but that will trigger a
|
|
CMPP_RC_RANGE error.
|
|
|
|
On success, *pzIn will be set to 1 byte after the last one parsed
|
|
for pOut and *pzOut will be set to one byte after the final output
|
|
(NUL-terminated). pOut->z will point to the start of *pzOut and
|
|
pOut->n will be set to the byte-length of pOut->z.
|
|
|
|
When the end of the input is reached, this function returns 0
|
|
and sets pOut->ttype to cmpp_tt_EOF.
|
|
|
|
General tokenization rules:
|
|
|
|
Tokens come in the following flavors:
|
|
|
|
- Quoted strings: single- or double-quoted. cmpp_arg::ttype:
|
|
cmpp_tt_StringDQ and friends.
|
|
|
|
- Decimal integers with an optional sign. cmpp_arg::ttype value:
|
|
cmpp_tt_Int.
|
|
|
|
- Groups: (...), {...}, and [...]. cmpp_arg::ttype values:
|
|
cmpp_tt_GroupParen, cmpp_tt_GroupSquiggly, and
|
|
cmpp_tt_GroupBrace. These types do not automatically get parsed
|
|
recursively. To recurse into one of these, pass cmpp_arg_parse()
|
|
the grouping argument's bytes as the input range. Their
|
|
interpretation is context-dependent but the general conventions
|
|
are that {} are used for strings and context-dependent ad hoc
|
|
data structures (tcl-style), (...) for subexpressions, and [...]
|
|
for either "function calls" or lists of values.
|
|
|
|
- Word: anything which doesn't look like one of these above. Token
|
|
type IDs: cmpp_tt_Word. These are most often interpreted as
|
|
#define keys but cmpp_f() implementations sometimes treat
|
|
them as literal values.
|
|
|
|
- A small subset of words and operator-like tokens, e.g. '=' and
|
|
'!=', get a very specific ttype, e.g. cmpp_tt_OpNeq, but these
|
|
can generally be treated as strings.
|
|
|
|
- Outside of strings and groups, spaces, tabs, carriage-returns,
|
|
and newlines are skipped.
|
|
|
|
These are explained in more detail in the user's manual
|
|
(a.k.a. README.md).
|
|
|
|
There are many other token types, mostly used internally.
|
|
|
|
This function supports _no_ backslash escape sequences in
|
|
tokens. All backslashes, with the obligatory exception of those
|
|
which make up backslash-escaped newlines in the input stream, are
|
|
retained as-is in all token types. That means, for example, that
|
|
strings may not contain their own quote character.
|
|
|
|
As an example of where this function is useful: cmpp_f()
|
|
implementations which need to selectively parse a subset of the
|
|
directive's arguments can use this. As input, their dx argument's
|
|
args.z and args.nz members delineate the current directive line's
|
|
arguments. See c-pp.c:cmpp_f_pipe() for an example.
|
|
*/
|
|
CMPP_EXPORT int cmpp_arg_parse(cmpp * pp,
|
|
cmpp_arg *pOut,
|
|
unsigned char const **pzIn,
|
|
unsigned char const *zInEnd,
|
|
unsigned char ** pzOut,
|
|
unsigned char const * zOutEnd);
|
|
|
|
CMPP_EXPORT bool cmpp_arg_equals(cmpp_arg const *arg,
|
|
char const *z);
|
|
|
|
/**
|
|
True if (cmpp_arg const *)ARG's contents match the string literal
|
|
STR, else false.
|
|
*/
|
|
#define cmpp_arg_equals_c(ARG,STR) \
|
|
(sizeof("" STR)-1==(ARG)->n && 0==memcmp(STR,(ARG)->z,sizeof(STR)-1))
|
|
|
|
/**
|
|
True if (cmpp_arg const *)ARG's contents match the string literal
|
|
STR or ("-" STR), else false. The intent is that "-flag" be passed
|
|
here to tolerantly accept either "-flag" or "--flag".
|
|
*/
|
|
#define cmpp_arg_isflag_c(ARG,STR) \
|
|
(cmpp_arg_equals_c(ARG,STR) || cmpp_arg_equals_c(ARG, "-" STR))
|
|
|
|
#define cmpp_if_arg_isflag_c(ARG,STR) \
|
|
if( cmpp_arg_isflag_c(ARG,STR) )
|
|
|
|
/**
|
|
Creates a copy of arg->z. If allocation fails then pp's persistent
|
|
error code is set to CMPP_RC_OOM. If pp's error code is not 0 when
|
|
this is called then this is a no-op and returns NULL. In other
|
|
words, if this function returns NULL, pp's error state was either
|
|
already set when this was called or it was set because allocation
|
|
failed.
|
|
|
|
Ownership of the returned memory is transferred to the caller, who
|
|
must eventually free it using cmpp_mfree().
|
|
*/
|
|
CMPP_EXPORT char * cmpp_arg_strdup(cmpp *pp, cmpp_arg const *arg);
|
|
|
|
/**
|
|
Flag bitmasks for use with cmpp_arg_interpolate(). With my apologies
|
|
for the long names (but consistency calls for them).
|
|
|
|
TODO (2026-09-07): rename this to cmpp_interpolate() and have it
|
|
closely mirror cmpp_itch_interpolate(). The plan is to have
|
|
directives opt in to automatic arg interpolation, which would
|
|
eliminate a digital ton (==some small fraction of a metric
|
|
microgram) of code from the built-in directives.
|
|
*/
|
|
enum cmpp_arg_interpolate_e {
|
|
/**
|
|
Specifies that the argument's string value should be used as-is,
|
|
rather than expanding it (if the arg's ttype would normally cause
|
|
it to be expanded).
|
|
*/
|
|
cmpp_arg_interpolate_FORCE_STRING = 0x01,
|
|
|
|
/**
|
|
Tells cmpp_arg_interpolate() to not expand arguments with type
|
|
cmpp_tt_Word, which it normally treats as define keys. It instead
|
|
treats these as strings.
|
|
*/
|
|
cmpp_arg_interpolate_NO_DEFINES = 0x02,
|
|
|
|
/**
|
|
If set, arguments with a ttype of cmpp_tt_GroupBrace will be
|
|
"called" by passing them to cmpp_call_arg(). The space-trimmed
|
|
result of the call becomes the output of the cmpp_arg_interpolate()
|
|
call.
|
|
|
|
FIXME? Make this opt-out instead of opt-in. We end up _almost_
|
|
always wanting this.
|
|
*/
|
|
cmpp_arg_interpolate_BRACE_CALL = 0x04,
|
|
|
|
/**
|
|
Explicitly disable [call] expansion even if
|
|
cmpp_arg_interpolate_BRACE_CALL is set in the flags.
|
|
*/
|
|
cmpp_arg_interpolate_NO_BRACE_CALL = 0x08,
|
|
|
|
#if 0
|
|
/**
|
|
TODO. Open questions: apply to all types, e.g. results of
|
|
[calls...], or just to string-style? This will eventually
|
|
backfire if the target buffer has existing content, as we'd end
|
|
up multi-unescaping the existing parts.
|
|
*/
|
|
cmpp_arg_interpolate_UNESCAPE = 0x10,
|
|
#endif
|
|
#if 0
|
|
/* Going to need this for porting the expression
|
|
builder from itch to the core. */
|
|
cmpp_arg_interpolate_PARENS_EXPR = 0x20,
|
|
#endif
|
|
|
|
/**
|
|
TODO? cmpp_arg_interpolate_UNESCAPE to run it through
|
|
cmpp_b_unescape().
|
|
*/
|
|
};
|
|
|
|
/**
|
|
Appends some form of arg to the given buffer.
|
|
|
|
arg->ttype values of cmpp_tt_Word (define keys) cause the value to
|
|
be expanded appropriately. Others get emitted as specified by the
|
|
flags argument.
|
|
|
|
The flags argument influences the expansion decisions, as
|
|
documented in the cmpp_arg_interpolate_e enum.
|
|
|
|
Returns 0 on success and all that.
|
|
|
|
If b is NULL then: (A) if arg->interp is not NULL then it is reused
|
|
as the target else (B) return CMPP_RC_MISUSE and update's pp's
|
|
error state with a description of the problem.
|
|
|
|
See: cmpp_atpol_get(), cmpp_atpol_set()
|
|
|
|
Reminder to self: though this function may, via script-side
|
|
function call resolution, recurse into the library, any such
|
|
recursion gets its own cmpp_dx instance. In this context that's
|
|
significant because it means this call won't invalidate arg's
|
|
memory like cmpp_dx_consume() or cmpp_dx_next() can (depending on
|
|
where args came from - typically it's owned by dx but
|
|
cmpp_dx_args_clone() exists solely to work around such potential
|
|
invalidation).
|
|
*/
|
|
CMPP_EXPORT int cmpp_arg_interpolate(cmpp *pp, cmpp_arg const *arg,
|
|
cmpp_b * b, cmpp_flag32_t flags);
|
|
|
|
/**
|
|
This is a helper routine for cmpp_f() implementations which
|
|
process flags like -f filename. This function treats arg as a
|
|
filename, consumes that file, and returns 0 on success. The
|
|
argument's value is expanded using the preprocessor's conventional
|
|
approaches, using cmpp_arg_interpolate().
|
|
|
|
HUGE WARNING: this routine necessarily requires recursing into dx,
|
|
which means that it will INVALIDATE dx->args when it does. It is
|
|
CRITICAL that the caller pass a second argument which was populated
|
|
by cmpp_dx_args_clone() and eventually, before returning, pass them to
|
|
cmpp_args_cleanup().
|
|
|
|
If name is not NULL then the buffer's contents get replaced with
|
|
the resolved name of the file.
|
|
|
|
content must not be NULL. If filename lookup passes then it is
|
|
passed to cmpp_b_reuse() before being streamed the content of the
|
|
file. If preprocessIt is true then the content will be piped
|
|
through dx->pp and sent to the content buffer, else it will be
|
|
consumed as-is into the buffer.
|
|
*/
|
|
CMPP_EXPORT int cmpp_arg_consume_as_file(
|
|
cmpp_dx * dx, cmpp_arg const *arg,
|
|
cmpp_b *name, cmpp_b * content, bool preprocessIt
|
|
);
|
|
|
|
/**
|
|
Flags for use with cmpp_call_str() and friends.
|
|
*/
|
|
enum cmpp_call_e {
|
|
/** Do not trim a newline from the result. */
|
|
cmpp_call_F_NO_TRIM = 0x01,
|
|
/** Trim all leading and trailing space and newlines
|
|
from the result. */
|
|
cmpp_call_F_TRIM_ALL = 0x02
|
|
};
|
|
|
|
/**
|
|
This assumes that arg->z holds a "callable" directive
|
|
string in the form:
|
|
|
|
directiveName ...args
|
|
|
|
This function composes a new cmpp input source from that line,
|
|
behaving as if it were prefixed with dx's current directive prefix
|
|
if it's not already got one. It processes it with
|
|
cmpp_process_string(), appending a (possibly trimmed) copy of that
|
|
output to dest.
|
|
|
|
To simplify common expected usage, by default the output is trimmed
|
|
of a single newline. The flags argument, 0 or a bitmask of values
|
|
from the cmpp_call_e enum, can be used to modify that behavior.
|
|
|
|
This is the basis of "function calls" in cmpp.
|
|
|
|
Returns 0 on success.
|
|
|
|
Special cases and considerations:
|
|
|
|
Case 1: dest is not modified if the call produces no output, and
|
|
dest is not modified until after the end of the call. That makes
|
|
it legal for [z,z+n) to live inside dest, but for that to work one
|
|
would need to:
|
|
|
|
1) Prefix d with a \0 byte.
|
|
2) Append what would normally be passed as z to dest.
|
|
3) dest->n = 0 (the magic sauce)
|
|
4) In place of (z,n) pass (dest->z+1,dest->n-1).
|
|
|
|
After this call, if dest was not modified then dest->n is still
|
|
pointing to a NUL-terminated buffer (part of the cmpp_b contract),
|
|
and if it _is_ updated then it will be overwritten from its
|
|
starting point, rather than appending after the end of the input
|
|
string.
|
|
|
|
Reminder to self: we cannot avoid copying the call's output here
|
|
solely because of the trimming. We can easily trim from dest's RHS
|
|
but not so easily from its start. If it were not for trimming we
|
|
could pass dest through as the direct output target, rather than
|
|
using an intermediary buffer. Bummer. Even so: a potential TODO is
|
|
optimize out the copy when we're not trimming. However... doing so
|
|
would rule out Case 1 described above, and we currently use that in
|
|
at least one place.
|
|
*/
|
|
int cmpp_call_str(cmpp *pp,
|
|
unsigned char const * z,
|
|
cmpp_strlen_t n,
|
|
cmpp_b * dest,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Convert an errno value to a cmpp_rc_e approximation, defaulting to
|
|
dflt if no known match is found. This is intended for use by
|
|
cmpp_f implementations which use errno-using APIs.
|
|
*/
|
|
CMPP_EXPORT int cmpp_errno_rc(int errNo, int dflt);
|
|
|
|
/**
|
|
Configuration object for use with cmpp_d_register().
|
|
*/
|
|
struct cmpp_d_reg {
|
|
/**
|
|
The name of the directive as it will be used in
|
|
input scripts, e.g. "mydirective". It will be copied by
|
|
cmpp_d_register().
|
|
*/
|
|
/* Refactoring crutch */
|
|
/* Directive name. The library makes a copy of this. */
|
|
char const * name;
|
|
/**
|
|
Enforcement of the arity is opt-in, only enforced if
|
|
cmpp_f_F_ENFORCE_ARITY is in this->flags. See the docs for that
|
|
flag for more info.
|
|
*/
|
|
int arity;
|
|
struct {
|
|
/**
|
|
Callback for the directive's opening tag.
|
|
*/
|
|
cmpp_f f;
|
|
/**
|
|
Flags from cmpp_f_e. Typically one of cmpp_f_F_ARGS_LIST or
|
|
cmpp_f_F_ARGS_RAW.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
} opener;
|
|
struct {
|
|
/**
|
|
Callback for the directive's closing tag, if any.
|
|
|
|
This is only relevant for directives which have both an open and
|
|
a closing tag (even if that closing tag is only needed in some
|
|
contexts, e.g. "#define X <<" (with a closer) vs "#define X Y"
|
|
(without)). See cmpp_f_dangling_closer() for a default
|
|
implementation which triggers an error if it's seen in the input
|
|
and not consumed by its counterpart opening directive. That
|
|
implementation has proven useful for #define, #pipe, and friends.
|
|
|
|
Design notes: it's as yet unclear how to model, in the public
|
|
interface, directives which have a whole family of cooperating
|
|
directives, namely #if/#elif/#else. OTOH, #query does that.
|
|
*/
|
|
cmpp_f f;
|
|
/**
|
|
Flags from cmpp_f_e. For closers this can typically be left at
|
|
0, as few do any work beyond ensuring that they match an
|
|
opener.
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
} closer;
|
|
/**
|
|
If not NULL then it is assigned to the directive's opener part
|
|
and will be called by the library in either of the following
|
|
cases:
|
|
|
|
- When the custom directive is cleaned up.
|
|
|
|
- If cmpp_d_register() fails (returns non-0), regardless of how
|
|
it fails.
|
|
|
|
It is passed this->state.
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
/**
|
|
Implementation state for the callbacks.
|
|
*/
|
|
void * state;
|
|
/**
|
|
Brief help text.
|
|
*/
|
|
char const * usage;
|
|
};
|
|
typedef struct cmpp_d_reg cmpp_d_reg;
|
|
/**
|
|
Empty-initialized cmpp_d_reg instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_d_reg_empty_m { \
|
|
.name=0,.arity=-1, \
|
|
.opener={.f=,.flags=0}, \
|
|
.closer={.f=0,.flags=0}, \
|
|
.dtor=0,.state=0, \
|
|
.usage="Missing cmpp_d::usage"\
|
|
}
|
|
|
|
/**
|
|
Empty-initialized instance, intended for non-const-copy
|
|
initialization.
|
|
*/
|
|
//extern const cmpp_d_reg cmpp_d_reg_empty;
|
|
|
|
/**
|
|
Registers a new directive, or a pair of opening/closing directives,
|
|
with pp.
|
|
|
|
The semantics of r's members are documented in the cmpp_d_reg
|
|
class. r->name and r->opener.f are required. The remainder may be
|
|
0/NULL. Its members are copied - r need not live longer than this
|
|
call.
|
|
|
|
When the new directive is seen in a script, r->opener.f() will be
|
|
called. If the closing directive (if any) is seen in a script,
|
|
r->closer.f() is called. In both cases, the callback
|
|
implementation can get access to the r->state object via
|
|
cmdd_dx::d::impl::state (a.k.a dx->d->impl.state).
|
|
|
|
If r->closer.f is not NULL then the closing directive will be named
|
|
"/${zName}". (Design note: it is thought that forcing a common
|
|
end-directive syntax will lead to fewer issues than allowing
|
|
free-form closing tag names, e.g. fewer chances of a name collision
|
|
or not quite remembering the spelling of a given closing tag
|
|
(#endef vs #enddefine vs #/define).)
|
|
|
|
Returns 0 on success and updates pp's error state on error. Similarly,
|
|
this is a no-op if pp has an error code when this is called, in which
|
|
case it returns that result code without other side-effects.
|
|
|
|
On success, if pOut is not NULL then it is set to the directive
|
|
pointer, memory owned by pp until it cleans up its directives. This
|
|
is the only place in the API a non-const pointer to a directive can
|
|
be found, and it is provided only for very specific use-cases where
|
|
a directive needs to be manipulated (carefully) after
|
|
registration[^post-reg-manipulation]. If this function also
|
|
registered a closing directive, it is available as (*pOut)->closer.
|
|
pOut should normally be NULL.
|
|
|
|
This function, for backwards compatility with directive
|
|
implementations, always adds the cmpp_f_F_NEEDS_DX flag to the
|
|
opening and closing directives.
|
|
|
|
Failure modes include:
|
|
|
|
- Returns CMPP_RC_RANGE if zName is not legal for use as a
|
|
directive name. See cmpp_is_legal_key().
|
|
|
|
- Returns CMPP_RC_OOM on an allocation error.
|
|
|
|
Errors from this function are recoverable (see cmpp_errf()). A
|
|
failed registration, even one which doesn't fail until the
|
|
registration of the closing element, will leave pp in a
|
|
well-defined state (with neither of r's directives being
|
|
registered).
|
|
|
|
[^post-reg-manipulation]: The one known use case if the #if family
|
|
of directives, all of which use the same #/if closing
|
|
directive. The public registration API does not account for sharing
|
|
of closers that way, and whether it _should_ is still TBD. The
|
|
workaround, for this case, is to get the directives as they're
|
|
registered and point the cmpp_d::closer of each of #if, #elif, and
|
|
#else to #/if.
|
|
*/
|
|
CMPP_EXPORT int cmpp_d_register(cmpp * pp, cmpp_d_reg const * r,
|
|
cmpp_d **pOut);
|
|
|
|
/**
|
|
A cmpp_f() impl which is intended to be used as a callback for
|
|
directive closing tags for directives in which the opening tag's
|
|
implementation consumes the input up to the closing tag. This impl
|
|
triggers an error if called, indicating that the directive closing
|
|
was seen in the input without its accompanying directive opening.
|
|
*/
|
|
CMPP_EXPORT void cmpp_f_dangling_closer(cmpp_f_args const *args);
|
|
|
|
/**
|
|
Writes the first n bytes of z to pp's current output channel
|
|
without performing any @token@ parsing.
|
|
|
|
In the following cases this returns current persistent error code
|
|
without side-effects:
|
|
|
|
- cmpp_err_has() is not 0 when this is called. The preprocessor
|
|
rejects all output so long as an unhandled error is
|
|
pending.
|
|
|
|
- cmpp_dx_current() is non-NULL and that context is in
|
|
skip-mode. (#if does this.)
|
|
|
|
Returns pp's persistent error code (0 on success) and sets that
|
|
code to non-0 on error. This is a no-op if dx->pp has a non-0 error
|
|
state, returning that code.
|
|
|
|
See: cmpp_out_expand()
|
|
*/
|
|
CMPP_EXPORT int cmpp_out_raw(cmpp* pp, void const *z,
|
|
cmpp_size_t n);
|
|
|
|
/**
|
|
Sends [zFrom,zFrom+n) to pOut, performing @token@ expansion if the
|
|
given policy says to (else it passes the content through as-is, as
|
|
per cmpp_out_raw()). A policy of cmpp_atpol_CURRENT uses dx->pp's
|
|
current policy. A policy of cmpp_atpol_OFF behaves exactly like
|
|
cmpp_out_raw().
|
|
|
|
Returns dx->pp's persistent error code (0 on success) and sets that
|
|
code to non-0 on error. This function is a no-op if dx->pp has a
|
|
non-0 error state, returning that code instead of doing any work.
|
|
|
|
If pOut is NULL then dx->pp's default channel is used, with the
|
|
caveat that atPolicy's only legal value in that case is
|
|
cmpp_atpol_CURRENT. (The internals do not allow the at-policy to be
|
|
overridden for that particular output channel, to avoid accidental
|
|
filtering when it's not enabled. They do not impose that
|
|
restriction for other output channels, which are frequently used
|
|
for filtering intermediary results.)
|
|
|
|
See: cmpp_out_raw()
|
|
|
|
Notes regarding how this is used internally:
|
|
|
|
- This function currently specifically does nothing when invoked in
|
|
skip-mode[^1]. Hypothetically it cannot ever be called in skip-mode
|
|
except when evaluating #elif expressions (a previous #if/#elif
|
|
having passed and put us in skip-mode), where it's expanding
|
|
expression operands. That part currently (as of 2025-10-21) uses
|
|
dx->pp's current policy, and it's not clear whether that is
|
|
sufficient or whether we need to force it to expand (and which
|
|
policy to use when doing so). We could possibly get away with
|
|
always using cmpp_atpol_ERROR for purposes of evaluating at-string
|
|
expression operands.
|
|
|
|
[^1]: Skip-mode is the internal mechanism which keeps directives
|
|
from running, and content from being emitted, within a falsy branch
|
|
of an #if/#elif block. Only flow-control directives are ever run
|
|
when skip-mode is active, and client-provided directives cannot
|
|
easily provide flow-control support. Ergo, much of this paragraph
|
|
is not relevant for client-level code, but it is for this library's
|
|
own use of this function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_out_expand(cmpp * pp,
|
|
cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n,
|
|
cmpp_atpol_e policy);
|
|
|
|
#if 0
|
|
#define cmpp_dx_out_expand(DX,OUT,SRC,N,POLICY) \
|
|
cmpp_out_expand((DX)->pp, OUT, SRC, N, POLICY)
|
|
#define cmpp_dx_out_raw(DX,SRC,N) \
|
|
cmpp_out_expand((DX)->pp, SRC, N)
|
|
#endif
|
|
|
|
/**
|
|
This creates a formatted string using sqlite3_mprintf() and emits
|
|
it using cmpp_out_raw(). Returns CMPP_RC_OOM if allocation of the
|
|
string fails, else it returns whatever cmpp_out_raw() returns.
|
|
|
|
This is a no-op if pp is in an error state, returning that code.
|
|
*/
|
|
CMPP_EXPORT int cmpp_outf(cmpp *pp, char const *zFmt, ...);
|
|
|
|
/** va_list counterpart of cmpp_outf(). */
|
|
CMPP_EXPORT int cmpp_outfv(cmpp *pp, char const *zFmt, va_list va);
|
|
|
|
/**
|
|
If pp's output channel has a flush method, it is called and its
|
|
result returned, else this is a no-op returning 0.
|
|
*/
|
|
CMPP_EXPORT int cmpp_flush(cmpp *pp);
|
|
|
|
|
|
/**
|
|
Convenience form of cmpp_delimiter_get() which returns the
|
|
delimiter which was active at the time when the currently-running
|
|
cmpp_f() was called. This memory may be invalidated by any calls
|
|
into cmpp_dx_process(), cmpp_delimiter_set(), or when the
|
|
cmpp_f() impl returns, so a copy of this pointer must not be
|
|
retained past such a point.
|
|
|
|
This function is primarily intended for use in generating debug and
|
|
error messages.
|
|
|
|
If the delimiter stack is empty, this function returns NULL, but
|
|
that would indicate library misuse because it always starts with
|
|
the build-time-default delimiter at the top of the stack.
|
|
*/
|
|
CMPP_EXPORT char const * cmpp_dx_delim(cmpp_dx const *dx);
|
|
|
|
/**
|
|
Borrows a buffer from pp's buffer recycling pool, allocating one if
|
|
needed. It returns NULL only on allocation error, in which case it
|
|
updates pp's error state.
|
|
|
|
This transfers ownership of the buffer to the caller, who is
|
|
obligated to eventually do ONE of the following:
|
|
|
|
- Pass it to cmpp_b_return() with the same pp argument.
|
|
|
|
- Pass it to cmpp_b_clear() then cmpp_mfree().
|
|
|
|
The purpose of this function is a memory reuse optimization. Most
|
|
directives, and many internals, need to use buffers for something
|
|
or other and this gives them a way to reuse buffers.
|
|
|
|
Potential TODO: How this pool optimizes (or not) buffer allotment
|
|
is an internal detail. Maybe add an argument which provides a hint
|
|
about the buffer usage. e.g. argument-conversion buffers are
|
|
normally small but block content buffers can be arbitrarily large.
|
|
|
|
The pp instance has internally-configured limits on the buffer
|
|
pool, both for the number of buffers and their total capacity.
|
|
When the count limit is reached, this function starts returning
|
|
newly-allocated buffers instead of ones from the pool, which
|
|
can increase total allocations of the library. The limits are
|
|
not yet configurable.
|
|
*/
|
|
CMPP_EXPORT cmpp_b * cmpp_b_borrow(cmpp *pp);
|
|
|
|
/**
|
|
Returns a buffer borrowed from cmpp_b_borrow(), transferring
|
|
ownership back to pp. Passing a non-NULL b which was not returned
|
|
by cmpp_b_borrow() invokes undefined behavior (possibly delayed
|
|
until the list is cleaned up). To simplify usage, b may be NULL.
|
|
|
|
After calling this, b must be considered "freed" - it must not be
|
|
used again. This function is free (as it were) to immediately free
|
|
the object's memory instead of recycling it.
|
|
|
|
If recycling b would take pp over its internal limits on buffer
|
|
count, b is immediately freed (see cmpp_b_return()). If b->nAlloc
|
|
is larger than its internal single-buffer capacity or keeping b->z
|
|
would go over the total capacity limit, b's owned memory is freed
|
|
(cmpp_b_clear()) but b is retained for further recycling.
|
|
*/
|
|
CMPP_EXPORT void cmpp_b_return(cmpp *pp, cmpp_b *b);
|
|
|
|
/**
|
|
If NUL-terminated z matches one of the strings listed below, its
|
|
corresponding cmpp_atpol_e entry is returned, else
|
|
cmpp_atpol_invalid is returned.
|
|
|
|
If pp is not NULL then (A) this also sets its current at-policy and
|
|
(B) it recognizes an additional string (see below). In this case,
|
|
if z is not a valid string then pp's persistent error state is set.
|
|
|
|
Its accepted values each correspond to a like-named policy value:
|
|
|
|
- "off": no processing of `@` is performed.
|
|
|
|
- "error": fail if an undefined `X` is referenced in @token@
|
|
parsing.
|
|
|
|
- "retain": emit any unresolved `@X@` tokens as-is to the output
|
|
stream. i.e. `@X@` renders as `@X@`.
|
|
|
|
- "elide": omit unresolved `@X@` from the output, as if their values
|
|
were empty. i.e. `@X@` renders as an empty string, i.e. is not
|
|
emitted at all.
|
|
|
|
- "current": if pp!=NULL then it returns the current policy, else
|
|
this string resolves to cmpp_atpol_invalid.
|
|
*/
|
|
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_from_str(cmpp * pp, char const *z);
|
|
|
|
/**
|
|
Returns pp's current at-token policy.
|
|
*/
|
|
CMPP_EXPORT cmpp_atpol_e cmpp_atpol_get(cmpp const * const pp);
|
|
|
|
/**
|
|
Sets pp's current at-token policy. Returns 0 if pol is valid, else
|
|
it updates pp's error state and returns CMPP_RC_RANGE. This is a
|
|
no-op if pp has error state, returning that code instead.
|
|
|
|
The policy cmpp_atpol_CURRENT is a no-op, permitted to simplify
|
|
certain client-side usage.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atpol_set(cmpp * const pp, cmpp_atpol_e pol);
|
|
|
|
/**
|
|
Pushes pol as the current at-policy. Returns 0 on success and
|
|
non-zero on error (bad pol value or allocation error). If this
|
|
returns 0 then the caller is obligated to eventually call
|
|
cmpp_atpol_pop() one time. If it returns non-0 then they _must not_
|
|
call that function.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atpol_push(cmpp *pp, cmpp_atpol_e pol);
|
|
|
|
/**
|
|
Must be called one time for each successful call to
|
|
cmpp_atpol_push(). It restores the at-policy to the value it
|
|
has when cmpp_atpol_push() was last called.
|
|
|
|
If called when no cmpp_delimiter_push() is active then debug builds
|
|
will fail an assert(), else pp's error state is updated if it has
|
|
none already.
|
|
*/
|
|
CMPP_EXPORT void cmpp_atpol_pop(cmpp *pp);
|
|
|
|
/**
|
|
The cmpp_unpol_e counterpart of cmpp_atpol_from_str(). It
|
|
behaves identically, just for a different policy group with
|
|
different names.
|
|
|
|
Its accepted values are: "null" and "error". The value "current" is
|
|
only legal if pp!=NULL, else it resolves to cmpp_unpol_invalid.
|
|
*/
|
|
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_from_str(cmpp * pp, char const *z);
|
|
|
|
/**
|
|
Returns pp's current policy regarding use of undefined define keys.
|
|
*/
|
|
CMPP_EXPORT cmpp_unpol_e cmpp_unpol_get(cmpp const * const pp);
|
|
|
|
/**
|
|
Sets pp's current policy regarding use of undefined define keys.
|
|
Returns 0 if pol is valid, else it updates pp's error state and
|
|
returns CMPP_RC_RANGE.
|
|
*/
|
|
CMPP_EXPORT int cmpp_unpol_set(cmpp * const pp, cmpp_unpol_e pol);
|
|
|
|
/**
|
|
The undefined-policy counterpart of cmpp_atpol_push().
|
|
*/
|
|
CMPP_EXPORT int cmpp_unpol_push(cmpp *pp, cmpp_unpol_e pol);
|
|
|
|
/**
|
|
The undefined-policy counterpart of cmpp_atpol_pop().
|
|
*/
|
|
CMPP_EXPORT void cmpp_unpol_pop(cmpp *pp);
|
|
|
|
/**
|
|
The at-token counterpart of cmpp_delimiter_get(). This sets *zOpen
|
|
(if zOpen is not NULL) to the opening delimiter and *zClose (if
|
|
zClose is not NULL) to the closing delimiter. The memory is owned
|
|
by pp and may be invalidated by any calls to cmpp_atdelim_set(),
|
|
cmpp_atdelim_push(), or any APIs which consume input. Each string
|
|
is NUL-terminated and must be copied by the caller if they need
|
|
these strings past a point where they might be invalidated.
|
|
|
|
If called when the the delimiter stack is empty, debug builds with
|
|
fail an assert() and non-debug builds will behave as if the stack
|
|
contains the compile-time default delimiters.
|
|
*/
|
|
CMPP_EXPORT void cmpp_atdelim_get(cmpp const * pp,
|
|
char const **zOpen,
|
|
char const **zClose);
|
|
/**
|
|
The `@token@`-delimiter counterpart of cmpp_delimeter_set().
|
|
|
|
This sets the delimiter for `@token@` content to the given opening
|
|
and closing strings (which the library makes a copy of). If zOpen
|
|
is NULL then the compile-time default is assumed. If zClose is NULL
|
|
then zOpen is assumed.
|
|
|
|
Returns 0 on success. Returns non-0 if called when the delimiter
|
|
stack is empty, if it cannot copy the string or zDelim is deemed
|
|
unsuitable for use as a delimiter.
|
|
|
|
In debug builds this will trigger an assert if no `@token@`
|
|
delimiter has been set, but pp starts with one level in place, so
|
|
it is safe to call without having made an explicit
|
|
cmpp_atdelim_push() unless cmpp_atdelim_pop() has been misused.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_set(cmpp * pp,
|
|
char const *zOpen,
|
|
char const *zClose);
|
|
|
|
/**
|
|
The `@token@`-delimiter counterpart of cmpp_delimeter_push().
|
|
|
|
See cmpp_atdelim_set() for the semantics of the arguments.
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_push(cmpp *pp,
|
|
char const *zOpen,
|
|
char const *zClose);
|
|
|
|
/**
|
|
The @token@-delimiter counterpart of cmpp_delimiter_pop().
|
|
*/
|
|
CMPP_EXPORT int cmpp_atdelim_pop(cmpp *pp);
|
|
|
|
/**
|
|
Searches the given path (zPath), split on the given path separator
|
|
(pathSep), for the given file (zBaseName), optionally with the
|
|
given file extension (zExt).
|
|
|
|
If zBaseName or zBaseName+zExt are found as-is, without any
|
|
search path prefix, that will be the result, else the result
|
|
is either zBaseName or zBaseName+zExt prefixed by one of the
|
|
search directories.
|
|
|
|
On success, returns a new string, transfering ownership to the
|
|
caller (who must eventually pass it to cmpp_mfree() to deallocate).
|
|
|
|
If no match is found, or on error, returns NULL. On a genuine
|
|
error, pp's error state is updated and the error is unlikely to be
|
|
recoverable (see cmpp_errf()).
|
|
|
|
This function is a no-op if called when pp's error state is set,
|
|
returning NULL.
|
|
|
|
Results are undefined (in the sense of whether it will work or not,
|
|
as opposed to whether it will crash or not) if pathSep is a control
|
|
character.
|
|
|
|
Design note: this is implemented as a Common Table Expression
|
|
query.
|
|
*/
|
|
CMPP_EXPORT char * cmpp_path_search(cmpp *pp,
|
|
char const *zPath,
|
|
char pathSep,
|
|
char const *zBaseName,
|
|
char const *zExt);
|
|
|
|
|
|
/**
|
|
Flags and constants related to the cmpp_args type.
|
|
*/
|
|
enum cmpp_args_e {
|
|
/**
|
|
cmpp_args_parse() flag which tells cmpp_args_parse() not to
|
|
dive into (...) group tokens. It insteads leaves them to be parsed
|
|
(or not) by downstream code. The only reason to parse them in
|
|
advance is to catch syntax errors sooner rather than later.
|
|
*/
|
|
cmpp_args_F_NO_PARENS = 0x01,
|
|
/**
|
|
Tells cmpp_args_parse() to unescape special character sequences,
|
|
as per cmpp_b_unescape().
|
|
*/
|
|
cmpp_args_F_UNESCAPE = 0x02,
|
|
|
|
/**
|
|
Tells the parser to apply the following hack to {...} tokens:
|
|
|
|
Leading spaces up to the first leading newline are stripped from
|
|
the token, along with that one newline.
|
|
|
|
A sample case where this is relevant:
|
|
|
|
```
|
|
#define x {
|
|
blah @foo@
|
|
}
|
|
#expand x {foo -> 1}
|
|
```
|
|
|
|
If we don't do this (to retain backwards compatibility) then such
|
|
constructs may produce a stray newline where they didn't before.
|
|
|
|
When the library parses directive arguments, directives with the
|
|
cmpp_f_F_NO_SQUIGGLY_HACK do not get this and all others do.
|
|
The legacy behavior is retained for directives which predate
|
|
this addition.
|
|
*/
|
|
cmpp_args_F_NO_SQUIGGLY_HACK = 0x04
|
|
//cmpp_args_F_SQUIGGLY_HACK = 0x04
|
|
};
|
|
|
|
/**
|
|
An internal detail of cmpp_args.
|
|
*/
|
|
typedef struct cmpp_args_pimpl cmpp_args_pimpl;
|
|
|
|
/**
|
|
This type is deprecated. It is retained to support the
|
|
pre-2026-09 directive API changes. It is not applicable to
|
|
directives whose callbacks use the cmpp_f_F_ARGS_V4 flag.
|
|
|
|
A container for parsing a line's worth of cmpp_arg objects.
|
|
|
|
Instances MUST be cleanly initialized by bitwise-copying either
|
|
cmpp_args_empty or (depending on the context) cmpp_args_empty_m
|
|
before passing them to any APIs.
|
|
|
|
Instances MUST eventually be passed to cmpp_args_cleanup().
|
|
|
|
Design notes: this class is provided to the public API as a
|
|
convenience, not as a core/required component. It offers one of
|
|
many possible solutions for dealing with argument lists and is not
|
|
the End All/Be All of solutions. i didn't _really_ want to expose
|
|
this class in the public API at all but i also want client-side
|
|
directives to have the _option_ to to do some of the things
|
|
currently builtin directives can do which are (as of this writing)
|
|
unavailable in the public API, e.g. evaluate expressions (in that
|
|
limited form which this library supports). A stepping stone to
|
|
doing so is making this class public.
|
|
*/
|
|
struct cmpp_args {
|
|
/**
|
|
Number of parsed args. In the context of a cmpp_f(), argument
|
|
lists do not include their directive's name as an argument because
|
|
they have it via cmpp_f_args::d.
|
|
*/
|
|
unsigned argc;
|
|
|
|
/**
|
|
This is the Olde Waye of doing this. Its state is undefined for
|
|
cmpp_args objects passed to cmpp_f() imps on behalf of directives
|
|
or commands which have the the cmpp_f_F_ARGS_V4 flag.
|
|
|
|
The list of args. This is very specifically NOT an array (or at
|
|
least not one which client code can rely on to behave
|
|
sensibly). Some internal APIs adjust a cmpp_args's arg list,
|
|
re-linking the entries via cmpp_arg::next and making array-style
|
|
traversal a foot-gun.
|
|
|
|
To loop over them:
|
|
|
|
for( cmpp_arg const * arg = args->arg0; arg; arg = arg->next ){...}
|
|
|
|
For directives with the cmpp_f_F_ARGS_RAW flag, this member will,
|
|
after a successful call to cmpp_dx_next(), point to a single
|
|
argument which holds the directive's entire argument string,
|
|
stripped of leading spaces. That memory IS INVALIDATED by any
|
|
APIs which consume input, so copy it if needed or use
|
|
cmpp_f_F_ARGS_V4 instead (which makes lifetime guarantees).
|
|
*/
|
|
cmpp_arg const * arg0;
|
|
|
|
/**
|
|
Internal implementation details. This is initialized via
|
|
cmpp_args_parse() and freed via cmpp_args_cleanup().
|
|
*/
|
|
cmpp_args_pimpl * pimpl;
|
|
};
|
|
typedef struct cmpp_args cmpp_args;
|
|
|
|
/**
|
|
Empty-initialized cmpp_args instance, intended for const-copy
|
|
initialization.
|
|
*/
|
|
#define cmpp_args_empty_m { \
|
|
.argc = 0, \
|
|
.arg0 = 0, \
|
|
.pimpl = 0 \
|
|
}
|
|
|
|
/**
|
|
Empty-initialized instance, intended for non-const-copy
|
|
initialization.
|
|
*/
|
|
extern const cmpp_args cmpp_args_empty;
|
|
|
|
/**
|
|
DEPRECATED. Probably. The need for this goes away with
|
|
cmpp_f_F_ARGS_V4-style args processing, and this can be moved into
|
|
the internals (where we currently still need it for the older
|
|
expression engine).
|
|
|
|
Parses the range [zInBegin,zInBegin+nIn) into a list of cmpp_arg
|
|
objects by iteratively processing that range with cmpp_arg_parse().
|
|
If nIn is negative, strlen() is used to calculate it.
|
|
|
|
Requires that arg be a cleanly-initialized instance (via
|
|
bitwise-copying cmpp_args_empty) or that it have been successfully
|
|
used with this function before. Behavior is undefined if pArgs was
|
|
not properly initialized.
|
|
|
|
The 3rd argument is an optional bitmask of flags from the
|
|
cmpp_args_e enum.
|
|
|
|
On success it populates arg, noting that an empty list is valid.
|
|
The memory pointed to by the arguments made available via
|
|
arg->arg0 is all owned by arg and will be invalidated by either a
|
|
subsequent call to this function (the memory will be overwritten or
|
|
reallocated) or cmpp_args_cleanup() (the memory will be freed).
|
|
|
|
On error, returns non-0 and updates pp's error state with info
|
|
about the problem.
|
|
*/
|
|
CMPP_EXPORT int cmpp_args_parse(cmpp * pp,
|
|
cmpp_args * pOut,
|
|
unsigned char const * zInBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_flag32_t flags);
|
|
|
|
/**
|
|
Frees any resources owned by its argument but does not free the
|
|
argument (which is typically stack-allocated). After calling this,
|
|
the object may again be used with cmpp_args_parse() (in which case
|
|
it eventually needs to be passed to this again).
|
|
|
|
This is a harmless no-op if `a` is already cleaned up but `a` must
|
|
not be NULL.
|
|
*/
|
|
CMPP_EXPORT void cmpp_args_cleanup(cmpp_args *a);
|
|
|
|
/**
|
|
DEPRECATED. Probably. The need for this goes away with
|
|
cmpp_f_F_ARGS_V4-style args processing.
|
|
|
|
A wrapper around cmpp_args_parse() which uses dx->args.z as an
|
|
input source. This is sometimes convenient in cmpp_f()
|
|
implementations which use cmpp_dx_next(), or similar, to read and
|
|
process custom directives, as doing so invalidates dx->arg's
|
|
memory.
|
|
|
|
On success, returns 0 and populates args. On error, returns non-0
|
|
and sets dx->pp's error state.
|
|
|
|
cmpp_dx_args_clone() does essentially the same thing, but is more
|
|
efficient when dx->args.arg0 is is already parsed.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_args_parse(cmpp_dx *dx, cmpp_args *args);
|
|
|
|
/**
|
|
DEPRECATED 2026-08-25: this is not needed for directives which use
|
|
cmpp_f_F_ARGS_V4, as that flag uses an approach which does not
|
|
invalidate argument memory when recursing into the library.
|
|
Once/if older directive code is updated, this function will go
|
|
away.
|
|
|
|
Populates pOut, replacing any current content, with a copy of each
|
|
arg in dx->args.arg0 (traversing arg0->next).
|
|
|
|
*pOut MUST be cleanly initialized via copying cmpp_args_empty or it
|
|
must have previously been used with either cmpp_args_parse() (which
|
|
has the same initialization requirement) or this function has
|
|
undefined results.
|
|
|
|
On success, pOut->argc and pOut->arg0 will refer to pOut's copy
|
|
of the arguments.
|
|
|
|
Copying of arguments is necessary in cmpp_f() implementations
|
|
which need to hold on to arguments for use _after_ calling
|
|
cmpp_dx_next() or any API which calls that (which most directives
|
|
don't do). See that function for why.
|
|
*/
|
|
CMPP_EXPORT int cmpp_dx_args_clone(cmpp_dx * dx, cmpp_args *pOut);
|
|
|
|
|
|
/**
|
|
Callback type for use with cmpp_kav_each().
|
|
|
|
cmpp_kav_each() calls this one time per key/value in such a list,
|
|
passing it the relevant key/value strings and lengths, plus the
|
|
opaque state pointer which is passed to cmpp_kav_each().
|
|
|
|
Must return 0 on success or update (or propagate) dx->pp's error
|
|
state on error.
|
|
*/
|
|
typedef int cmpp_kav_each_f(
|
|
cmpp *pp,
|
|
unsigned char const *zKey, cmpp_size_t nKey,
|
|
unsigned char const *zVal, cmpp_size_t nVal,
|
|
void* callbackState
|
|
);
|
|
|
|
/**
|
|
Flag bitmask for use with cmpp_kav_each().
|
|
*/
|
|
enum cmpp_kav_each_e {
|
|
/**
|
|
The key argument should be expanded using cmpp_arg_interpolate()
|
|
with a 0 flags value. This flag should normally not be used.
|
|
*/
|
|
cmpp_kav_each_F_EXPAND_KEY = 0x01,
|
|
/**
|
|
The key argument should be expanded using cmpp_arg_interpolate()
|
|
with a 0 flags value. This flag should normally be used.
|
|
*/
|
|
cmpp_kav_each_F_EXPAND_VAL = 0x02,
|
|
/**
|
|
Treat (...) value tokens (ttype=cmpp_tt_GroupParen) as integer
|
|
expressions. Keys are never treated this way. Without this flag,
|
|
the token expands to the ... part of (...).
|
|
*/
|
|
cmpp_kav_each_F_PARENS_EXPR = 0x04,
|
|
/**
|
|
Indicates that an empty input list is an error. If this flag is
|
|
not set and the list is empty, the callback will not be called
|
|
and no error will be triggered.
|
|
*/
|
|
cmpp_kav_each_F_NOT_EMPTY = 0x08,
|
|
/**
|
|
Indicates that the list does not have the '->' part(s). That is,
|
|
the list needs to be in pairs of KEY VAL rather than triples of
|
|
KEY -> VALUE.
|
|
*/
|
|
cmpp_kav_each_F_NO_ARROW = 0x10,
|
|
|
|
/**
|
|
If set, keys get the cmpp_arg_interpolate_BRACE_CALL flag added
|
|
to them. This implies cmpp_kav_each_F_EXPAND_KEY.
|
|
*/
|
|
cmpp_kav_each_F_CALL_KEY = 0x20,
|
|
/** Value counterpart of cmpp_kav_each_F_CALL_KEY. */
|
|
cmpp_kav_each_F_CALL_VAL = 0x40,
|
|
/** Both cmpp_kav_each_F_CALL_KEY and cmpp_kav_each_F_CALL_VAL. */
|
|
cmpp_kav_each_F_CALL = 0x60,
|
|
|
|
//TODO: append to defines which already exist
|
|
cmpp_kav_each_F_APPEND = 0,
|
|
cmpp_kav_each_F_APPEND_SPACE = 0,
|
|
cmpp_kav_each_F_APPEND_NL = 0
|
|
};
|
|
|
|
/**
|
|
A helper for cmpp_f() implementations in processing directive
|
|
arguments which are lists in this form:
|
|
|
|
{ a -> b c -> d ... }
|
|
|
|
("kav" is short for "key arrow value".)
|
|
|
|
The range [zBegin,zBegin+nIn) contains the raw list (not including
|
|
any containing braces, parentheses, quotes, or the like). If nIn is
|
|
negative, strlen() is used to calculate it.
|
|
|
|
The range is parsed using cmpp_args_parse().
|
|
|
|
For each key/arrow/value triplet in that list, f() is passed the
|
|
stringified form of the key and the value, plus the callbackState
|
|
pointer.
|
|
|
|
The flags argument controls whether the keys and values get
|
|
expanded or not. (Typically the keys should not be expanded but the
|
|
values should.)
|
|
|
|
Returns 0 on success. If the callback returns non-0, it is expected
|
|
to have updated dx's error state. f() will never be called when
|
|
dx's error state is non-0.
|
|
|
|
Error results include:
|
|
|
|
- CMPP_RC_RANGE: the list is empty does not contain the correct
|
|
number of entries (groups of 3, or 2 if flags has
|
|
cmpp_kav_each_F_NO_ARROW).
|
|
|
|
- CMPP_RC_OOM: allocation error.
|
|
|
|
- Any value returned by cmpp_args_parse().
|
|
|
|
- Any number of errors can be triggered during expansion of
|
|
keys and values.
|
|
*/
|
|
CMPP_EXPORT int cmpp_kav_each(
|
|
cmpp *pp,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_kav_each_f callback, void *callbackState,
|
|
cmpp_flag32_t flags
|
|
);
|
|
|
|
#if 0
|
|
// untested
|
|
/**
|
|
This works like cmpp_kav_each() except that it treats each token in
|
|
the list as a single entry.
|
|
|
|
When the callback is triggered, the "key" part will be the raw
|
|
token and the "value" part will be the expanded form of that
|
|
value. Its flags may contain most of the cmpp_kav_each_F_... flags,
|
|
with the exception that cmpp_kav_each_F_EXPAND_KEY has no effect
|
|
here. If cmpp_kav_each_F_EXPAND_VAL is not in the flags then the
|
|
callback receives the same string for both the key and value.
|
|
*/
|
|
XCMPP_EXPORT int cmpp_str_each(
|
|
cmpp * pp,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_kav_each_f callback, void *callbackState,
|
|
cmpp_flag32_t flags
|
|
);
|
|
#endif
|
|
|
|
/**
|
|
An interface for clients to provide directives to the library
|
|
on-demand.
|
|
|
|
This is called when pp has encountered a directive name is does not
|
|
know. It is passed the cmpp object, the name of the directive, and
|
|
the opaque state pointer which was passed to cmpp_d_autoloader_et().
|
|
|
|
Implementations should compare dname to any directives they know
|
|
about. If they find no match they must return CMPP_RC_NO_DIRECTIVE
|
|
_without_ using cmpp_errf() to make the error persistent.
|
|
|
|
If they find a match, they must use cmpp_d_register() to register
|
|
it and (on success) return 0. The library will then look again in
|
|
the registered directive list for the directive before giving up.
|
|
|
|
If they find a match but registration fails then the result of that
|
|
failure must be returned.
|
|
|
|
For implementation-specific errors, e.g. trying to load a directive
|
|
from a DLL but the loading of the DLL fails, implementations are
|
|
expected to use cmpp_errf() to report the error and to return
|
|
that result code after performing any necessary cleanup.
|
|
|
|
It is legal for an implementation to register multiple directives
|
|
in a single invocation (in particular a pair of opening/closing
|
|
directives), as well as to register directives other than the one
|
|
requested (if necessary). Regardless of which one(s) it registers,
|
|
it must return 0 only if it registers one named dname.
|
|
*/
|
|
typedef int (*cmpp_d_autoloader_f)(cmpp *pp, char const *dname, void *state);
|
|
|
|
/**
|
|
A c-pp directive "autoloader". See cmpp_d_autoloader_set()
|
|
and cmpp_d_autoloader_take().
|
|
*/
|
|
struct cmpp_d_autoloader {
|
|
/** The autoloader callback. */
|
|
cmpp_d_autoloader_f f;
|
|
/**
|
|
Finalizer for this->state. After calling this, if there's any
|
|
chance that this object might be later used, then it is important
|
|
that this->state be set to 0 (which this finalizer cannot
|
|
do). "Best practice" is to bitwise copy cmpp_d_autoloader_empty
|
|
over any instances immediately after calling dtor().
|
|
*/
|
|
cmpp_finalizer_f dtor;
|
|
/**
|
|
Implementation-specific state, to be passed as the final argument
|
|
to this->f and this->dtor.
|
|
*/
|
|
void * state;
|
|
};
|
|
typedef struct cmpp_d_autoloader cmpp_d_autoloader;
|
|
/**
|
|
Empty-initialized cmpp_d_autoloader instance, intended for
|
|
const-copy initialization.
|
|
*/
|
|
#define cmpp_d_autoloader_empty_m {.f=0,.dtor=0,.state=0}
|
|
/**
|
|
Empty-initialized cmpp_d_autoloader instance, intended for
|
|
non-const-copy initialization.
|
|
*/
|
|
extern const cmpp_d_autoloader cmpp_d_autoloader_empty;
|
|
|
|
/**
|
|
Sets pp's "directive autoloader". Each cmpp instance has but a
|
|
single autoloader but this API is provided so that several
|
|
instances may be chained from client-side code.
|
|
|
|
This function will call the existing autoloader's destructor (if
|
|
any), invalidating any pointers to its state object.
|
|
|
|
If pNew is not NULL then pp's autoloader is set to a bitwise copy
|
|
of *pNew, otherwise it is zeroed out. This transfers ownership of
|
|
pNew->state to pp.
|
|
|
|
See cmpp_d_autoloader_f()'s docs for how pNew must behave.
|
|
|
|
This function has no error conditions but downstream results are
|
|
undefined if if pNew and an existing autoloader refer to the same
|
|
dtor/state values (a gateway to double-frees).
|
|
*/
|
|
CMPP_EXPORT void cmpp_d_autoloader_set(cmpp *pp, cmpp_d_autoloader const * pNew);
|
|
|
|
/**
|
|
Moves pp's current autoloader state into pOld, transerring
|
|
ownership of it to the caller.
|
|
|
|
This obligates the caller to eventually either pass that same
|
|
pointer to cmpp_d_autoloader_set() (to transfer ownership back to
|
|
pp) or to call pOld->dtor() (if it's not NULL), passing it it
|
|
pOld->state (even if pOld->state is NULL). In either case, all
|
|
contents of pOld are semantically invalidated and perhaps freed.
|
|
|
|
This would normally be a prelude to cmpp_d_autoloader_set() to
|
|
install a custom, perhaps chained, autoloader.
|
|
*/
|
|
CMPP_EXPORT void cmpp_d_autoloader_take(cmpp *pp, cmpp_d_autoloader * pOld);
|
|
|
|
/**
|
|
True only for ' ' and '\t'.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_isspace(int ch);
|
|
|
|
/**
|
|
Reassigns *p to the address of the first non-space character at or
|
|
after the initial *p value. It stops looking if it reaches
|
|
zEnd. Newlines are not consider to be spaces (see cmpp_skip_snl()).
|
|
|
|
If `*p` does not point to memory before zEnd, or is not a part of
|
|
the same logical string, results are undefined.
|
|
|
|
|
|
Achtung: do not pass this the address of a cmpp_b::z,
|
|
or similar, as that will effectively corrupt the buffer's
|
|
memory. To trim a whole buffer, use something like:
|
|
|
|
```
|
|
cmpp_b ob = cmpp_b_empty;
|
|
... populate ob...;
|
|
// get the trimmed range:
|
|
unsigned char const *zB = ob.z;
|
|
unsigned char const *zE = zB + n;
|
|
cmpp_skip_snl(&zB, zE);
|
|
assert( zB<=zE );
|
|
cmpp_skip_snl_trailing(zB, &zE);
|
|
assert( zE>=zB );
|
|
printf("trimmed range: [%.*s]\n", (int)(zE-zB), zB);
|
|
```
|
|
|
|
Those assert()s are not error handling - they're demonstrating
|
|
invariants of the calls made before them.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_space(unsigned char const **p,
|
|
unsigned char const *zEnd );
|
|
|
|
/**
|
|
Works just like cmpp_skip_space() but it also
|
|
skips newlines.
|
|
|
|
FIXME (2026-02-21): it does not recognize CRNL pairs as
|
|
atomic newlines.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_snl( unsigned char const **p,
|
|
unsigned char const *zEnd );
|
|
|
|
/**
|
|
"Trims" trailing cmpp_isspace() characters from the range [zBegin,
|
|
*p). *p must initially point to one byte after the end of zBegin
|
|
(i.e. its NUL byte or virtual EOF). Upon return *p will be modified
|
|
leftwards (if at all) until a non-space is found or *p==zBegin.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_space_trailing( unsigned char const *zBegin,
|
|
unsigned char const **p );
|
|
|
|
/**
|
|
Works just like cmpp_skip_space_trailing() but skips
|
|
cmpp_skip_snl() characters.
|
|
|
|
FIXME (2026-02-21): it does not recognize CRNL pairs as
|
|
atomic newlines.
|
|
*/
|
|
CMPP_EXPORT void cmpp_skip_snl_trailing( unsigned char const *zBegin,
|
|
unsigned char const **p );
|
|
|
|
|
|
/**
|
|
Generic array-of-T list memory-reservation routine.
|
|
|
|
*list is the input array-of-T. nDesired is the number of entries to
|
|
reserve (list entry count, not byte length). *nAlloc is the number
|
|
of entries allocated in the list. sizeOfEntry is the sizeof(T) for
|
|
each entry in *list. T may be either a value type or a pointer
|
|
type and sizeofEntry must match, i.e. it must be sizeof(T*) for a
|
|
list-of-pointers and sizeof(T) for a list-of-objects.
|
|
|
|
If pp is not NULL then this function updates pp's error state on
|
|
error, else it simply returns CMPP_RC_OOM on error. If pp is not
|
|
NULL then this function is a no-op if called when pp's error state
|
|
is set, returning that code without other side-effects.
|
|
|
|
If nDesired > *nAlloc then *list is reallocated to contain at least
|
|
nDesired entries, else this function returns without side effects.
|
|
|
|
On success *list is re-assigned to the reallocated list memory, all
|
|
newly-(re)allocated memory is zeroed out, and *nAlloc is updated to
|
|
the new allocation size of *list (the number of list entries, not
|
|
the number of bytes).
|
|
|
|
On failure neither *list nor *nAlloc are modified.
|
|
|
|
Returns 0 on success or CMPP_RC_OOM on error. Errors generated by
|
|
this routine are, at least in principle, recoverable (see
|
|
cmpp_errf()), though that simply means that the pp object is
|
|
left in a well-defined state, not that the app can necessarily
|
|
otherwise recover from an OOM.
|
|
|
|
This seemingly-out-of-API-scope routine is in the public API as a
|
|
convenience for client-level cmpp_f() implementations[^1]. This API
|
|
internally has an acute need for basic list management and non-core
|
|
extensions inherit that as well.
|
|
|
|
[^1]: this project's own directives are written as if they were
|
|
client-side whenever feasible. Some require cmpp-internal state to
|
|
do their jobs, though.
|
|
*/
|
|
CMPP_EXPORT int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
|
|
cmpp_size_t * nAlloc, unsigned sizeOfEntry);
|
|
|
|
/**
|
|
State for a cmpp__dx_pimpl which we need in order to snapshot the
|
|
parse position for purposes of restoring it later. This is
|
|
basically to support that #query can contain other #query
|
|
directives, but this same capability is required by any directives
|
|
which want to both process directives in their content block and
|
|
loop over the content block.
|
|
|
|
See cmpp_dx_pos_save() and cmpp_dx_pos_restore().
|
|
*/
|
|
struct cmpp_dx_pos {
|
|
/** Current parse pos. */
|
|
unsigned char const *z;
|
|
/** Current line number. */
|
|
cmpp_size_t lineNo;
|
|
};
|
|
typedef struct cmpp_dx_pos cmpp_dx_pos;
|
|
#define cmpp_dx_pos_empty_m {.z=0,.lineNo=0U}
|
|
|
|
/**
|
|
Stores dx's current input position into pos. pos gets completely
|
|
initialized by this routine - it need not (in contrast to many
|
|
other functions in this library) be cleanly initialized by the
|
|
caller first.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_pos_save(cmpp_dx const * dx, cmpp_dx_pos *pos);
|
|
|
|
/**
|
|
Restores dx's input position from pos. Results are undefined if pos
|
|
is not populated with the result of having passed the same dx/pos
|
|
pointer combination to cmpp_dx_pos_save().
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_pos_restore(cmpp_dx * dx, cmpp_dx_pos const * pos);
|
|
|
|
/**
|
|
Returns, via its output parameters, the current name and input line
|
|
number of dx's current directive. This is intended for use with
|
|
synthesizing names such as those of embedded scripts. The *zName
|
|
bytes are owned by dx and are valid until it is destroyed.
|
|
|
|
Caveat: this specifically refers to the line holding the current
|
|
directive, not any non-directive lines consumed since that
|
|
directive was captured.
|
|
*/
|
|
CMPP_EXPORT void cmpp_dx_src_pos_info(cmpp_dx const *dx, char const **zName,
|
|
cmpp_size_t * lineNo);
|
|
|
|
/**
|
|
Expects to be passed a non-NULL pointer and its ostensible length.
|
|
If n<0 then this returns strlen(z) else it returns n.
|
|
|
|
This implements the library's common "if n<0 then use strlen()"
|
|
convention.
|
|
*/
|
|
CMPP_EXPORT cmpp_size_t cmpp_strlen(char const *z, cmpp_strlen_t n);
|
|
/**
|
|
Unsigned counterpart of cmpp_strlen().
|
|
*/
|
|
CMPP_EXPORT cmpp_size_t cmpp_strlenu(unsigned char const *z, cmpp_strlen_t n);
|
|
|
|
|
|
/**
|
|
Returns true unless:
|
|
|
|
- !*z
|
|
- '0'==*z and !z[1]
|
|
|
|
z may be NULL only if n is 0. If non-NULL then it must of course be
|
|
a valid poiner (even if it's the terminating NUL byte of a string).
|
|
*/
|
|
CMPP_EXPORT bool cmpp_simple_truth(unsigned char const *z, cmpp_strlen_t n);
|
|
|
|
/**
|
|
If [z,n) is unambiguously a base-10 int, sets *pOut (if not NULL)
|
|
to that value and returns true, else returns false.
|
|
|
|
Warning: because this uses strtol() it may scan more bytes than n.
|
|
n is used to validate that strtol()'s ending position matches n,
|
|
not to limit the range of z scanned. This is only a warning if z is
|
|
shorter than 10 bytes (20 for cmpp_is_int64()) and not
|
|
NUL-terminated, otherwise it's irrelevant.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_int(unsigned char const *z, unsigned n,
|
|
int *pOut);
|
|
|
|
/** 64-bit counterpart of cmpp_is_int() which uses strtoll(). See
|
|
cmpp_is_int() for a caveat about the scan range.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_is_int64(unsigned char const *z, unsigned n,
|
|
int64_t *pOut);
|
|
|
|
/**
|
|
Returns true if the string [z,z+n) is valid JSON, per SQLite3's
|
|
json_valid(theString,flags). If n is negative, strlen() is used to
|
|
calculate it.
|
|
|
|
This function is a no-op, returning false, if pp has pending error
|
|
state. That is arguable but it's inherited from lower-level APIs
|
|
which this one builds on.
|
|
|
|
It is wildly hypothetically possible that this call can put pp into
|
|
an error state due to db-level issues, but that can be effectively
|
|
ruled out for the sake of usage sanity. It will always return false
|
|
in that case but may update pp's error state.
|
|
|
|
Similarly: json_valid() triggers a db error if passed a flags value
|
|
which is not in the range [1,15]. To avoid that a client doing so
|
|
puts pp into an error state, this function returns false without
|
|
side-effects if passed a value out of that range.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_json_valid(cmpp *pp, unsigned char const *z,
|
|
cmpp_strlen_t n, int flags);
|
|
|
|
|
|
/**
|
|
Expects [z,z+n) to point to a single JSON value. If
|
|
cmpp_json_valid() returns true for the range, it is appended to be
|
|
as-is, else it is appended as a JSON-quoted string. Returns 0 on
|
|
success, non-0 (OOM or related range overflow) on error, updating
|
|
pp's error state if it returns non-0.
|
|
|
|
For cmpp_json_valid() purposes, a flags value of 3 (JSON/JSON5) is
|
|
used.
|
|
*/
|
|
CMPP_EXPORT int cmpp_b_append_json_value(cmpp *pp, cmpp_b *b,
|
|
unsigned char const *z,
|
|
cmpp_strlen_t n);
|
|
|
|
|
|
/**
|
|
Returns true if [z,z+n) begins with '[', ends with ']', and
|
|
cmpp_json_valid() for the same range returns true. i.e. if it's a
|
|
JSON-format list. Because this checks for '[' and ']' before
|
|
attempting to parse it, this is very fast for most non-list
|
|
strings.
|
|
|
|
If allowJson5 is true then it uses SQLite3 json_valid() flags of 3,
|
|
else flags of 1.
|
|
*/
|
|
CMPP_EXPORT bool cmpp_json_is_list(cmpp *pp, unsigned char const *z,
|
|
cmpp_strlen_t n, bool allowJson5);
|
|
|
|
/**
|
|
The JSON-object counterpart of cmpp_json_is_list().
|
|
*/
|
|
CMPP_EXPORT bool cmpp_json_is_object(cmpp *pp, unsigned char const *z,
|
|
cmpp_strlen_t n, bool allowJson5);
|
|
|
|
/**
|
|
Appends some form of [z,z+n) to str, using strlen() to calculate n
|
|
if n is negative:
|
|
|
|
- 0==n: append "" (and empty JSON string).
|
|
|
|
- cmpp_json_valid(z,n) is true: append the bytes as-is.
|
|
|
|
- Else append n bytes of z as a JSON-format string using
|
|
sqlite3_str_appendf()'s `%J` formatter.
|
|
|
|
This returns void because that's how sqlite3_str's APIs work. It
|
|
will only fail if str gets "too big" (the limits of which varies by
|
|
str's source but all of which are arguably out of scope for
|
|
cmpp-sized inputs and outputs).
|
|
*/
|
|
void cmpp_str_json_append(cmpp *pp, sqlite3_str * str,
|
|
unsigned char const * z,
|
|
cmpp_strlen_t n);
|
|
|
|
/**
|
|
A proxy for sqlite3_prepare_v2() which uses pp's database, supports
|
|
sqlite3_str formatting, and updates pp's error state on error.
|
|
|
|
zSql is the SQL string. nSql is the byte length of zSql, or its
|
|
strlen() if nSql is negative.
|
|
|
|
If pp has error state when this is called, it is a no-op returning
|
|
that error code.
|
|
|
|
For empty inputs sqlite3_prepare() and friends return success but a
|
|
NULL *pStmt. This wrapper triggers a CMPP_RC_MISUSE error for that
|
|
case.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_prepare(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, cmpp_strlen_t nSql);
|
|
|
|
/**
|
|
Variadic form of cmpp_stmt_prepare() which supports sqlite3_str
|
|
formatting.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_preparef(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, ...);
|
|
|
|
/**
|
|
va_list counterpart of cmpp_stmt_preparef().
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_preparev(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, va_list);
|
|
|
|
/**
|
|
Steps statement q one time.
|
|
|
|
On success it returns SQLITE_ROW or SQLITE_DONE, as per
|
|
sqlite3_step(). On error it returns another non-0 SQLITE_... code
|
|
and updates pp's error state with a corresponding CMPP_RC_... code
|
|
and error string.
|
|
|
|
This is a no-op if called when pp has an error set, returning
|
|
SQLITE_ERROR.
|
|
|
|
If resetIt is true, q is passed to cmpp_stmt_reset(), else the
|
|
caller must eventually reset it. Only pass true when no result
|
|
columns need to be fetched.
|
|
|
|
Reminder to self: this must return an SQLITE_... code, not a
|
|
CMPP_RC_... code, because it's more useful that way (but also
|
|
slightly confusing).
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_step(cmpp * const pp, sqlite3_stmt * const q,
|
|
bool resetIt);
|
|
|
|
/**
|
|
Resets and clears bindings from q if q is not NULL. Returns 0 if q
|
|
is NULL or if sqlite3_reset() indicates no error (SQLITE_DONE and
|
|
SQLITE_ROW are considered to be success codes in this context). If
|
|
sqlite3_reset() indicates an error and pp's error state is not
|
|
already set then pp's error state is updated and a non-0 value is
|
|
returned.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_reset(cmpp *pp, sqlite3_stmt * const q);
|
|
|
|
/**
|
|
Expects an SQLite result value. If it's SQLITE_OK, SQLITE_ROW, or
|
|
SQLITE_DONE, 0 is returned without side-effects, otherwise pp->err
|
|
is updated with pp->db's current error state. zMsgSuffix is an
|
|
optional suffix for the generated error message.
|
|
|
|
nMsgSuffix is the length of zMsgSuffix, or negative to use strlen()
|
|
to figure it out.
|
|
*/
|
|
CMPP_EXPORT int cmpp_db_rc_v2(cmpp *pp, int dbRc, char const *zMsgSuffix,
|
|
cmpp_strlen_t nMsgSuffix);
|
|
|
|
/** Older signature for cmpp_db_rc_v2(). */
|
|
#define cmpp_db_rc(PP,RC,ZMSG) cmpp_db_rc_v2(PP, RC, ZMSG, -1)
|
|
|
|
/** Proxy for sqlite3_bind_int64() which updates pp's error state on
|
|
error. */
|
|
CMPP_EXPORT int cmpp_stmt_bind_int(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
|
|
|
|
/**
|
|
Proxy for cmpp_stmt_bind_text() which encodes val as a string and
|
|
updates pp's error state on error.
|
|
|
|
For queries which compare values, it's important that they all have
|
|
the same type, so some cases where we might want an int needs to be
|
|
bound as text instead. As of this writing (2026-08-23), arg.c
|
|
depends on this for expression evaluation.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_bind_int_text(cmpp *pp, sqlite3_stmt *pStmt, int col, int64_t val);
|
|
|
|
/** SQL NULL counterpart of cmpp_stmt_bind_int(). */
|
|
CMPP_EXPORT int cmpp_stmt_bind_null(cmpp *pp, sqlite3_stmt *pStmt, int col);
|
|
|
|
/**
|
|
Proxy for sqlite3_bind_text() which updates pp's error state on
|
|
error.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_bind_text(cmpp *pp,sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr);
|
|
|
|
/**
|
|
Proxy for sqlite3_bind_text() which updates pp's error state on
|
|
error.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_bind_textn(cmpp *pp,sqlite3_stmt *pStmt, int col,
|
|
unsigned const char *zStr, cmpp_strlen_t len);
|
|
|
|
/**
|
|
Like cmpp_stmt_bind_textn() but takes a finalizer function as its final
|
|
argument. Don't use this without fully understanding why.
|
|
|
|
Maintenance reminder: if finalizer is declared as void (*dtor)(void
|
|
*) then tool/pullthunk.sh is emitting it for the #define cmpp_xyz
|
|
part but not the cmpp_api_thunk_map() part. Weird.
|
|
*/
|
|
CMPP_EXPORT int cmpp_stmt_bind_textx(cmpp *pp, sqlite3_stmt *pStmt, int col,
|
|
unsigned const char * zStr, cmpp_strlen_t n,
|
|
cmpp_finalizer_f finalizer);
|
|
|
|
|
|
|
|
|
|
/**
|
|
The current cmpp_api_thunk::apiVersion value. See
|
|
cmpp_api_thunk_map.
|
|
*/
|
|
#define cmpp_api_thunk_version 20260929
|
|
|
|
/**
|
|
This stub object is provided for cmpp interface compatibility with
|
|
loadable-module-capable builds, but that feature is stripped from
|
|
this build.
|
|
*/
|
|
struct cmpp_api_thunk {
|
|
uint32_t const thunkVersion;
|
|
};
|
|
|
|
#ifdef __cplusplus
|
|
} /* extern "C" */
|
|
#endif
|
|
#endif /*NET_WANDERINGHORSE_CMPP_H_INCLUDED_*/
|
|
#endif /* NET_WANDERINGHORSE_LIBCMPP_H_INCLUDED */
|
|
#if !defined(NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED)
|
|
#define NET_WANDERINGHORSE_CMPP_INTERNAL_H_INCLUDED
|
|
/**
|
|
This file houses declarations and macros for the private/internal
|
|
libcmpp APIs.
|
|
*/
|
|
#include "sqlite3.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
|
|
/* write() and friends */
|
|
#if defined(_WIN32) || defined(WIN32)
|
|
# include <io.h>
|
|
# include <fcntl.h>
|
|
# ifndef access
|
|
# define access(f,m) _access((f),(m))
|
|
# endif
|
|
#else
|
|
# include <unistd.h>
|
|
# include <sys/wait.h>
|
|
#endif
|
|
|
|
#ifndef CMPP_DEFAULT_DELIM
|
|
#define CMPP_DEFAULT_DELIM "##"
|
|
#endif
|
|
|
|
#ifndef CMPP_ATSIGN
|
|
#define CMPP_ATSIGN (unsigned char)'@'
|
|
#endif
|
|
|
|
#ifndef CMPP_MODULE_PATH
|
|
#define CMPP_MODULE_PATH "."
|
|
#endif
|
|
|
|
#if defined(NDEBUG)
|
|
#define cmpp__staticAssert(NAME,COND) (void)1
|
|
#else
|
|
#define cmpp__staticAssert(NAME,COND) \
|
|
static const char staticAssert_ ## NAME[ (COND) ? 1 : -1 ] = {0}; \
|
|
(void)staticAssert_ ## NAME
|
|
#endif
|
|
|
|
#if defined(CMPP_OMIT_ALL_UNSAFE)
|
|
#undef CMPP_OMIT_D_PIPE
|
|
#define CMPP_OMIT_D_PIPE
|
|
#undef CMPP_OMIT_D_DB
|
|
#define CMPP_OMIT_D_DB
|
|
#undef CMPP_OMIT_D_INCLUDE
|
|
#define CMPP_OMIT_D_INCLUDE
|
|
#undef CMPP_OMIT_D_MODULE
|
|
#define CMPP_OMIT_D_MODULE
|
|
#endif
|
|
|
|
#if !defined(CMPP_VERSION)
|
|
#error "expecting CMPP_VERSION to have been set up"
|
|
#endif
|
|
|
|
#define CMPP__DB_MAIN_NAME "cmpp"
|
|
|
|
#if defined(CMPP_AMALGAMATION)
|
|
#define CMPP_PRIVATE static
|
|
#else
|
|
#define CMPP_PRIVATE
|
|
#endif
|
|
|
|
#if CMPP_PLATFORM_IS_WASM
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "wasm"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# define CMPP__EXPORT_NAMED(X) \
|
|
__attribute__((export_name(#X),used,visibility("default")))
|
|
// See also:
|
|
//__attribute__((export_name("theExportedName"), used, visibility("default")))
|
|
# define CMPP_OMIT_FILE_IO /* potential todo but with a large footprint */
|
|
# if !defined(CMPP_PLATFORM_EXT_DLL)
|
|
# define CMPP_PLATFORM_EXT_DLL ""
|
|
# endif
|
|
#else
|
|
//# define CMPP_WASM_EXPORT
|
|
# define CMPP__EXPORT_NAMED(X)
|
|
# if defined(_WIN32) || defined(WIN32)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 1
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "windows"
|
|
# define CMPP_PATH_SEPARATOR ';'
|
|
//# include <io.h>
|
|
# elif defined(__MINGW32__) || defined(__MINGW64__)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 1
|
|
# define CMPP_PLATFORM_IS_UNIX 0
|
|
# define CMPP_PLATFORM_PLATFORM "windows"
|
|
# define CMPP_PATH_SEPARATOR ':' /*?*/
|
|
# elif defined(__CYGWIN__)
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 1
|
|
# define CMPP_PLATFORM_PLATFORM "unix"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# else
|
|
# define CMPP_PLATFORM_IS_WINDOWS 0
|
|
# define CMPP_PLATFORM_IS_UNIX 1
|
|
# define CMPP_PLATFORM_PLATFORM "unix"
|
|
# define CMPP_PATH_SEPARATOR ':'
|
|
# endif
|
|
#endif
|
|
|
|
#define CMPP__EXPORT(RETTYPE,NAME) CMPP__EXPORT_NAMED(NAME) RETTYPE NAME
|
|
|
|
#if !defined(CMPP_PLATFORM_EXT_DLL)
|
|
# error "Expecting CMPP_PLATFORM_EXT_DLL to have been set by the auto-configured bits"
|
|
# define CMPP_PLATFORM_EXT_DLL "???"
|
|
#endif
|
|
|
|
#if 1
|
|
# define CMPP_NORETURN __attribute__((noreturn))
|
|
#else
|
|
# define CMPP_NORETURN
|
|
#endif
|
|
|
|
#if !defined(CMPP_SWITCH_FALL_THROUGH)
|
|
# if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 7)
|
|
/*
|
|
#define CMPP_USING_GCC
|
|
|
|
gcc v7+ treats implicit 'switch' fallthrough as a warning
|
|
(i.e. error because we always build with -Wall -Werror -Wextra
|
|
-pedantic).
|
|
|
|
Similarly curious, clang #defines __GNUC__.
|
|
|
|
_Sigh_.
|
|
*/
|
|
# define CMPP_SWITCH_FALL_THROUGH __attribute__ ((fallthrough))
|
|
# else
|
|
# define CMPP_SWITCH_FALL_THROUGH
|
|
# endif
|
|
#endif
|
|
/* /CMPP_SWITCH_FALL_THROUGH */
|
|
|
|
|
|
/**
|
|
Declares struct T as a container for a list-of-MT. MT may be
|
|
pointer-qualified. cmpp__ListType_impl() with the same arguments
|
|
implements T_reserve() for basic list allocation. Cleanup, alas, is
|
|
MT-dependent.
|
|
*/
|
|
#define cmpp__ListType_decl(T,MT) \
|
|
struct T { \
|
|
MT * list; \
|
|
cmpp_size_t n; \
|
|
cmpp_size_t na; \
|
|
}; \
|
|
typedef struct T T; \
|
|
int T ## _reserve(cmpp *pp, T *li, cmpp_size_t min)
|
|
|
|
#define cmpp__ListType_impl(T,MT) \
|
|
int T ## _reserve(cmpp *pp,struct T *li, cmpp_size_t min) { \
|
|
return cmpp_array_reserve(pp, (void**)&li->list, min, \
|
|
&li->na, sizeof(MT)); \
|
|
}
|
|
|
|
#define cmpp__LIST_T_empty_m {.list=0,.n=0,.na=0}
|
|
|
|
/**
|
|
Stack of POD values. Intended for use with cmpp at-token and
|
|
undefined key policies.
|
|
*/
|
|
#define cmpp__PodList_decl(ST,ET) \
|
|
struct ST { \
|
|
/* current stack index */ \
|
|
cmpp_size_t n; \
|
|
cmpp_size_t na; \
|
|
ET * stack; \
|
|
}; typedef struct ST ST; \
|
|
void ST ## _wipe(ST * s, ET v); \
|
|
int ST ## _push(cmpp *pp, ST * s, ET v); \
|
|
void ST ## _set(ST * s, ET v); \
|
|
void ST ## _finalize(ST * s); \
|
|
void ST ## _pop(ST *s); \
|
|
int ST ## _reserve(cmpp *, ST *, cmpp_size_t min)
|
|
|
|
#define cmpp__PodList_impl(ST,ET) \
|
|
void ST ## _wipe(ST * const s, ET v){ \
|
|
if( s->na ) memset(s->stack, (int)v, sizeof(ET)*s->na); \
|
|
s->n = 0; \
|
|
} \
|
|
int ST ## _reserve(cmpp * const pp, ST * const s, \
|
|
cmpp_size_t min){ \
|
|
return cmpp_array_reserve(pp, (void**)&s->stack, min>0 \
|
|
? min : (s->n \
|
|
? (s->n==s->na-1 \
|
|
? s->na*2 : s->n+1) \
|
|
: 8), \
|
|
&s->na, sizeof(ET)); \
|
|
} \
|
|
int ST ## _push(cmpp * const pp, ST * s, ET v){ \
|
|
if( 0== ST ## _reserve(pp, s, 0) ) s->stack[++s->n] = v; \
|
|
return ppCode; \
|
|
} \
|
|
void ST ## _set(ST * s, ET v){ \
|
|
assert(s->n); \
|
|
if( 0== ST ## _reserve(NULL, s, 0) ){ \
|
|
s->stack[s->n] = v; \
|
|
} \
|
|
} \
|
|
ET ST ## _get(ST const * const s){ \
|
|
assert(s->na && s->na >=s->n); \
|
|
return s->stack[s->n]; \
|
|
} \
|
|
void ST ## _pop(ST *s){ \
|
|
assert(s->n); \
|
|
if(s->n) --s->n; \
|
|
} \
|
|
void ST ## _finalize(ST *s){ \
|
|
cmpp_mfree(s->stack); \
|
|
s->stack = NULL; \
|
|
s->n = s->na = 0; \
|
|
}
|
|
|
|
/**
|
|
Internal-use-only flags for use with cmpp_d::flags.
|
|
|
|
These supplement the ones from the public API's cmpp_d_e.
|
|
*/
|
|
enum cmpp_f_ext_e {
|
|
/**
|
|
Mask of flag bits from this enum and cmpp_d_e which are for
|
|
internal use only and are disallowed in client-side directives.
|
|
*/
|
|
cmpp_f_F_MASK_INTERNAL = ~cmpp_f_F_MASK,
|
|
|
|
/**
|
|
If true, and if cmpp_f_F_ARGS_LIST is set, then cmpp_args_parse()
|
|
will pass its results to cmpp_args__not_simplify(). Only
|
|
directives which eval cmpp_arg expressions need this, and the
|
|
library does not expose the pieces for evaluating such
|
|
expressions. As such, this flag is for internal use only. This
|
|
only has an effect if cmpp_f_F_ARGS_LIST is also used.
|
|
*/
|
|
cmpp_f_F_NOT_SIMPLIFY = 0x10000,
|
|
|
|
/**
|
|
Most directives are inert when they are seen in the "falsy" part
|
|
of an if/else. The callbacks for such directives are skipped, as
|
|
opposed to requiring each directive's callback to check whether
|
|
they should be skipping. This flag indicates that a directive
|
|
must always be run, even when skipping content (e.g. inside of an
|
|
#if 0 block). Only flow-control directives may have the
|
|
FLOW_CONTROL bit set. The library API does not expose enough of
|
|
its internals for client-defined directives to make flow-control
|
|
decisions.
|
|
|
|
i really want to get rid of this flag but it seems to be a
|
|
necessary evil.
|
|
*/
|
|
cmpp_f_F_FLOW_CONTROL = 0x20000
|
|
};
|
|
|
|
/**
|
|
A single directive line from an input stream.
|
|
*/
|
|
struct CmppDLine {
|
|
/** 1-based line number in the source input. */
|
|
cmpp_size_t lineNo;
|
|
/** Start of the line within its source input, immediately
|
|
after the directive delimiter. */
|
|
unsigned char const * zBegin;
|
|
/** Start of the line including the directive delimiter. */
|
|
unsigned char const * zReallyBegin;
|
|
/** One-past-the-end byte of the line. A virtual EOF. It will only
|
|
actually be NUL-terminated if it is the last line of the input
|
|
and that input has no trailing newline. */
|
|
unsigned char const * zEnd;
|
|
};
|
|
typedef struct CmppDLine CmppDLine;
|
|
#define CmppDLine_empty_m \
|
|
{.lineNo=0U,.zBegin=0,.zReallyBegin=0,.zEnd=0}
|
|
|
|
/**
|
|
A "stamp" type to turn arbitrary memory blocks into a recycling
|
|
list. It can be used to recycle blocks of any size equal to
|
|
or greater than its own.
|
|
*/
|
|
struct CmppRecyclerOverlay {
|
|
/* Size */
|
|
int32_t size;
|
|
void * next;
|
|
};
|
|
typedef struct CmppRecyclerOverlay CmppRecyclerOverlay;
|
|
|
|
struct CmppRecyclerList {
|
|
/** Head of recycling list. */
|
|
CmppRecyclerOverlay *head;
|
|
/**
|
|
The number of entries in this->head. We store this only so that
|
|
we can set some cap on it.
|
|
*/
|
|
cmpp_size_t n;
|
|
};
|
|
typedef struct CmppRecyclerList CmppRecyclerList;
|
|
#define CmppRecyclerList_empty_m {0,0}
|
|
|
|
|
|
/**
|
|
CmppLvl represents one "level" of parsing, pushing one level
|
|
for each of `#if` and popping one for each `#/if`.
|
|
|
|
These pieces are ONLY for use with flow-control directives. It's
|
|
not proven that they can be of any use to more than a single
|
|
flow-control directive. e.g. if we had a hypothetical #foreach, we
|
|
may need to extend this.
|
|
*/
|
|
struct CmppLvl {
|
|
/**
|
|
Bitmask of CmppLvl_F_...
|
|
*/
|
|
cmpp_flag32_t flags;
|
|
/**
|
|
The directive line number which started this level. This is used for
|
|
reporting the starting lines of erroneously unclosed block
|
|
constructs.
|
|
*/
|
|
cmpp_size_t lineNo;
|
|
};
|
|
typedef struct CmppLvl CmppLvl;
|
|
#define CmppLvl_empty_m {/*.d=0, .state=0,*/ .flags=0U, .lineNo=0U}
|
|
|
|
/**
|
|
A dynamically-allocated list of CmppLvl objects.
|
|
*/
|
|
cmpp__ListType_decl(CmppLvlList,CmppLvl*);
|
|
#define CmppLvlList_empty_m cmpp__LIST_T_empty_m
|
|
CMPP_PRIVATE CmppLvl * CmppLvl_push(cmpp_dx *dx);
|
|
CMPP_PRIVATE CmppLvl * CmppLvl_get(cmpp_dx const *dx);
|
|
CMPP_PRIVATE void CmppLvl_pop(cmpp_dx *dx, CmppLvl *lvl);
|
|
CMPP_PRIVATE void CmppLvl_elide(CmppLvl *lvl, bool on);
|
|
CMPP_PRIVATE bool CmppLvl_is_eliding(CmppLvl const *lvl);
|
|
CMPP_PRIVATE bool cmpp_dx_is_eliding(cmpp_dx const *dx);
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_b objects.
|
|
*/
|
|
cmpp__ListType_decl(cmpp_b_list,cmpp_b*);
|
|
#define cmpp_b_list_empty_m cmpp__LIST_T_empty_m
|
|
extern const cmpp_b_list cmpp_b_list_empty;
|
|
CMPP_PRIVATE void cmpp_b_list_cleanup(cmpp_b_list *li);
|
|
//CMPP_PRIVATE cmpp_b * cmpp_b_list_push(cmpp_b_list *li);
|
|
//CMPP_PRIVATE void cmpp_b_list_reuse(cmpp_b_list *li);
|
|
/**
|
|
cmpp_b_list sorting policies. NULL entries must
|
|
always sort last.
|
|
*/
|
|
enum cmpp_b_list_e {
|
|
cmpp_b_list_UNSORTED,
|
|
/* Smallest first. */
|
|
cmpp_b_list_ASC,
|
|
/* Largest first. */
|
|
cmpp_b_list_DESC
|
|
};
|
|
|
|
struct cmpp_b_library {
|
|
cmpp_b_list list;
|
|
/** How/whether this->list is sorted. */
|
|
enum cmpp_b_list_e sort;
|
|
/** Max number of buffers. 0 disables borrowing,
|
|
always allocating. */
|
|
uint32_t limitCount;
|
|
/** Largest capacity of single buffer to keep.
|
|
If exceeded, we keep the cmpp_b instance but
|
|
toss its memory. A value of 0 disables this
|
|
limit. */
|
|
uint32_t limitSingleCapacity;
|
|
/** Max total of cmpp_b::nAlloc in the buffers. 0 means no
|
|
limit. */
|
|
uint64_t limitCapacity;
|
|
/** Current cmpp_b::nAlloc in the buffers. */
|
|
uint64_t currentCapacity;
|
|
};
|
|
typedef struct cmpp_b_library cmpp_b_library;
|
|
#define cmpp_b_library_empty_m { \
|
|
.list=cmpp_b_list_empty_m, \
|
|
.sort=cmpp_b_list_UNSORTED, \
|
|
.limitCount=50, \
|
|
.limitSingleCapacity=512 * 8, \
|
|
.limitCapacity=1024 * 1024 * 4, \
|
|
.currentCapacity=0 \
|
|
}
|
|
|
|
#if 0
|
|
// todo
|
|
CMPP_PRIVATE int cmpp_b_library_borrow(cmpp_b_library *li);
|
|
CMPP_PRIVATE void cmpp_b_library_return(cmpp_b_library *li);
|
|
#endif
|
|
/** Cleans up and clears li but retains its various
|
|
limit-related members. */
|
|
CMPP_PRIVATE void cmpp_b_library_dtor(cmpp_b_library *li);
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_arg objects. Used primarily by
|
|
cmmp_args.
|
|
*/
|
|
struct CmppArgList{
|
|
/**
|
|
The list of arguments.
|
|
*/
|
|
cmpp_arg * list;
|
|
/**
|
|
From cmpp_b_borrow(). Holds the argv pointer-array memory.
|
|
*/
|
|
cmpp_b * avb;
|
|
/** Current number of args. */
|
|
cmpp_size_t n;
|
|
/** Allocated slots for args. */
|
|
cmpp_size_t na;
|
|
};
|
|
typedef struct CmppArgList CmppArgList;
|
|
#define CmppArgList_empty_m {.list=0,.avb=0,.n=0,.na=0}
|
|
/** Allocate a new arg, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. */
|
|
CMPP_PRIVATE cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li);
|
|
/** Free all memory owned by li but does not free li. */
|
|
CMPP_PRIVATE void CmppArgList_dtor(cmpp *pp, CmppArgList *li);
|
|
CMPP_PRIVATE int CmppArgList_reserve(cmpp *pp, CmppArgList *li,
|
|
cmpp_size_t min);
|
|
/**
|
|
Returns a list of args suitable for settings as cmpp_f_args::argv,
|
|
or NULL if li->n==0 or on OOM (in which case pp's error state is
|
|
updated).
|
|
|
|
If ensureNulTerminated is true then this also redirects each
|
|
cmpp_arg::z pointer which does _not_ point to NUL-terminated memory
|
|
to to an internal copy of the original which is guaranteed to be
|
|
NUL-terminated. (A stepping stone towards removing
|
|
cmpp_args_pimpl's internal buffer.)
|
|
|
|
The memory is owned by this object and is invalidated on any list
|
|
operations, so should be set up (with this) immediately before the
|
|
callback needs them. It is invalidated by CmppArgList_append() or
|
|
CmppArgList_reserve().
|
|
*/
|
|
CMPP_PRIVATE cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp,
|
|
CmppArgList*li,
|
|
bool ensureNulTerminated);
|
|
|
|
/**
|
|
The internal part of the cmpp_args interface.
|
|
*/
|
|
struct cmpp_args_pimpl {
|
|
/**
|
|
We need(?) a (cmpp*) here for finalization/recycling purposes.
|
|
*/
|
|
cmpp *pp;
|
|
/**
|
|
True if these are the arguments for directive [call...] syntax.
|
|
This is not set for cmpp_itch calls.
|
|
|
|
2026-08-25: this is being replaced. We currently need it to keep
|
|
some older bits working.
|
|
*/
|
|
bool isDxCall;
|
|
/**
|
|
Next entry in the free-list.
|
|
|
|
TODO (2026-08-31): replace with CmppRecyclerOverlay. 2026-09-19:
|
|
we can't do that unless we give up (or reimplement) recycling of
|
|
the state reused by cmpp__args_pimpl_reuse().
|
|
*/
|
|
cmpp_args_pimpl * nextFree;
|
|
/** Version 3 and 4 of the real args memory. */
|
|
CmppArgList argli;
|
|
/**
|
|
cmpp_args_parse() copies each argument's bytes into here,
|
|
each one NUL-terminated.
|
|
*/
|
|
cmpp_b argOut;
|
|
};
|
|
#define cmpp_args_pimpl_empty_m { \
|
|
.pp = 0, \
|
|
.isDxCall = false, \
|
|
.nextFree = 0, \
|
|
.argli = CmppArgList_empty_m, \
|
|
.argOut = cmpp_b_empty_m \
|
|
}
|
|
extern const cmpp_args_pimpl cmpp_args_pimpl_empty;
|
|
CMPP_PRIVATE void cmpp__args_pimpl_cleanup(cmpp_args_pimpl *p);
|
|
|
|
/**
|
|
The internal part of the cmpp_dx interface.
|
|
*/
|
|
struct cmpp__dx_pimpl {
|
|
/** Start of input. */
|
|
unsigned const char * zBegin;
|
|
/** One-after-the-end of input. */
|
|
unsigned const char * zEnd;
|
|
/** For use in pinpointing line/column during error
|
|
reporting. */
|
|
unsigned const char * zErrPos;
|
|
/**
|
|
Next context up in the stack. When cmpp_dx's share an input range
|
|
in a parent/child manner, We can use this to provide better stack
|
|
traces and line/column info by delaying line count until an error
|
|
happens, discovering (through this pointer) the whole input
|
|
range, and using cmpp_count_nl() to find the line. It's slow,
|
|
but (A) we only count lines when generating errors and (B) it's
|
|
more accurate than the current approach, which is definitely
|
|
off-by-one (or-more).
|
|
|
|
That said: these don't typically share an input range, but can
|
|
for [calls].
|
|
*/
|
|
cmpp_dx const * up;
|
|
/**
|
|
Current input position. Generally speaking, only
|
|
cmpp_dx_delim_search() should update this, but it turns out that
|
|
the ability to rewind the input is necessary for looping
|
|
constructs, like #query, when they want to be able to include
|
|
other directives in their bodies. Thus we have cmpp_dx_pos_save()
|
|
and cmpp_dx_pos_restore() to manipulate this.
|
|
|
|
Achtung: as of 2026-09-23 this is only updated when we find a
|
|
directive line. Places needing more precise line counts are
|
|
encouraged to count them. See c.c:cmpp__dx_guess_lineno().
|
|
*/
|
|
cmpp_dx_pos pos;
|
|
/**
|
|
Currently input line.
|
|
*/
|
|
CmppDLine dline;
|
|
/** Number of active #savepoints. */
|
|
unsigned nSavepoint;
|
|
/** Current directive's args. Updated by the parsing and dispatching
|
|
steps. */
|
|
cmpp_args args;
|
|
/**
|
|
A stack of state used by #if and friends to inform the innards
|
|
that they must not generate output. This is largely historical
|
|
and could have been done differently had this code started as a
|
|
library instead of a monolithic app.
|
|
|
|
TODO is to figure out how best to move this state completely into
|
|
the #if handler, rather than fiddle with this all throughout the
|
|
processing. We could maybe move this stack into CmppIfState?
|
|
*/
|
|
CmppLvlList dxLvl;
|
|
struct {
|
|
/**
|
|
A copy of this->d's input line which gets translated
|
|
slightly from its native form for futher processing.
|
|
*/
|
|
cmpp_b line;
|
|
/**
|
|
Holds the semi-raw input line, stripped only of backslash-escaped
|
|
newlines and leading spaces. This is primarily for debug output
|
|
but also for custom arg parsing for some directives.
|
|
*/
|
|
cmpp_b argsRaw;
|
|
} buf;
|
|
|
|
/**
|
|
Record IDs for/from cmpp_[un]define_shadow().
|
|
*/
|
|
struct {
|
|
/** Shadow-define rowid for __FILE__. */
|
|
int64_t sidFile;
|
|
/** Shadow-define rowid for #include path entry. */
|
|
int64_t ridInclPath;
|
|
} shadow;
|
|
|
|
struct {
|
|
/**
|
|
True if the next directive is the start of a [call].
|
|
*/
|
|
bool nextIsCall;
|
|
} flags;
|
|
};
|
|
|
|
/**
|
|
Calculates the whole input range of dx and its parents, for
|
|
purposes of calculating line counts in. Sets *zBegin and *zEnd
|
|
to the half-open range.
|
|
*/
|
|
CMPP_PRIVATE void cmpp__dx_input_range(cmpp_dx const * dx,
|
|
unsigned char const **zBegin,
|
|
unsigned char const **zEnd);
|
|
|
|
/**
|
|
Initializes or resets a. Returns non-0 on OOM.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__args_init(cmpp *pp, cmpp_args *a);
|
|
|
|
/**
|
|
If a has state then it's recycled for reuse, else this zeroes out a
|
|
except for a->pimpl, which is retained (but may be NULL).
|
|
*/
|
|
CMPP_PRIVATE void cmpp__args_reuse(cmpp_args *a);
|
|
|
|
#define cmpp__dx_pimpl_empty_m { \
|
|
.zBegin=0, .zEnd=0, .zErrPos=0,\
|
|
.pos=cmpp_dx_pos_empty_m, \
|
|
.up=0, \
|
|
.dline=CmppDLine_empty_m, \
|
|
.nSavepoint=0, \
|
|
.args = cmpp_args_empty_m, \
|
|
.dxLvl = CmppLvlList_empty_m, \
|
|
.buf = { \
|
|
cmpp_b_empty_m, \
|
|
cmpp_b_empty_m \
|
|
}, \
|
|
.shadow = { \
|
|
.sidFile = 0, \
|
|
.ridInclPath = 0 \
|
|
}, \
|
|
.flags = { \
|
|
.nextIsCall = false \
|
|
} \
|
|
}
|
|
|
|
/**
|
|
A level of indirection for CmppDList in order to be able to
|
|
manage ownership of their name (string) lifetimes.
|
|
*/
|
|
struct CmppDList_entry {
|
|
/** this->d.name.z points to this, which is owned by the CmppDList
|
|
which manages this object. */
|
|
char * zName;
|
|
cmpp_d d;
|
|
};
|
|
typedef struct CmppDList_entry CmppDList_entry;
|
|
#define CmppDList_entry_empty_m {0,cmpp_d_empty_m/*,cmpp_d_reg_empty_m*/}
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp_arg objects. Used by CmmpArgs.
|
|
We use a list of pointers instead of a flat list so that the
|
|
entries are not invalidated by addition of new directives).
|
|
*/
|
|
cmpp__ListType_decl(CmppDList,CmppDList_entry*);
|
|
#define CmppDList_empty_m cmpp__LIST_T_empty_m
|
|
|
|
/**
|
|
State for keeping track of DLL handles, a.k.a. shared-object
|
|
handles, a.k.a. "soh".
|
|
|
|
Instances of this must be either cleanly initialized by bitwise
|
|
copying CmppSohList_empty, memset() (or equivalent) them to 0, or
|
|
allocating them with CmppSohList_new().
|
|
*/
|
|
cmpp__ListType_decl(CmppSohList,void*);
|
|
#define CmppSohList_empty_m cmpp__LIST_T_empty_m
|
|
|
|
|
|
/**
|
|
Operators and operator policies for use with X=Y-format keys. This
|
|
is legacy stuff, actually, but some of the #define management still
|
|
needs it.
|
|
*/
|
|
#define CmppKvp_op_map(E) \
|
|
E(none,"") \
|
|
E(eq1,"=")
|
|
|
|
enum CmppKvp_op_e {
|
|
#define E(N,S) CmppKvp_op_ ## N,
|
|
CmppKvp_op_map(E)
|
|
#undef E
|
|
};
|
|
typedef enum CmppKvp_op_e CmppKvp_op_e;
|
|
|
|
/**
|
|
Result type for CmppKvp_parse().
|
|
*/
|
|
struct CmppKvp {
|
|
/* Key part of the kvp. */
|
|
cmpp_token k;
|
|
/* Key part of the kvp. Might be empty. */
|
|
cmpp_token v;
|
|
/* Operator part of kvp, if any. */
|
|
CmppKvp_op_e op;
|
|
};
|
|
typedef struct CmppKvp CmppKvp;
|
|
extern const CmppKvp CmppKvp_empty;
|
|
|
|
/**
|
|
Parses X or X=Y into p. Sets pp's error state on error.
|
|
|
|
If nKey is negative then strlen() is used to calculate it.
|
|
|
|
The third argument specifies whether/how to permit/treat the '='
|
|
part of X=Y.
|
|
*/
|
|
CMPP_PRIVATE int CmppKvp_parse(cmpp *pp, CmppKvp * p,
|
|
unsigned char const *zKey,
|
|
cmpp_strlen_t nKey,
|
|
CmppKvp_op_e opPolicy);
|
|
|
|
cmpp__PodList_decl(PodList__atpol,cmpp_atpol_e);
|
|
cmpp__PodList_decl(PodList__unpol,cmpp_unpol_e);
|
|
|
|
#define cmpp__epol(PP,WHICH) (PP)->pimpl->policy.WHICH
|
|
#define cmpp__policy(PP,WHICH) \
|
|
cmpp__epol(PP,WHICH).stack[cmpp__epol(PP,WHICH).n]
|
|
|
|
/**
|
|
A "delimiter" object. That is, the "#" referred to in the libcmpp
|
|
docs. It's also outfitted for a second delimiter so that it can be
|
|
used for the opening/closing delimiters of @tokens@.
|
|
*/
|
|
struct cmpp__delim {
|
|
/**
|
|
Bytes of the directive delimiter/prefix or the @token@ opening
|
|
delimiter. Owned elsewhere but often points at this->zOwns.
|
|
*/
|
|
cmpp_token open;
|
|
/**
|
|
Closing @token@ delimiter. This has no meaning for the directive
|
|
delimiter.
|
|
*/
|
|
cmpp_token close;
|
|
/**
|
|
Memory, owned by this object, for this->open and this->close. In
|
|
the latter case, it's one string with both delimiters encoded in
|
|
it.
|
|
*/
|
|
unsigned char * zOwns;
|
|
};
|
|
typedef struct cmpp__delim cmpp__delim;
|
|
#define cmpp__delim_empty_m { \
|
|
.open={ \
|
|
.z=(unsigned char*)CMPP_DEFAULT_DELIM, \
|
|
.n=sizeof(CMPP_DEFAULT_DELIM)-1 \
|
|
}, \
|
|
.close=cmpp_token_empty_m, \
|
|
.zOwns=0 \
|
|
}
|
|
|
|
extern const cmpp__delim cmpp__delim_empty;
|
|
void cmpp__delim_cleanup(cmpp__delim *d);
|
|
|
|
/**
|
|
A dynamically-allocated list of cmpp__delim objects.
|
|
*/
|
|
cmpp__ListType_decl(cmpp__delim_list,cmpp__delim);
|
|
#define cmpp__delim_list_empty_m {0,0,0}
|
|
extern const cmpp__delim_list cmpp__delim_list_empty;
|
|
|
|
CMPP_PRIVATE cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li);
|
|
static inline cmpp__delim * cmpp__delim_list_get(cmpp__delim_list const *li){
|
|
return li->n ? li->list+(li->n-1) : NULL;
|
|
}
|
|
static inline void cmpp__delim_list_pop(cmpp__delim_list *li){
|
|
assert(li->n);
|
|
if( li->n ) cmpp__delim_cleanup(li->list + --li->n);
|
|
}
|
|
static inline void cmpp__delim_list_reuse(cmpp__delim_list *li){
|
|
while( li->n ) cmpp__delim_cleanup(li->list + --li->n);
|
|
}
|
|
|
|
/**
|
|
The main public-API context type for this library.
|
|
*/
|
|
struct cmpp__pimpl {
|
|
/* Internal workhorse. */
|
|
struct {
|
|
sqlite3 * dbh;
|
|
/**
|
|
Optional filename. Memory is owned by this object.
|
|
*/
|
|
char * zName;
|
|
} db;
|
|
/**
|
|
Current directive context. This was historically const
|
|
but that was changed on 2026-08-24 for reasons now stored
|
|
in the SCM history from when this line was added.
|
|
*/
|
|
cmpp_dx * dx;
|
|
/** Output channel. */
|
|
cmpp_outputer out;
|
|
/**
|
|
Delimiters version 2.
|
|
*/
|
|
struct {
|
|
/**
|
|
Directive delimiter.
|
|
*/
|
|
cmpp__delim_list d;
|
|
/**
|
|
@token@ delimiters.
|
|
*/
|
|
cmpp__delim_list at;
|
|
} delim;
|
|
|
|
/** All frequently-reused queries. */
|
|
struct {
|
|
|
|
#define CMPP__SEL_V_FROM(N) \
|
|
"(SELECT v FROM " CMPP__DB_MAIN_NAME ".vdef WHERE k=?" #N \
|
|
" ORDER BY source LIMIT 1)"
|
|
|
|
/** Name of library-generated savepoints. */
|
|
#define CMPP__SAVEPOINT_NAME "_cmpp_"
|
|
|
|
/**
|
|
One entry for each distinct query used by cmpp: E(X,SQL), where
|
|
X is the member's name and SQL is its SQL.
|
|
*/
|
|
#define CmppStmt_map(E) \
|
|
E(sdefIns, \
|
|
"INSERT INTO " \
|
|
CMPP__DB_MAIN_NAME ".sdef" \
|
|
"(t,k,v) VALUES(?1,?2,?3) RETURNING id") \
|
|
E(defIns, \
|
|
"INSERT INTO " \
|
|
CMPP__DB_MAIN_NAME ".def" \
|
|
"(t,k,v) VALUES(?1,?2,?3) " \
|
|
"ON CONFLICT(k) DO UPDATE SET v=EXCLUDED.v" \
|
|
) \
|
|
E(defDelLegacy, \
|
|
"DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".def" \
|
|
" WHERE k GLOB ?1") \
|
|
E(defDelExact, \
|
|
"DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".def" \
|
|
" WHERE k = ?1") \
|
|
E(sdefDel, \
|
|
"DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".sdef" \
|
|
" WHERE k=?1 AND id>=?2") \
|
|
E(defHas, \
|
|
"SELECT 1 FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1") \
|
|
E(defGet, \
|
|
"SELECT source,t,k,v FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1 ORDER BY source LIMIT 1") \
|
|
E(defGetBool, \
|
|
"SELECT cmpp_truthy(v) FROM " \
|
|
CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1" \
|
|
" ORDER BY source LIMIT 1") \
|
|
E(defGetInt, \
|
|
"SELECT CAST(v AS INTEGER)" \
|
|
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
|
|
" WHERE k = ?1" \
|
|
" ORDER BY source LIMIT 1") \
|
|
E(defSelAll, "SELECT t,k,v" \
|
|
" FROM " CMPP__DB_MAIN_NAME ".vdef" \
|
|
" ORDER BY source, k") \
|
|
E(inclIns," INSERT OR FAIL INTO " \
|
|
CMPP__DB_MAIN_NAME ".incl(" \
|
|
" file,srcFile, srcLine" \
|
|
") VALUES(?,?,?)") \
|
|
E(inclDel, "DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
|
|
E(inclHas, "SELECT 1 FROM " \
|
|
CMPP__DB_MAIN_NAME ".incl WHERE file=?") \
|
|
E(inclPathAdd, "INSERT INTO " \
|
|
CMPP__DB_MAIN_NAME ".inclpath(priority,dir) " \
|
|
"VALUES(coalesce(?1,0),?2) " \
|
|
"ON CONFLICT DO NOTHING " \
|
|
"RETURNING rowid /*xlates to 0 on conflict*/") \
|
|
E(inclPathRmId, "DELETE FROM " \
|
|
CMPP__DB_MAIN_NAME ".inclpath WHERE rowid=?1 " \
|
|
"RETURNING rowid") \
|
|
E(inclSearch, \
|
|
"SELECT ?1 fn WHERE cmpp_file_exists(fn) " \
|
|
"UNION ALL SELECT fn FROM (" \
|
|
" SELECT replace(dir||'/'||?1, '//','/') AS fn " \
|
|
" FROM " CMPP__DB_MAIN_NAME ".inclpath" \
|
|
" WHERE cmpp_file_exists(fn) " \
|
|
" ORDER BY priority DESC, rowid LIMIT 1" \
|
|
")") \
|
|
/* Compare two values. */ \
|
|
E(cmpVV, "SELECT cmpp_compare(?1,?2)") \
|
|
/* Compare LHS define to value ?2. */ \
|
|
E(cmpDV, \
|
|
"SELECT cmpp_compare(" \
|
|
CMPP__SEL_V_FROM(1) ", ?2" \
|
|
")") \
|
|
/* Compare value ?1 to RHS define. */ \
|
|
E(cmpVD, \
|
|
"SELECT cmpp_compare(" \
|
|
"?1," CMPP__SEL_V_FROM(2) \
|
|
")") \
|
|
/* Compare defines ?1 and ?2. */ \
|
|
E(cmpDD, \
|
|
"SELECT cmpp_compare(" \
|
|
CMPP__SEL_V_FROM(1) \
|
|
"," \
|
|
CMPP__SEL_V_FROM(2) \
|
|
")") \
|
|
E(dbAttach, \
|
|
"ATTACH ?1 AS ?2") \
|
|
E(dbDetach, \
|
|
"DETACH ?1") \
|
|
E(spBegin, "SAVEPOINT " CMPP__SAVEPOINT_NAME) \
|
|
E(spRollback, \
|
|
"ROLLBACK TO SAVEPOINT " CMPP__SAVEPOINT_NAME) \
|
|
E(spRelease, \
|
|
"RELEASE SAVEPOINT " CMPP__SAVEPOINT_NAME) \
|
|
/*E(insTtype, \
|
|
"INSERT INTO " CMPP__DB_MAIN_NAME ".ttype" \
|
|
"(t,n,s) VALUES(?1,?2,?3)")*/ \
|
|
E(selPathSearch, \
|
|
/*sqlite.org/forum/forumpost/840c98a8e87c2207*/ \
|
|
"WITH path(basename, sep, ext, path) AS (\n" \
|
|
" select\n" \
|
|
" ?1 basename,\n" \
|
|
" ?2 sep,\n" \
|
|
" ?3 ext,\n" \
|
|
" ?4 path\n" \
|
|
"),\n" \
|
|
"pathsplit(i, l, c, r) AS (\n" \
|
|
"-- i = sequential ID\n" \
|
|
"-- l = Length remaining\n" \
|
|
"-- c = text remaining\n" \
|
|
"-- r = current unpacked value\n" \
|
|
" SELECT 1,\n" \
|
|
" length(p.path)+length(p.sep),\n" \
|
|
" p.path||p.sep, ''\n" \
|
|
" FROM path p\n" \
|
|
" UNION ALL\n" \
|
|
" SELECT i+1, instr( c, p.sep ) l,\n" \
|
|
" substr( c, instr( c, p.sep ) + 1) c,\n" \
|
|
" trim( substr( c, 1,\n" \
|
|
" instr( c, p.sep) - 1) ) r\n" \
|
|
" FROM pathsplit, path p\n" \
|
|
" WHERE l > 0\n" \
|
|
"),\n" \
|
|
"thefile (f) AS (\n" \
|
|
" select basename f FROM path\n" \
|
|
" union all\n" \
|
|
" select basename||ext\n" \
|
|
" from path where ext is not null\n" \
|
|
")\n" \
|
|
"select 0 i, replace(f,'//','/') AS fn\n" \
|
|
"from thefile where cmpp_file_exists(fn)\n" \
|
|
"union all\n" \
|
|
"select i, replace(r||'/'||f,'//','/') fn\n" \
|
|
"from pathsplit, thefile\n" \
|
|
"where r<>'' and cmpp_file_exists(fn)\n" \
|
|
"order by i\n" \
|
|
"limit 1;") \
|
|
E(jsonValid, \
|
|
/* beware of validation of json_valid() ?2 */ \
|
|
"SELECT json_valid(?1,?2)") \
|
|
|
|
#define E(N,S) sqlite3_stmt * N;
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
|
|
} stmt;
|
|
|
|
cmpp_errinfo err;
|
|
|
|
/** State for SQL tracing. */
|
|
struct {
|
|
bool expandSql;
|
|
cmpp_size_t counter;
|
|
cmpp_outputer out;
|
|
} sqlTrace;
|
|
|
|
struct {
|
|
/**
|
|
How many dirs we believe are in the #include search list. We
|
|
only do this for the sake of the historical "if no path was
|
|
added, assume '.'" behavior. This really ought to go away.
|
|
*/
|
|
unsigned nIncludeDir;
|
|
/**
|
|
The current depth of cmpp_process_string() calls. We do this so
|
|
the directory part of #include'd files can get added to the
|
|
#include path and be given a higher priority than previous
|
|
include path entries in the stack.
|
|
*/
|
|
int nDxDepth;
|
|
/* Number of active #savepoints. */
|
|
unsigned nSavepoint;
|
|
/* If >0, enables certain debugging output. */
|
|
char doDebug;
|
|
/* If true, chomp() files read via -Fx=file. */
|
|
unsigned char chompF;
|
|
/* Flags passed to cmpp_ctor(). */
|
|
cmpp_flag32_t ctorFlags;
|
|
|
|
/**
|
|
An ugly hack for getting cmpp_d_register() to get
|
|
syntactically-illegal directive names, like "@policy",
|
|
to register.
|
|
*/
|
|
bool isInternalDirectiveReg;
|
|
/**
|
|
True if the next directive is the start of a [call]. This is
|
|
used for:
|
|
|
|
1) To set cmpp_dx::isCall, which is useful in certain
|
|
directives.
|
|
|
|
2) So that the cmpp_dx object created for the call can inherit
|
|
the line number from its parent context. That's significant
|
|
for error reporting.
|
|
|
|
3) So that #2's cmpp_dx object can communicate that flag to
|
|
cmpp_dx_next().
|
|
*/
|
|
bool nextIsCall;
|
|
/**
|
|
Set to true by cmpp_call_string() if the being-called string
|
|
does not start with a directive delimiter. This tells
|
|
cmpp_next_chunk() to assume that it found a delimiter and
|
|
immediately treat the rest of the line as a directive line.
|
|
*/
|
|
bool fakeNextDelim;
|
|
/**
|
|
True until the cmpp's (sometimes) lazy init has been run. This
|
|
is essentially a kludge to work around a wrench cmpp_reset()
|
|
throws into cmpp state. Maybe we should just remove
|
|
cmpp_reset() from the interface, since error recovery in this
|
|
context is not really a thing.
|
|
*/
|
|
bool needsLazyInit;
|
|
} flags;
|
|
|
|
/** Policies. */
|
|
struct {
|
|
/** @token@-parsing policy. */
|
|
PodList__atpol at;
|
|
/** Policy towards referencing undefined symbols. */
|
|
PodList__unpol un;
|
|
} policy;
|
|
|
|
/**
|
|
Directive state.
|
|
*/
|
|
struct {
|
|
/**
|
|
Runtime-installed directives.
|
|
*/
|
|
CmppDList list;
|
|
/**
|
|
Directive autoloader/auto-registerer.
|
|
*/
|
|
cmpp_d_autoloader autoload;
|
|
} d;
|
|
|
|
/**
|
|
Module state. Maintenance reminder: filtering this out in
|
|
non-module-capable builds is breaking stuff.
|
|
*/
|
|
struct {
|
|
/**
|
|
List of DLL handles opened by cmpp_module_extract().
|
|
*/
|
|
CmppSohList sohList;
|
|
/**
|
|
Search path for DLLs, delimited by this->pathSep.
|
|
*/
|
|
cmpp_b path;
|
|
/**
|
|
File extension for DLLs.
|
|
*/
|
|
char const * soExt;
|
|
/**
|
|
Separator char for this->path. Must be a non-control character.
|
|
*/
|
|
char pathSep;
|
|
} mod;
|
|
|
|
struct {
|
|
/**
|
|
Buffer cache. Managed by cmpp_b_borrow() and cmpp_b_return().
|
|
*/
|
|
cmpp_b_library buf;
|
|
/**
|
|
Head of the free-list.
|
|
*/
|
|
cmpp_args_pimpl * argPimpl;
|
|
} recycler;
|
|
};
|
|
|
|
/** Distinct IDs for each cmpp::stmt member. */
|
|
enum CmppStmt_e {
|
|
CmppStmt_none = 0,
|
|
#define E(N,S) CmppStmt_ ## N,
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
};
|
|
|
|
static inline cmpp__delim * cmpp__pp_delim(cmpp const *pp){
|
|
return cmpp__delim_list_get(&pp->pimpl->delim.d);
|
|
}
|
|
static inline char const * cmpp__pp_zdelim(cmpp const *pp){
|
|
cmpp__delim const * const d = cmpp__pp_delim(pp);
|
|
return d ? (char const *)d->open.z : NULL;
|
|
}
|
|
#define cmpp__dx_delim(DX) cmpp__pp_delim(DX->pp)
|
|
#define cmpp__dx_zdelim(DX) cmpp__pp_zdelim(DX->pp)
|
|
|
|
/**
|
|
Emit [z,(char*)z+n) to the given output channel if
|
|
(A) pOut->out is not NULL and (B) pp has no error state and (C)
|
|
n>0. On error, pp's error state is updated. Returns pp->err.code.
|
|
|
|
Skip level is not honored.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__out2(cmpp *pp, cmpp_outputer *pOut, void const *z, cmpp_size_t n);
|
|
|
|
static inline void cmpp__err_reuse(cmpp *pp){
|
|
cmpp_errinfo_reuse(&pp->pimpl->err);
|
|
}
|
|
|
|
static inline bool cmpp__is_safemode(cmpp const * const pp){
|
|
return (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags);
|
|
}
|
|
|
|
/**
|
|
Initialize pp->db.dbh. If it's already open or ppCode!=0 then
|
|
ppCode is returned.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__db_init(cmpp *pp);
|
|
|
|
/**
|
|
Returns the pp->pimpl->stmt.X corresponding to `which`, initializing
|
|
it if needed. If it returns NULL then either this was called when pp
|
|
has its error state set or this function will have set the error
|
|
state.
|
|
|
|
These queries MUST NOT be used in contexts where they may be reached
|
|
concurrently via recursion. The caller is obligated to
|
|
cmpp_stmt_reset() (or equivalent) the statement when they're done
|
|
and NOT to sqlite3_finalize() it.
|
|
|
|
If prepEvenIfErr is true then the ppCode check is bypassed, but it
|
|
will still fail if pp->pimpl->db is not opened or if the preparation
|
|
itself fails.
|
|
*/
|
|
CMPP_PRIVATE sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
|
|
bool prepEvenIfErr);
|
|
/**
|
|
Adds zDir to the include path, using the given priority value (use
|
|
0 except for the implicit cwd path which #include should (but does
|
|
not yet) set). If pRowid is not NULL then *pRowid gets set to
|
|
either 0 (if zDir was already in the path) or the row id of the
|
|
newly-inserted record, which can later be used to delete just that
|
|
entry.
|
|
|
|
If this returns a non-zero value via pRowid, the caller is
|
|
obligated to eventually pass *pRowid to cmpp__include_dir_rm_id(),
|
|
even if pp is in an error state.
|
|
|
|
TODO: normalize zDir (at least remove trailing slashes) before
|
|
insertion to avoid that both a/b and a/b/ get be inserted.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority, int64_t * pRowid);
|
|
|
|
/**
|
|
Deletes the include path entry with the given rowid. This will make
|
|
make the attempt even if pp is in an error state but also retains
|
|
any existing error rather than overwriting it if this operation
|
|
somehow fails. Returns pp's error code.
|
|
|
|
It is not an error for the given entry to not exist.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__include_dir_rm_id(cmpp *pp, int64_t pRowid);
|
|
|
|
/**
|
|
Returns a resolved path of PREFIX+'/'+zKey, where PREFIX is one of
|
|
the `#include` dirs (cmpp_include_dir_add()). If no file match is
|
|
found, NULL is returned. Memory must eventually be passed to
|
|
cmpp_mfree() to free it. If nVal is not NULL then it is set to the
|
|
byte length of the returned string, or 0 on error or if no result
|
|
is found.
|
|
*/
|
|
CMPP_PRIVATE char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
|
|
cmpp_size_t * nVal);
|
|
|
|
/**
|
|
Searches pp's list of directives. If found, return it else return
|
|
NULL. See cmpp__d_search3().
|
|
*/
|
|
CMPP_PRIVATE cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName);
|
|
|
|
/**
|
|
Flags for use with the final argument to
|
|
cmpp__d_search3().
|
|
*/
|
|
enum cmpp__d_search3_e {
|
|
/** Internal delayed-registered directives. */
|
|
cmpp__d_search3_F_DELAYED = 0x01,
|
|
/** Directive autoloader. */
|
|
cmpp__d_search3_F_AUTOLOADER = 0x02,
|
|
/** As a fallback, search for a DLL if built with that support and
|
|
not in safe-mode. */
|
|
cmpp__d_search3_F_DLL = 0x04,
|
|
/** Options which do not trigger DLL lookup. */
|
|
cmpp__d_search3_F_NO_DLL = 0
|
|
| cmpp__d_search3_F_DELAYED
|
|
| cmpp__d_search3_F_AUTOLOADER,
|
|
/** All options. */
|
|
cmpp__d_search3_F_ALL = 0
|
|
| cmpp__d_search3_F_DELAYED
|
|
| cmpp__d_search3_F_AUTOLOADER
|
|
| cmpp__d_search3_F_DLL
|
|
};
|
|
|
|
/**
|
|
Like cmpp__d_search() but if no match is found then it will search
|
|
through its other options and, if found, register it.
|
|
|
|
The final argument specifies where to search. cmpp__d_search()
|
|
always checked first. After that, depending on "what", the search
|
|
order is: (1) internal delayed-load modules, (2) autoloader, (3)
|
|
DLL.
|
|
|
|
This may update pp's error state, in which case it will return
|
|
NULL.
|
|
*/
|
|
CMPP_PRIVATE cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
|
|
cmpp_flag32_t what);
|
|
|
|
/**
|
|
Sets pp's error state (A) if it's not set already and (B) if
|
|
!cmpp_is_legal_key(zKey). If permitEqualSign is true then '=' is
|
|
legal (to support legacy CLI pieces). Returns ppCode.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_strlen_t nKey,
|
|
bool permitEqualSign);
|
|
|
|
|
|
/**
|
|
Helper for #query and friends. Binds aVal's value to column bindNdx
|
|
of q.
|
|
|
|
It expands cmpp_TT_Word aVal. cmpp_TT_String* and cmpp_TT_Int* are
|
|
bound as strings. A cmpp_TT_GroupParen aVal is eval'ed as an
|
|
integer and that int gets bound as a string.
|
|
|
|
This function strictly binds everything as strings, even if the
|
|
value being bound is of type cmpp_TT_Int or cmpp_TT_GroupParen, so that
|
|
comparison queries will work as expected.
|
|
|
|
Returns ppCode.
|
|
|
|
TODO (2026-09-24): add [call] support to this, or a flags arg to
|
|
specify how to interpolate it, and move it to the public API.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__bind_arg(cmpp *pp, sqlite3_stmt * q,
|
|
int bindNdx, cmpp_arg const * aVal);
|
|
|
|
/**
|
|
Helper for #query's bind X part, where aGroup is that X.
|
|
|
|
A wrapper around cmpp__bind_arg(). Requires aGroup->ttype to be
|
|
either cmpp_TT_GroupBrace or cmpp_TT_GroupSquiggly and to have
|
|
non-empty content. cmpp_TT_GroupBrace treats it as a list of values
|
|
to bind. cmpp_TT_GroupSquiggly expects sets of 3 tokens per stmt
|
|
column in one of these forms:
|
|
|
|
:bindName -> value
|
|
$bindName -> value
|
|
|
|
Each LHS refers to a same-named binding in q's SQL, including the
|
|
':' or '$' prefix. (SQLite supports an '@' prefix but we did not
|
|
historically allow it here to avoid confusion with the now-defunct
|
|
cmpp_TT_StringAt tokens.)
|
|
|
|
Each bound value is passed to cmpp__bind_arg() for processing.
|
|
|
|
On success, each aGroup entry is bound to q. On error q's state is
|
|
unspecified. Returns ppCode.
|
|
|
|
See cmpp__bind_arg() for notes about the bind data type.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__bind_group(cmpp *pp, sqlite3_stmt * const q,
|
|
cmpp_arg const * const aGroup);
|
|
|
|
/**
|
|
Returns true if the given key is already in the `#define` list,
|
|
else false. Sets pp's error state on db error.
|
|
|
|
nName is the length of the key part of zName (which might have a
|
|
following =y part (TODO 2026-09-21: get rid of that - we no longer
|
|
rely on it). If it's negative, strlen() is used to calculate it.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_t nName);
|
|
|
|
/**
|
|
Returns true if the given key is already in the `#define` list, and
|
|
it has a truthy value (is not empty and not equal to '0'), else
|
|
false. If zName contains an '=' then only the part preceding that
|
|
is used as the key.
|
|
|
|
nName is the length of zName, or <0 to use strlen() to figure
|
|
it out.
|
|
|
|
Updates ppCode on error.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__get_bool(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName);
|
|
|
|
/**
|
|
Fetches the given define. If found, sets *pOut to it, else pOut is
|
|
unmodified. Returns pp->err.code. If bRequire is true and no entry
|
|
is found p->err.code is updated.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__get_int(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, int *pOut);
|
|
|
|
#if 0
|
|
/**
|
|
Searches for a define where (k == zName). If one is found, a copy
|
|
of it is assigned to *zVal (the caller must eventually db_free()
|
|
it), *nVal (if nVal is not NULL) is assigned its strlen, and
|
|
returns non-0. If no match is found, 0 is returned and neither
|
|
*zVal nor *nVal are modified. If more than one result matches, a
|
|
fatal error is triggered.
|
|
|
|
It is legal for *zVal to be NULL (and *nVal to be 0) if it returns
|
|
non-0. That just means that the key was defined with no value part.
|
|
|
|
Fixme: return 0 on success and set output *gotOne=0|1.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__get(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName,
|
|
unsigned char **zVal, unsigned int *nVal);
|
|
#endif
|
|
|
|
/**
|
|
Searches for a define where (k == zName). If one is found, a copy
|
|
of it is appended to b. Returns true if a record is found, else
|
|
false.
|
|
|
|
If enforceUndefPolicy is true, no match is found, and the
|
|
undefined-policy specifies to, pp's error state is set to reflect
|
|
the unresolved key.
|
|
|
|
Wonky return semantics.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__get_b(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, cmpp_b * b,
|
|
bool enforceUndefPolicy);
|
|
|
|
#if 0
|
|
/**
|
|
Like cmpp__get_b() but with more conventional return semantics.
|
|
|
|
*gotOne is set to true if a record is found, else false. Returns
|
|
pp's current error code and does not run the query if pp has error
|
|
state.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__get_b2(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, cmpp_b * b,
|
|
bool enforceUndefPolicy,
|
|
bool *gotOne);
|
|
#endif
|
|
|
|
/**
|
|
Helper for #query and friends.
|
|
|
|
It expects that q has just been stepped. For each column in the
|
|
row, it sets a define named after the column. If q has row data
|
|
then the values come from there. If q has no row then: if
|
|
defineIfNoRow is true then it defines each column name to an empty
|
|
value else it defines nothing.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
|
|
bool defineIfNoRow,
|
|
cmpp_b * optionalCaptureBuffer);
|
|
|
|
/** Start a new savepoint for dx. */
|
|
CMPP_PRIVATE int cmpp__dx_sp_begin(cmpp_dx * const dx);
|
|
/** Commit and close dx's current savepoint. */
|
|
CMPP_PRIVATE int cmpp__dx_sp_commit(cmpp_dx * const dx);
|
|
/** Roll back and close dx's current savepoint. */
|
|
CMPP_PRIVATE int cmpp__dx_sp_rollback(cmpp_dx * const dx);
|
|
|
|
/**
|
|
Append's dx's file/line information to sstr. It returns void
|
|
because that's how sqlite3_str_appendf() and friends work.
|
|
*/
|
|
CMPP_PRIVATE void cmpp__dx_append_script_info(cmpp_dx const * dx,
|
|
sqlite3_str * sstr);
|
|
|
|
/**
|
|
If zName matches one of the delayed-load directives, that directive
|
|
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
|
|
no match is found, but pp's error state is not updated in that
|
|
case. If a match is found and registration fails, that result code
|
|
will propagate via pp.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__d_delayed_load(cmpp *pp, char const *zName);
|
|
|
|
CMPP_PRIVATE void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent);
|
|
|
|
/**
|
|
For each character in ('(' '{' '[' '"' '\'' '`') return
|
|
a corresponding closing character (which will be the same as
|
|
the opener for the string types).
|
|
*/
|
|
static inline unsigned char cmpp__closer(unsigned char opener){
|
|
switch(opener){
|
|
case '(': return ')';
|
|
case '[': return ']';
|
|
case '{': return '}';
|
|
//case '<': return '>';
|
|
case '"': case '\'': case '`': return opener;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Expects **zPos to be value valid for cmpp__closer() and zEnd
|
|
to be the logical EOF for *zPos.
|
|
|
|
This looks for a matching closing token, accounting for nesting. On
|
|
success, returns 0 and sets *zPos to the closing character.
|
|
|
|
It does not honor backslash-escaping of the closing token within
|
|
the content, nor does it ensure that other grouping tokens found
|
|
between its opening and closing bytes are balanced. Similarly, it
|
|
does not recognize quoted strings in the content _as such_ so
|
|
inputs like "{ '}'}" will misbehave.
|
|
|
|
On error it update's pp's error state and returns that code. pp may
|
|
be NULL.
|
|
|
|
If pNl is not NULL then *pNl is incremented for each '\n' character
|
|
seen while looking for the closing character.
|
|
|
|
Potential TODO (2026-08-20): make this routine recursive so that it
|
|
can process, at a minimum, quoted strings within non-quoted-string
|
|
inputs.
|
|
*/
|
|
#define CMPP__FIND_CLOSING 3
|
|
#if 2 == CMPP__FIND_CLOSING
|
|
CMPP_PRIVATE int cmpp__find_closing2(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t *pNl);
|
|
#define cmpp__find_closing(PP,Z0,Z1) \
|
|
cmpp__find_closing2(PP, Z0, Z1, NULL)
|
|
#define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \
|
|
cmpp__find_closing2(PP, Z0, Z1, LCPTR)
|
|
#else
|
|
/**
|
|
Works just like cmpp__find_closing2() but honors backslash
|
|
escaping in that it treats all backslash-escaped content as
|
|
escaped, not as a closer.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__find_closing3(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t *pNl);
|
|
#define cmpp__find_closing(PP,Z0,Z1) \
|
|
cmpp__find_closing3(PP, Z0, Z1, NULL)
|
|
#define cmpp__find_closing_lc(PP,Z0,Z1,LCPTR) \
|
|
cmpp__find_closing3(PP, Z0, Z1, LCPTR)
|
|
#endif
|
|
|
|
|
|
static inline cmpp_size_t cmpp__strlen(char const *z, cmpp_strlen_t n){
|
|
return n<0 ? (cmpp_size_t)strlen(z) : (cmpp_size_t)n;
|
|
}
|
|
static inline cmpp_size_t cmpp__strlenu(unsigned char const *z, cmpp_strlen_t n){
|
|
return n<0 ? (cmpp_size_t)strlen((char const *)z) : (cmpp_size_t)n;
|
|
}
|
|
|
|
/**
|
|
"define"s zKey to zVal, recording the value type as tType.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__define2(cmpp *pp,
|
|
unsigned char const * zKey,
|
|
cmpp_strlen_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_strlen_t nVal,
|
|
cmpp_tt_e tType);
|
|
|
|
/**
|
|
Evals pArgs's arguments as an integer expression. On success, sets
|
|
*pResult to the value.
|
|
|
|
Returns ppCode.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__args_evalToInt(cmpp *pp, cmpp_args const *pArgs,
|
|
int * pResult);
|
|
|
|
/** Passes the contents of arg through to cmpp__args_evalToInt(). */
|
|
CMPP_PRIVATE int cmpp__arg_evalSubToInt(cmpp *pp, cmpp_arg const *arg,
|
|
int * pResult);
|
|
|
|
/**
|
|
Evaluated arg as an integer/bool, placing the result in *pResult
|
|
and setting *pNext to the first argument to arg's right which this
|
|
routine did not consume. Non-0 on error and all that.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__arg_toBool(cmpp * pp, cmpp_arg const *arg,
|
|
int * pResult, cmpp_arg const **pNext);
|
|
|
|
typedef struct cmpp_argOp cmpp_argOp;
|
|
typedef void (*cmpp_argOp_f)(cmpp *pp,
|
|
cmpp_argOp const *op,
|
|
cmpp_arg const *vLhs,
|
|
cmpp_arg const **pvRhs,
|
|
int *pResult);
|
|
struct cmpp_argOp {
|
|
int ttype;
|
|
/* 1 or 2 */
|
|
unsigned char arity;
|
|
/* 0=none/left, 1=right (unary ops only) */
|
|
signed char assoc;
|
|
cmpp_argOp_f xCall;
|
|
};
|
|
|
|
CMPP_PRIVATE cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt_e tt);
|
|
|
|
|
|
|
|
CMPP_PRIVATE char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p);
|
|
//CMPP_PRIVATE char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p);
|
|
|
|
/**
|
|
Uncerimoniously bitwise-replaces pp's output channel with oNew. It
|
|
does _not_ clean up the previous channel, on the assumption that
|
|
the caller is taking any necessary measures.
|
|
|
|
Apropos necessary measures for cleanup: if oPrev is not NULL,
|
|
*oPrev is set to a bitwise copy of the previous channel.
|
|
|
|
Intended usage:
|
|
|
|
```
|
|
cmpp_outputer oMine = cmpp_outputer_b;
|
|
cmpp_b bMine = cmpp_b_empty;
|
|
cmpp_outputer oOld = {0};
|
|
oMine.state = &bMine;
|
|
cmpp__outputer_swap(pp, &oMine, &oOld);
|
|
...do some work then ALWAYS do...
|
|
cmpp__outputer_swap(pp, &oOld, NULL);
|
|
```
|
|
|
|
Because this involves bitwise copying, care must be taken with
|
|
stream state, e.g. bMine.z (above) can be reallocated, so we have
|
|
to be sure to swap it back before using bMine again.
|
|
*/
|
|
CMPP_PRIVATE void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
|
|
cmpp_outputer *oPrev);
|
|
|
|
/**
|
|
Init code which is usually run as part of the ctor but may have to
|
|
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
|
|
because that could leave post-reset in an error state, which is
|
|
icky. This call is a no-op after the first.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__lazy_init(cmpp *pp);
|
|
|
|
|
|
/* Fatally exits the app with the given printf-style message. */
|
|
CMPP_NORETURN CMPP_PRIVATE void cmpp__fatalv_base(char const *zFile, int line,
|
|
char const *zFmt, va_list);
|
|
#define cmpp__fatalv(...) cmpp__fatalv_base(__FILE__,__LINE__,__VA_ARGS__)
|
|
CMPP_NORETURN CMPP_PRIVATE void cmpp__fatal_base(char const *zFile, int line,
|
|
char const *zFmt, ...);
|
|
#define cmpp__fatal(...) cmpp__fatal_base(__FILE__,__LINE__,__VA_ARGS__)
|
|
|
|
/**
|
|
Outputs a printf()-formatted message to stderr.
|
|
*/
|
|
CMPP_PRIVATE void g_stderr(char const *zFmt, ...);
|
|
#define g_warn(zFmt,...) g_stderr("%s:%d %s() " zFmt "\n", __FILE__, __LINE__, __func__, __VA_ARGS__)
|
|
#define g_warn0(zMsg) g_stderr("%s:%d %s() %s\n", __FILE__, __LINE__, __func__, zMsg)
|
|
#if 0
|
|
#define g_debug(PP,lvl,pfexpr) (void)0
|
|
#else
|
|
#define g_debug(PP,lvl,pfexpr) \
|
|
if(lvl<=(PP)->pimpl->flags.doDebug) { \
|
|
if( (PP)->pimpl->dx ){ \
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": ", \
|
|
(PP)->pimpl->dx->sourceName, \
|
|
(PP)->pimpl->dx->pimpl->dline.lineNo); \
|
|
} \
|
|
g_stderr("%s():%d: ", \
|
|
__func__,__LINE__); \
|
|
g_stderr pfexpr; \
|
|
} (void)0
|
|
#endif
|
|
|
|
/** Returns true if zFile is readable, else false. */
|
|
CMPP_PRIVATE bool cmpp__file_is_readable(char const *zFile);
|
|
|
|
#define ustr_c(X) ((unsigned char const *)X)
|
|
#define ustr_nc(X) ((unsigned char *)X)
|
|
#define ppCode pp->pimpl->err.code
|
|
#define dxppCode dx->ppCode
|
|
#define itchppCode itch->ppCode
|
|
#define cmpp__pi(PP) cmpp__pimpl * const pi = PP->pimpl
|
|
#define cmpp__dx_pi(DX) cmpp__dx_pimpl * const dpi = DX->pimpl
|
|
#define serr(...) cmpp_errf(pp, CMPP_RC_SYNTAX, __VA_ARGS__)
|
|
#define dxserr(...) cmpp_errf(dx->pp, CMPP_RC_SYNTAX, __VA_ARGS__)
|
|
#define cmpp__li_reserve1_size(li,nInitial) \
|
|
(li->n ? (li->n==li->na ? li->na * 3 / 2 : li->n+1) : nInitial)
|
|
|
|
#define MARKER(pfexp) \
|
|
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
|
|
printf pfexp; \
|
|
} while(0)
|
|
|
|
//CMPP__EXPORT(int, cmpp_isnl)(char const * z, char const *zEnd){}
|
|
static inline int cmpp__isnl(unsigned char const * z, unsigned char const *zEnd){
|
|
//assert(z<zEnd && "Caller-level pointer mis-traversal");
|
|
switch( z<zEnd ? *z : 0 ){
|
|
case 0: return 0;
|
|
case '\r': return ((z+1<zEnd) && '\n'==z[1]) ? 2 : 0;
|
|
default: return '\n'==*z;
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
static inline bool cmpp__issnl(int ch){
|
|
/* TODO: replace this in line with cmpp__isnl(), but it needs
|
|
a new interface for that. It's only used in two places and they
|
|
have different traversal directions, so we can probably
|
|
get rid of this and do the direct CRNL check in each of those
|
|
places. */
|
|
return ' '==ch || '\t'==ch || '\n'==ch;
|
|
}
|
|
#endif
|
|
|
|
static inline sqlite3_str * cmpp__sqlite3_str_new(cmpp*pp){
|
|
sqlite3_str * const s = sqlite3_str_new(pp ? pp->pimpl->db.dbh : 0);
|
|
return sqlite3_str_errcode(s)
|
|
? (
|
|
cmpp_check_oom(pp,NULL), NULL
|
|
/* s is a static singleton in this case, not leaked */
|
|
)
|
|
: s;
|
|
}
|
|
|
|
/**
|
|
Returns true if z starts with "::" not immediately followed by a
|
|
NUL.
|
|
*/
|
|
static inline bool cmpp__has_ns_prefix(unsigned char const *z){
|
|
return (unsigned char)':'==z[0] && (unsigned char)':'==z[1] && 0!=z[2];
|
|
}
|
|
|
|
CMPP_PRIVATE int cmpp__tt_for_group_char(int ch);
|
|
/**
|
|
Like cmpp_tt_cstr(), but if bSymbolName is false then it returns
|
|
the higher-level token name, which is NULL for most token types.
|
|
*/
|
|
CMPP_PRIVATE char const * cmpp__tt_cstr2(int tt, bool bSymbolName);
|
|
|
|
|
|
/**
|
|
Given UTF8 character value c, this calculates its length, in bytes,
|
|
writes the UTF-8 encoding to output, and returns the number of
|
|
bytes written. If the calculated size is >length then -1 is
|
|
returned (which cannot happen if length>=4). If !output then only
|
|
the UTF8 length of c is calculated and returned (and the length
|
|
argument is ignored).
|
|
|
|
Returns -1 if c is not a valid UTF8 character. The most bytes it
|
|
will ever write to *output is four, so an output buffer of four
|
|
bytes is sufficient for all encoding cases.
|
|
|
|
It does not NUL-terminate the output unless the character is
|
|
incidentally a NUL byte. That is the only case in which the output
|
|
will be NUL-terminated.
|
|
|
|
License note: the above docs and the corresponding code were pulled
|
|
into this tree by the same person who wrote them for the upstream
|
|
copy.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__utf8_char_to_cstr(unsigned int c,
|
|
unsigned char *output,
|
|
cmpp_size_t length);
|
|
|
|
/**
|
|
For unescaping \uNNNN and \UNNNNNNNN sequences. Requires zPos to
|
|
point to at least howMany bytes of hex digits and must be a
|
|
multiple of 2. On success, sets *rv to the decoded hex value and
|
|
returns the number of bytes consumed. On error, returns -1.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__read_hex_bytes(unsigned char const *zPos,
|
|
unsigned int howMany,
|
|
unsigned int * rv );
|
|
|
|
/**
|
|
Examines [z,z+n), expected to be a freshly-parsed token, to try to
|
|
determine a more precise token type, which it returns.
|
|
*/
|
|
CMPP_PRIVATE int cmpp__tt_tok_op(unsigned char const * z, uint8_t n);
|
|
#define cmpp__dx_current(PP) (PP)->pimpl->dx
|
|
|
|
/**
|
|
Empty-initialized cmpp_outputer instance, intended for
|
|
non-const-copy initialization.
|
|
*/
|
|
extern const cmpp_arg cmpp_arg_empty;
|
|
|
|
|
|
#endif /* include guard */
|
|
|
|
char const * cmpp_version(void){ return CMPP_VERSION; }
|
|
|
|
const cmpp_errinfo cmpp_errinfo_empty = cmpp_errinfo_empty_m;
|
|
const cmpp__delim cmpp__delim_empty = cmpp__delim_empty_m;
|
|
const cmpp__delim_list cmpp__delim_list_empty = cmpp__delim_list_empty_m;
|
|
const cmpp_outputer cmpp_outputer_empty = cmpp_outputer_empty_m;
|
|
const cmpp_outputer cmpp_outputer_FILE = {
|
|
.out = cmpp_output_f_FILE,
|
|
.flush = cmpp_flush_f_FILE,
|
|
.cleanup = cmpp_outputer_cleanup_f_FILE,
|
|
.state = NULL
|
|
};
|
|
const cmpp_b_list cmpp_b_list_empty =
|
|
cmpp_b_list_empty_m;
|
|
const cmpp_outputer cmpp_outputer_b = {
|
|
.out = cmpp_output_f_b,
|
|
.flush = 0,
|
|
.cleanup = cmpp_outputer_cleanup_f_b,
|
|
.state = NULL
|
|
};
|
|
|
|
/**
|
|
Default delimiters for @tokens@.
|
|
*/
|
|
static const cmpp__delim delimAtDefault = {
|
|
.open = { .z = ustr_c("@"), .n = 1 },
|
|
.close = { .z = ustr_c("@"), .n = 1 },
|
|
.zOwns = NULL
|
|
};
|
|
|
|
static const cmpp_api_thunk cmppApiMethods = {
|
|
.thunkVersion = cmpp_api_thunk_version,
|
|
};
|
|
|
|
CMPP__EXPORT(bool, cmpp_isspace)(int ch){
|
|
return ' '==ch || '\t'==ch;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_space)(
|
|
unsigned char const **p,
|
|
unsigned char const *zEnd
|
|
){
|
|
assert( *p <= zEnd );
|
|
unsigned char const * z = *p;
|
|
while( z<zEnd && cmpp_isspace(*z) ) ++z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_snl)( unsigned char const **p,
|
|
unsigned char const *zEnd ){
|
|
unsigned char const * z = *p;
|
|
/* FIXME: CRNL. */
|
|
while( z<zEnd && cmpp__issnl(*z) ) ++z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_space_trailing)( unsigned char const *zBegin,
|
|
unsigned char const **p ){
|
|
assert( *p >= zBegin );
|
|
unsigned char const * z = *p;
|
|
while( z>zBegin && cmpp_isspace(z[-1]) ) --z;
|
|
*p = z;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_skip_snl_trailing)( unsigned char const *zBegin,
|
|
unsigned char const **p ){
|
|
assert( *p >= zBegin );
|
|
unsigned char const * z = *p;
|
|
/* FIXME? CRNL */
|
|
while( z>zBegin && cmpp__issnl(z[-1]) ) --z;
|
|
*p = z;
|
|
}
|
|
|
|
/**
|
|
Sets pp's error state.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_errf)(cmpp *pp, int rc, char const *zFmt, ...);
|
|
#define cmpp__err cmpp_errf
|
|
#define cmpp_dx_err cmpp_dx_errf
|
|
|
|
/* Open/close pp's output channel. */
|
|
static int cmpp__out_fopen(cmpp *pp, const char *zName);
|
|
static void cmpp__out_close(cmpp *pp);
|
|
|
|
#define CmppKvp_empty_m \
|
|
{cmpp_token_empty_m,cmpp_token_empty_m,CmppKvp_op_none}
|
|
const CmppKvp CmppKvp_empty = CmppKvp_empty_m;
|
|
|
|
/* Wrapper around a cmpp_FILE handle. Legacy stuff from when we just
|
|
supported cmpp_FILE input. */
|
|
typedef struct FileWrapper FileWrapper;
|
|
struct FileWrapper {
|
|
/* File's name. */
|
|
char const *zName;
|
|
/* cmpp_FILE handle. */
|
|
cmpp_FILE * pFile;
|
|
/* Where FileWrapper_slurp() stores the file's contents. */
|
|
unsigned char * zContent;
|
|
/* Size of this->zContent, as set by FileWrapper_slurp(). */
|
|
cmpp_size_t nContent;
|
|
};
|
|
#define FileWrapper_empty_m {0,0,0,0}
|
|
static const FileWrapper FileWrapper_empty = FileWrapper_empty_m;
|
|
|
|
/**
|
|
Proxy for cmpp_fclose() and frees all memory owned by p. It is not
|
|
an error if p is already closed.
|
|
*/
|
|
static void FileWrapper_close(FileWrapper * p);
|
|
|
|
/** Proxy for cmpp_fopen(). Closes p first if it's currently opened. */
|
|
static int FileWrapper_open(FileWrapper * p, const char * zName, const char *zMode);
|
|
|
|
/* Populates p->zContent and p->nContent from p->pFile. */
|
|
//static int FileWrapper_slurp(FileWrapper * p, int bCloseFile );
|
|
|
|
/**
|
|
If p->zContent ends in \n or \r\n, that part is replaced with 0 and
|
|
p->nContent is adjusted. Returns true if it chomps, else false.
|
|
*/
|
|
static bool FileWrapper_chomp(FileWrapper * p);
|
|
|
|
/*
|
|
** Outputs a printf()-formatted message to stderr.
|
|
*/
|
|
static void g_stderrv(char const *zFmt, va_list);
|
|
|
|
CMPP__EXPORT(char const *, cmpp_rc_cstr)(int rc){
|
|
switch((cmpp_rc_e)rc){
|
|
#define E(N,V,H) case N: return # N;
|
|
cmpp_rc_e_map(E)
|
|
// These == other entries:
|
|
// E(CMPP_RC_ITCH_begin,,)
|
|
// E(CMPP_RC_ITCH_end,,)
|
|
#undef E
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_mfree)(void *p){
|
|
/* This MUST be a proxy for sqlite3_free() because allocate memory
|
|
exclusively using sqlite3_malloc() and friends. */
|
|
sqlite3_free(p);
|
|
}
|
|
|
|
CMPP__EXPORT(void *, cmpp_mrealloc)(void * p, size_t n){
|
|
return sqlite3_realloc64(p, n);
|
|
}
|
|
|
|
CMPP__EXPORT(void *, cmpp_malloc)(size_t n){
|
|
#if 1
|
|
return sqlite3_malloc64(n);
|
|
#else
|
|
void * p = sqlite3_malloc64(n);
|
|
if( p ) memset(p, 0, n);
|
|
return p;
|
|
#endif
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_chomp)(unsigned char * z, cmpp_size_t * n){
|
|
if( *n && '\n'==z[*n-1] ){
|
|
z[--*n] = 0;
|
|
if( *n && '\r'==z[*n-1] ){
|
|
z[--*n] = 0;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_slurp)(cmpp_input_f fIn, void *sIn,
|
|
unsigned char **pOut, cmpp_size_t * nOut){
|
|
unsigned char zBuf[1024 * 16];
|
|
unsigned char * pDest = 0;
|
|
unsigned nAlloc = 0;
|
|
unsigned nOff = 0;
|
|
int rc = 0;
|
|
cmpp_size_t nr = 0;
|
|
while( 0==rc ){
|
|
nr = sizeof(zBuf);
|
|
if( (rc = fIn(sIn, zBuf, &nr)) ){
|
|
break;
|
|
}
|
|
if(nr>0){
|
|
if(nAlloc < nOff + nr + 1){
|
|
nAlloc = nOff + nr + 1;
|
|
pDest = cmpp_mrealloc(pDest, nAlloc);
|
|
}
|
|
memcpy(pDest + nOff, zBuf, nr);
|
|
nOff += nr;
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
if( 0==rc ){
|
|
if(pDest) pDest[nOff] = 0;
|
|
*pOut = pDest;
|
|
*nOut = nOff;
|
|
}else{
|
|
cmpp_mfree(pDest);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
cmpp_FILE * cmpp_fopen(const char *zName, const char *zMode){
|
|
cmpp_FILE *f;
|
|
if(zName && ('-'==*zName && !zName[1])){
|
|
f = (strchr(zMode, 'w') || strchr(zMode,'+'))
|
|
? stdout
|
|
: stdin
|
|
;
|
|
}else{
|
|
f = fopen(zName, zMode);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void cmpp_fclose( cmpp_FILE * f ){
|
|
if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
void FileWrapper_close(FileWrapper * p){
|
|
if(p->pFile) cmpp_fclose(p->pFile);
|
|
if(p->zContent) cmpp_mfree(p->zContent);
|
|
*p = FileWrapper_empty;
|
|
}
|
|
|
|
int FileWrapper_open(FileWrapper * p, const char * zName,
|
|
const char * zMode){
|
|
FileWrapper_close(p);
|
|
if( (p->pFile = cmpp_fopen(zName, zMode)) ){
|
|
p->zName = zName;
|
|
return 0;
|
|
}else{
|
|
return cmpp_errno_rc(errno, CMPP_RC_IO);
|
|
}
|
|
}
|
|
|
|
int FileWrapper_slurp(FileWrapper * p, int bCloseFile){
|
|
assert(!p->zContent);
|
|
assert(p->pFile);
|
|
int const rc = cmpp_slurp(cmpp_input_f_FILE, p->pFile,
|
|
&p->zContent, &p->nContent);
|
|
if( bCloseFile ){
|
|
cmpp_fclose(p->pFile);
|
|
p->pFile = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_slurp_file)(cmpp *pp, char const *fn,
|
|
cmpp_b * b){
|
|
if( pp && ppCode ) return ppCode;
|
|
int rc;
|
|
cmpp_FILE * const fp = cmpp_fopen(fn, "rb");
|
|
if( fp ){
|
|
/* TODO: get file size (seek() to end, tell(), rewind()) and
|
|
cmpp_b_reserve() enough space. */
|
|
rc = cmpp_stream(cmpp_input_f_FILE, fp, cmpp_output_f_b, b);
|
|
cmpp_fclose(fp);
|
|
if( rc && pp ){
|
|
rc = cmpp_errf(pp, rc, "Error reading file %s", fn);
|
|
}
|
|
}else{
|
|
rc = cmpp_errf(pp, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Error opening file %s", fn);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool FileWrapper_chomp(FileWrapper * p){
|
|
return cmpp_chomp(p->zContent, &p->nContent);
|
|
}
|
|
|
|
/** Flags and constants related to CmppLvl. */
|
|
enum CmppLvl_e {
|
|
/**
|
|
Flag indicating that all ostensible output for a CmpLevel should
|
|
be elided. This also suppresses non-flow-control directives from
|
|
being processed.
|
|
*/
|
|
CmppLvl_F_ELIDE = 0x01,
|
|
/**
|
|
Mask of CmppLvl::flags which are inherited when
|
|
CmppLvl_push() is used.
|
|
*/
|
|
CmppLvl_F_INHERIT_MASK = CmppLvl_F_ELIDE
|
|
};
|
|
|
|
//static const CmppDLine CmppDLine_empty = CmppDLine_empty_m;
|
|
|
|
/** Free all memory owned by li but does not free li. */
|
|
static void CmppLvlList_cleanup(CmppLvlList *li);
|
|
|
|
/**
|
|
Allocate a list entry, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. It is expected
|
|
that the caller will populate the entry's zName using
|
|
sqlite3_mprintf() or equivalent.
|
|
*/
|
|
static CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li);
|
|
|
|
/** Returns the most-recently-appended element of li back to li's
|
|
free-list. It expects to receive that value as a sanity-checking
|
|
measure and may fail fatally of that's not upheld. */
|
|
static void CmppLvlList_pop(cmpp *pp, CmppLvlList *li, CmppLvl * lvl);
|
|
|
|
static const cmpp__dx_pimpl cmpp__dx_pimpl_empty =
|
|
cmpp__dx_pimpl_empty_m;
|
|
|
|
#define cmpp_dx_empty_m { \
|
|
.pp=0, \
|
|
.d=0, \
|
|
.sourceName=0, \
|
|
.args={ \
|
|
.z=0, .nz=0, \
|
|
.argc=0, .arg0=0 \
|
|
}, \
|
|
.pimpl = 0 \
|
|
}
|
|
|
|
const cmpp_dx cmpp_dx_empty = cmpp_dx_empty_m;
|
|
#define cmpp_d_empty_m {.name={0,0},.arity=-1,.flags=0,.closer=0,.impl=cmpp_d_impl_empty_m}
|
|
//static const cmpp_d cmpp_f_empty = cmpp_f_empty_m;
|
|
|
|
static const CmppDList_entry CmppDList_entry_empty =
|
|
CmppDList_entry_empty_m;
|
|
|
|
/** Free all memory owned by li but does not free li. */
|
|
static void CmppDList_cleanup(CmppDList *li);
|
|
/**
|
|
Allocate a list entry, owned by li, and return it (cleanly zeroed
|
|
out). Returns NULL and updates pp->err on error. It is expected
|
|
that the caller will populate the entry's zName using
|
|
sqlite3_mprintf() or equivalent.
|
|
*/
|
|
static CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li);
|
|
/** Returns the most-recently-appended element of li back to li's
|
|
free-list. */
|
|
static void CmppDList_unappend(CmppDList *li);
|
|
/** Resets li's list for re-use but does not free it. Returns li. */
|
|
//static CmppDList * CmppDList_reuse(CmppDList *li);
|
|
/** Searches li for a matching NUL-terminated name. */
|
|
static CmppDList_entry * CmppDList_search(CmppDList const * li,
|
|
char const *zName);
|
|
|
|
/** Reset dx and free any memory it may own. */
|
|
static void cmpp_dx_cleanup(cmpp_dx * const dx);
|
|
/**
|
|
Reset some of dx's parsing-related state in prep for fetching the
|
|
next line.
|
|
*/
|
|
static void cmpp_dx__reset(cmpp_dx * const dx);
|
|
|
|
/* Returns dx's current directive. */
|
|
static inline cmpp_d const * cmpp_dx_d(cmpp_dx const * const dx){
|
|
return dx->d;
|
|
}
|
|
|
|
static const cmpp__pimpl cmpp__pimpl_empty = {
|
|
.db = {
|
|
.dbh = 0,
|
|
.zName = 0
|
|
},
|
|
.dx = 0,
|
|
.out = cmpp_outputer_empty_m,
|
|
.delim = {
|
|
.d = cmpp__delim_list_empty_m,
|
|
.at = cmpp__delim_list_empty_m
|
|
},
|
|
.stmt = {
|
|
#define E(N,S) .N = 0,
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
},
|
|
.err = cmpp_errinfo_empty_m,
|
|
.sqlTrace = {
|
|
.expandSql = false,
|
|
.counter = 0,
|
|
.out = cmpp_outputer_empty_m
|
|
},
|
|
.flags = {
|
|
.nIncludeDir = 0,
|
|
.nDxDepth = 0,
|
|
.nSavepoint = 0,
|
|
.doDebug = 0,
|
|
.chompF = 0,
|
|
.ctorFlags = 0,
|
|
.isInternalDirectiveReg = false,
|
|
.nextIsCall = false,
|
|
.fakeNextDelim = false,
|
|
.needsLazyInit = true
|
|
},
|
|
.policy = {
|
|
.at = {0,0,0},
|
|
.un = {0,0,0}
|
|
},
|
|
.d = {
|
|
.list = CmppDList_empty_m,
|
|
.autoload = cmpp_d_autoloader_empty_m
|
|
},
|
|
.mod = {
|
|
.sohList = CmppSohList_empty_m,
|
|
.path = cmpp_b_empty_m,
|
|
.soExt = CMPP_PLATFORM_EXT_DLL,
|
|
/* Yes, '*'. It makes sense in context. */
|
|
.pathSep = '*'
|
|
// 0x1e /* "record separator" */ doesn't work. Must be non-ctrl.
|
|
},
|
|
.recycler = {
|
|
.buf = cmpp_b_library_empty_m,
|
|
.argPimpl = NULL
|
|
}
|
|
};
|
|
|
|
#if 0
|
|
static inline int cmpp__out(cmpp *pp, void const *z, cmpp_size_t n){
|
|
return cmpp__out2(pp, &pp->out, z, n);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
Returns an approximate cmpp_tt for the given SQLITE_... value from
|
|
sqlite3_column_type() or sqlite3_value_type().
|
|
*/
|
|
static cmpp_tt_e cmpp__tt_for_sqlite(int sqType);
|
|
|
|
/**
|
|
Init code which is usually run as part of the ctor but may have to
|
|
be run later, after cmpp_reset(). We can't run it from cmpp_reset()
|
|
because that could leave post-reset in an error state, which is
|
|
icky.
|
|
*/
|
|
int cmpp__lazy_init(cmpp *pp){
|
|
if( !ppCode && pp->pimpl->flags.needsLazyInit ){
|
|
cmpp__pimpl * const pi = pp->pimpl;
|
|
pi->flags.needsLazyInit = false;
|
|
cmpp__delim_list * li = &pi->delim.d;
|
|
if( !li->n ) cmpp_delimiter_push(pp, NULL);
|
|
li = &pi->delim.at;
|
|
if( !li->n ) cmpp_atdelim_push(pp, NULL, NULL);
|
|
#if defined(CMPP_CTOR_INSTANCE_INIT)
|
|
if( !ppCode ){
|
|
extern int CMPP_CTOR_INSTANCE_INIT(cmpp*);
|
|
int const rc = CMPP_CTOR_INSTANCE_INIT(pp);
|
|
if( rc && !ppCode ){
|
|
cmpp__err(pp, rc,
|
|
"Initialization via CMPP_CTOR_INSTANCE_INIT() failed "
|
|
"with code %d/%s.", rc, cmpp_rc_cstr(rc) );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
If pp is not in an OOM state then ensure that the at-policy and
|
|
undefined-policy stacks are allocated and then wipe them. Updates
|
|
pp's error state on OOM.
|
|
*/
|
|
static void cmpp__wipe_policies(cmpp *pp){
|
|
if( ppCode!=CMPP_RC_OOM /* b/c we're about to allocate */ ){
|
|
PodList__atpol_reserve(pp, &cmpp__epol(pp,at), 0);
|
|
PodList__unpol_reserve(pp, &cmpp__epol(pp,un), 0);
|
|
if( 0==ppCode ){
|
|
PodList__atpol_wipe(&cmpp__epol(pp,at), cmpp_atpol_DEFAULT);
|
|
PodList__unpol_wipe(&cmpp__epol(pp,un), cmpp_unpol_DEFAULT);
|
|
}
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_ctor)(cmpp **pOut, cmpp_ctor_opt const * cfg){
|
|
cmpp__pimpl * pi = 0;
|
|
cmpp * pp = 0;
|
|
void * const mv = cmpp_malloc(sizeof(cmpp) + sizeof(*pi));
|
|
if( mv ){
|
|
if( !cfg ){
|
|
static const cmpp_ctor_opt dfltCfg = {0};
|
|
cfg = &dfltCfg;
|
|
}
|
|
cmpp const x = {
|
|
.api = &cmppApiMethods,
|
|
.pimpl = (cmpp__pimpl*)((unsigned char *)mv + sizeof(cmpp))
|
|
/* ^^^ (T const * const) members */
|
|
};
|
|
memcpy(mv, &x, sizeof(x))
|
|
/* FWIW, i'm convinced that this is a legal way to transfer
|
|
these const-pointers-to-const. If not, we'll need to change
|
|
those cmpp members from (T const * const) to (T const *). */;
|
|
pp = mv;
|
|
assert(pp->api == &cmppApiMethods);
|
|
assert(pp->pimpl);
|
|
pi = pp->pimpl;
|
|
*pOut = pp;
|
|
*pi = cmpp__pimpl_empty;
|
|
assert( pi->flags.needsLazyInit );
|
|
pi->flags.ctorFlags = cfg->flags;
|
|
if( cfg->dbFile ){
|
|
pi->db.zName = sqlite3_mprintf("%s", cfg->dbFile);
|
|
cmpp_check_oom(pp, pi->db.zName);
|
|
}
|
|
cmpp__wipe_policies(pp);
|
|
cmpp__lazy_init(pp);
|
|
}
|
|
return pp ? ppCode : CMPP_RC_OOM;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_reset)(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
cmpp_outputer_cleanup(&pi->sqlTrace.out);
|
|
pi->sqlTrace.out = cmpp_outputer_empty;
|
|
if( pi->d.autoload.dtor ){
|
|
pi->d.autoload.dtor(pi->d.autoload.state);
|
|
}
|
|
pi->d.autoload = cmpp__pimpl_empty.d.autoload;
|
|
cmpp_b_clear(&pi->mod.path);
|
|
if( pi->stmt.spRelease && pi->stmt.spRollback ){
|
|
/* Cleanly kill all savepoint levels. This is truly superfluous,
|
|
as they'll all be rolled back (if the db is persistent) or
|
|
nuked (if using a :memory: db) momentarily. However, we'll
|
|
eventually need this for a partial-clear operation which leaves
|
|
the db and custom directives intact. For now it lives here but
|
|
will eventually move to wherever that ends up being.
|
|
|
|
2025-11-16: or not. It's fine here, really.
|
|
*/
|
|
sqlite3_reset(pi->stmt.spRelease);
|
|
while( SQLITE_DONE==sqlite3_step(pi->stmt.spRelease) ){
|
|
sqlite3_reset(pi->stmt.spRollback);
|
|
sqlite3_step(pi->stmt.spRollback);
|
|
sqlite3_reset(pi->stmt.spRelease);
|
|
}
|
|
}
|
|
cmpp__out_close(pp);
|
|
CmppDList_cleanup(&pi->d.list);
|
|
#define E(N,S) \
|
|
if(pi->stmt.N) {sqlite3_finalize(pi->stmt.N); pi->stmt.N = 0;}
|
|
CmppStmt_map(E) (void)0;
|
|
#undef E
|
|
if( pi->db.dbh ){
|
|
if( SQLITE_TXN_WRITE==sqlite3_txn_state(pi->db.dbh, NULL) ){
|
|
sqlite3_exec(pi->db.dbh, "COMMIT;", 0, 0, NULL)
|
|
/* ignoring error */;
|
|
}
|
|
sqlite3_close(pi->db.dbh);
|
|
pi->db.dbh = 0;
|
|
}
|
|
cmpp__delim_list_reuse(&pi->delim.d);
|
|
cmpp__delim_list_reuse(&pi->delim.at);
|
|
//why? cmpp_b_list_reuse(&pi->cache.buf);
|
|
cmpp__err_reuse(pp);
|
|
{/* Zero out pi but save some pieces for later, when pp is
|
|
cmpp_dtor()'d */
|
|
cmpp__pimpl const tmp = *pi;
|
|
*pi = cmpp__pimpl_empty;
|
|
pi->err = tmp.err;
|
|
pi->db = tmp.db /* restore db.zName */;
|
|
pi->recycler = tmp.recycler;
|
|
pi->policy = tmp.policy;
|
|
pi->delim = tmp.delim;
|
|
pi->mod.sohList = tmp.mod.sohList;
|
|
cmpp__wipe_policies(pp);
|
|
pi->flags.ctorFlags = tmp.flags.ctorFlags;
|
|
pi->flags.needsLazyInit = true;
|
|
}
|
|
}
|
|
|
|
static void cmpp__delim_list_cleanup(cmpp__delim_list *li);
|
|
|
|
CMPP__EXPORT(void, cmpp_dtor)(cmpp *pp){
|
|
if( pp ){
|
|
cmpp__pi(pp);
|
|
cmpp_reset(pp);
|
|
cmpp_mfree(pi->db.zName);
|
|
PodList__atpol_finalize(&cmpp__epol(pp,at));
|
|
assert(!cmpp__epol(pp,at).na);
|
|
PodList__unpol_finalize(&cmpp__epol(pp,un));
|
|
assert(!cmpp__epol(pp,un).na);
|
|
cmpp_b_library_dtor(&pi->recycler.buf)
|
|
/* Reminder to self: that retains the limits */;
|
|
cmpp__delim_list_cleanup(&pi->delim.d);
|
|
cmpp__delim_list_cleanup(&pi->delim.at);
|
|
for( cmpp_args_pimpl * apNext = 0, * ap = pi->recycler.argPimpl;
|
|
ap; ap = apNext ){
|
|
apNext = ap->nextFree;
|
|
ap->nextFree = 0;
|
|
cmpp__args_pimpl_cleanup(ap);
|
|
cmpp_mfree(ap);
|
|
}
|
|
cmpp_errinfo_dtor(&pi->err);
|
|
*pi = cmpp__pimpl_empty;
|
|
cmpp_mfree(pp);
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_is_safemode)(cmpp const * pp){
|
|
return pp && 0!=(cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags);
|
|
}
|
|
|
|
/** Sets ppCode if m is NULL. Returns ppCode. */
|
|
CMPP__EXPORT(int, cmpp_check_oom)(cmpp * const pp, void const * const m ){
|
|
int rc;
|
|
if( pp ){
|
|
if( !m ){
|
|
//assert(!"oom");
|
|
cmpp__err(pp, CMPP_RC_OOM, 0);
|
|
}
|
|
rc = ppCode;
|
|
}else{
|
|
rc = m ? 0 : CMPP_RC_OOM;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//CxMPP_WASM_EXPORT
|
|
CMPP__EXPORT(void *,cmpp_malloc2)(cmpp *pp, cmpp_size_t n){
|
|
void *p = 0;
|
|
if( 0==ppCode ){
|
|
p = cmpp_malloc(n);
|
|
cmpp_check_oom(pp, p);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
If ppCode is not 0 then it flushes pp's output channel. If that
|
|
fails, it sets ppCode. Returns ppCode.
|
|
*/
|
|
static int cmpp__flush(cmpp *pp){
|
|
if( !ppCode && pp->pimpl->out.flush ){
|
|
int const rc = pp->pimpl->out.flush(pp->pimpl->out.state);
|
|
if( rc && !ppCode ){
|
|
cmpp_err(pp, rc, "Flush failed.", -1);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp__out_close(cmpp *pp){
|
|
cmpp__flush(pp)/*ignoring result*/;
|
|
cmpp_outputer_cleanup(&pp->pimpl->out);
|
|
pp->pimpl->out = cmpp__pimpl_empty.out;
|
|
}
|
|
|
|
int cmpp__out_fopen(cmpp *pp, const char *zName){
|
|
cmpp__out_close(pp);
|
|
if( !ppCode ){
|
|
cmpp_FILE * const f = cmpp_fopen(zName, "wb");
|
|
if( f ){
|
|
ppCode = 0;
|
|
pp->pimpl->out = cmpp_outputer_FILE;
|
|
pp->pimpl->out.state = f;
|
|
pp->pimpl->out.name = zName;
|
|
}else{
|
|
ppCode = cmpp__err(
|
|
pp, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Error opening file %s", zName
|
|
);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__FileWrapper_open(cmpp *pp, FileWrapper * fw,
|
|
const char * zName,
|
|
const char * zMode){
|
|
int const rc = FileWrapper_open(fw, zName, zMode);
|
|
if( rc ){
|
|
cmpp__err(pp, rc, "Error %s opening file [%s] "
|
|
"with mode [%s]",
|
|
cmpp_rc_cstr(rc), zName, zMode);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__FileWrapper_slurp(cmpp* pp, FileWrapper * fw){
|
|
assert( fw->pFile );
|
|
int const rc = FileWrapper_slurp(fw, 1);
|
|
if( rc ){
|
|
cmpp__err(pp, rc, "Error %s slurping file %s",
|
|
cmpp_rc_cstr(rc), fw->zName);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void g_stderrv(char const *zFmt, va_list va){
|
|
vfprintf(0 ? stdout : stderr, zFmt, va);
|
|
}
|
|
|
|
void g_stderr(char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
g_stderrv(zFmt, va);
|
|
va_end(va);
|
|
}
|
|
|
|
CMPP__EXPORT(char const *, cmpp_dx_delim)(cmpp_dx const *dx){
|
|
return (char const *)cmpp__dx_zdelim(dx);
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(int, cmpp_flush)(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
if( pi->out.flush ){
|
|
return pi->out.flush(pi->out.state);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int cmpp__out2(cmpp *pp, cmpp_outputer *pOut,
|
|
void const *z, cmpp_size_t n){
|
|
assert( pOut );
|
|
if( !ppCode && pOut->out && n ){
|
|
int const rc = pOut->out(pOut->state, z, n);
|
|
if( rc ){
|
|
cmpp__err(pp, rc,
|
|
"Write of %" CMPP_SIZE_T_PFMT
|
|
" bytes to output stream failed.", n);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_out_raw)(cmpp * pp, void const *z, cmpp_size_t n){
|
|
cmpp_dx * const dx = cmpp__dx_current(pp);
|
|
if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode;
|
|
return cmpp__out2(pp, &pp->pimpl->out, z, n);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outfv2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, va_list va){
|
|
assert( out );
|
|
cmpp_dx const * const dx = cmpp__dx_current(pp);
|
|
if( dx && cmpp_dx_is_eliding(dx) ) return ppCode;
|
|
if( !ppCode && zFmt && *zFmt && out->out ){
|
|
char * s = sqlite3_vmprintf(zFmt, va);
|
|
if( 0==cmpp_check_oom(pp, s) ){
|
|
cmpp__out2(pp, out, s, cmpp__strlen(s, -1));
|
|
}
|
|
cmpp_mfree(s);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outf2)(cmpp *pp, cmpp_outputer *out, char const *zFmt, ...){
|
|
assert( out );
|
|
cmpp_dx const * const dx = cmpp__dx_current(pp);
|
|
if( dx && cmpp_dx_is_eliding(dx) ) return ppCode;
|
|
if( !ppCode && zFmt && *zFmt && out->out ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp_outfv2(pp, out, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outfv)(cmpp *pp, char const *zFmt, va_list va){
|
|
return cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outf)(cmpp *pp, char const *zFmt, ...){
|
|
if( !ppCode ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp_outfv2(pp, &pp->pimpl->out, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__affirm_undef_policy(cmpp *pp,
|
|
unsigned char const *zName,
|
|
cmpp_size_t nName){
|
|
if( 0==ppCode
|
|
&& cmpp_unpol_ERROR==cmpp__policy(pp,un) ){
|
|
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
|
|
"Key '%.*s' was not found and the undefined-value "
|
|
"policy is 'error'.",
|
|
(int)nName, zName);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__out_expand(cmpp * pp, cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n, cmpp_atpol_e atPolicy){
|
|
enum state_e {
|
|
/* looking for @token@ opening @ */
|
|
state_opening,
|
|
/* looking for @token@ closing @ */
|
|
state_closing
|
|
};
|
|
cmpp__pi(pp);
|
|
if( ppCode ) return ppCode;
|
|
if( cmpp_atpol_CURRENT==atPolicy ) atPolicy = cmpp__policy(pp,at);
|
|
assert( cmpp_atpol_invalid!=atPolicy );
|
|
unsigned char const *zLeft = zFrom;
|
|
unsigned char const * const zEnd = zFrom + n;
|
|
unsigned char const *z =
|
|
(cmpp_atpol_OFF==atPolicy || cmpp_atpol_invalid==atPolicy)
|
|
? zEnd
|
|
: zLeft;
|
|
unsigned char const chEol = (unsigned char)'\n';
|
|
cmpp__delim const * delim =
|
|
cmpp__delim_list_get(&pp->pimpl->delim.at);
|
|
if( !delim && z<zEnd ){
|
|
return cmpp_errf(pp, CMPP_RC_ERROR,
|
|
"@token@ delimiter stack is empty.");
|
|
}
|
|
enum state_e state = state_opening;
|
|
cmpp_b * const bCall = cmpp_b_borrow(pp);
|
|
cmpp_b * const bVal = cmpp_b_borrow(pp);
|
|
if( !bCall || !bVal ) return ppCode;
|
|
if(0) g_warn("looking to expand from %d bytes: [%.*s]", (int)n,
|
|
(int)n,zLeft);
|
|
if( !pOut ){
|
|
if( 0 && atPolicy!=cmpp__policy(pp,at) ){
|
|
/* This might be too strict. It was initially in place to ensure
|
|
that we did not _accidentally_ do @token@ parsing to the main
|
|
output channel. We frequently use it internally on other
|
|
output channels to buffer/build results.
|
|
|
|
Advantage to removing this check: #query could impose
|
|
its own at-policy without having to use an intermediary
|
|
buffer.
|
|
|
|
Disadvantage: less protection against accidentally
|
|
@-filtering the output when we shouldn't.
|
|
*/
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"%s(): when sending to the default "
|
|
"output channel, the @policy must be "
|
|
"cmpp_atpol_CURRENT.", __func__);
|
|
}
|
|
pOut = &pi->out;
|
|
}
|
|
assert( pi->dx ? !cmpp_dx_is_eliding(pi->dx) : 1 );
|
|
|
|
#define tflush \
|
|
if(z>zEnd) z=zEnd; \
|
|
if(zLeft<z){ \
|
|
if(0) g_warn("flush %d [%.*s]", (int)(z-zLeft), (int)(z-zLeft), zLeft); \
|
|
cmpp__out2(pp, pOut, zLeft, (z-zLeft)); \
|
|
} zLeft = z
|
|
for( ; z<zEnd && 0==ppCode; ++z ){
|
|
zLeft = z;
|
|
for( ;z<zEnd && 0==ppCode; ++z ){
|
|
again:
|
|
if( chEol==*z ){
|
|
state = state_opening;
|
|
continue;
|
|
}
|
|
if( state_opening==state ){
|
|
if( z + delim->open.n < zEnd
|
|
&& 0==memcmp(z, delim->open.z, delim->open.n) ){
|
|
tflush;
|
|
z += delim->open.n;
|
|
if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
|
|
if( 0 ){
|
|
g_warn("\nzLeft..=[%s]\nz=[%s]", zLeft, z);
|
|
}
|
|
state = state_closing;
|
|
/* Handle call of @[directive ...args]@
|
|
|
|
i'm not a huge fan of this syntax, but that may go away
|
|
if we replace the single-char separator with a pair of
|
|
opening/closing delimiters.
|
|
|
|
Minor annoyance: the part of @[...]@ may span lines but
|
|
are processed as directives, so they have the same
|
|
limitation that newlines must be either backslash-escaped
|
|
or be contained within a grouping token.
|
|
*/
|
|
if( z<zEnd && '['==*z ){
|
|
unsigned char const * zb = z;
|
|
cmpp_size_t nl = 0;
|
|
//g_warn("Scanning: <<%.*s>>", (int)(zEnd-zb), zb);
|
|
if( cmpp__find_closing_lc(pp, &zb, zEnd, &nl) ){
|
|
break;
|
|
}
|
|
//g_warn("Found: <<%.*s>>", (int)(zb+1-z), z);
|
|
if( zb + delim->close.n >= zEnd
|
|
|| 0!=memcmp(zb+1, delim->close.z, delim->close.n) ){
|
|
serr("Expecting '%s' after closing ']'.", delim->close.z);
|
|
break;
|
|
}
|
|
//g_warn("Found: <<%.*s>>", (int)(zb - z -1), z+1);
|
|
cmpp_call_str(pp, z+1, (zb - z - 1),
|
|
cmpp_b_reuse(bCall), 0);
|
|
if( 0==ppCode ){
|
|
cmpp__out2(pp, pOut, bCall->z, bCall->n);
|
|
state = state_opening;
|
|
zLeft = z = zb + delim->close.n + 1;
|
|
//g_warn("post-@[]@ z=%.*s", (int)(zEnd-z), z);
|
|
}
|
|
}
|
|
if( z>=zEnd ) break;
|
|
goto again /* avoid adjusting z again */;
|
|
}
|
|
}else{/*we're looking for delim->closer*/
|
|
assert( state_closing==state );
|
|
if( z + delim->close.n <= zEnd
|
|
&& 0==memcmp(z, delim->close.z, delim->close.n ) ){
|
|
/* process the ... part of @...@ */
|
|
assert( state_closing==state );
|
|
assert( zLeft<z );
|
|
assert( z<=zEnd );
|
|
if( 0 ) g_warn("zLeft..z=[%.*s]", (int)(z-zLeft), zLeft);
|
|
if( 0 ) g_warn("zLeft..=%s", zLeft);
|
|
assert( 0==memcmp(zLeft, delim->open.z, delim->open.n) );
|
|
unsigned char const *zKey =
|
|
zLeft + delim->open.n;
|
|
cmpp_strlen_t const nKey = z - zLeft - delim->open.n;
|
|
if( 0 ) g_warn("nKey=%d zKey=[%.*s]", nKey, nKey, zKey);
|
|
assert( nKey>= 0 );
|
|
if( !nKey ){
|
|
serr("Empty key is not permitted in %s...%s.",
|
|
delim->open.z, delim->close.z);
|
|
break;
|
|
}
|
|
if( cmpp__get_b(pp, zKey, nKey, cmpp_b_reuse(bVal), true) ){
|
|
if(0){
|
|
g_warn("nVal=%d zVal=[%.*s]", (int)bVal->n,
|
|
(int)bVal->n, bVal->z);
|
|
}
|
|
if( bVal->n ){
|
|
cmpp__out2(pp, pOut, bVal->z, bVal->n);
|
|
}else{
|
|
/* Elide it */
|
|
}
|
|
zLeft = z + delim->close.n;
|
|
assert( zLeft<=zEnd );
|
|
}else if( !ppCode ){
|
|
assert( !bVal->n );
|
|
/* No matching define . */
|
|
switch( atPolicy ){
|
|
case cmpp_atpol_ELIDE: zLeft = z + delim->close.n; break;
|
|
case cmpp_atpol_RETAIN: tflush; break;
|
|
case cmpp_atpol_ERROR:
|
|
cmpp__err(pp, CMPP_RC_NOT_DEFINED,
|
|
"Undefined %skey%s: %.*s",
|
|
delim->open.z, delim->close.z, nKey, zKey);
|
|
break;
|
|
case cmpp_atpol_invalid:
|
|
case cmpp_atpol_CURRENT:
|
|
case cmpp_atpol_OFF:
|
|
assert(!"this shouldn't be reachable" );
|
|
cmpp__err(pp, CMPP_RC_ERROR, "Unhandled atPolicy #%d",
|
|
atPolicy);
|
|
break;
|
|
}
|
|
}/* process @...@ */
|
|
state = state_opening;
|
|
assert( z<=zEnd );
|
|
}/*matched a closer*/
|
|
}/*state_closer==state*/
|
|
assert( z<=zEnd );
|
|
}/*per-line loop*/
|
|
}/*outer loop*/
|
|
#if 0
|
|
if( 0==ppCode && state_closer==state ){
|
|
serr("Opening '%s' found without a closing '%s'.",
|
|
delim->open.z, delim->close.z);
|
|
}
|
|
#endif
|
|
tflush;
|
|
#undef tflush
|
|
cmpp_b_return(pp, bCall);
|
|
cmpp_b_return(pp, bVal);
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_out_expand)(cmpp * const pp,
|
|
cmpp_outputer * pOut,
|
|
unsigned char const * zFrom,
|
|
cmpp_size_t n,
|
|
cmpp_atpol_e atPolicy){
|
|
cmpp_dx const * const dx = cmpp__dx_current(pp);
|
|
if( ppCode || (dx && cmpp_dx_is_eliding(dx)) ) return ppCode;
|
|
return cmpp__out_expand(pp, pOut, zFrom, n, atPolicy);
|
|
}
|
|
|
|
CmppLvl * CmppLvl_get(cmpp_dx const *dx){
|
|
return dx->pimpl->dxLvl.n
|
|
? dx->pimpl->dxLvl.list[dx->pimpl->dxLvl.n-1]
|
|
: 0;
|
|
}
|
|
|
|
static const CmppLvl CmppLvl_empty = CmppLvl_empty_m;
|
|
|
|
CmppLvl * CmppLvl_push(cmpp_dx *dx){
|
|
CmppLvl * p = 0;
|
|
if( !dxppCode ){
|
|
CmppLvl * const pPrev = CmppLvl_get(dx);
|
|
p = CmppLvlList_push(dx->pp, &dx->pimpl->dxLvl);
|
|
if( p ){
|
|
*p = CmppLvl_empty;
|
|
p->lineNo = dx->pimpl->dline.lineNo;
|
|
//p->d = dx->d;
|
|
if( pPrev ){
|
|
p->flags = (CmppLvl_F_INHERIT_MASK & pPrev->flags);
|
|
//if(CLvl_isSkip(pPrev)) p->flags |= CmppLvl_F_ELIDE;
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void CmppLvl_pop(cmpp_dx *dx, CmppLvl * lvl){
|
|
CmppLvlList_pop(dx->pp, &dx->pimpl->dxLvl, lvl);
|
|
}
|
|
|
|
void CmppLvl_elide(CmppLvl *lvl, bool on){
|
|
if( on ) lvl->flags |= CmppLvl_F_ELIDE;
|
|
else lvl->flags &= ~CmppLvl_F_ELIDE;
|
|
}
|
|
|
|
bool CmppLvl_is_eliding(CmppLvl const *lvl){
|
|
return lvl && !!(lvl->flags & CmppLvl_F_ELIDE);
|
|
}
|
|
|
|
#if 0
|
|
void cmpp_dx_elide_mode(cmpp_dx *dx, bool on){
|
|
CmppLvl_elide(CmppLvl_get(dx), on);
|
|
}
|
|
#endif
|
|
|
|
bool cmpp_dx_is_eliding(cmpp_dx const *dx){
|
|
return CmppLvl_is_eliding(CmppLvl_get(dx));
|
|
}
|
|
|
|
CMPP__EXPORT(sqlite3_str *, cmpp_sqlite3_str_new)(cmpp*pp){
|
|
sqlite3_str * const s = sqlite3_str_new(pp ? pp->pimpl->db.dbh : 0);
|
|
return sqlite3_str_errcode(s)
|
|
? (
|
|
cmpp_check_oom(pp,NULL), NULL
|
|
/* s is a static singleton in this case, not leaked */
|
|
)
|
|
: s;
|
|
}
|
|
|
|
CMPP__EXPORT(char *, cmpp_sqlite3_str_finish)(cmpp *pp, sqlite3_str *s, int * n){
|
|
char * z = 0;
|
|
int const rc = sqlite3_str_errcode(s);
|
|
if( !ppCode ){
|
|
cmpp_db_rc(pp, rc, "sqlite3_str_errcode()");
|
|
}
|
|
if(0==rc){
|
|
int const nStr = sqlite3_str_length(s);
|
|
if(n) *n = nStr;
|
|
z = sqlite3_str_finish(s);
|
|
if( !z ){
|
|
assert( 0==nStr && "else rc!=0" );
|
|
}
|
|
}else{
|
|
cmpp_mfree( sqlite3_str_finish(s) );
|
|
}
|
|
return z;
|
|
}
|
|
|
|
void cmpp_outputer_set(cmpp *pp, cmpp_outputer const *out,
|
|
char const *zName){
|
|
cmpp__pi(pp);
|
|
cmpp_outputer_cleanup(&pi->out);
|
|
if( out ) pi->out = *out;
|
|
else pi->out = cmpp_outputer_empty;
|
|
pi->out.name = zName;
|
|
}
|
|
|
|
void cmpp__outputer_swap(cmpp *pp, cmpp_outputer const *oNew,
|
|
cmpp_outputer *oPrev){
|
|
if( oPrev ){
|
|
*oPrev = pp->pimpl->out;
|
|
}
|
|
pp->pimpl->out = *oNew;
|
|
}
|
|
|
|
#if 0
|
|
static void delim__list_dump(cmpp const *pp){
|
|
cmpp__delim_list const *li = &pp->pimpl->delim.d;
|
|
if( li->n ){
|
|
g_warn0("delimiter stack:");
|
|
for(cmpp_size_t i = 0; i < li->n; ++i ){
|
|
g_warn("#%d: %s", (int)i, li->list[i].z);
|
|
}
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
static bool cmpp__valid_delim(cmpp * const pp,
|
|
char const *z,
|
|
char const *zEnd){
|
|
char const * const zB = z;
|
|
for( ; z < zEnd; ++z ){
|
|
if( *z<33 || 127==*z ){
|
|
cmpp_errf(pp, CMPP_RC_SYNTAX,
|
|
"Delimiters may not contain "
|
|
"control characters.");
|
|
return false;
|
|
}
|
|
}
|
|
if( zB==z ){
|
|
cmpp_errf(pp, CMPP_RC_SYNTAX,
|
|
"Delimiters may not be empty.");
|
|
}
|
|
return z>zB;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_set)(cmpp *pp, char const *zDelim){
|
|
if( ppCode ) return ppCode;
|
|
unsigned n = 0;
|
|
if( zDelim ){
|
|
n = cmpp__strlen(zDelim, -1);
|
|
if( !cmpp__valid_delim(pp, zDelim, zDelim+n) ){
|
|
return ppCode;
|
|
}else if( n>32 /* arbitrary but seems sensible enough */ ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Invalid delimiter (too long): %s", zDelim);
|
|
}
|
|
}
|
|
cmpp__pi(pp);
|
|
if( pi->delim.d.n ){
|
|
cmpp__delim * const delim = cmpp__pp_delim(pp);
|
|
if( !cmpp_check_oom(pp, delim) ){
|
|
cmpp__delim_cleanup(delim);
|
|
if( zDelim ){
|
|
delim->open.n = n;
|
|
delim->open.z = delim->zOwns =
|
|
(unsigned char*)sqlite3_mprintf("%.*s", n, zDelim);
|
|
cmpp_check_oom(pp, delim->zOwns);
|
|
}else{
|
|
assert( delim->open.z );
|
|
assert( !delim->zOwns );
|
|
assert( delim->open.n==sizeof(CMPP_DEFAULT_DELIM)-1 );
|
|
}
|
|
}
|
|
}else{
|
|
assert(!"Cannot set delimiter on an empty stack!");
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Directive delimter stack is empty.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_delimiter_get)(cmpp const *pp, char const **zDelim){
|
|
cmpp__delim const * d = cmpp__pp_delim(pp);
|
|
if( !d ) d = &cmpp__delim_empty;
|
|
*zDelim = (char const *)d->open.z;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_push)(cmpp *pp, char const *zDelim){
|
|
cmpp__delim * const d =
|
|
cmpp__delim_list_push(pp, &pp->pimpl->delim.d);
|
|
if( d && cmpp_delimiter_set(pp, zDelim) ){
|
|
cmpp__delim_list_pop(&pp->pimpl->delim.d);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_delimiter_pop)(cmpp *pp){
|
|
cmpp__delim_list * const li = &pp->pimpl->delim.d;
|
|
if( li->n ){
|
|
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
|
|
cmpp__delim_list_pop(li);
|
|
if( 0 && li->n ){
|
|
g_warn("restored delimiter: %s", cmpp__pp_zdelim(pp));
|
|
}
|
|
}else if( !ppCode ){
|
|
assert(!"Attempt to pop an empty delimiter stack.");
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Cannot pop an empty delimiter stack.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_set)(cmpp * const pp,
|
|
char const *zOpen,
|
|
char const *zClose){
|
|
if( 0==ppCode ){
|
|
cmpp__pi(pp);
|
|
cmpp__delim * const d = pi->delim.at.n
|
|
? &pi->delim.at.list[pi->delim.at.n-1]
|
|
: NULL;
|
|
assert( d );
|
|
if( !d ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"@token@ delimiter stack is currently empty.");
|
|
}
|
|
if( 0==zOpen ){
|
|
zOpen = (char const *)delimAtDefault.open.z;
|
|
zClose = (char const *)delimAtDefault.close.z;
|
|
}else if( 0==zClose ){
|
|
zClose = zOpen;
|
|
}
|
|
cmpp_size_t const nO = cmpp__strlen(zOpen, -1);
|
|
cmpp_size_t const nC = cmpp__strlen(zClose, -1);
|
|
assert( zOpen && zClose );
|
|
if( !cmpp__valid_delim(pp, zOpen, zOpen+nO)
|
|
|| !cmpp__valid_delim(pp, zClose, zClose+nC) ){
|
|
return ppCode;
|
|
}
|
|
cmpp_b b = cmpp_b_empty
|
|
/* Don't use cmpp_b_borrow() here because we'll unconditionally
|
|
transfer ownership of b.z to d. */;
|
|
if( 0==cmpp_b_reserve3(pp, &b, nO + nC + 2) ){
|
|
#ifndef NDEBUG
|
|
unsigned char const * const zReallocCheck = b.z;
|
|
#endif
|
|
/* Copy the open/close tokens to a single string to simplify
|
|
management. */
|
|
cmpp_b_append4(pp, &b, zOpen, nO);
|
|
cmpp_b_append_ch(&b, '\0');
|
|
cmpp_b_append4(pp, &b, zClose, nC);
|
|
assert( zReallocCheck==b.z
|
|
&& "Else buffer was not properly pre-sized" );
|
|
cmpp__delim_cleanup(d);
|
|
d->open.z = b.z;
|
|
d->open.n = nO;
|
|
d->close.z = d->open.z + nO + 1/*NUL*/;
|
|
d->close.n = nC;
|
|
d->zOwns = b.z;
|
|
b = cmpp_b_empty /* transfer memory ownership */;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_push)(cmpp *pp, char const *zOpen,
|
|
char const *zClose){
|
|
cmpp__delim * const d =
|
|
cmpp__delim_list_push(pp, &pp->pimpl->delim.at);
|
|
if( d && cmpp_atdelim_set(pp, zOpen, zClose) ){
|
|
cmpp__delim_list_pop(&pp->pimpl->delim.at);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atdelim_pop)(cmpp *pp){
|
|
cmpp__delim_list * const li = &pp->pimpl->delim.at;
|
|
if( li->n ){
|
|
//g_warn("Popping delimiter: %s", cmpp__pp_zdelim(pp));
|
|
cmpp__delim_list_pop(li);
|
|
}else if( !ppCode ){
|
|
assert(!"Attempt to pop an empty @token@ delim stack.");
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Cannot pop an empty @token@ delimiter stack.");
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_atdelim_get)(cmpp const * const pp,
|
|
char const **zOpen,
|
|
char const **zClose){
|
|
cmpp__delim const * d
|
|
= cmpp__delim_list_get(&pp->pimpl->delim.at);
|
|
assert( d );
|
|
if( !d ) d = &delimAtDefault;
|
|
if( zClose ) *zClose = (char const *)d->close.z;
|
|
if( zOpen ) *zOpen = (char const *)d->open.z;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_is_int)(unsigned char const *z, unsigned n,
|
|
int *pOut){
|
|
if( !n || n > 10 ) return false;
|
|
char const * zz = (char *)z;
|
|
char /*const sigh*/* zEnd = 0;
|
|
int32_t d = strtol(zz, &zEnd, 10);
|
|
if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){
|
|
if( pOut ) *pOut = d;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_is_int64)(unsigned char const *z, unsigned n,
|
|
int64_t *pOut){
|
|
if( !n || n > 20 ) return false;
|
|
char const * zz = (char *)z;
|
|
char /*const sigh*/ * zEnd = 0;
|
|
int64_t d = strtoll(zz, &zEnd, 10);
|
|
if( zEnd && zEnd!=zz && n==(unsigned)(zEnd-zz) ){
|
|
if( pOut ) *pOut = d;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Impl for the -Fx=filename flag.
|
|
|
|
TODO?: refactor to take an optional zVal to make it suitable for
|
|
the public API. This impl requires that zKey contain
|
|
"key=filename".
|
|
*/
|
|
static int cmpp__set_file(cmpp *pp, unsigned const char * zKey,
|
|
cmpp_strlen_t nKey){
|
|
if(ppCode) return ppCode;
|
|
CmppKvp kvp = CmppKvp_empty;
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
if( CmppKvp_parse(pp, &kvp, zKey, nKey, CmppKvp_op_eq1) ){
|
|
return ppCode;
|
|
}
|
|
sqlite3_stmt * q = 0;
|
|
FileWrapper fw = FileWrapper_empty;
|
|
if( cmpp__FileWrapper_open(pp, &fw, (char const *)kvp.v.z, "rb") ){
|
|
assert(ppCode);
|
|
return ppCode;
|
|
}
|
|
cmpp__FileWrapper_slurp(pp, &fw);
|
|
q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( q && 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, (int)kvp.k.n) ){
|
|
//g_warn("zKey=%.*s", (int)kvp.k.n, kvp.k.z);
|
|
if( pp->pimpl->flags.chompF ){
|
|
FileWrapper_chomp(&fw);
|
|
}
|
|
if( fw.nContent ){
|
|
cmpp_stmt_bind_textx(pp, q, 3, fw.zContent,
|
|
(cmpp_strlen_t)fw.nContent, SQLITE_STATIC);
|
|
fw.zContent = 0 /* transferred ownership */;
|
|
fw.nContent = 0;
|
|
}else{
|
|
cmpp_stmt_bind_null(pp, q, 2);
|
|
}
|
|
cmpp_stmt_step(pp, q, true);
|
|
g_debug(pp,2,("define: %s%s%s\n",
|
|
kvp.k.z,
|
|
kvp.v.z ? " with value " : "",
|
|
kvp.v.z ? (char const *)kvp.v.z : ""));
|
|
}
|
|
FileWrapper_close(&fw);
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__has(cmpp *pp, const char * zName, cmpp_strlen_t nName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defHas, false);
|
|
if( q ){
|
|
nName = cmpp__strlen(zName, nName);
|
|
cmpp_stmt_bind_textn(pp, q, 1, ustr_c(zName), nName);
|
|
if(SQLITE_ROW == cmpp_stmt_step(pp, q, true)){
|
|
rc = 1;
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
//g_debug(pp,1,("has [%s] ?= %d\n",zName, rc));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__get_bool(cmpp *pp, unsigned const char *zName, cmpp_strlen_t nName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetBool, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp_stmt_bind_textn(pp, q, 1, zName, nName);
|
|
assert(0==ppCode);
|
|
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
|
|
rc = sqlite3_column_int(q, 0);
|
|
}else{
|
|
rc = 0;
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp__get_int(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, int *pOut ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGetInt, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp_stmt_bind_textn(pp, q, 1, zName, nName);
|
|
assert(0==ppCode);
|
|
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
|
|
*pOut = sqlite3_column_int(q,0);
|
|
}else{
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__get_b(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, cmpp_b * os, bool enforceUndefPolicy){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defGet, false);
|
|
if( q ){
|
|
nName = cmpp__strlenu(zName, nName);
|
|
cmpp_stmt_bind_textx(pp, q, 1, zName, nName, SQLITE_STATIC);
|
|
int n = 0;
|
|
if(SQLITE_ROW == cmpp_stmt_step(pp, q, false)){
|
|
const unsigned char * z = sqlite3_column_text(q, 3);
|
|
n = sqlite3_column_bytes(q, 3);
|
|
cmpp_b_append4(pp, os, z, (cmpp_size_t)n);
|
|
rc = 1;
|
|
}else{
|
|
if( enforceUndefPolicy ){
|
|
cmpp__affirm_undef_policy(pp, zName, nName);
|
|
}
|
|
rc = 0;
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
g_debug(pp,1,("get-define [%.*s] ?= %d %.*s\n",
|
|
nName, zName, rc, os->n, os->z));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#if 0
|
|
int cmpp__get_b2(cmpp *pp, unsigned const char * zName,
|
|
cmpp_strlen_t nName, cmpp_b * os, bool enforceUndefPolicy,
|
|
bool *gotOne){
|
|
static bool nope;
|
|
*(gotOne ? gotOne : &nope) = cmpp__get_b(pp, zName, nName, os,
|
|
enforceUndefPolicy);
|
|
return ppCode;
|
|
}
|
|
#endif
|
|
|
|
CMPP__EXPORT(int, cmpp_undef_legacy)(cmpp *pp, const char * zKey,
|
|
unsigned int *nRemoved){
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(pp, CmppStmt_defDelLegacy, false);
|
|
if( q ){
|
|
unsigned int const n = strlen(zKey);
|
|
cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), (cmpp_strlen_t)n,
|
|
SQLITE_STATIC);
|
|
cmpp_stmt_step(pp, q, true);
|
|
if( nRemoved ){
|
|
*nRemoved = (unsigned)sqlite3_changes(pp->pimpl->db.dbh);
|
|
}
|
|
g_debug(pp,2,("undefine: %.*s\n",n, zKey));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_undefine)(cmpp *pp, const char * zKey,
|
|
cmpp_strlen_t n){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defDelExact, false);
|
|
if( q ){
|
|
n = cmpp__strlen(zKey, n);
|
|
cmpp_stmt_bind_textx(pp, q, 1, ustr_c(zKey), n, SQLITE_STATIC);
|
|
cmpp_stmt_step(pp, q, true);
|
|
g_debug(pp,2,("undefine: %.*s\n",n, zKey));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__include_dir_add(cmpp *pp, const char * zDir, int priority,
|
|
int64_t * pRowid){
|
|
if( pRowid ) *pRowid = 0;
|
|
if( !ppCode && zDir && *zDir ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathAdd, false);
|
|
if( q ){
|
|
/* TODO: normalize zDir before insertion so that a/b and a/b/
|
|
are equivalent. The relavent code is in another tree,
|
|
awaiting a decision on whether to import it or re-base cmpp
|
|
on top of that library (which would, e.g., replace cmpp_b
|
|
with that one, which is more mature).
|
|
*/
|
|
cmpp_stmt_bind_int(pp, q, 1, priority);
|
|
cmpp_stmt_bind_textx(pp, q, 2, ustr_c(zDir), -1, SQLITE_STATIC);
|
|
int const rc = cmpp_stmt_step(pp, q, false);
|
|
if( SQLITE_ROW==rc ){
|
|
++pp->pimpl->flags.nIncludeDir;
|
|
if( pRowid ){
|
|
*pRowid = sqlite3_column_int64(q, 0);
|
|
}
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
/*g_warn("inclpath add: rc=%d rowid=%" PRIi64 " prio=%d %s",
|
|
rc, pRowid ? *pRowid : 0, priority, zDir);*/
|
|
g_debug(pp,2,("inclpath add: prio=%d %s\n", priority, zDir));
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_include_dir_add)(cmpp *pp, const char * zDir){
|
|
return cmpp__include_dir_add(pp, zDir, 0, NULL);
|
|
}
|
|
|
|
int cmpp__include_dir_rm_id(cmpp *pp, int64_t rowid){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
|
|
if( q ){
|
|
/* Hoop-jumping to allow this to work even if pp's in an error
|
|
state. */
|
|
int rc = sqlite3_bind_int64(q, 1, rowid);
|
|
if( 0==rc ){
|
|
rc = sqlite3_step(q);
|
|
if( SQLITE_ROW==rc ){
|
|
--pp->pimpl->flags.nIncludeDir;
|
|
rc = 0;
|
|
}else if( SQLITE_DONE==rc ){
|
|
rc = 0;
|
|
}
|
|
}
|
|
if( rc && !ppCode ){
|
|
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
g_debug(pp,2,("inclpath rm #%"PRIi64 "\n", rowid));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
//%cmpp if not defined strip.module
|
|
CMPP__EXPORT(int, cmpp_module_dir_add)(cmpp *pp, const char * zDirs){
|
|
#if CMPP_ENABLE_DLLS
|
|
if( !ppCode ){
|
|
cmpp_b * const ob = &pp->pimpl->mod.path;
|
|
if( !zDirs && !ob->n ){
|
|
zDirs = getenv("CMPP_MODULE_PATH");
|
|
if( !zDirs ){
|
|
zDirs = CMPP_MODULE_PATH;
|
|
}
|
|
}
|
|
if( !zDirs || !*zDirs ) return 0;
|
|
char const * z = zDirs;
|
|
char const * const zEnd = zDirs + strlen(zDirs);
|
|
if( 0==cmpp_b_reserve3(pp, ob, ob->n + (zEnd - z) + 3) ){
|
|
unsigned char * zo = ob->z + ob->n;
|
|
unsigned i = 0;
|
|
for( ; z < zEnd && !ppCode; ++z ){
|
|
switch( *z ){
|
|
case CMPP_PATH_SEPARATOR:
|
|
*zo++ = pp->pimpl->mod.pathSep;
|
|
break;
|
|
default:
|
|
if( 1==++i && ob->n ){
|
|
cmpp_b_append_ch(ob, pp->pimpl->mod.pathSep);
|
|
}
|
|
*zo++ = *z;
|
|
break;
|
|
}
|
|
}
|
|
*zo = 0;
|
|
ob->n = (zo - ob->z);
|
|
}
|
|
}
|
|
return ppCode;
|
|
#else
|
|
return CMPP_RC_UNSUPPORTED;
|
|
#endif
|
|
}
|
|
//%cmpp /if
|
|
|
|
CMPP__EXPORT(int, cmpp_db_name_set)(cmpp *pp, const char * zName){
|
|
if( 0==ppCode ){
|
|
cmpp__pi(pp);
|
|
if( pi->db.dbh ){
|
|
return cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"DB name cannot be set after db initialization.");
|
|
}
|
|
if( zName ){
|
|
char * const z = sqlite3_mprintf("%s", zName);
|
|
if( 0==cmpp_check_oom(pp, z) ){
|
|
cmpp_mfree(pi->db.zName);
|
|
pi->db.zName = z;
|
|
}
|
|
}else{
|
|
cmpp_mfree(pi->db.zName);
|
|
pi->db.zName = 0;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
bool cmpp__is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos,
|
|
bool equalIsLegal){
|
|
if( !n || n>64/*arbitrary*/ ){
|
|
if( zErrPos ) *zErrPos = 0;
|
|
return false;
|
|
}
|
|
unsigned char const * z = zName;
|
|
unsigned char const * const zEnd = zName + n;
|
|
for( ; z<zEnd; ++z ){
|
|
if( !((*z>='a' && *z<='z')
|
|
|| (*z>='A' && *z<='Z')
|
|
|| (z>zName &&
|
|
('-'==*z
|
|
/* This is gonna bite us if we extend the expresions to
|
|
support +/-. Expressions currently parse X=Y (no
|
|
spaces) as the three tokens X = Y, but we'd need to
|
|
require a space between X-Y in expressions because
|
|
'-' is a legal symbol character. i've looked at
|
|
making '-' illegal but it's just too convenient for
|
|
use in define keys. Once one is used to
|
|
tcl-style-naming of stuff, it's painful to have to go
|
|
back to snake_case.
|
|
*/
|
|
|| (*z>='0' && *z<='9')))
|
|
|| (*z>='.' && *z<='/')
|
|
|| (*z==':')
|
|
|| (*z=='_')
|
|
|| (equalIsLegal && z>zName && '='==*z)
|
|
|| (*z & 0x80)
|
|
) ){
|
|
if( zErrPos ) *zErrPos = z;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cmpp_is_legal_key(unsigned char const *zName,
|
|
cmpp_size_t n,
|
|
unsigned char const **zErrPos){
|
|
return cmpp__is_legal_key(zName, n, zErrPos, false);
|
|
}
|
|
|
|
int cmpp__legal_key_check(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_strlen_t nKey, bool permitEqualSign){
|
|
if( !ppCode ){
|
|
unsigned char const *zAt = 0;
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
if( !cmpp__is_legal_key(zKey, nKey, &zAt, permitEqualSign) ){
|
|
cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Illegal character 0x%02x in key [%.*s]",
|
|
(int)*zAt, nKey, zKey);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static cmpp_size_t cmpp__dx_guess_lineno(cmpp_dx const * dx,
|
|
unsigned char const * zWhere){
|
|
cmpp__dx_pi(dx);
|
|
unsigned char const *zB = 0;
|
|
unsigned char const *zE = 0;
|
|
if( !zWhere ) zWhere = dpi->zErrPos;
|
|
cmpp__dx_input_range(dx, &zB, &zE);
|
|
//g_warn("Whole script:\n%.*s", (int)(zE-zB), zB);
|
|
assert( zB && zE && zB<=zE );
|
|
if( !zWhere ){
|
|
if( dpi->pos.z<zE && dpi->pos.z>=zB ){
|
|
/* This is frequently at zEnd because we read line by line and
|
|
it can be at EOF at the end of that line. Similarly,
|
|
[call...] contexts have a virtual EOF at the ']'. */
|
|
zWhere = dpi->pos.z;
|
|
}else{
|
|
zWhere = dpi->dline.zBegin;
|
|
}
|
|
}
|
|
return zWhere
|
|
? 1 + cmpp_count_nl(zB, zE, zWhere, NULL)
|
|
: dpi->dline.lineNo;
|
|
}
|
|
|
|
/**
|
|
Scans [dx->pos.z,dx->zEnd) for a directive delimiter. Emits any
|
|
non-delimiter output found along the way to dx->pp's output
|
|
channel.
|
|
|
|
This updates dx->pimpl->pos.z as it goes.
|
|
|
|
If a delimiter is found, it sets *gotOne to true and updates
|
|
dx->pimpl->dline to point to the remainder of that line. On no match
|
|
*gotOne will be false and EOF will have been reached.
|
|
|
|
Returns dxppCode. If it returns non-0 then the state of dx's
|
|
tokenization pieces are unspecified. i.e. it's illegal to call this
|
|
again without a reset.
|
|
*/
|
|
static int cmpp__dx_delim_search(cmpp_dx * const dx, bool * gotOne){
|
|
if( dxppCode ) return dxppCode;
|
|
cmpp__dx_pimpl * const dxp = dx->pimpl;
|
|
if(!dxp->pos.z) dxp->pos.z = dxp->zBegin;
|
|
if( dxp->pos.z>=dxp->zEnd ){
|
|
*gotOne = false;
|
|
return 0;
|
|
}
|
|
assert( (dxp->pos.z==dxp->zBegin || dxp->pos.z[-1]=='\n')
|
|
&& "Else we've mismanaged something.");
|
|
cmpp__pi(dx->pp);
|
|
cmpp__delim const * const delim = cmpp__dx_delim(dx);
|
|
if(!delim) {
|
|
return cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"The directive delimiter stack is empty.");
|
|
}
|
|
bool const isCall = dxp->flags.nextIsCall
|
|
/* If true then this call is in response to cmpp_call_str(). This
|
|
lets us treat the whole input as arguments to the directive,
|
|
rather than looking for an EOL. This allows @[call...]@ to span
|
|
lines without backslash-escaping them. */;
|
|
unsigned char const * const zD = delim->open.z;
|
|
unsigned short const nD = delim->open.n;
|
|
unsigned char const * const zEnd = dxp->zEnd;
|
|
unsigned char const * zLeft = dxp->pos.z;
|
|
unsigned char const * z = zLeft;
|
|
#if 0
|
|
cmpp_size_t const prevLineNo = dxp->dline.lineNo;
|
|
unsigned char const * const zPrevDLineBegin =
|
|
(dxp->dline.zBegin >= dxp->zBegin
|
|
&& dxp->dline.zBegin<=zEnd)
|
|
? dxp->dline.zBegin
|
|
: 0;
|
|
#endif
|
|
assert(zD);
|
|
assert(nD);
|
|
while( z<zEnd && '\n'==*z ){
|
|
/* Skip leading newlines. We have to delay the handling of
|
|
leading whitepace until later so that:
|
|
|
|
| #if
|
|
|^^ those two spaces do not get emitted.
|
|
*/
|
|
++z;
|
|
}
|
|
#define tflush \
|
|
if( z>zEnd ) z=zEnd; \
|
|
if( z>zLeft && cmpp_out_expand(dx->pp, &pi->out, zLeft, \
|
|
(cmpp_size_t)(z-zLeft), \
|
|
cmpp_atpol_CURRENT) ){ \
|
|
return dxppCode; \
|
|
} zLeft = z
|
|
|
|
CmppDLine * const dline = &dxp->dline;
|
|
bool atBOL = true /* At the start of a line? Successful calls to
|
|
this always end at either BOL or EOF. */;
|
|
bool const alreadyGotDelim = pi->flags.fakeNextDelim;
|
|
pi->flags.fakeNextDelim = false;
|
|
assert( alreadyGotDelim ? isCall : 1 );
|
|
/*if( alreadyGotDelim ){
|
|
g_warn("alreadyGotDelim: %.*s", (int)(zEnd-z), z);
|
|
}*/
|
|
if( 0 ){
|
|
g_warn("scanning... <<%.*s...>>",
|
|
(zEnd-z)>20?20:(zEnd-z), z);
|
|
}
|
|
while( z<zEnd && !dxppCode ){
|
|
if( alreadyGotDelim ){
|
|
dline->zReallyBegin = z;
|
|
goto got_delim;
|
|
}
|
|
if( !atBOL ){
|
|
/* We're continuing the scan of a line on which the first bytes
|
|
didn't match a delimiter. */
|
|
while( z<zEnd ){
|
|
while((z<zEnd && '\n'==*z)
|
|
|| (z+1<zEnd && '\r'==*z && '\n'==z[1]) ){
|
|
z += 1 + ('\r'==*z);
|
|
atBOL = true;
|
|
}
|
|
if( atBOL ){
|
|
break;
|
|
}
|
|
++z;
|
|
}
|
|
if( !atBOL ) break;
|
|
}
|
|
if( 0 ){
|
|
g_warn("at BOL... <<%.*s...>>",
|
|
(zEnd-z) > 20 ? 20 : (zEnd-z), z);
|
|
}
|
|
|
|
/* We're at BOL. Check for a delimiter with optional leading
|
|
spaces. */
|
|
unsigned char const * const zBeforeSpace = z;
|
|
cmpp_skip_space(&z, zEnd);
|
|
int const skip = cmpp__isnl(z, zEnd);
|
|
if( skip ){
|
|
/* Special case: a line comprised solely of whitespace. If we
|
|
don't catch this here, we won't recognize a delimiter which
|
|
starts on the next line. */
|
|
z += skip;
|
|
continue;
|
|
}
|
|
if( 0 ){
|
|
g_warn("at BOL... <<%.*s...>>",
|
|
(zEnd-z) > 20 ? 20 : (zEnd-z), z);
|
|
}
|
|
if( z + nD>zEnd ){
|
|
/* Too short for a delimiter. We'll catch the z+nD==zEnd corner
|
|
case in a moment. */
|
|
z = zEnd;
|
|
break;
|
|
}
|
|
if( memcmp(z, zD, nD) ){
|
|
/* Not a delimiter. Keep trying. */
|
|
atBOL = false;
|
|
++z;
|
|
continue;
|
|
}
|
|
/* z now points to a delimiter which sits at the start of a line
|
|
(ignoring leading spaces). */
|
|
if( z>zLeft ){
|
|
/* Flush up to, but not including, any spaces we skipped. */
|
|
unsigned char const * const zx = z;
|
|
if( zBeforeSpace<z ) z = zBeforeSpace;
|
|
tflush;
|
|
z = zLeft = zx;
|
|
}
|
|
dline->zReallyBegin = z;
|
|
z += nD /* skip the delimiter */;
|
|
got_delim:
|
|
/* Set up dx->pimpl->dline to encompass the whole directive line sans
|
|
delimiter and leading spaces. */
|
|
#if 0
|
|
/* Only count from the previous dline->zBegin.
|
|
Nope. This is buggy with:
|
|
|
|
#query ...
|
|
#include ...
|
|
#/query
|
|
|
|
because #query captures the body and re-processes it.
|
|
*/
|
|
if( zPrevDLineBegin ){
|
|
dline->lineNo = prevLineNo
|
|
+ cmpp_count_nl(zPrevDLineBegin, z, z, NULL);
|
|
}else{
|
|
dline->lineNo = cmpp__dx_guess_lineno(dx, z);
|
|
}
|
|
#else
|
|
dline->lineNo = cmpp__dx_guess_lineno(dx, z);
|
|
#endif
|
|
dxp->pos.lineNo = dline->lineNo;
|
|
dline->zBegin = z
|
|
/* dx->pimpl->dline starts at the directive name and extends until the
|
|
next EOL/EOF. We don't yet know if it's a legal directive
|
|
name - cmpp_dx_next() figures that part out. */;
|
|
/* Now find the end of the line or EOF, accounting for
|
|
backslash-escaped newlines and _not_ requiring backslashes to
|
|
escape newlines inside of {...}, (...), or [...]. */
|
|
cmpp_tizer tz = cmpp_tizer_empty;
|
|
cmpp_tizer_init(&tz, &pi->err, z, (zEnd-z));
|
|
int tzrc;
|
|
unsigned int nTok = 0;
|
|
cmpp_tizer_foreach3(&tz, tzrc, cmpp_tizer_next_SKIP_C_COMMENTS){
|
|
#if 0
|
|
if( 0 ){
|
|
g_warn("tok=%s %.*s", cmpp_tt_cstr(tz.token.ttype),
|
|
(int)tz.token.n, tz.token.z);
|
|
}
|
|
#endif
|
|
if( cmpp_tt_EOL==tz.token.ttype ){
|
|
if( !isCall ){
|
|
tz.zPos = tz.token.z /* we assert this below */;
|
|
break;
|
|
}
|
|
}else{
|
|
++nTok;
|
|
}
|
|
}
|
|
if( tzrc ){
|
|
assert( dxppCode && "Expecting via tz.err" );
|
|
return dxppCode;
|
|
}
|
|
if( !nTok ){
|
|
dxserr("No directive name found after %s.", zD);
|
|
/* We could arguably treat this as no match and pass this line
|
|
through as-is but that currently sounds like a pothole. cpp
|
|
does that and i consider it a serious flaw. In the context of
|
|
cpp's early history that made some sense but it does not
|
|
here. */
|
|
break;
|
|
}
|
|
z = tz.zPos;
|
|
if(0){
|
|
g_warn("tokenized: %.*s", (z - dline->zBegin), dline->zBegin);
|
|
}
|
|
assert( z==zEnd || '\n'==*z );
|
|
dline->zEnd = z;
|
|
dxp->pos.z = dline->zEnd + 1
|
|
/* For the next call to this function, skip the trailing newline
|
|
or EOF */;
|
|
assert( dline->zBegin < dline->zEnd && "Was checked above" );
|
|
if( 0 ){
|
|
g_warn("line= %u <<%.*s>>", (dline->zEnd-dline->zBegin),
|
|
(dline->zEnd-dline->zBegin), dline->zBegin);
|
|
}
|
|
*gotOne = true;
|
|
assert( !dxppCode );
|
|
return 0;
|
|
}
|
|
/* No directives found. We're now at EOL or EOF. Flush any pending
|
|
LHS content. */
|
|
tflush;
|
|
dx->pimpl->pos.z = z;
|
|
*gotOne = false;
|
|
return dxppCode;
|
|
#undef tflush
|
|
}
|
|
|
|
int CmppKvp_parse(cmpp *pp, CmppKvp * p, unsigned char const *zKey,
|
|
cmpp_strlen_t nKey, CmppKvp_op_e opPolicy){
|
|
if(ppCode) return ppCode;
|
|
char chEq = 0;
|
|
char opLen = 0;
|
|
*p = CmppKvp_empty;
|
|
p->k.z = zKey;
|
|
p->k.n = cmpp__strlenu(zKey, nKey);
|
|
switch( opPolicy ){
|
|
case CmppKvp_op_none:// break;
|
|
case CmppKvp_op_eq1:
|
|
chEq = '=';
|
|
opLen = 1;
|
|
break;
|
|
default:
|
|
assert(!"don't use these");
|
|
/* no longer todo: ==, !=, <=, <, >, >= */
|
|
chEq = '=';
|
|
opLen = 1;
|
|
break;
|
|
}
|
|
assert( chEq );
|
|
p->op = CmppKvp_op_none;
|
|
unsigned const char * const zEnd = p->k.z + p->k.n;
|
|
for(unsigned const char * zPos = p->k.z ; *zPos && zPos<zEnd ; ++zPos) {
|
|
if( chEq==*zPos ){
|
|
if( CmppKvp_op_none==opPolicy ){
|
|
cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Illegal operator in key: %s", zKey);
|
|
}else{
|
|
p->op = CmppKvp_op_eq1;
|
|
p->k.n = (unsigned)(zPos - ustr_c(zKey));
|
|
zPos += opLen;
|
|
assert( zPos <= zEnd );
|
|
p->v.z = zPos;
|
|
p->v.n = (unsigned)(zEnd - zPos);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
cmpp__legal_key_check(pp, p->k.z, p->k.n, false);
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp_array_reserve(cmpp *pp, void **list, cmpp_size_t nDesired,
|
|
cmpp_size_t * nAlloc, unsigned sizeOfEntry){
|
|
int rc = pp ? ppCode : 0;
|
|
if( 0==rc && nDesired > *nAlloc ){
|
|
cmpp_size_t const nA = nDesired < 10 ? 10 : nDesired;
|
|
void * const p = cmpp_mrealloc(*list, sizeOfEntry * nA);
|
|
rc = cmpp_check_oom(pp, p);
|
|
if( p ){
|
|
memset((unsigned char *)p +
|
|
(sizeOfEntry * *nAlloc), 0,
|
|
sizeOfEntry * (nA - *nAlloc));
|
|
*list = p;
|
|
*nAlloc = nA;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CmppLvl * CmppLvlList_push(cmpp *pp, CmppLvlList *li){
|
|
CmppLvl * p = 0;
|
|
assert( li->list ? li->na : 0==li->na );
|
|
if( 0==ppCode
|
|
&& 0==CmppLvlList_reserve(pp, li,
|
|
cmpp__li_reserve1_size(li,5)) ){
|
|
p = li->list[li->n];
|
|
if( !p ){
|
|
p = cmpp_malloc2(pp, sizeof(*p));
|
|
}
|
|
if( p ){
|
|
li->list[li->n++] = p;
|
|
*p = CmppLvl_empty;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void CmppLvlList_pop(cmpp * const pp, CmppLvlList * const li,
|
|
CmppLvl * const lvl){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
if( lvl==li->list[li->n-1] ){
|
|
*lvl = CmppLvl_empty;
|
|
cmpp_mfree(lvl);
|
|
li->list[--li->n] = 0;
|
|
}else{
|
|
if( pp ){
|
|
cmpp_errf(pp, CMPP_RC_ASSERT,
|
|
"Misuse of %s(): not passed the top of the stack. "
|
|
"The CmppLvl stack is now out of whack.",
|
|
__func__);
|
|
}else{
|
|
cmpp__fatal("Misuse of %s(): not passed the top of the stack",
|
|
__func__);
|
|
}
|
|
/* do not free it - CmppLvlList_cleanup() will catch it. */
|
|
}
|
|
}
|
|
}
|
|
|
|
void CmppLvlList_cleanup(CmppLvlList *li){
|
|
const CmppLvlList CmppLvlList_empty = CmppLvlList_empty_m;
|
|
while( li->na ){
|
|
cmpp_mfree( li->list[--li->na] );
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = CmppLvlList_empty;
|
|
}
|
|
|
|
static inline void CmppDList_entry_clean(CmppDList_entry * const e){
|
|
assert( e && "Else internal API misuse." );
|
|
if( e->d.impl.dtor ){
|
|
e->d.impl.dtor( e->d.impl.state );
|
|
}
|
|
cmpp_mfree(e->zName);
|
|
*e = CmppDList_entry_empty;
|
|
}
|
|
|
|
#if 0
|
|
CmppDList * CmppDList_reuse(CmppDList *li){
|
|
while( li->n ){
|
|
CmppDList_entry_clean( li->list[--li->n] );
|
|
}
|
|
return li;
|
|
}
|
|
#endif
|
|
|
|
void CmppDList_cleanup(CmppDList *li){
|
|
static const CmppDList CmppDList_empty = CmppDList_empty_m;
|
|
while( li->na ){
|
|
CmppDList_entry * const dl = li->list[--li->na];
|
|
if( dl ){
|
|
CmppDList_entry_clean( dl );
|
|
cmpp_mfree( dl );
|
|
}
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = CmppDList_empty;
|
|
}
|
|
|
|
void CmppDList_unappend(CmppDList *li){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
CmppDList_entry_clean(li->list[--li->n]);
|
|
}
|
|
}
|
|
|
|
|
|
/** bsearch()/qsort() comparison for (cmpp_d**), sorting by name. */
|
|
static int CmppDList_entry_cmp_pp(const void *p1, const void *p2){
|
|
CmppDList_entry const * eL = *(CmppDList_entry const * const *)p1;
|
|
CmppDList_entry const * eR = *(CmppDList_entry const * const *)p2;
|
|
uint32_t const minN = eL->d.name.n < eR->d.name.n
|
|
? eL->d.name.n
|
|
: eR->d.name.n;
|
|
int const rc = memcmp(eL->d.name.z, eR->d.name.z, minN);
|
|
return rc ? rc : (int)eL->d.name.n - (int)eR->d.name.n;
|
|
}
|
|
|
|
static void CmppDList_sort(CmppDList * const li){
|
|
if( li->n>1 ){
|
|
qsort(li->list, li->n, sizeof(CmppDList_entry*),
|
|
CmppDList_entry_cmp_pp);
|
|
}
|
|
}
|
|
|
|
CmppDList_entry * CmppDList_append(cmpp *pp, CmppDList *li){
|
|
CmppDList_entry * p = 0;
|
|
assert( li->list ? li->na : 0==li->na );
|
|
if( 0==ppCode
|
|
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
|
|
cmpp__li_reserve1_size(li, 15),
|
|
&li->na, sizeof(p)) ){
|
|
p = li->list[li->n];
|
|
if( !p ){
|
|
li->list[li->n] = p = cmpp_malloc2(pp, sizeof(*p));
|
|
}
|
|
if( p ){
|
|
++li->n;
|
|
*p = CmppDList_entry_empty;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
CmppDList_entry * CmppDList_search(CmppDList const * li,
|
|
char const *zName){
|
|
if( li->n > 32 ){
|
|
/* ^^^^^^^^^ See notes itch.c:cmpp_itch_enum_search() */
|
|
CmppDList_entry const key = {
|
|
.d = {
|
|
.name = {
|
|
.z = zName,
|
|
.n = strlen(zName)
|
|
}
|
|
}
|
|
};
|
|
CmppDList_entry const * pKey = &key;
|
|
CmppDList_entry ** pRv
|
|
= bsearch(&pKey, li->list, li->n, sizeof(li->list[0]),
|
|
CmppDList_entry_cmp_pp);
|
|
return pRv ? *pRv : 0;
|
|
}else{
|
|
cmpp_size_t const nName = cmpp__strlen(zName, -1);
|
|
for( cmpp_size_t i = 0; i < li->n; ++i ){
|
|
CmppDList_entry * const e = li->list[i];
|
|
if( nName==e->d.name.n && 0==strcmp(zName, e->d.name.z) ){
|
|
return e;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void cmpp__delim_cleanup(cmpp__delim *d){
|
|
cmpp__delim const dd = cmpp__delim_empty_m;
|
|
cmpp_mfree(d->zOwns);
|
|
*d = dd;
|
|
assert(!d->zOwns);
|
|
assert(d->open.z);
|
|
assert(0==strcmp((char*)d->open.z, CMPP_DEFAULT_DELIM));
|
|
assert(d->open.n == sizeof(CMPP_DEFAULT_DELIM)-1);
|
|
}
|
|
|
|
cmpp__delim * cmpp__delim_list_push(cmpp *pp, cmpp__delim_list *li){
|
|
cmpp__delim * p = 0;
|
|
assert( li->list ? li->na : 0==li->na );
|
|
if( 0==ppCode
|
|
&& 0==cmpp_array_reserve(pp, (void **)&li->list,
|
|
cmpp__li_reserve1_size(li,4),
|
|
&li->na, sizeof(cmpp__delim)) ){
|
|
p = &li->list[li->n++];
|
|
*p = cmpp__delim_empty;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void cmpp__delim_list_cleanup(cmpp__delim_list *li){
|
|
while( li->na ) cmpp__delim_cleanup(li->list + --li->na);
|
|
cmpp_mfree(li->list);
|
|
*li = cmpp__delim_list_empty;
|
|
}
|
|
|
|
void cmpp__dx_input_range(cmpp_dx const * dx, unsigned char const **zBegin,
|
|
unsigned char const **zEnd){
|
|
cmpp_dx const * up;
|
|
while( (up = dx->pimpl->up) ){
|
|
/* If up lives in the same byte range, use it for purposes of
|
|
figuring out the source range. */
|
|
if( dx->pimpl->zBegin>=up->pimpl->zBegin
|
|
&& dx->pimpl->zEnd<=up->pimpl->zEnd ){
|
|
dx = up;
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
*zBegin = dx->pimpl->zBegin;
|
|
*zEnd = dx->pimpl->zEnd;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_next)(cmpp_dx * const dx, bool * pGotOne){
|
|
if( dxppCode ) return dxppCode;
|
|
cmpp__dx_pimpl * const dpi = dx->pimpl;
|
|
CmppDLine * const dline = &dpi->dline;
|
|
if( !dpi->zBegin ){
|
|
*pGotOne = false;
|
|
return 0;
|
|
}
|
|
assert(dpi->zEnd);
|
|
assert(dpi->zEnd > dpi->zBegin);
|
|
*pGotOne = false;
|
|
dpi->zErrPos = dline->zBegin;
|
|
cmpp_dx__reset(dx);
|
|
bool foundDelim = false;
|
|
if( cmpp__dx_delim_search(dx, &foundDelim) || !foundDelim ){
|
|
return dxppCode;
|
|
}
|
|
if( cmpp__args_init(dx->pp, &dpi->args) ){
|
|
return dxppCode;
|
|
}
|
|
cmpp_skip_space( &dline->zBegin, dline->zEnd );
|
|
g_debug(dx->pp,2,("Directive @ line %u: <<%.*s>>\n",
|
|
dline->lineNo,
|
|
(int)(dline->zEnd-dline->zBegin), dline->zBegin));
|
|
/* Normalize the directive's line and parse arguments */
|
|
const unsigned lineLen = (unsigned)(dline->zEnd - dline->zBegin);
|
|
if(!lineLen){
|
|
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
|
|
"Line #%u has no directive after %s",
|
|
dline->lineNo, cmpp_dx_delim(dx));
|
|
}
|
|
|
|
unsigned char const * zi = dline->zBegin /* Start of input */;
|
|
unsigned char const * ziEnd = dline->zEnd /* Input EOF */;
|
|
cmpp_b * const bufLine = cmpp_b_reuse(&dpi->buf.line)
|
|
/* Slightly-transformed copy of the input. */;
|
|
if( cmpp_b_reserve3(dx->pp, bufLine, lineLen+1) ){
|
|
return dxppCode;
|
|
}
|
|
unsigned char * zo = bufLine->z /* Start of output */;
|
|
unsigned char const * const zoEnd =
|
|
zo + bufLine->nAlloc /* Output EOF. */;
|
|
g_debug(dx->pp,2,("Directive @ line %u len=%u <<%.*s>>\n",
|
|
dline->lineNo, lineLen, lineLen, dline->zBegin));
|
|
if( 0 ){
|
|
g_warn("Directive @ line %u len=%u <<%.*s>>\n",
|
|
dline->lineNo, lineLen, lineLen, dline->zBegin);
|
|
}
|
|
//memset(bufLine->z, 0, bufLine->nAlloc);
|
|
//#define out(CH) if(zo==zoEnd) break; (*zo++)=CH
|
|
#define out(CH) assert(zo<zoEnd); (*zo++)=CH
|
|
/*
|
|
bufLine is now populated with a copy of the whole input line.
|
|
Now normalize that buffer a bit before trying to parse it.
|
|
*/
|
|
unsigned char const * zEsc = 0;
|
|
for( ; zi<ziEnd && *zi && zo<zoEnd;
|
|
++zi ){
|
|
dpi->zErrPos = zi;
|
|
/* Write the line to bufLine for the upcoming args parsing to deal
|
|
with. Strip backslashes from backslash-escaped newlines. We
|
|
leave the newlines intact so that downstream error reporting
|
|
can get more precise location info. Backslashes which do not
|
|
precede a newline are retained.
|
|
|
|
2026-09-01: we really don't want this for V4 args, but at this
|
|
point we don't know which directive we're looking at, so don't
|
|
know if it has v4-style args. It "would be cool" to move the
|
|
search for the directive up, "the problem" being that we have
|
|
to NUL-terminate it for the search so we need bufLine, anyway.
|
|
*/
|
|
switch((int)*zi){
|
|
case (int)'\\':
|
|
if( !zi[1] || zi==ziEnd-1 ){
|
|
// special case: ending input with a backslash
|
|
out(*zi);
|
|
zEsc = 0;
|
|
}else if( zEsc ){
|
|
assert( zEsc==zi-1 );
|
|
/* Put them both back. */
|
|
out(*zEsc);
|
|
out(*zi);
|
|
zEsc = 0;
|
|
}else{
|
|
zEsc = zi;
|
|
}
|
|
break;
|
|
case (int)'\n':
|
|
out(*zi);
|
|
zEsc = 0;
|
|
break;
|
|
default:
|
|
if(zEsc){
|
|
assert( zEsc==zi-1 );
|
|
out(*zEsc);
|
|
zEsc = 0;
|
|
}
|
|
out(*zi);
|
|
break;
|
|
}
|
|
}
|
|
if( zo>=zoEnd ){
|
|
return cmpp_dx_err(dx, CMPP_RC_RANGE,
|
|
"Ran out of argument-processing space.");
|
|
}
|
|
bufLine->n = (cmpp_size_t)(zo - bufLine->z);
|
|
if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
|
|
*zo = 0;
|
|
#undef out
|
|
if( 0 ) g_warn("bufLine.n=%u line=<<%s>>", bufLine->n, bufLine->z);
|
|
/* Line has now been normalized into bufLine->z. */
|
|
//unsigned char *zNul = 0;
|
|
for( zo = bufLine->z; zo<zoEnd && *zo; ++zo ){
|
|
/* NULL-terminate the directive so we can search for it. */
|
|
if( cmpp_isspace(*zo) ){
|
|
//zNul = zo;
|
|
*zo = 0;
|
|
break;
|
|
}
|
|
}
|
|
unsigned char * const zDirective = bufLine->z;
|
|
dx->d = cmpp__d_search3(dx->pp, (char const *)zDirective,
|
|
cmpp__d_search3_F_ALL);
|
|
if( dxppCode ){
|
|
return dxppCode;
|
|
}else if(!dx->d){
|
|
return cmpp_dx_err(dx, CMPP_RC_NOT_FOUND,
|
|
"Unknown directive at line %"
|
|
CMPP_SIZE_T_PFMT ": %.*s\n",
|
|
cmpp__dx_guess_lineno(dx, 0),
|
|
(int)bufLine->n, bufLine->z);
|
|
}
|
|
bool const v4 = (cmpp_f_F_ARGS_V4 & dx->d->flags);
|
|
//if( v4 && zNul ) *zNul = ' '/*undo the NULL terminator*/;
|
|
dpi->zErrPos = 0;
|
|
assert( zDirective == bufLine->z );
|
|
const bool isDxCall
|
|
= dpi->args.pimpl->isDxCall
|
|
= dpi->flags.nextIsCall;
|
|
dpi->flags.nextIsCall = dx->pp->pimpl->flags.nextIsCall = false;
|
|
if( isDxCall ){
|
|
if( cmpp_f_F_NO_CALL & dx->d->flags ){
|
|
return cmpp_dx_err(dx, CMPP_RC_SYNTAX,
|
|
"%s%s cannot be used in a [call] context.",
|
|
cmpp_dx_delim(dx),
|
|
dx->d->name.z);
|
|
}
|
|
}else if( cmpp_f_F_CALL_ONLY & dx->d->flags ){
|
|
return cmpp_dx_err(dx, CMPP_RC_TYPE,
|
|
"'%s' is a call-only directive, "
|
|
"not legal here.", dx->d->name.z);
|
|
}
|
|
if( v4 ){
|
|
/*
|
|
For V4 ignore all of that writing to bufLine (except that we
|
|
needed it in order to find the directive).
|
|
|
|
a) Include the directive name in the args.
|
|
|
|
b) We also want the cmpp_arg::token entries to point back into
|
|
dx's memory so that we can us it to calculate error locations
|
|
precisely.
|
|
*/
|
|
dx->args.z = dline->zBegin;
|
|
dx->args.nz = dline->zEnd - dline->zBegin;
|
|
}else if( bufLine->n > dx->d->name.n ){
|
|
dx->args.z = zDirective + dx->d->name.n + 1;
|
|
assert( dx->args.z > bufLine->z );
|
|
assert( dx->args.z <= bufLine->z+bufLine->n );
|
|
dx->args.nz = cmpp__strlenu(dx->args.z, -1);
|
|
assert( bufLine->nAlloc > dx->args.nz );
|
|
}else{
|
|
dx->args.z = ustr_c("\0");
|
|
dx->args.nz = 0;
|
|
}
|
|
if( 0 ){
|
|
g_warn("bufLine.n=%u zArgs offset=%u line=<<%s>>\nzArgs=<<%s>>",
|
|
bufLine->n, (dx->args.z - zDirective), bufLine->z, dx->args.z);
|
|
}
|
|
cmpp_skip_snl(&dx->args.z, dx->args.z + dx->args.nz);
|
|
if(0){
|
|
g_warn("zArgs %u = <<%.*s>>", (int)dx->args.nz,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}
|
|
assert( !dpi->buf.argsRaw.n );
|
|
if( dx->args.nz ){
|
|
if( 0 ){
|
|
g_warn("lineLen=%u zargs len=%u: [%.*s]\n",
|
|
(unsigned)lineLen,
|
|
(int)dx->args.nz, (int)dx->args.nz,
|
|
dx->args.z
|
|
);
|
|
}
|
|
if( cmpp_b_append4(dx->pp, &dpi->buf.argsRaw,
|
|
dx->args.z, dx->args.nz) ){
|
|
return dxppCode;
|
|
}
|
|
}
|
|
assert( !dpi->args.arg0 );
|
|
assert( !dpi->args.argc );
|
|
assert( !dpi->args.pimpl->argOut.n );
|
|
assert( !dpi->args.pimpl->argli.n );
|
|
assert( dx->args.z );
|
|
if( !cmpp_dx_is_eliding(dx)
|
|
|| 0!=(cmpp_f_F_FLOW_CONTROL & dx->d->flags) ){
|
|
if( (cmpp_f_F_ARGS_LIST & dx->d->flags)
|
|
|| (v4 && !(cmpp_f_F_ARGS_RAW & dx->d->flags)) ){
|
|
cmpp_dx_args_parse(dx, &dpi->args);
|
|
//g_warn("parsed arg rc=%d count=%d", dxppCode, dpi->args.argc);
|
|
assert( v4 ? (dpi->args.argc>0 || dxppCode) : 1 );
|
|
if( v4 ){
|
|
assert( dpi->args.argc>0 || dxppCode );
|
|
dpi->args.arg0 = 0;
|
|
}
|
|
}else if( cmpp_f_F_ARGS_RAW & dx->d->flags ){
|
|
/* Treat rest of line as one token */
|
|
if( !dpi->buf.argsRaw.z
|
|
/*ensure it's alloced to simplify directives which use it.
|
|
We won't need this if/when we can get all of the
|
|
directives ported from ARGS_LIST to ARGS_V4, as we can then
|
|
instead set arg->z="" below. .*/
|
|
&& cmpp_b_append4(dx->pp, &dpi->buf.argsRaw, "", 0) ){
|
|
return dxppCode;
|
|
}
|
|
cmpp_arg * const arg =
|
|
CmppArgList_append(dx->pp, &dpi->args.pimpl->argli);
|
|
if( !arg ) return dxppCode;
|
|
arg->ttype = cmpp_tt_RawLine;
|
|
dpi->args.arg0 = arg;
|
|
dpi->args.argc = 1;
|
|
assert( dpi->buf.argsRaw.z );
|
|
arg->z = dpi->buf.argsRaw.n ? dpi->buf.argsRaw.z : ustr_c("");
|
|
arg->n = dpi->buf.argsRaw.n;
|
|
//g_warn("arg->n/z=%u %s", (unsigned)arg->n, arg->z);
|
|
}
|
|
}
|
|
if( 0==dxppCode ){
|
|
dx->args.arg0 = dpi->args.arg0;
|
|
dx->args.argc = dpi->args.argc;
|
|
*pGotOne = true;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_d_register)(cmpp * pp, cmpp_d_reg const * r,
|
|
cmpp_d ** dOut){
|
|
CmppDList_entry * e1 = 0, * e2 = 0;
|
|
bool const isCallOnly =
|
|
(cmpp_f_F_CALL_ONLY & r->opener.flags);
|
|
if( ppCode ){
|
|
goto end;
|
|
}
|
|
if( (cmpp_f_F_NOT_IN_SAFEMODE & (r->opener.flags | r->closer.flags))
|
|
&& (cmpp_ctor_F_SAFEMODE & pp->pimpl->flags.ctorFlags) ){
|
|
cmpp__err(pp, CMPP_RC_ACCESS,
|
|
"Directive %s%s flag cmpp_f_F_NOT_IN_SAFE_MODE is set "
|
|
"and the preprocessor is running in safe mode.",
|
|
cmpp__pp_zdelim(pp), r->name);
|
|
goto end;
|
|
}
|
|
if( isCallOnly && r->closer.f ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Call-only directives may not have a closing directive.");
|
|
goto end;
|
|
}
|
|
#if 0
|
|
if( pp->pimpl->dx ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Directives may not be added while a "
|
|
"directive is running."
|
|
/* because that might reallocate being-run directives.
|
|
2025-10-25: that's since been resolved but we need a
|
|
use case before enabling this.
|
|
*/);
|
|
goto end;
|
|
}
|
|
#endif
|
|
cmpp_size_t const nName = strlen(r->name);
|
|
if( !pp->pimpl->flags.isInternalDirectiveReg
|
|
&& !cmpp__is_legal_key(ustr_c(r->name), nName, NULL, false) ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"\"%s\" is not a legal directive name.",
|
|
r->name);
|
|
goto end;
|
|
}
|
|
if( cmpp__d_search(pp, r->name) ){
|
|
cmpp__err(pp, CMPP_RC_ALREADY_EXISTS,
|
|
"Directive name '%s' is already in use.",
|
|
r->name);
|
|
goto end;
|
|
}
|
|
e1 = CmppDList_append(pp, &pp->pimpl->d.list);
|
|
if( !e1 ) goto end;
|
|
e1->d.impl.f = r->opener.f;
|
|
e1->d.impl.state = r->state;
|
|
e1->d.impl.dtor = r->dtor;
|
|
if( pp->pimpl->flags.isInternalDirectiveReg ){
|
|
e1->d.flags = r->opener.flags;
|
|
}else{
|
|
e1->d.flags = r->opener.flags & cmpp_f_F_MASK;
|
|
}
|
|
e1->d.flags |= cmpp_f_F_NEEDS_DX;
|
|
e1->zName = sqlite3_mprintf("%.*s", (int)nName, r->name);
|
|
if( 0==cmpp_check_oom(pp, e1->zName) ){
|
|
e1->d.name.z = e1->zName;
|
|
e1->d.name.n = nName;
|
|
if( r->closer.f
|
|
&& (e2 = CmppDList_append(pp, &pp->pimpl->d.list)) ){
|
|
e2->d.impl.f = r->closer.f;
|
|
e2->d.impl.state = r->state;
|
|
if( pp->pimpl->flags.isInternalDirectiveReg ){
|
|
e2->d.flags = r->closer.flags;
|
|
}else{
|
|
e2->d.flags = r->closer.flags & cmpp_f_F_MASK;
|
|
}
|
|
e2->d.flags |= cmpp_f_F_NEEDS_DX;
|
|
e1->d.closer = &e2->d;
|
|
e2->zName = sqlite3_mprintf("/%s", r->name);
|
|
if( 0==cmpp_check_oom(pp, e2->zName) ){
|
|
e2->d.name.z = e2->zName;
|
|
e2->d.name.n = e1->d.name.n + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
end:
|
|
if( ppCode ){
|
|
if( e2 ) CmppDList_unappend(&pp->pimpl->d.list);
|
|
if( e1 ) CmppDList_unappend(&pp->pimpl->d.list);
|
|
else if( r->dtor ){
|
|
r->dtor( r->state );
|
|
}
|
|
}else{
|
|
/* TODO (2026-09-22): delay sort under search. Except then
|
|
the list is const. */
|
|
CmppDList_sort(&pp->pimpl->d.list);
|
|
if( dOut ){
|
|
*dOut = &e1->d;
|
|
}
|
|
if( 0 ){
|
|
g_warn("Registered: %s%s%s", e1->zName,
|
|
e2 ? " and " : "",
|
|
e2 ? e2->zName : "");
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_consume)(cmpp_dx * const dx, cmpp_outputer * const os,
|
|
cmpp_d const * const * const dClosers,
|
|
unsigned nClosers,
|
|
cmpp_flag32_t flags){
|
|
assert( !dxppCode );
|
|
bool gotOne = false;
|
|
cmpp_outputer const oldOut = dx->pp->pimpl->out;
|
|
bool const allowOtherDirectives =
|
|
(flags & cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
cmpp_d const * const d = cmpp_dx_d(dx);
|
|
cmpp_size_t const lineNo = dx->pimpl->dline.lineNo;
|
|
bool const pushAt = (cmpp_dx_consume_F_ATPOL_OFF & flags);
|
|
if( pushAt && cmpp_atpol_push(dx->pp, cmpp_atpol_OFF) ){
|
|
return dxppCode;
|
|
}
|
|
if( os ){
|
|
dx->pp->pimpl->out = *os;
|
|
}
|
|
while( 0==dxppCode
|
|
&& 0==cmpp_dx_next(dx, &gotOne)
|
|
/* ^^^^^^^ resets dx->d, dx->pimpl->args and friends */ ){
|
|
if( !gotOne ){
|
|
dxserr("No closing directive found for "
|
|
"%s%s opened on line %" CMPP_SIZE_T_PFMT ".",
|
|
cmpp_dx_delim(dx), d->name.z, lineNo);
|
|
}else{
|
|
cmpp_d const * const d2 = cmpp_dx_d(dx);
|
|
gotOne = false;
|
|
for( unsigned i = 0; !gotOne && i < nClosers; ++i ){
|
|
gotOne = d2==dClosers[i];
|
|
}
|
|
//g_warn("gotOne=%d d2=%s", gotOne, d2->name.z);
|
|
if( gotOne ) break;
|
|
else if( !allowOtherDirectives ){
|
|
dxserr("%s%s at line %" CMPP_SIZE_T_PFMT
|
|
" may not contain %s%s.",
|
|
cmpp_dx_delim(dx), d->name.z, lineNo,
|
|
cmpp_dx_delim(dx), d2->name.z);
|
|
}else{
|
|
cmpp_dx_process(dx);
|
|
}
|
|
}
|
|
}
|
|
if( pushAt ){
|
|
cmpp_atpol_pop(dx->pp);
|
|
}
|
|
if( os ){
|
|
dx->pp->pimpl->out = oldOut;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_consume_v2)(
|
|
cmpp_dx * const dx, cmpp_outputer * const os,
|
|
cmpp_d const * const * const dClosers,
|
|
unsigned nClosers, cmpp_flag32_t flags,
|
|
cmpp_dx_consumed * const con){
|
|
memset(con, 0, sizeof(*con));
|
|
con->beginDirective = dx->pimpl->dline.zBegin;
|
|
con->beginContent = dx->pimpl->pos.z;
|
|
assert( con->beginContent > con->beginDirective );
|
|
con->dOpen = dx->d;
|
|
int const rc = cmpp_dx_consume(dx, os, dClosers, nClosers,
|
|
flags);
|
|
con->endContent = dx->pimpl->dline.zReallyBegin /* of the closer */;
|
|
con->endDirective = dx->pimpl->pos.z;
|
|
con->dClose = dx->d;
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_consume_b)(cmpp_dx * const dx, cmpp_b * const b,
|
|
cmpp_d const * const * dClosers,
|
|
unsigned nClosers, cmpp_flag32_t flags){
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
oss.state = b;
|
|
return cmpp_dx_consume(dx, &oss, dClosers, nClosers, flags);
|
|
}
|
|
|
|
char const * cmpp__atpol_name(cmpp *pp, cmpp_atpol_e p){
|
|
again:
|
|
switch(p){
|
|
case cmpp_atpol_CURRENT:{
|
|
if( pp ){
|
|
assert( p!=cmpp__policy(pp, at) );
|
|
p = cmpp__policy(pp, at);
|
|
pp = 0;
|
|
goto again;
|
|
}
|
|
return NULL;
|
|
}
|
|
case cmpp_atpol_invalid: return NULL;
|
|
case cmpp_atpol_OFF: return "off";
|
|
case cmpp_atpol_RETAIN: return "retain";
|
|
case cmpp_atpol_ELIDE: return "elide";
|
|
case cmpp_atpol_ERROR: return "error";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
cmpp_atpol_e cmpp_atpol_from_str(cmpp * const pp, char const *z){
|
|
cmpp_atpol_e rv = cmpp_atpol_invalid;
|
|
if( 0==strcmp(z, "retain") ) rv = cmpp_atpol_RETAIN;
|
|
else if( 0==strcmp(z, "elide") ) rv = cmpp_atpol_ELIDE;
|
|
else if( 0==strcmp(z, "error") ) rv = cmpp_atpol_ERROR;
|
|
else if( 0==strcmp(z, "off") ) rv = cmpp_atpol_OFF;
|
|
if( pp ){
|
|
if( cmpp_atpol_invalid==rv
|
|
&& 0==strcmp(z, "current") ){
|
|
rv = cmpp__policy(pp,at);
|
|
}else if( cmpp_atpol_invalid==rv ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"Invalid @ policy value: %s."
|
|
" Try one of retain|elide|error|off|current.", z);
|
|
}else{
|
|
cmpp__policy(pp,at) = rv;
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
cmpp__PodList_impl(PodList__atpol,cmpp_atpol_e)
|
|
cmpp__PodList_impl(PodList__unpol,cmpp_unpol_e)
|
|
|
|
int cmpp_atpol_push(cmpp * pp, cmpp_atpol_e pol){
|
|
if( cmpp_atpol_CURRENT==pol ) pol = cmpp__policy(pp,at);
|
|
assert( cmpp_atpol_CURRENT!=pol && "Else internal mismanagement." );
|
|
if( 0==PodList__atpol_push(pp, &cmpp__epol(pp,at), pol)
|
|
&& 0!=cmpp_atpol_set(pp, pol)/*for validation*/ ){
|
|
PodList__atpol_pop(&cmpp__epol(pp,at));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp_atpol_pop(cmpp * pp){
|
|
assert( cmpp__epol(pp,at).n );
|
|
if( cmpp__epol(pp,at).n ){
|
|
PodList__atpol_pop(&cmpp__epol(pp,at));
|
|
}else if( !ppCode ){
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"%s() called when no cmpp_atpol_push() is active.",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
int cmpp_unpol_push(cmpp * pp, cmpp_unpol_e pol){
|
|
if( 0==PodList__unpol_push(pp, &cmpp__epol(pp,un), pol)
|
|
&& cmpp_unpol_set(pp, pol)/*for validation*/ ){
|
|
PodList__unpol_pop(&cmpp__epol(pp,un));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
void cmpp_unpol_pop(cmpp * pp){
|
|
assert( cmpp__epol(pp,un).n );
|
|
if( cmpp__epol(pp,un).n ){
|
|
PodList__unpol_pop(&cmpp__epol(pp,un));
|
|
}else if( !ppCode ){
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"%s() called when no cmpp_unpol_push() is active.",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_atpol_e, cmpp_atpol_get)(cmpp const * const pp){
|
|
return cmpp__epol(pp,at).na
|
|
? cmpp__policy(pp,at) : cmpp_atpol_DEFAULT;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_atpol_set)(cmpp * const pp, cmpp_atpol_e pol){
|
|
if( 0==ppCode ){
|
|
switch(pol){
|
|
case cmpp_atpol_OFF:
|
|
case cmpp_atpol_RETAIN:
|
|
case cmpp_atpol_ELIDE:
|
|
case cmpp_atpol_ERROR:
|
|
assert(cmpp__epol(pp,at).na);
|
|
cmpp__policy(pp,at) = pol;
|
|
break;
|
|
case cmpp_atpol_CURRENT:
|
|
break;
|
|
default:
|
|
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
|
|
(int)pol);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
|
|
#if 0
|
|
char const * cmpp__unpol_name(cmpp *pp, cmpp_unpol_e p){
|
|
(void)pp;
|
|
switch(p){
|
|
case cmpp_unpol_NULL: return "null";
|
|
case cmpp_unpol_ERROR: return "error";
|
|
case cmpp_unpol_invalid: return NULL;
|
|
}
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
cmpp_unpol_e cmpp_unpol_from_str(cmpp * const pp,
|
|
char const *z){
|
|
cmpp_unpol_e rv = cmpp_unpol_invalid;
|
|
if( 0==strcmp(z, "null") ) rv = cmpp_unpol_NULL;
|
|
else if( 0==strcmp(z, "error") ) rv = cmpp_unpol_ERROR;
|
|
if( pp ){
|
|
if( cmpp_unpol_invalid==rv
|
|
&& 0==strcmp(z, "current") ){
|
|
rv = cmpp__policy(pp,un);
|
|
}else if( cmpp_unpol_invalid==rv ){
|
|
cmpp__err(pp, CMPP_RC_RANGE,
|
|
"Invalid undefined key policy value: %s."
|
|
" Try one of null|error.", z);
|
|
}else{
|
|
cmpp_unpol_set(pp, rv);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_unpol_e, cmpp_unpol_get)(cmpp const * const pp){
|
|
return cmpp__epol(pp,un).na
|
|
? cmpp__policy(pp,un) : cmpp_unpol_DEFAULT;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_unpol_set)(cmpp * const pp, cmpp_unpol_e pol){
|
|
if( 0==ppCode ){
|
|
switch(pol){
|
|
case cmpp_unpol_NULL:
|
|
case cmpp_unpol_ERROR:
|
|
cmpp__policy(pp,un) = pol;
|
|
break;
|
|
default:
|
|
cmpp__err(pp, CMPP_RC_RANGE, "Invalid policy value: %d",
|
|
(int)pol);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
Reminders to self re. savepoint tracking:
|
|
|
|
cmpp_dx tracks per-input-source savepoints. We always want
|
|
savepoints which are created via scripts to be limited to that
|
|
script. cmpp instances, on the other hand, don't care about that.
|
|
|
|
Thus we have two different APIs for starting/ending savepoints.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_sp_begin)(cmpp *pp){
|
|
if( 0==ppCode ){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spBegin, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
|
|
++pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_begin(cmpp_dx * const dx){
|
|
if( 0==dxppCode && 0==cmpp_sp_begin(dx->pp) ){
|
|
++dx->pimpl->nSavepoint;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_sp_rollback)(cmpp *const pp){
|
|
/* Remember that rollback must (mostly) ignore the
|
|
pending error state. */
|
|
if( !pp->pimpl->flags.nSavepoint ){
|
|
if( 0==ppCode ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Cannot roll back: no active savepoint");
|
|
}
|
|
}else{
|
|
sqlite3_stmt * q = cmpp__stmt(pp, CmppStmt_spRollback, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
|
|
q = cmpp__stmt(pp, CmppStmt_spRelease, true);
|
|
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
|
|
--pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_rollback(cmpp_dx * const dx){
|
|
/* Remember that rollback must (mostly) ignore the pending error state. */
|
|
if( !dx->pimpl->nSavepoint ){
|
|
if( 0==dxppCode ){
|
|
cmpp_dx_err(dx, CMPP_RC_MISUSE,
|
|
"Cannot roll back: no active savepoint");
|
|
}
|
|
}else{
|
|
cmpp_sp_rollback(dx->pp);
|
|
--dx->pimpl->nSavepoint;
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_sp_commit)(cmpp * const pp){
|
|
if( 0==ppCode ){
|
|
if( !pp->pimpl->flags.nSavepoint ){
|
|
cmpp__err(pp, CMPP_RC_MISUSE,
|
|
"Cannot commit: no active savepoint");
|
|
}else{
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_spRelease, true);
|
|
assert( q || !"db init would have otherwise failed");
|
|
if( q && SQLITE_DONE==cmpp_stmt_step(pp, q, true) ){
|
|
--pp->pimpl->flags.nSavepoint;
|
|
}
|
|
}
|
|
}else{
|
|
cmpp_sp_rollback(pp);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__dx_sp_commit(cmpp_dx * const dx){
|
|
if( 0==dxppCode ){
|
|
if( !dx->pimpl->nSavepoint ){
|
|
cmpp_dx_err(dx, CMPP_RC_MISUSE,
|
|
"Cannot commit: no active savepoint");
|
|
}else if( 0==cmpp_sp_commit(dx->pp) ){
|
|
--dx->pimpl->nSavepoint;
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
static void cmpp__dx_pimpl_reuse(cmpp__dx_pimpl *p){
|
|
#if 0
|
|
/* no: we need most of the state to remain
|
|
intact. */
|
|
cmpp__dx_pimpl const tmp = *p;
|
|
*p = cmpp__dx_pimpl_empty;
|
|
p->buf = tmp.buf;
|
|
p->args = tmp.args;
|
|
#endif
|
|
cmpp_b_reuse(&p->buf.line);
|
|
cmpp_b_reuse(&p->buf.argsRaw);
|
|
cmpp__args_reuse(&p->args);
|
|
}
|
|
|
|
void cmpp__dx_pimpl_cleanup(cmpp__dx_pimpl *p){
|
|
cmpp_b_clear(&p->buf.line);
|
|
cmpp_b_clear(&p->buf.argsRaw);
|
|
cmpp_args_cleanup(&p->args);
|
|
*p = cmpp__dx_pimpl_empty;
|
|
}
|
|
|
|
void cmpp_dx__reset(cmpp_dx * const dx){
|
|
dx->args = cmpp_dx_empty.args;
|
|
cmpp__dx_pimpl_reuse(dx->pimpl);
|
|
dx->d = 0;
|
|
//no: dx->sourceName = 0;
|
|
}
|
|
|
|
void cmpp_dx_cleanup(cmpp_dx * const dx){
|
|
unsigned prev = 0;
|
|
CmppLvlList_cleanup(&dx->pimpl->dxLvl);
|
|
while( dx->pimpl->nSavepoint && prev!=dx->pimpl->nSavepoint ){
|
|
prev = dx->pimpl->nSavepoint;
|
|
cmpp__dx_sp_rollback(dx);
|
|
}
|
|
cmpp__dx_pimpl_cleanup(dx->pimpl);
|
|
memset(dx, 0, sizeof(*dx));
|
|
}
|
|
|
|
#if 2 == CMPP__FIND_CLOSING
|
|
int cmpp__find_closing2(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t * pNl){
|
|
unsigned char const * z = *zPos;
|
|
unsigned char const opener = *z;
|
|
unsigned char const closer = cmpp__closer(opener);
|
|
int count = 1;
|
|
int rc = 0;
|
|
if( !closer ){
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Invalid character (0x%02x) for %s().",
|
|
(int)opener, __func__);
|
|
}
|
|
for( ++z; z < zEnd; ++z ){
|
|
if( closer == *z && 0==--count ){
|
|
/* Have to check this first for the case of "" and ''. */
|
|
break;
|
|
}else if( opener == *z ){
|
|
++count;
|
|
}else if( pNl && '\n'==*z ){
|
|
++*pNl;
|
|
}
|
|
}
|
|
if( closer!=*z ){
|
|
if( 0 ){
|
|
g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
|
|
(zEnd - *zPos), *zPos);
|
|
}
|
|
//assert(!"here");
|
|
rc = cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Unbalanced %c%c: %.*s",
|
|
opener, closer,
|
|
(int)(z-*zPos), *zPos);
|
|
assert( CMPP_RC_SYNTAX==rc );
|
|
}else{
|
|
if( 0 ){
|
|
g_warn("group: n=%u <<%.*s>>", (z + 1 - *zPos), (z +1 - *zPos), *zPos);
|
|
}
|
|
*zPos = z;
|
|
}
|
|
return rc;
|
|
}
|
|
#else
|
|
int cmpp__find_closing3(cmpp *pp,
|
|
unsigned char const **zPos,
|
|
unsigned char const *zEnd,
|
|
cmpp_size_t * pNl){
|
|
unsigned char const * z = *zPos;
|
|
unsigned char const opener = *z;
|
|
unsigned char const closer = cmpp__closer(opener);
|
|
bool esc = false;
|
|
int count = 1;
|
|
int rc = 0;
|
|
if( !closer ){
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Invalid character (0x%02x) for %s().",
|
|
(int)opener, __func__);
|
|
}
|
|
for( ++z ; z < zEnd; ++z ){
|
|
switch( *z ){
|
|
case '\\':
|
|
esc = !esc;
|
|
continue;
|
|
case '\n':
|
|
if( pNl && '\n'==*z ) ++*pNl;
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
default:
|
|
if( esc ){
|
|
esc = 0;
|
|
}else if( closer == *z && 0==--count ){
|
|
/* Have to check this first for the case of "" and ''. */
|
|
goto out;
|
|
}else if( opener == *z ){
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
if( z>=zEnd || count ){
|
|
if( 0 ){
|
|
g_warn("Closer=%dd Full range: <<%.*s>>", (int)*z,
|
|
(zEnd - *zPos), *zPos);
|
|
g_warn("Closer=captured: <<%.*s>>",
|
|
(z - *zPos), z);
|
|
}
|
|
//assert(!"here");
|
|
rc = cmpp__err(pp, CMPP_RC_SYNTAX,
|
|
"Unbalanced %c%c: %.*s",
|
|
opener, closer,
|
|
(int)(z-*zPos), *zPos);
|
|
assert( CMPP_RC_SYNTAX==rc );
|
|
}else{
|
|
if( 0 ){
|
|
g_warn("group: n=%u <<%.*s>>",
|
|
(z + 1 - *zPos), (z +1 - *zPos), *zPos);
|
|
}
|
|
*zPos = z;
|
|
}
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
cmpp_tt_e cmpp__tt_for_sqlite(int sqType){
|
|
cmpp_tt_e rv;
|
|
switch( sqType ){
|
|
case SQLITE_INTEGER: rv = cmpp_tt_IntDec; break;
|
|
case SQLITE_NULL: rv = cmpp_tt_Null; break;
|
|
default: rv = cmpp_tt_StringDQ; break;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int cmpp__define_from_row(cmpp * const pp, sqlite3_stmt * const q,
|
|
bool defineIfNoRow, cmpp_b * bCol0){
|
|
if( 0==ppCode ){
|
|
int const nCol = sqlite3_column_count(q);
|
|
assert( sqlite3_data_count(q)>0 || defineIfNoRow);
|
|
/* Create a #define for each column */
|
|
bool const hasRow = sqlite3_data_count(q)>0;
|
|
if( bCol0 ) cmpp_b_reuse(bCol0);
|
|
for( int i = 0; !ppCode && i < nCol; ++i ){
|
|
char const * const zCol = sqlite3_column_name(q, i);
|
|
if( hasRow ){
|
|
unsigned char const * const zVal = sqlite3_column_text(q, i);
|
|
int const nVal = sqlite3_column_bytes(q, i);
|
|
cmpp_tt_e const ttype =
|
|
cmpp__tt_for_sqlite(sqlite3_column_type(q,i));
|
|
cmpp__define2(pp, ustr_c(zCol), -1, zVal, nVal, ttype);
|
|
if( bCol0 ){
|
|
cmpp_b_append4(pp, bCol0, zVal, nVal);
|
|
}
|
|
}else if(defineIfNoRow){
|
|
cmpp__define2(pp, ustr_c(zCol), -1, ustr_c(""), 0, cmpp_tt_Null);
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
cmpp_d const * cmpp__d_search(cmpp *pp, const char *zName){
|
|
cmpp_d const * d = 0;//cmpp__d_search(zName);
|
|
if( !d ){
|
|
CmppDList_entry const * e =
|
|
CmppDList_search(&pp->pimpl->d.list, zName);
|
|
if( e ) d = &e->d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
cmpp_d const * cmpp__d_search3(cmpp *pp, const char *zName,
|
|
cmpp_flag32_t what){
|
|
cmpp_d const * d = cmpp__d_search(pp, zName);
|
|
if( !d ){
|
|
CmppDList_entry const * e = 0;
|
|
if( cmpp__d_search3_F_DELAYED & what ){
|
|
int rc = cmpp__d_delayed_load(pp, zName);
|
|
if( 0==rc ){
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}else if( CMPP_RC_NO_DIRECTIVE!=rc ){
|
|
assert( ppCode );
|
|
return NULL;
|
|
}
|
|
}
|
|
if( !e
|
|
&& (cmpp__d_search3_F_AUTOLOADER & what)
|
|
&& pp->pimpl->d.autoload.f
|
|
&& 0==pp->pimpl->d.autoload.f(pp, zName, pp->pimpl->d.autoload.state) ){
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}
|
|
#if CMPP_D_MODULE
|
|
if( !e
|
|
&& !ppCode
|
|
&& (cmpp__d_search3_F_DLL & what) ){
|
|
char * z = sqlite3_mprintf("libcmpp-d-%s", zName);
|
|
cmpp_check_oom(pp, z);
|
|
int rc = cmpp_module_load(pp, z, NULL);
|
|
sqlite3_free(z);
|
|
if( rc ){
|
|
if( CMPP_RC_NOT_FOUND==rc ){
|
|
cmpp__err_reuse(pp);
|
|
}
|
|
return NULL;
|
|
}
|
|
e = CmppDList_search(&pp->pimpl->d.list, zName);
|
|
}
|
|
#endif
|
|
if( e ) d = &e->d;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_dx_process)(cmpp_dx * const dx){
|
|
if( dxppCode ) return dxppCode;
|
|
cmpp_d const * const d = cmpp_dx_d(dx);
|
|
assert( d );
|
|
if( cmpp_dx_is_eliding(dx) && !(d->flags & cmpp_f_F_FLOW_CONTROL) ){
|
|
return dxppCode;
|
|
}
|
|
cmpp_flag32_t const callFlags = 0/*TODO?*/;
|
|
bool const isDxCall = dx->pimpl->args.pimpl->isDxCall;
|
|
if( (cmpp_f_F_NOT_IN_SAFEMODE & d->flags)
|
|
&& (cmpp_ctor_F_SAFEMODE & dx->pp->pimpl->flags.ctorFlags) ){
|
|
return cmpp_dx_err(dx, CMPP_RC_ACCESS,
|
|
"Directive %s%s is disabled by safe mode.",
|
|
cmpp_dx_delim(dx), dx->d->name.z);
|
|
}
|
|
/*
|
|
Reminder to self: directives which have modes predating
|
|
cmpp_f_F_ARGS_V4 _must_ have their args memory pointing into the
|
|
massaged copy of the argument line, not the original source
|
|
memory, because that one has backslash-escaped newlines removed
|
|
and pre-V4 directive impls rely on the library to have removed
|
|
them.
|
|
*/
|
|
#define argsReallyCommon .pp=dx->pp,.dx=dx,.d=d
|
|
# define argsCommon argsReallyCommon
|
|
if( cmpp_f_F_ARGS_V4 & d->flags ){
|
|
/** ================ V4 ================ */
|
|
cmpp_args args = dx->pimpl->args /* transfer ownership. We can't do this
|
|
for pre-v4 arg modes. */;
|
|
dx->pimpl->args = cmpp_args_empty;
|
|
args.arg0 = NULL /*pre-v4 baggage*/;
|
|
assert( d->impl.f );
|
|
assert( args.pimpl );
|
|
assert( args.argc && "V4 gets the directive name as arg[0]" );
|
|
if( (d->flags & cmpp_f_F_ENFORCE_ARITY)
|
|
&& d->arity>=0
|
|
&& (unsigned)d->arity!=dx->pimpl->args.argc - 1
|
|
/* -1 for the command name*/ ){
|
|
cmpp_errf(
|
|
dx->pp, CMPP_RC_MISUSE,
|
|
"Invalid argument count (%d) for directive %s. "
|
|
"Usage: %s",
|
|
(int)dx->pimpl->args.argc, d->name.z,
|
|
d->usage ? d->usage : "<missing cmpp_d::usage>"
|
|
);
|
|
goto bail;
|
|
}
|
|
CmppArgList * const ali = &args.pimpl->argli;
|
|
assert( ali->n==args.argc && "V4 these should not differ in v4"
|
|
/* They can differ in pre-v4 because of the old expression
|
|
engine's token-snipping */ );
|
|
unsigned char const * const zRaw =
|
|
/* token source range */ dx->args.z;
|
|
assert( zRaw );
|
|
cmpp_size_t const nRaw = dx->args.nz;
|
|
assert( nRaw && "V4 gets the directive name as argv[0]" );
|
|
if( cmpp_f_F_ARGS_RAW & d->flags ){
|
|
/** =========== V4/RAW ========== */
|
|
cmpp_f_args const fargs = {
|
|
argsCommon,
|
|
.argc = ali->n, .argv = 0,
|
|
.flags = callFlags, .isDxCall = isDxCall,
|
|
.raw = {.z = zRaw, .n = nRaw}
|
|
};
|
|
d->impl.f(&fargs);
|
|
}else{
|
|
/** ========== V4 cooked ========== */
|
|
/*
|
|
2026-09-04 checkout [d28f326f116f8]. Bug somewhere. This block
|
|
is working around a reproducible legitimate assert if we do
|
|
_not_ take ownership of ali->avb. That "should work" (it does
|
|
in itch.c) but it's inexplicably leading to a legitimate
|
|
assert via:
|
|
|
|
cmpp_dtor() => cmpp__args_pimpl_cleanup() =>
|
|
CmppArgList_dtor() => cmpp_b_return()
|
|
|
|
for the buffer ali->avb. Reproducer:
|
|
|
|
gdb --args ./c-pp -I. -I./src -Dsrcdir=./src \
|
|
-o libcmpp.c ./tool/libcmpp.c-pp.c
|
|
|
|
If, below, we leave ali->avb in place and skip the
|
|
cmpp_b_return() of it (which is fine, as ali owns that
|
|
buffer), then the repro triggers. Under the hood that is
|
|
doing literally the exact same thing except that the
|
|
lifetimes of the buffers (ba, ali->avb) differ. That suggests
|
|
that ali->avb is being used uninitialized or being returned
|
|
without setting it to 0 somewhere, but my eyes are not seeing
|
|
that anywhere. Maybe we have a lingering misuse somewhere
|
|
unrelated which is stomping it. Valgrind is happy, which
|
|
implies that such hypothetically misuse would be well within
|
|
our rights to do (as opposed to stomping unallocated
|
|
memory). Hmm.
|
|
*/
|
|
cmpp_arg const ** const argv =
|
|
CmppArgList_makeArgv(dx->pp, ali, true);
|
|
if( !argv ) goto bail;
|
|
cmpp_b * const ba = ali->avb;
|
|
ali->avb = 0;
|
|
assert( ba && "argv lives in here and argv[X]->z might" );
|
|
cmpp_f_args const fargs = {
|
|
argsCommon,
|
|
.argc = ali->n, .argv = argv,
|
|
.flags = callFlags, .isDxCall = isDxCall,
|
|
.raw = {.z = zRaw, .n = nRaw}
|
|
};
|
|
d->impl.f(&fargs);
|
|
cmpp_b_return(dx->pp, ba);
|
|
}
|
|
bail:
|
|
cmpp_args_cleanup(&args);
|
|
}else{
|
|
/* V1..3 modes get/handle the args via the legacy dx->args, with
|
|
the caveat that directives which consume from the input stream
|
|
invalidate that memory (consumed directives each overwrite
|
|
it). */
|
|
assert(d->impl.f);
|
|
cmpp_b * const braw = cmpp_b_borrow(dx->pp)
|
|
/* Ensure that the raw args memory remains valid for
|
|
the life of the call. */;
|
|
if( braw ){
|
|
cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw);
|
|
cmpp_f_args const fargs = {
|
|
argsCommon,
|
|
.argc = 0, .argv = 0,
|
|
.flags = callFlags, .isDxCall = isDxCall,
|
|
.raw = {.z = braw->z ? braw->z : ustr_c(""),
|
|
.n = braw->n}
|
|
};
|
|
d->impl.f(&fargs);
|
|
cmpp_b_swap(braw, &dx->pimpl->buf.argsRaw);
|
|
cmpp_b_return(dx->pp, braw);
|
|
}
|
|
}
|
|
#undef argsCommon
|
|
#undef argsReallyCommon
|
|
return dxppCode;
|
|
}
|
|
|
|
|
|
static void cmpp_dx__setup_include_path(cmpp_dx * dx){
|
|
/* Add the leading dir part of dx->sourceName as the
|
|
highest-priority include path. It gets removed
|
|
in cmpp_dx__teardown(). */
|
|
assert( dx->sourceName );
|
|
enum { BufSize = 512 * 4 };
|
|
unsigned char buf[BufSize] = {0};
|
|
unsigned char *z = &buf[0];
|
|
cmpp_size_t n = cmpp__strlenu(dx->sourceName, -1);
|
|
if( n > (unsigned)BufSize-1 ) return;
|
|
memcpy(z, dx->sourceName, n);
|
|
buf[n] = 0;
|
|
cmpp_strlen_t i = n - 1;
|
|
for( ; i > 0; --i ){
|
|
if( '/'==z[i] || '\\'==z[i] ){
|
|
z[i] = 0;
|
|
n = i;
|
|
break;
|
|
}
|
|
}
|
|
if( n>(cmpp_size_t)i ){
|
|
/* No path separator found. Assuming '.'. This is intended to
|
|
replace the historical behavior of automatically adding '.' if
|
|
no -I flags are used. Potential TODO is getcwd() here instead
|
|
of using '.' */
|
|
n = 1;
|
|
buf[0] = '.';
|
|
buf[1] = 0;
|
|
}
|
|
int64_t rowid = 0;
|
|
cmpp__include_dir_add(dx->pp, (char const*)buf,
|
|
dx->pp->pimpl->flags.nDxDepth,
|
|
&rowid);
|
|
if( rowid ){
|
|
//g_warn("Adding #include path #%" PRIi64 ": %s", rowid, z);
|
|
dx->pimpl->shadow.ridInclPath = rowid;
|
|
}
|
|
}
|
|
|
|
static int cmpp_dx__setup(cmpp *pp, cmpp_dx *dx,
|
|
unsigned char const * zIn,
|
|
cmpp_strlen_t nIn){
|
|
if( 0==ppCode ){
|
|
assert( dx->sourceName );
|
|
assert( dx->pimpl );
|
|
assert( pp==dx->pp );
|
|
nIn = cmpp__strlenu(zIn, nIn);
|
|
if( !nIn ) return 0;
|
|
pp->pimpl->dx = dx;
|
|
dx->pimpl->zBegin = zIn;
|
|
dx->pimpl->zEnd = zIn + nIn;
|
|
cmpp_define_shadow(pp, "__FILE__", (char const *)dx->sourceName,
|
|
&dx->pimpl->shadow.sidFile);
|
|
++dx->pp->pimpl->flags.nDxDepth;
|
|
cmpp_dx__setup_include_path(dx);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static void cmpp_dx__teardown(cmpp_dx *dx){
|
|
if( dx->pimpl->shadow.ridInclPath>0 ){
|
|
cmpp__include_dir_rm_id(dx->pp, dx->pimpl->shadow.ridInclPath);
|
|
dx->pimpl->shadow.ridInclPath = 0;
|
|
}
|
|
if( dx->pimpl->shadow.sidFile ){
|
|
cmpp_define_unshadow(dx->pp, "__FILE__",
|
|
dx->pimpl->shadow.sidFile);
|
|
}
|
|
--dx->pp->pimpl->flags.nDxDepth;
|
|
cmpp_dx_cleanup(dx);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_process_string)(
|
|
cmpp *pp, const char * zName,
|
|
unsigned char const * zIn,
|
|
cmpp_strlen_t nIn
|
|
){
|
|
if( !nIn || (nIn<0 && !*zIn) ) return 0;
|
|
if( 0==cmpp__db_init(pp) ){
|
|
if( !zName ) zName = "";
|
|
cmpp_dx * const oldDx = pp->pimpl->dx;
|
|
cmpp__dx_pimpl dxp = cmpp__dx_pimpl_empty;
|
|
cmpp_dx dx = {
|
|
.pp = pp,
|
|
.sourceName = ustr_c(zName),
|
|
.args = cmpp_dx_empty.args,
|
|
.pimpl = &dxp
|
|
};
|
|
dxp.up = oldDx;
|
|
dxp.flags.nextIsCall = pp->pimpl->flags.nextIsCall;
|
|
pp->pimpl->flags.nextIsCall = false;
|
|
if( dxp.flags.nextIsCall ){
|
|
assert( pp->pimpl->dx );
|
|
dxp.pos.lineNo = oldDx->pimpl->pos.lineNo;
|
|
}
|
|
bool gotOne = false;
|
|
(void)cmpp__stmt(pp, CmppStmt_sdefIns, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_inclPathAdd, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_inclPathRmId, true);
|
|
(void)cmpp__stmt(pp, CmppStmt_sdefDel, true)
|
|
/* hack: ensure that those queries are allocated now, as an
|
|
error in processing may keep them from being created
|
|
later. We might want to rethink the
|
|
prepare-no-statements-on-error bits, but will have to go back
|
|
and fix routines which currently rely on that. */;
|
|
cmpp_dx__setup(pp, &dx, zIn, nIn);
|
|
assert( !dxp.pos.z );
|
|
while(0==ppCode
|
|
&& 0==cmpp_dx_next(&dx, &gotOne)
|
|
&& gotOne){
|
|
cmpp_dx_process(&dx);
|
|
}
|
|
if(0==ppCode && 0!=dx.pimpl->dxLvl.n){
|
|
CmppLvl const * const lv = CmppLvl_get(&dx);
|
|
cmpp_dx_err(&dx, CMPP_RC_SYNTAX,
|
|
"Input ended inside an unterminated nested construct "
|
|
"opened at [%s] line %" CMPP_SIZE_T_PFMT ".", zName,
|
|
lv ? lv->lineNo : (cmpp_size_t)0);
|
|
}
|
|
cmpp_dx__teardown(&dx);
|
|
pp->pimpl->dx = oldDx;
|
|
}
|
|
if( !ppCode ){
|
|
cmpp_outputer_flush(&pp->pimpl->out)
|
|
/* We're going to ignore a result code just this once. */;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp_process_file(cmpp *pp, const char * zName){
|
|
if( 0==ppCode ){
|
|
FileWrapper fw = FileWrapper_empty;
|
|
if( 0==cmpp__FileWrapper_open(pp, &fw, zName, "rb")
|
|
&& 0==cmpp__FileWrapper_slurp(pp, &fw) ){
|
|
cmpp_process_string(pp, zName, fw.zContent, fw.nContent);
|
|
}
|
|
FileWrapper_close(&fw);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_process_stream)(cmpp *pp, const char * zName,
|
|
cmpp_input_f src, void * srcState){
|
|
if( 0==ppCode ){
|
|
cmpp_b * const os = cmpp_b_borrow(pp);
|
|
int const rc = os
|
|
? cmpp_stream(src, srcState, cmpp_output_f_b, os)
|
|
: ppCode;
|
|
if( 0==rc ){
|
|
if( os->n ){
|
|
cmpp_process_string(pp, zName, os->z, os->n);
|
|
}
|
|
}else{
|
|
cmpp__err(pp, rc, "Error reading from input stream '%s'.", zName);
|
|
}
|
|
cmpp_b_return(pp, os);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_call_str)(
|
|
cmpp *pp, unsigned char const * z, cmpp_strlen_t n,
|
|
cmpp_b * dest, cmpp_flag32_t flags
|
|
){
|
|
if( ppCode ) return ppCode;
|
|
cmpp_b * const bo = cmpp_b_borrow(pp);
|
|
cmpp_outputer oB = cmpp_outputer_b;
|
|
if( !bo ) return ppCode;
|
|
cmpp__pi(pp);
|
|
oB.state = bo;
|
|
oB.name = pi->out.name;//"[call]";
|
|
n = cmpp__strlenu(z, n);
|
|
//g_warn("calling: <<%.*s>>", (int)n, z);
|
|
unsigned char const * zEnd = z+n;
|
|
cmpp_skip_snl(&z, zEnd);
|
|
cmpp_skip_snl_trailing(z, &zEnd);
|
|
n = (zEnd-z);
|
|
if( !n ){
|
|
cmpp_errf(pp, CMPP_RC_SYNTAX,
|
|
"Empty [call] is not permitted.");
|
|
goto end;
|
|
}
|
|
//g_warn("calling: <<%.*s>>", (int)n, z);
|
|
cmpp__delim const * const delim = cmpp__pp_delim(pp);
|
|
assert(delim);
|
|
pi->flags.fakeNextDelim = (cmpp_size_t)n<=delim->open.n
|
|
|| 0!=memcmp(z, delim->open.z, delim->open.n)
|
|
/* If it doesn't start with the current delimiter, pretend that it
|
|
does by telling the pending cmpp_dx_next() that we have already
|
|
parsed the delimiter, causing it to treat the first token as a
|
|
directive name. */;
|
|
pi->flags.nextIsCall = true
|
|
/* Convey (indirectly, via cmpp_process_string()) that the first
|
|
cmpp_dx_next() call made via the upcoming cmpp_process_string()
|
|
is considered a call context. */;
|
|
cmpp_outputer oOld = cmpp_outputer_empty;
|
|
cmpp__outputer_swap(pp, &oB, &oOld);
|
|
cmpp_process_string(pp, "call"/*if we knew that z was NUL-terminated
|
|
then we could use it as the name*/,
|
|
z, n);
|
|
cmpp__outputer_swap(pp, &oOld, NULL);
|
|
assert( !pi->flags.nextIsCall || ppCode );
|
|
assert( !pi->flags.fakeNextDelim || ppCode );
|
|
pi->flags.nextIsCall = pi->flags.fakeNextDelim = false;
|
|
if( !ppCode ){
|
|
unsigned char const * zz = bo->z;
|
|
unsigned char const * zzEnd = bo->z + bo->n;
|
|
if( cmpp_call_F_TRIM_ALL & flags ){
|
|
cmpp_skip_snl(&zz, zzEnd);
|
|
cmpp_skip_snl_trailing(zz, &zzEnd);
|
|
}else if( 0==(cmpp_call_F_NO_TRIM & flags) ){
|
|
cmpp_b_chomp(bo);
|
|
zzEnd = bo->z + bo->n;
|
|
}
|
|
if( (zzEnd-zz) ){
|
|
cmpp_b_append4(pp, dest, zz, (cmpp_size_t)(zzEnd-zz));
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(pp, bo);
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_errno_rc)(int errNo, int dflt){
|
|
switch(errNo){
|
|
/* Please expand on this as tests/use cases call for it... */
|
|
case 0:
|
|
return 0;
|
|
case EINVAL:
|
|
return CMPP_RC_MISUSE;
|
|
case ENOMEM:
|
|
return CMPP_RC_OOM;
|
|
case EROFS:
|
|
case EACCES:
|
|
case EBUSY:
|
|
case EPERM:
|
|
case EDQUOT:
|
|
case EAGAIN:
|
|
case ETXTBSY:
|
|
return CMPP_RC_ACCESS;
|
|
case EISDIR:
|
|
case ENOTDIR:
|
|
return CMPP_RC_TYPE;
|
|
case ENAMETOOLONG:
|
|
case ELOOP:
|
|
case ERANGE:
|
|
return CMPP_RC_RANGE;
|
|
case ENOENT:
|
|
case ESRCH:
|
|
return CMPP_RC_NOT_FOUND;
|
|
case EEXIST:
|
|
case ENOTEMPTY:
|
|
return CMPP_RC_ALREADY_EXISTS;
|
|
case EIO:
|
|
return CMPP_RC_IO;
|
|
default:
|
|
return dflt;
|
|
}
|
|
}
|
|
|
|
int cmpp_flush_f_FILE(void * _FILE){
|
|
return fflush(_FILE) ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
|
|
}
|
|
|
|
int cmpp_output_f_FILE( void * state,
|
|
void const * src, cmpp_size_t n ){
|
|
return (1 == fwrite(src, n, 1, state ? (cmpp_FILE*)state : stdout))
|
|
? 0 : CMPP_RC_IO;
|
|
}
|
|
|
|
int cmpp_output_f_fd( void * state, void const * src, cmpp_size_t n ){
|
|
int const fd = *((int*)state);
|
|
ssize_t const wn = write(fd, src, n);
|
|
return wn<0 ? cmpp_errno_rc(errno, CMPP_RC_IO) : 0;
|
|
}
|
|
|
|
int cmpp_input_f_FILE( void * state, void * dest, cmpp_size_t * n ){
|
|
cmpp_FILE * f = state;
|
|
cmpp_size_t const rn = *n;
|
|
*n = (cmpp_size_t)fread(dest, 1, rn, f);
|
|
return *n==rn ? 0 : (feof(f) ? 0 : CMPP_RC_IO);
|
|
}
|
|
|
|
int cmpp_input_f_fd( void * state, void * dest, cmpp_size_t * n ){
|
|
int const fd = *((int*)state);
|
|
ssize_t const rn = read(fd, dest, *n);
|
|
if( rn<0 ){
|
|
return cmpp_errno_rc(errno, CMPP_RC_IO);
|
|
}else{
|
|
*n = (cmpp_size_t)rn;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void cmpp_outputer_cleanup_f_FILE(cmpp_outputer *self){
|
|
if( self->state ){
|
|
cmpp_fclose( self->state );
|
|
self->name = NULL;
|
|
self->state = NULL;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_outputer_cleanup_f_b)(cmpp_outputer *self){
|
|
if( self->state ) cmpp_b_clear(self->state);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outputer_out)(cmpp_outputer *o, void const *p, cmpp_size_t n){
|
|
return o->out ? o->out(o->state, p, n) : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_outputer_flush)(cmpp_outputer *o){
|
|
return o->flush ? o->flush(o->state) : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_outputer_cleanup)(cmpp_outputer *o){
|
|
if( o->cleanup ){
|
|
o->cleanup( o );
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stream)( cmpp_input_f inF, void * inState,
|
|
cmpp_output_f outF, void * outState ){
|
|
int rc = 0;
|
|
enum { BufSize = 1024 * 4 };
|
|
unsigned char buf[BufSize];
|
|
cmpp_size_t rn = BufSize;
|
|
while( 0==rc
|
|
&& (rn==BufSize)
|
|
&& (0==(rc=inF(inState, buf, &rn))) ){
|
|
if(rn) rc = outF(outState, buf, rn);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_size_t, cmpp_count_nl)(unsigned char const *zBegin,
|
|
unsigned char const * const zEnd,
|
|
unsigned char const * const zPos,
|
|
cmpp_size_t *pCol){
|
|
assert( zPos >= zBegin );
|
|
assert( zPos <= zEnd );
|
|
unsigned char const * z = zBegin;
|
|
cmpp_size_t nl = 0, col = 0;
|
|
while( z < zPos ){
|
|
switch( *z ){
|
|
case '\n': ++nl; col = 0; break;
|
|
default: ++col; break;
|
|
}
|
|
++z;
|
|
}
|
|
if( pCol ) *pCol = col;
|
|
return nl;
|
|
}
|
|
|
|
void cmpp__fatalv_base(char const *zFile, int line,
|
|
char const *zFmt, va_list va){
|
|
cmpp_FILE * const fp = stderr;
|
|
fflush(stdout);
|
|
fprintf(fp, "\n%s:%d: ", zFile, line);
|
|
if(zFmt && *zFmt){
|
|
vfprintf(fp, zFmt, va);
|
|
fputc('\n', fp);
|
|
}
|
|
fflush(fp);
|
|
exit(1);
|
|
}
|
|
|
|
void cmpp__fatal_base(char const *zFile, int line,
|
|
char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
cmpp__fatalv_base(zFile, line, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_err_get)(cmpp *pp, char const **zMsg){
|
|
if( zMsg ){
|
|
*zMsg = ppCode
|
|
? pp->pimpl->err.zStatic
|
|
: 0;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
#if 0
|
|
CMPP__EXPORT(int, cmpp_err_take)(cmpp *pp, char **zMsg){
|
|
int const rc = ppCode;
|
|
if( rc ){
|
|
*zMsg = (char *)pp->pimpl->err.b.z;
|
|
pp->pimpl->err = cmpp_errinfo_empty_m;
|
|
}else{
|
|
*zMsg = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
CMPP__EXPORT(int, cmpp_err_has)(cmpp const * pp){
|
|
return pp ? pp->pimpl->err.code : 0;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_dx_pos_save)(cmpp_dx const * dx, cmpp_dx_pos *pos){
|
|
*pos = dx->pimpl->pos;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_dx_pos_restore)(cmpp_dx * dx, cmpp_dx_pos const * pos){
|
|
dx->pimpl->pos = *pos;
|
|
}
|
|
|
|
//CMPP_WASM_EXPORT
|
|
void cmpp__dx_append_script_info(cmpp_dx const * dx,
|
|
sqlite3_str * const sstr){
|
|
cmpp__dx_pi(dx);
|
|
unsigned char const *zB = 0;
|
|
unsigned char const *zE = 0;
|
|
unsigned char const *zErr = dpi->zErrPos;
|
|
cmpp__dx_input_range(dx, &zB, &zE);
|
|
//g_warn("Whole script:\n%.*s", (int)(zE-zB), zB);
|
|
assert( zB );
|
|
assert( zE );
|
|
assert( zB<=zE );
|
|
if( !zErr ){
|
|
if( dpi->pos.z>=zB && dpi->pos.z<zE ){
|
|
/* This is frequently past zEnd */
|
|
zErr = dpi->pos.z;
|
|
}else{
|
|
zErr = dpi->dline.zBegin;
|
|
}
|
|
}
|
|
//g_warn("dpi->zErrPos: %p\n%.3s ...", dpi->zErrPos, dpi->zErrPos);
|
|
//g_warn("dpi->pos.z: %p\n %.3s ...", dpi->pos.z, dpi->pos.z);
|
|
//g_warn("zErr: %p\n%.3s ...", zErr, zErr);
|
|
assert( (!zErr || (zErr>=zB && zErr<=zE))
|
|
&& "Else we're pointing to incompatible sources");
|
|
cmpp_size_t const nl = zErr
|
|
? 1 + cmpp_count_nl(zB, zE, zErr, NULL)
|
|
: dpi->dline.lineNo;
|
|
if( dx->d ){
|
|
sqlite3_str_appendf(sstr, "%s @ %s:%d",
|
|
dx->d->name.z,
|
|
(dx->sourceName
|
|
&& 0==strcmp("-", (char const *)dx->sourceName))
|
|
? "<stdin>"
|
|
: (char const *)dx->sourceName,
|
|
(int)dpi->dline.lineNo);
|
|
}
|
|
if( nl != dpi->dline.lineNo ){
|
|
/* Input position past the directive line. */
|
|
sqlite3_str_appendf(sstr, " from line %" CMPP_SIZE_T_PFMT, nl);
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_errinfo_dtor)(cmpp_errinfo *err){
|
|
cmpp_b_clear(&err->b);
|
|
*err = cmpp_errinfo_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_errinfo *, cmpp_errinfo_reuse)(cmpp_errinfo *err){
|
|
cmpp_b_reuse(&err->b);
|
|
err->zStatic = 0;
|
|
err->code = 0;
|
|
return err;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_errinfo_set)(cmpp_errinfo *err, int rc,
|
|
char const *zMsg,
|
|
cmpp_strlen_t nMsg){
|
|
if( err ){
|
|
cmpp_errinfo_reuse(err);
|
|
if( rc && zMsg && *zMsg ){
|
|
if( cmpp_b_append_str(&err->b, zMsg, nMsg) ){
|
|
rc = err->b.errCode;
|
|
}
|
|
}
|
|
err->code = rc;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp_errinfo_setv(cmpp_errinfo *err, int rc, char const *zFmt, va_list va){
|
|
if( err ){
|
|
cmpp_errinfo_reuse(err);
|
|
err->code = 0 /* some APIs become no-ops if pp->pimpl->err.code
|
|
is set, so delay setting this. */;
|
|
if( 0==rc ) return rc;
|
|
if( CMPP_RC_OOM==rc ){
|
|
oom:
|
|
err->zStatic ="An allocation failed.";
|
|
return err->code = CMPP_RC_OOM;
|
|
}
|
|
assert( !err->zStatic );
|
|
if( zFmt && *zFmt ){
|
|
sqlite3_str * sstr = sqlite3_str_new(0);
|
|
sqlite3_str_vappendf(sstr, zFmt, va);
|
|
int const nz = sqlite3_str_length(sstr);
|
|
char * const z = sqlite3_str_finish(sstr);
|
|
sstr = 0;
|
|
if( !z ){
|
|
goto oom;
|
|
}
|
|
if( err->b.z ){
|
|
cmpp_b_append_str(&err->b, z, nz);
|
|
sqlite3_free(z);
|
|
if( err->b.errCode ) goto oom;
|
|
}else{
|
|
err->b.z = (unsigned char*)z;
|
|
err->b.n = (cmpp_size_t)nz;
|
|
err->b.nAlloc = err->b.n + 1/*NUL byte*/;
|
|
}
|
|
err->zStatic = (char const *)err->b.z;
|
|
}else{
|
|
err->zStatic = "No error info provided.";
|
|
}
|
|
err->code = rc;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp_errinfo_setf(cmpp_errinfo *err, int rc,
|
|
char const *zFmt, ...){
|
|
if( err ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
rc = cmpp_errinfo_setv(err, rc, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(int, cmpp_err)(cmpp *pp, int rc, char const *zMsg,
|
|
cmpp_strlen_t nMsg){
|
|
if( pp ){
|
|
if( rc ){
|
|
nMsg = cmpp__strlen(zMsg, nMsg);
|
|
rc = cmpp_errf(pp, rc, "%.*s", (int)nMsg, zMsg)
|
|
/* Reminder to self: we have to go through cmpp_errf(),
|
|
instead of cmpp_errinfo_set(), so that error location
|
|
information gets tacked on. */;
|
|
}else{
|
|
cmpp_errinfo_reuse(&pp->pimpl->err);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int cmpp_errfv(cmpp *pp, int rc, char const *zFmt, va_list va){
|
|
if( pp ){
|
|
cmpp_errinfo * const err = &pp->pimpl->err;
|
|
cmpp_errinfo_reuse(err);
|
|
assert( 0==err->code )
|
|
/* some APIs become no-ops if pp->pimpl->err.code
|
|
is set, so delay setting this. */;
|
|
if( 0==rc ) return rc;
|
|
if( CMPP_RC_OOM==rc ){
|
|
oom:
|
|
err->zStatic ="An allocation failed.";
|
|
return err->code = CMPP_RC_OOM;
|
|
}
|
|
assert( !err->zStatic );
|
|
if( pp->pimpl->dx || (zFmt && *zFmt) ){
|
|
sqlite3_str * const sstr = cmpp__sqlite3_str_new(pp);
|
|
if( !sstr ) goto oom;
|
|
if( pp->pimpl->dx ){
|
|
cmpp__dx_append_script_info(pp->pimpl->dx, sstr);
|
|
sqlite3_str_append(sstr, ": ", 2);
|
|
}
|
|
if( zFmt && *zFmt ){
|
|
sqlite3_str_vappendf(sstr, zFmt, va);
|
|
}else{
|
|
sqlite3_str_appendf(sstr, "No error info provided.");
|
|
}
|
|
int nz = 0;
|
|
char * const z = cmpp_sqlite3_str_finish(pp, sstr, &nz);
|
|
if( !z ) goto oom;
|
|
#if 0
|
|
cmpp_b_append(&err->b, z, nz);
|
|
sqlite3_free(z);
|
|
#else
|
|
if( err->b.z ){
|
|
if( cmpp_b_append4_str(pp, &err->b, z, nz) ){
|
|
sqlite3_free(z);
|
|
goto oom;
|
|
}
|
|
sqlite3_free(z);
|
|
}else{
|
|
/* Transfer ownership. */
|
|
err->b.z = (unsigned char*)z;
|
|
err->b.n = (cmpp_size_t)nz;
|
|
err->b.nAlloc = err->b.n + 1 /*NUL terminator*/;
|
|
}
|
|
#endif
|
|
err->zStatic = (char *)err->b.z;
|
|
}else{
|
|
err->zStatic = "No error info provided.";
|
|
}
|
|
err->code = rc;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
//CMPP_WASM_EXPORT no - variadic
|
|
int cmpp_errf(cmpp *pp, int rc,
|
|
char const *zFmt, ...){
|
|
if( pp ){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
//rc = cmpp_errinfo_setv(&pp->pimpl->err, rc, zFmt, va);
|
|
rc = cmpp_errfv(pp, rc, zFmt, va);
|
|
va_end(va);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
const cmpp_d_autoloader cmpp_d_autoloader_empty =
|
|
cmpp_d_autoloader_empty_m;
|
|
|
|
CMPP__EXPORT(void, cmpp_d_autoloader_set)(cmpp *pp, cmpp_d_autoloader const * pNew){
|
|
if( pp->pimpl->d.autoload.dtor ) pp->pimpl->d.autoload.dtor(pp->pimpl->d.autoload.state);
|
|
if( pNew ) pp->pimpl->d.autoload = *pNew;
|
|
else pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_d_autoloader_take)(cmpp *pp, cmpp_d_autoloader * pOld){
|
|
*pOld = pp->pimpl->d.autoload;
|
|
pp->pimpl->d.autoload = cmpp_d_autoloader_empty;
|
|
}
|
|
|
|
#undef cmpp_dx_err
|
|
|
|
//CMPP_WASM_EXPORT no - variadic
|
|
int cmpp_dx_errf(cmpp_dx *dx, int rc,
|
|
char const *zFmt, ...){
|
|
if( rc ){
|
|
if( !dx->pimpl->zErrPos ){
|
|
dx->pimpl->zErrPos = dx->pimpl->dline.zBegin;
|
|
//g_warn("dx->pimpl->dline.zBegin: %.3s...", dx->pimpl->dline.zBegin);
|
|
if( !dx->pimpl->zErrPos ) dx->pimpl->zErrPos = dx->pimpl->zBegin;
|
|
//g_warn("dx->pimpl->zBegin: %.3s...", dx->pimpl->zBegin);
|
|
}
|
|
}else{
|
|
dx->pimpl->zErrPos = 0;
|
|
}
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
rc = cmpp_errfv(dx->pp, rc, zFmt, va);
|
|
va_end(va);
|
|
return rc;
|
|
}
|
|
|
|
int cmpp_dx_errfv(cmpp_dx *dx, int rc, char const *zFmt, va_list vargs){
|
|
return cmpp_errfv(dx->pp, rc, zFmt, vargs);
|
|
}
|
|
|
|
//no: CMPP_WASM_EXPORT
|
|
char * cmpp_path_search(cmpp *pp,
|
|
char const *zPath,
|
|
char pathSep,
|
|
char const *zBaseName,
|
|
char const *zExt){
|
|
char * zrc = 0;
|
|
if( !ppCode ){
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(pp, CmppStmt_selPathSearch, false);
|
|
if( q ){
|
|
unsigned char sep[2] = {pathSep, 0};
|
|
cmpp_stmt_bind_text(pp, q, 1, ustr_c(zBaseName));
|
|
cmpp_stmt_bind_text(pp, q, 2, sep);
|
|
cmpp_stmt_bind_text(pp, q, 3, ustr_c((zExt ? zExt : "")));
|
|
cmpp_stmt_bind_text(pp, q, 4, ustr_c((zPath ? zPath: "")));
|
|
int const dbrc = cmpp_stmt_step(pp, q, false);
|
|
if( SQLITE_ROW==dbrc ){
|
|
unsigned char const * s = sqlite3_column_text(q, 1);
|
|
int const n = sqlite3_column_bytes(q,1);
|
|
if( n ){
|
|
zrc = sqlite3_mprintf("%.*s", n, s);
|
|
cmpp_check_oom(pp, zrc);
|
|
}else{
|
|
g_warn("WARNING: empty path result from: %s",
|
|
sqlite3_sql(q));
|
|
}
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
}
|
|
return zrc;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_dx *,cmpp_dx_current)(cmpp *pp){
|
|
return cmpp__dx_current(pp);
|
|
}
|
|
|
|
|
|
//cmpp__ListType_impl(cmpp__delim_list,cmpp__delim)
|
|
//cmpp__ListType_impl(CmppDList,CmppDList_entry*)
|
|
//cmpp__ListType_impl(CmppSohList,void*)
|
|
cmpp__ListType_impl(cmpp_b_list,cmpp_b*)
|
|
cmpp__ListType_impl(CmppLvlList,CmppLvl*)
|
|
|
|
/**
|
|
Expects that *ndx points to the current argv entry and that it is a
|
|
flag which expects a value. This function checks for --flag=val and
|
|
(--flag val) forms. If a value is found then *ndx is adjusted (if
|
|
needed) to point to the next argument after the value and *zVal is
|
|
* pointed to the value. If no value is found then it returns false.
|
|
*/
|
|
static bool get_flag_val(int argc,
|
|
char const * const * argv, int * ndx,
|
|
char const **zVal){
|
|
char const * zEq = strchr(argv[*ndx], '=');
|
|
if( zEq ){
|
|
*zVal = zEq+1;
|
|
return 1;
|
|
}else if(*ndx+1>=argc){
|
|
return 0;
|
|
}else{
|
|
*zVal = argv[++*ndx];
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static
|
|
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
|
|
char const **zValIfEqX );
|
|
bool cmpp__arg_is_flag( char const *zFlag, char const *zArg,
|
|
char const **zValIfEqX ){
|
|
if( zValIfEqX ) *zValIfEqX = 0;
|
|
if( 0==strcmp(zFlag, zArg) ) return true;
|
|
char const * z = strchr(zArg,'=');
|
|
if( z && z>zArg ){
|
|
/* compare the part before the '=' */
|
|
if( 0==strncmp(zFlag, zArg, z-zArg) ){
|
|
if( !zFlag[z-zArg] ){
|
|
if( zValIfEqX ) *zValIfEqX = z+1;
|
|
return true;
|
|
}
|
|
/* Else it was a prefix match. */
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_size_t, cmpp_strlen)(char const *z, cmpp_strlen_t n){
|
|
return cmpp__strlen(z, n);
|
|
}
|
|
CMPP__EXPORT(cmpp_size_t, cmpp_strlenu)(unsigned char const *z, cmpp_strlen_t n){
|
|
return cmpp__strlenu(z, n);
|
|
}
|
|
|
|
CMPP__EXPORT(void,cmpp_dx_src_pos_info)(
|
|
cmpp_dx const *dx, char const **zName,
|
|
cmpp_size_t * lineNo
|
|
){
|
|
if( zName ) *zName = (char const *)dx->sourceName;
|
|
if( lineNo ) *lineNo = dx->pimpl->pos.lineNo;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_f_v4_err_usage)(cmpp_f_args const *args){
|
|
return cmpp_errf(args->pp, CMPP_RC_ERROR,
|
|
"Usage: %s %s",
|
|
args->argv[0]->z, args->usage);
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_simple_truth)(unsigned char const *z,
|
|
cmpp_strlen_t n){
|
|
switch(n){
|
|
case 0: return false;
|
|
case 1: return *z && '0'!=*z;
|
|
//case 4: return 0==memcmp(z,"true",4+1);
|
|
//case 5: return 0!=memcmp(z,"false",5+1);
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_json_valid)(cmpp *pp, unsigned char const *z,
|
|
cmpp_strlen_t n, int flags){
|
|
bool rv = false;
|
|
n = cmpp__strlenu(z, n);
|
|
if( n && flags>0 && flags<16 ){
|
|
/* ^^^^ json_valid() is picky about its flags argument. We skip
|
|
processing for that case because it can put pp into an error
|
|
state which the caller is not likely to check for because who
|
|
would after such an innocuous function? */
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_jsonValid, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_textx(pp, q, 1, z, n, SQLITE_STATIC);
|
|
cmpp_stmt_bind_int(pp, q, 2, flags);
|
|
if( SQLITE_ROW==cmpp_stmt_step(pp, q, false) ){
|
|
rv = 0!=sqlite3_column_int(q, 0);
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static inline bool cmpp__json_is_container(cmpp * const pp,
|
|
char opener,
|
|
char closer,
|
|
unsigned char const * const z,
|
|
cmpp_strlen_t n, bool allowJson5){
|
|
n = cmpp__strlenu(z, n);
|
|
return (
|
|
n>1
|
|
&& z[0]==(unsigned char)opener
|
|
&& z[n-1]==(unsigned char)closer
|
|
) ? (
|
|
n==2
|
|
? true
|
|
: cmpp_json_valid(pp, z, n, allowJson5 ? 3 : 1)
|
|
) : false;
|
|
}
|
|
|
|
CMPP__EXPORT(bool,cmpp_json_is_list)(cmpp *pp,
|
|
unsigned char const * const z,
|
|
cmpp_strlen_t n, bool allowJson5){
|
|
return cmpp__json_is_container(pp, '[', ']', z, n, allowJson5);
|
|
}
|
|
|
|
CMPP__EXPORT(bool,cmpp_json_is_object)(cmpp *pp,
|
|
unsigned char const * const z,
|
|
cmpp_strlen_t n, bool allowJson5){
|
|
return cmpp__json_is_container(pp, '{', '}', z, n, allowJson5);
|
|
}
|
|
|
|
CMPP__EXPORT(void,cmpp_str_json_append)(cmpp *pp, sqlite3_str * str,
|
|
unsigned char const * z,
|
|
cmpp_strlen_t n){
|
|
n = cmpp__strlenu(z, n);
|
|
if( !n ){
|
|
sqlite3_str_append(str, "\"\"", 2);
|
|
return;
|
|
}
|
|
int64_t j;
|
|
if( cmpp_is_int64(z, n, &j) ){
|
|
sqlite3_str_appendf(str, "%lld", j);
|
|
}else{
|
|
/* If it's JSON then append it as-is, don't escape it. We
|
|
"could" handle ints here but it's much faster to do them
|
|
above. */
|
|
//g_warn("arg %d: %.*s", (int)i, (int)n, z);
|
|
if( cmpp_json_valid(pp, z, n, 1) ){
|
|
/* Using any json_valid() flags which allow json5 will end
|
|
up generating JSON5 output, which we don't want here
|
|
because it would probably give us headaches everywhere
|
|
else. e.g. [list new a b c] would result in [a, b, c]
|
|
instead of ["a", "b", "c"]. */
|
|
//g_warn("is-valid 1: %.*s", (int)n, z);
|
|
sqlite3_str_append(str, (char *)z, (int)n);
|
|
}else{
|
|
//g_warn("is-valid 0: %.*s", (int)n, z);
|
|
sqlite3_str_appendf(str, "%.*J", (int)n, z);
|
|
}
|
|
}
|
|
}
|
|
|
|
void cmpp__dump_defines(cmpp *pp, cmpp_FILE * fp, int bIndent){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defSelAll, false);
|
|
if( q ){
|
|
while( SQLITE_ROW==sqlite3_step(q) ){
|
|
int const tt = sqlite3_column_int(q, 0);
|
|
unsigned char const * zK = sqlite3_column_text(q, 1);
|
|
unsigned char const * zV = sqlite3_column_text(q, 2);
|
|
int const nK = sqlite3_column_bytes(q, 1);
|
|
int const nV = sqlite3_column_bytes(q, 2);
|
|
char const * zTt = cmpp__tt_cstr2(tt, true);
|
|
if( tt && zTt ) zTt += 3;
|
|
else zTt = "String";
|
|
fprintf(fp, "%s%.*s = [%s] %.*s\n", bIndent ? "\t" : "",
|
|
nK, zK, zTt, nV, zV);
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
This is what was originally the main() of cmpp v1, back when it was
|
|
a monolithic app. It still serves as the driver for main() but is
|
|
otherwise unused.
|
|
*/
|
|
CMPP__EXPORT(int, cmpp_process_argv)(cmpp *pp, int argc,
|
|
char const * const * argv){
|
|
if( ppCode ) return ppCode;
|
|
int nFile = 0 /* number of files/-e scripts seen */;
|
|
|
|
#define ARGVAL if( !zVal && !get_flag_val(argc, argv, &i, &zVal) ){ \
|
|
cmpp__err(pp, CMPP_RC_MISUSE, "Missing value for flag '%s'", \
|
|
argv[i]); \
|
|
break; \
|
|
}
|
|
#define M(X) cmpp__arg_is_flag(X, zArg, &zVal)
|
|
#define ISFLAG(X) else if(M(X))
|
|
#define ISFLAG2(X,Y) else if(M(X) || M(Y))
|
|
#define NOVAL if( zVal ){ \
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Unexpected value for %s", zArg); \
|
|
break; \
|
|
} (void)0
|
|
|
|
#define open_output_if_needed \
|
|
if( !pp->pimpl->out.out && cmpp__out_fopen(pp, "-") ) break
|
|
|
|
cmpp__staticAssert(TT_None,0==(int)cmpp_tt_None);
|
|
cmpp__staticAssert(Mask1, cmpp_f_F_MASK_INTERNAL & cmpp_f_F_FLOW_CONTROL);
|
|
cmpp__staticAssert(Mask2, cmpp_f_F_MASK_INTERNAL & cmpp_f_F_NOT_SIMPLIFY);
|
|
cmpp__staticAssert(Mask3, 0==(cmpp_f_F_MASK_INTERNAL & cmpp_f_F_MASK));
|
|
|
|
for(int doIt = 0; doIt<2 && 0==ppCode; ++doIt){
|
|
/**
|
|
Loop through the flags twice. The first time we just validate
|
|
and look for --help/-?. The second time we process the flags.
|
|
This approach allows us to easily chain multiple files and
|
|
flags:
|
|
|
|
./c-pp -Dfoo -o foo x.y -Ufoo -Dbar -o bar x.y
|
|
|
|
Which, it turns out, is a surprisingly useful way to work.
|
|
*/
|
|
#define DOIT if(1==doIt)
|
|
for(int i = 0; i < argc && 0==ppCode; ++i){
|
|
char const * zVal = 0;
|
|
int isNoFlag = 0;
|
|
char const * zArg = argv[i];
|
|
//g_stderr("i=%d zArg=%s\n", i, zArg);
|
|
zVal = 0;
|
|
while('-'==*zArg) ++zArg;
|
|
if(zArg==argv[i]/*not a flag*/){
|
|
zVal = zArg;
|
|
goto do_infile;
|
|
}
|
|
//g_warn("zArg=%s", zArg);
|
|
if( 0==strncmp(zArg,"no-",3) ){
|
|
zArg += 3;
|
|
isNoFlag = 1;
|
|
}
|
|
if( M("?") || M("help") ){
|
|
NOVAL;
|
|
cmpp__err(pp, CMPP_RC_HELP, "%s", argv[i]);
|
|
break;
|
|
}else if('D'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -D");
|
|
}else DOIT {
|
|
cmpp_define_legacy(pp, zArg, 0);
|
|
}
|
|
}else if('F'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -F");
|
|
}else DOIT {
|
|
cmpp__set_file(pp, ustr_c(zArg), -1);
|
|
}
|
|
}
|
|
ISFLAG("e"){
|
|
ARGVAL;
|
|
DOIT {
|
|
++nFile;
|
|
open_output_if_needed;
|
|
cmpp_process_string(pp, "-e script",
|
|
(unsigned char const *)zVal, -1);
|
|
}
|
|
}else if('U'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing key for -U");
|
|
}else DOIT {
|
|
cmpp_undef_legacy(pp, zArg, NULL);
|
|
}
|
|
}else if('I'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -I");
|
|
}else DOIT {
|
|
cmpp_include_dir_add(pp, zArg);
|
|
}
|
|
}else if('L'==*zArg){
|
|
++zArg;
|
|
if(!*zArg){
|
|
cmpp__err(pp,CMPP_RC_MISUSE,"Missing directory for -L");
|
|
}else DOIT {
|
|
cmpp_module_dir_add(pp, zArg);
|
|
}
|
|
}
|
|
ISFLAG2("o","outfile"){
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp__out_fopen(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG2("f","file"){
|
|
ARGVAL;
|
|
do_infile:
|
|
DOIT {
|
|
if( !pp->pimpl->mod.path.z ){
|
|
cmpp_module_dir_add(pp, NULL);
|
|
}
|
|
++nFile;
|
|
if( 0
|
|
&& !pp->pimpl->flags.nIncludeDir
|
|
&& cmpp_include_dir_add(pp, ".") ){
|
|
break;
|
|
}
|
|
open_output_if_needed;
|
|
cmpp_process_file(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG("@"){
|
|
NOVAL;
|
|
DOIT {
|
|
assert( cmpp_atpol_DEFAULT_FOR_FLAG!=cmpp_atpol_OFF );
|
|
cmpp_atpol_set(pp, isNoFlag
|
|
? cmpp_atpol_OFF
|
|
: cmpp_atpol_DEFAULT_FOR_FLAG);
|
|
}
|
|
}
|
|
ISFLAG("@policy"){
|
|
ARGVAL;
|
|
cmpp_atpol_from_str(pp, zVal);
|
|
}
|
|
ISFLAG("debug"){
|
|
NOVAL;
|
|
DOIT {
|
|
pp->pimpl->flags.doDebug += isNoFlag ? -1 : 1;
|
|
}
|
|
}
|
|
ISFLAG2("u","undefined-policy"){
|
|
ARGVAL;
|
|
cmpp_unpol_from_str(pp, zVal);
|
|
}
|
|
ISFLAG("sql-trace"){
|
|
NOVAL;
|
|
/* Needs to be set before the start of the second pass, when
|
|
the db is inited. */
|
|
DOIT {
|
|
pp->pimpl->sqlTrace.expandSql = false;
|
|
do_trace_flag:
|
|
cmpp_outputer_cleanup(&pp->pimpl->sqlTrace.out);
|
|
if( isNoFlag ){
|
|
pp->pimpl->sqlTrace.out = cmpp_outputer_empty;
|
|
}else{
|
|
pp->pimpl->sqlTrace.out = cmpp_outputer_FILE;
|
|
pp->pimpl->sqlTrace.out.state = stderr;
|
|
}
|
|
}
|
|
}
|
|
ISFLAG("sql-trace-x"){
|
|
NOVAL;
|
|
DOIT {
|
|
pp->pimpl->sqlTrace.expandSql = true;
|
|
goto do_trace_flag;
|
|
}
|
|
}
|
|
ISFLAG("chomp-F"){
|
|
NOVAL;
|
|
DOIT pp->pimpl->flags.chompF = !isNoFlag;
|
|
}
|
|
ISFLAG2("d","delimiter"){
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp_delimiter_set(pp, zVal);
|
|
}
|
|
}
|
|
ISFLAG2("dd", "dump-defines"){
|
|
DOIT {
|
|
cmpp_FILE * const fp =
|
|
/* tcl's exec treats output to stderr as failure.
|
|
If we use [exec -ignorestderr] then it instead replaces
|
|
stderr's output with its own message, invalidating
|
|
test expectations. */
|
|
1 ? stdout : stderr;
|
|
fprintf(fp, "All %sdefine entries:\n",
|
|
cmpp__pp_zdelim(pp));
|
|
cmpp__dump_defines(pp, fp, 1);
|
|
}
|
|
}
|
|
#if !defined(CMPP_OMIT_D_DB)
|
|
ISFLAG2("db", "db-file"){
|
|
/* Undocumented flag used for testing purposes. */
|
|
ARGVAL;
|
|
DOIT {
|
|
cmpp_db_name_set(pp, zVal);
|
|
}
|
|
}
|
|
#endif
|
|
ISFLAG("version"){
|
|
NOVAL;
|
|
#if !defined(CMPP_OMIT_FILE_IO)
|
|
fprintf(stdout, "c-pp version %s\nwith SQLite %s %s\n",
|
|
cmpp_version(),
|
|
sqlite3_libversion(),
|
|
sqlite3_sourceid());
|
|
#endif
|
|
doIt = 100;
|
|
break;
|
|
}
|
|
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
|
|
ISFLAG("safe-mode"){
|
|
if( i>0 ){
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"--%s, if used, must be the first argument.",
|
|
zArg);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
else{
|
|
cmpp__err(pp,CMPP_RC_MISUSE,
|
|
"Unhandled flag: %s", argv[i]);
|
|
}
|
|
}
|
|
DOIT {
|
|
if(!nFile){
|
|
/* We got no file arguments, so read from stdin. */
|
|
if(0
|
|
&& !pp->pimpl->flags.nIncludeDir
|
|
&& cmpp_include_dir_add(pp, ".") ){
|
|
break;
|
|
}
|
|
open_output_if_needed;
|
|
cmpp_process_file(pp, "-");
|
|
}
|
|
}
|
|
#undef DOIT
|
|
}
|
|
return ppCode;
|
|
#undef ARGVAL
|
|
#undef M
|
|
#undef ISFLAG
|
|
#undef ISFLAG2
|
|
#undef NOVAL
|
|
#undef open_output_if_needed
|
|
}
|
|
|
|
void cmpp_process_argv_usage(char const *zAppName, cmpp_FILE *fOut){
|
|
#if defined(CMPP_OMIT_FILE_IO)
|
|
(void)zAppName; (void)fOut;
|
|
#else
|
|
fprintf(fOut, "%s version %s\nwith SQLite %s %s\n",
|
|
zAppName ? zAppName : "c-pp",
|
|
cmpp_version(),
|
|
sqlite3_libversion(),
|
|
sqlite3_sourceid());
|
|
fprintf(fOut, "Usage: %s [flags] [infile...]\n", zAppName);
|
|
fprintf(fOut,
|
|
"Flags and filenames may be in any order and "
|
|
"they are processed in that order.\n"
|
|
"\nFlags:\n");
|
|
#define GAP " "
|
|
#define arg(F,D) fprintf(fOut,"\n %s\n" GAP "%s\n",F, D)
|
|
#if defined(CMPP_MAIN) && !defined(CMPP_MAIN_SAFEMODE)
|
|
arg("--safe-mode",
|
|
"Disables preprocessing directives which use the filesystem "
|
|
"or invoke external processes. If used, it must be the first "
|
|
"argument.");
|
|
#endif
|
|
|
|
arg("-o|--outfile FILE","Send output to FILE (default=- (stdout)).\n"
|
|
GAP "Because arguments are processed in order, this should\n"
|
|
GAP "normally be given before -f.");
|
|
arg("-f|--file FILE","Process FILE (default=- (stdin)).\n"
|
|
GAP "All non-flag arguments are assumed to be the input files.");
|
|
arg("-e SCRIPT",
|
|
"Treat SCRIPT as a complete c-pp input and process it.\n"
|
|
GAP "Doing anything marginally useful with this requires\n"
|
|
GAP "using it several times, once per directive. It will not\n"
|
|
GAP "work with " CMPP_DEFAULT_DELIM "if but is fine for "
|
|
CMPP_DEFAULT_DELIM "expr, "
|
|
CMPP_DEFAULT_DELIM "assert, and "
|
|
CMPP_DEFAULT_DELIM "define.");
|
|
arg("-DXYZ[=value]","Define XYZ to the given value (default=1).");
|
|
arg("-UXYZ","Undefine all defines matching glob XYZ.");
|
|
arg("-IXYZ","Add dir XYZ to the " CMPP_DEFAULT_DELIM "include path.");
|
|
arg("-LXYZ","Add dir XYZ to the loadable module search path.");
|
|
arg("-FXYZ=filename",
|
|
"Define XYZ to the raw contents of the given file.\n"
|
|
GAP "The file is not processed as by " CMPP_DEFAULT_DELIM"include.\n"
|
|
GAP "Maybe it should be. Or maybe we need a new flag for that.");
|
|
arg("-d|--delimiter VALUE", "Set directive delimiter to VALUE "
|
|
"(default=" CMPP_DEFAULT_DELIM ").");
|
|
arg("--@policy retain|elide|error|off",
|
|
"Specifies how to handle @tokens@ (default=off).\n"
|
|
GAP "off = do not look for @tokens@\n"
|
|
GAP "retain = parse @tokens@ and retain any undefined ones\n"
|
|
GAP "elide = parse @tokens@ and elide any undefined ones\n"
|
|
GAP "error = parse @tokens@ and error out for any undefined ones"
|
|
);
|
|
arg("-u|--undefined-policy NAME",
|
|
"Sets the policy for how to handle references to undefined key:\n"
|
|
GAP "null = treat them as empty/falsy. This is the default.\n"
|
|
GAP "error = trigger an error. This should probably be "
|
|
"the default."
|
|
);
|
|
arg("-@", "Equivalent to --@policy=error.");
|
|
arg("-no-@", "Equivalent to --@policy=off (the default).");
|
|
arg("--sql-trace", "Send a trace of all SQL to stderr.");
|
|
arg("--sql-trace-x",
|
|
"Like --sql-trace but expand all bound values in the SQL.");
|
|
arg("--no-sql-trace", "Disable SQL tracing (default).");
|
|
arg("--chomp-F", "One trailing newline is trimmed from files "
|
|
"read via -FXYZ=filename.");
|
|
arg("--no-chomp-F", "Disable --chomp-F (default).");
|
|
#undef arg
|
|
#undef GAP
|
|
fputs("\nFlags which require a value accept either "
|
|
"--flag=value or --flag value. "
|
|
"The exceptions are that the -D... and -F... flags "
|
|
"require their '=' to be part of the flag (because they "
|
|
"are parsed elsewhere).\n\n",fOut);
|
|
#endif /*CMPP_OMIT_FILE_IO*/
|
|
}
|
|
|
|
const cmpp_b cmpp_b_empty = cmpp_b_empty_m;
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append4)(cmpp * const pp,
|
|
cmpp_b * const os,
|
|
void const * src,
|
|
cmpp_size_t n){
|
|
if( !ppCode && cmpp_b_append(os, src, n) ){
|
|
cmpp_check_oom(pp, 0);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_str)(cmpp_b * b, char const * str,
|
|
cmpp_strlen_t n){
|
|
return cmpp_b_append(b, str, cmpp__strlen(str, n));
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append4_str)(cmpp * pp, cmpp_b * b,
|
|
char const * str,
|
|
cmpp_strlen_t n){
|
|
return cmpp_b_append4(pp, b, str, cmpp__strlen(str, n));
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(int, cmpp_b_reserve3)(cmpp * const pp,
|
|
cmpp_b * const os,
|
|
cmpp_size_t n){
|
|
if( !ppCode && cmpp_b_reserve(os, n) ){
|
|
cmpp_check_oom(pp, 0);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(void, cmpp_b_clear)(cmpp_b *s){
|
|
if( s->z ) cmpp_mfree(s->z);
|
|
*s = cmpp_b_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_b *, cmpp_b_reuse)(cmpp_b * const s){
|
|
if( s->z ){
|
|
#if 1
|
|
memset(s->z, 0, s->nAlloc)
|
|
/* valgrind pushes for this, which is curious because
|
|
cmpp_b_reserve[3]() memset()s new space to 0.
|
|
|
|
Try the following without this block using one commit after
|
|
[5f9c31d1da1d] (that'll be the commit that this comment and #if
|
|
block were added):
|
|
|
|
##define foo
|
|
##if not defined a
|
|
##/if
|
|
##query define {select ?1 a} bind [1]
|
|
|
|
There's a misuse complaint about a jump depending on
|
|
uninitialized memory deep under cmpp__is_int(), in strlen(), on
|
|
the "define" argument of the ##query. It does not appear if
|
|
the lines above it are removed, which indicates that it's at
|
|
least semi-genuine. gcc v13.3.0, if it matters.
|
|
*/;
|
|
#else
|
|
s->z[0] = 0;
|
|
#endif
|
|
s->n = 0;
|
|
}
|
|
s->errCode = 0;
|
|
return s;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_swap)(cmpp_b * const l, cmpp_b * const r){
|
|
if( l!=r ){
|
|
cmpp_b const x = *l;
|
|
*l = *r;
|
|
*r = x;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_reserve)(cmpp_b *s, cmpp_size_t n){
|
|
if( 0==s->errCode && s->nAlloc < n ){
|
|
void * const m = cmpp_mrealloc(s->z, s->nAlloc + n);
|
|
if( m ){
|
|
memset((unsigned char *)m + s->nAlloc, 0, (n - s->nAlloc))
|
|
/* valgrind convincingly recommends this. */;
|
|
s->z = m;
|
|
s->nAlloc += n;
|
|
}else{
|
|
s->errCode = CMPP_RC_OOM;
|
|
}
|
|
}
|
|
return s->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append)(cmpp_b * os, void const *src,
|
|
cmpp_size_t n){
|
|
if(0==os->errCode){
|
|
cmpp_size_t const nNeeded = os->n + n + 1;
|
|
if( nNeeded>=os->nAlloc && cmpp_b_reserve(os, nNeeded) ){
|
|
assert( CMPP_RC_OOM==os->errCode );
|
|
return os->errCode;
|
|
}
|
|
if( n ){
|
|
memcpy(os->z + os->n, src, n);
|
|
os->n += n;
|
|
}
|
|
os->z[os->n] = 0;
|
|
if( 0 ) {
|
|
g_warn("n=%u z=[%.*s] nUsed=%d", (unsigned)n, (int)n,
|
|
(char const*) src, (int)os->n);
|
|
}
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_ch)(cmpp_b * os, char ch){
|
|
if( 0==os->errCode
|
|
&& (os->n+1<os->nAlloc
|
|
|| 0==cmpp_b_reserve(os, os->n+2)) ){
|
|
os->z[os->n++] = (unsigned char)ch;
|
|
os->z[os->n] = 0;
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_i32)(cmpp_b * os, int32_t d){
|
|
if( 0==os->errCode ){
|
|
char buf[16] = {0};
|
|
int const n = snprintf(buf, sizeof(buf), "%" PRIi32, d);
|
|
cmpp_b_append(os, buf, (unsigned)n);
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_b_append_i64)(cmpp_b * os, int64_t d){
|
|
if( 0==os->errCode ){
|
|
char buf[32] = {0};
|
|
int const n = snprintf(buf, sizeof(buf), "%" PRIi64, d);
|
|
cmpp_b_append(os, buf, (unsigned)n);
|
|
}
|
|
return os->errCode;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_b_chomp)(cmpp_b * b){
|
|
return cmpp_chomp(b->z, &b->n);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_list_cleanup)(cmpp_b_list *li){
|
|
while( li->na ){
|
|
cmpp_b * const b = li->list[--li->na];
|
|
if(b){
|
|
cmpp_b_clear(b);
|
|
cmpp_mfree(b);
|
|
}
|
|
}
|
|
cmpp_mfree(li->list);
|
|
*li = cmpp_b_list_empty;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_list_reuse)(cmpp_b_list *li){
|
|
while( li->n ){
|
|
cmpp_b * const b = li->list[--li->n];
|
|
if(b) cmpp_b_reuse(b);
|
|
}
|
|
}
|
|
|
|
static cmpp_b * cmpp_b_list_push(cmpp_b_list *li){
|
|
cmpp_b * p = 0;
|
|
assert( li->list ? li->na : 0==li->na );
|
|
if( !cmpp_b_list_reserve(NULL, li,
|
|
cmpp__li_reserve1_size(li, 20)) ){
|
|
p = li->list[li->n];
|
|
if( p ){
|
|
cmpp_b_reuse(p);
|
|
}else{
|
|
p = cmpp_malloc(sizeof(*p));
|
|
if( p ){
|
|
li->list[li->n++] = p;
|
|
*p = cmpp_b_empty;
|
|
}
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
|
|
largest first and empty slots last.
|
|
*/
|
|
static int cmpp_b__cmp_desc(const void *p1, const void *p2){
|
|
cmpp_b const * const eL = *(cmpp_b const **)p1;
|
|
cmpp_b const * const eR = *(cmpp_b const **)p2;
|
|
if( eL==eR ) return 0;
|
|
else if( !eL ) return 1;
|
|
else if (!eR ) return -1;
|
|
return (int)(/*largest first*/eL->nAlloc - eR->nAlloc);
|
|
}
|
|
|
|
/**
|
|
bsearch()/qsort() comparison for (cmpp_b**), sorting by size,
|
|
smallest first and empty slots last.
|
|
*/
|
|
static int cmpp_b__cmp_asc(const void *p1, const void *p2){
|
|
cmpp_b const * const eL = *(cmpp_b const **)p1;
|
|
cmpp_b const * const eR = *(cmpp_b const **)p2;
|
|
if( eL==eR ) return 0;
|
|
else if( !eL ) return 1;
|
|
else if (!eR ) return -1;
|
|
return (int)(/*smallest first*/eR->nAlloc - eL->nAlloc);
|
|
}
|
|
|
|
/**
|
|
Sort li's buffer list using the given policy. NULL entries always
|
|
sort last. This is a no-op of how == cmpp_b_list_UNSORTED or
|
|
li->n<2.
|
|
*/
|
|
static void cmpp_b_list__sort(cmpp_b_list * const li,
|
|
enum cmpp_b_list_e how){
|
|
switch( li->n<2 ? cmpp_b_list_UNSORTED : how ){
|
|
case cmpp_b_list_UNSORTED:
|
|
break;
|
|
case cmpp_b_list_DESC:
|
|
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_desc);
|
|
break;
|
|
case cmpp_b_list_ASC:
|
|
qsort(li->list, li->n, sizeof(cmpp_b*), cmpp_b__cmp_asc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(cmpp_b *, cmpp_b_borrow)(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
#define re pi->recycler.buf
|
|
cmpp_b_list * const li = &re.list;
|
|
cmpp_b * b = 0;
|
|
if( cmpp_b_list_UNSORTED==re.sort ){
|
|
re.sort = cmpp_b_list_DESC;
|
|
cmpp_b_list__sort(li, re.sort);
|
|
assert( cmpp_b_list_UNSORTED!=re.sort
|
|
|| re.list.n<2 );
|
|
}
|
|
for( cmpp_size_t i = 0; i < li->n; ++i ){
|
|
b = li->list[i];
|
|
if( b ){
|
|
assert( b->nAlloc <= re.currentCapacity );
|
|
re.currentCapacity -= b->nAlloc;
|
|
li->list[i] = 0;
|
|
assert( !b->n &&
|
|
"Someone wrote to a buffer after giving it back" );
|
|
if( i < li->n-1 ){
|
|
re.sort = cmpp_b_list_UNSORTED;
|
|
}
|
|
return cmpp_b_reuse(b);
|
|
}
|
|
}
|
|
/**
|
|
Allocate the list entry now and then remove the buffer from it to
|
|
"borrow" it. We allocate now, instead of in cmpp_b_return(), so
|
|
that that function has no OOM condition (handling it properly in
|
|
higher-level code would be a mess).
|
|
*/
|
|
b = cmpp_b_list_push(li);
|
|
if( 0==cmpp_check_oom(pp, b) ) {
|
|
assert( b==li->list[li->n-1] );
|
|
li->list[li->n-1] = 0;
|
|
}
|
|
#undef re
|
|
return b;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_b_return)(cmpp *pp, cmpp_b *b){
|
|
if( !b ) return;
|
|
cmpp__pi(pp);
|
|
#define re pi->recycler.buf
|
|
if( !re.limitCount || (re.list.n+1>re.limitCount) ){
|
|
goto free_it;
|
|
}
|
|
if( (re.limitCapacity
|
|
&& (b->nAlloc + re.currentCapacity > re.limitCapacity))
|
|
|| (re.limitSingleCapacity
|
|
&& b->nAlloc>re.limitSingleCapacity) ){
|
|
/* keep b but not its memory. */
|
|
cmpp_b_clear(b);
|
|
}
|
|
cmpp_b_list * const li = &re.list;
|
|
/* TODO (2026-09-05): we should go from the back of the list, as
|
|
these are always returned in reverse order of borrow, sometimes
|
|
out of order by 2-3 for a batch of buffers. */
|
|
cmpp_size_t i = li->n;
|
|
while( i ){
|
|
--i;
|
|
if( !li->list[i] ){
|
|
re.currentCapacity += b->nAlloc;
|
|
li->list[i] = cmpp_b_reuse(b);
|
|
if( i+1 < li->n ) re.sort = cmpp_b_list_UNSORTED;
|
|
return;
|
|
}
|
|
}
|
|
#undef re
|
|
assert( !"This shouldn't be possible - no available "
|
|
"slot in buffer recycler. "
|
|
"This suggests that cmpp_b_return() was used without a "
|
|
"corresponding cmpp_b_borrow() or that a given buffer "
|
|
"was returned multiple times." );
|
|
free_it:
|
|
cmpp_b_clear(b);
|
|
cmpp_mfree(b);
|
|
}
|
|
|
|
void cmpp_b_library_dtor(cmpp_b_library *li){
|
|
static const cmpp_b_library empty = cmpp_b_library_empty_m;
|
|
cmpp_b_list_cleanup(&li->list);
|
|
cmpp_b_library const old = *li;
|
|
*li = empty;
|
|
li->limitCount = old.limitCount;
|
|
li->limitSingleCapacity = old.limitSingleCapacity;
|
|
li->limitCapacity = old.limitCapacity;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_output_f_b)(
|
|
void * state, void const * src, cmpp_size_t n
|
|
){
|
|
if( state ){
|
|
return cmpp_b_append(state, src, n);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if CMPP__OBUF
|
|
int cmpp__obuf_flush(cmpp__obuf * b);
|
|
int cmpp__obuf_write(cmpp__obuf * b, void const * src, cmpp_size_t n);
|
|
void cmpp__obuf_cleanup(cmpp__obuf * b);
|
|
int cmpp_output_f_obuf(void * state, void const * src, cmpp_size_t n);
|
|
int cmpp_flush_f_obuf(void * state);
|
|
void cmpp_outputer_cleanup_f_obuf(cmpp_outputer * o);
|
|
const cmpp__obuf cmpp__obuf_empty = cmpp__obuf_empty_m;
|
|
const cmpp_outputer cmpp_outputer_obuf = {
|
|
.out = cmpp_output_f_obuf,
|
|
.flush = cmpp_flush_f_obuf,
|
|
.cleanup = cmpp_outputer_cleanup_f_obuf
|
|
};
|
|
#endif
|
|
|
|
/* base64 pieces copied from sqlite3.org/src/file/ext/misc/base64.c on
|
|
2026-07-19. */
|
|
#define PC 0x80 /* pad character */
|
|
#define WS 0x81 /* whitespace */
|
|
#define ND 0x82 /* Not above or digit-value */
|
|
#define PAD_CHAR '='
|
|
|
|
static const char b64Numerals[64+1]
|
|
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
#define BX_NUMERAL(dv) (b64Numerals[(uint8_t)(dv)])
|
|
/* Width of base64 lines. Should be an integer multiple of 4. */
|
|
#define B64_DARK_MAX 72
|
|
|
|
/* Encode a byte buffer into base64 text with linefeeds appended to limit
|
|
** encoded group lengths to B64_DARK_MAX or to terminate the last group.
|
|
*/
|
|
static char* toBase64( uint8_t const *pIn, cmpp_size_t nbIn, char *pOut ){
|
|
int nCol = 0;
|
|
while( nbIn >= 3 ){
|
|
/* Do the bit-shuffle, exploiting unsigned input to avoid masking. */
|
|
pOut[0] = BX_NUMERAL(pIn[0]>>2);
|
|
pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f);
|
|
pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6));
|
|
pOut[3] = BX_NUMERAL(pIn[2]&0x3f);
|
|
pOut += 4;
|
|
nbIn -= 3;
|
|
pIn += 3;
|
|
if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){
|
|
*pOut++ = '\n';
|
|
nCol = 0;
|
|
}
|
|
}
|
|
if( nbIn > 0 ){
|
|
signed char nco = nbIn+1;
|
|
cmpp_strlen_t nbe;
|
|
unsigned long qv = *pIn++;
|
|
for( nbe=1; nbe<3; ++nbe ){
|
|
qv <<= 8;
|
|
if( (cmpp_size_t)nbe<nbIn ) qv |= *pIn++;
|
|
}
|
|
for( nbe=3; nbe>=0; --nbe ){
|
|
char ce = (nbe<nco)? BX_NUMERAL((uint8_t)(qv & 0x3f)) : PAD_CHAR;
|
|
qv >>= 6;
|
|
pOut[nbe] = ce;
|
|
}
|
|
pOut += 4;
|
|
*pOut++ = '\n';
|
|
}
|
|
*pOut = 0;
|
|
return pOut;
|
|
}
|
|
|
|
#if 0 /* from-base-64. We don't yet have a #base64 -d[ecode] */
|
|
/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */
|
|
static const uint8_t b64DigitValues[128] = {
|
|
/* HT LF VT FF CR */
|
|
ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND,
|
|
/* US */
|
|
ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND,
|
|
/*sp + / */
|
|
WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63,
|
|
/* 0 1 5 9 = */
|
|
52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND,
|
|
/* A O */
|
|
ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
|
|
/* P Z */
|
|
15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND,
|
|
/* a o */
|
|
ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
|
|
/* p z */
|
|
41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND
|
|
};
|
|
#define BX_DV_PROTO(c) \
|
|
((((uint8_t)(c))<0x80)? (uint8_t)(b64DigitValues[(uint8_t)(c)]) : 0x80)
|
|
#define IS_BX_DIGIT(bdp) (((uint8_t)(bdp))<0x80)
|
|
#define IS_BX_WS(bdp) ((bdp)==WS)
|
|
#define IS_BX_PAD(bdp) ((bdp)==PC)
|
|
/* Skip over text which is not base64 numeral(s). */
|
|
static char * skipNonB64( char *s, int nc ){
|
|
char c;
|
|
while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s;
|
|
return s;
|
|
}
|
|
|
|
/* Decode base64 text into a byte buffer. */
|
|
static uint8_t* fromBase64( char *pIn, int ncIn, uint8_t *pOut ){
|
|
if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn;
|
|
while( ncIn>0 && *pIn!=PAD_CHAR ){
|
|
static signed char nboi[] = { 0, 0, 1, 2, 3 };
|
|
char *pUse = skipNonB64(pIn, ncIn);
|
|
unsigned long qv = 0L;
|
|
int nti, nbo, nac;
|
|
ncIn -= (pUse - pIn);
|
|
pIn = pUse;
|
|
nti = (ncIn>4)? 4 : ncIn;
|
|
ncIn -= nti;
|
|
nbo = nboi[nti];
|
|
if( nbo==0 ) break;
|
|
for( nac=0; nac<4; ++nac ){
|
|
char c = (nac<nti)? *pIn++ : b64Numerals[0];
|
|
uint8_t bdp = BX_DV_PROTO(c);
|
|
switch( bdp ){
|
|
case ND:
|
|
/* Treat dark non-digits as pad, but they terminate decode too. */
|
|
ncIn = 0;
|
|
deliberate_fall_through; /* FALLTHRU */
|
|
case WS:
|
|
/* Treat whitespace as pad and terminate this group.*/
|
|
nti = nac;
|
|
deliberate_fall_through; /* FALLTHRU */
|
|
case PC:
|
|
bdp = 0;
|
|
--nbo;
|
|
deliberate_fall_through; /* FALLTHRU */
|
|
default: /* bdp is the digit value. */
|
|
qv = qv<<6 | bdp;
|
|
break;
|
|
}
|
|
}
|
|
switch( nbo ){
|
|
case 3:
|
|
pOut[2] = (qv) & 0xff;
|
|
deliberate_fall_through; /* FALLTHRU */
|
|
case 2:
|
|
pOut[1] = (qv>>8) & 0xff;
|
|
deliberate_fall_through; /* FALLTHRU */
|
|
case 1:
|
|
pOut[0] = (qv>>16) & 0xff;
|
|
break;
|
|
}
|
|
pOut += nbo;
|
|
}
|
|
return pOut;
|
|
}
|
|
#endif /* base64 decode */
|
|
|
|
/**
|
|
Base64-encodes the contents of bIn and replaces bIn's contents
|
|
(if any) with these.
|
|
|
|
Potential TODO is have it append to bOut, but it's simpler to
|
|
replace it and that's all our use cases currently need.
|
|
*/
|
|
int cmpp__b_base64_encode(cmpp *pp, cmpp_b const * bIn, cmpp_b * bOut){
|
|
cmpp_size_t nc;
|
|
cmpp_size_t const nv = bIn->n;
|
|
char *cBuf;
|
|
cmpp_size_t const nvMax = 1000 * 1000 * 1000
|
|
/* 1B == default SQLITE_MAX_LENGTH, but that symbol is not exposed
|
|
in its public API. We "could" extract that limit from pp's db
|
|
here, but... no. We're only using it as a guideline: bOut is
|
|
not not part of the db state so we're not beholden to the db's
|
|
limits. In this context the limit is arbitrary, anyway - we
|
|
inherit it from this code's origins and retain it for
|
|
simplicity. */;
|
|
if( ppCode ) return ppCode;
|
|
nc = 4*((nv+2)/3); /* quads needed */
|
|
nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */
|
|
if( nvMax < nc ){
|
|
return cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Blob expanded to base64 is too big.");
|
|
}
|
|
cmpp_b_reuse(bOut);
|
|
if( !bIn->z || !bIn->n ){
|
|
/* empty is okay */
|
|
return 0;
|
|
}
|
|
if( 0==cmpp_b_reserve3(pp, bOut, nc) ){
|
|
cBuf = (char*)bOut->z;
|
|
bOut->n = (cmpp_size_t)(toBase64(bIn->z, bIn->n, cBuf) - cBuf);
|
|
assert( bOut->n < bOut->nAlloc );
|
|
cBuf[bOut->n] = 0;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__hexbyte( int ch ){
|
|
if(ch>='0' && ch<='9') return ch - '0';
|
|
else if(ch>='a' && ch<='f') return ch - 'a' + 10;
|
|
else if(ch>='A' && ch<='F') return ch - 'A' + 10;
|
|
else return -1;
|
|
}
|
|
|
|
int cmpp__utf8_char_to_cstr(unsigned int c, unsigned char *output,
|
|
cmpp_size_t length){
|
|
/*
|
|
Stolen from the internet many years ago, adapted from several
|
|
variations which all _seem_ to have derived from librdf.
|
|
*/
|
|
cmpp_size_t size=0;
|
|
|
|
/* check for illegal code positions:
|
|
* U+D800 to U+DFFF (UTF-16 surrogates)
|
|
* U+FFFE and U+FFFF
|
|
*/
|
|
if((c > 0xD7FF && c < 0xE000)
|
|
|| c == 0xFFFE || c == 0xFFFF) return -1;
|
|
/* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */
|
|
if(c > 0x10ffff) return -1;
|
|
if(c < 0x00000080) size = 1;
|
|
else if(c < 0x00000800) size = 2;
|
|
else if(c < 0x00010000) size = 3;
|
|
else size = 4;
|
|
if(!output) return (int)size;
|
|
else if(size > length) return -1;
|
|
else switch(size) {
|
|
case 0:
|
|
assert(!"can't happen anymore");
|
|
output[0] = 0;
|
|
return 0;
|
|
case 4:
|
|
output[3] = 0x80 | (c & 0x3F);
|
|
c = c >> 6;
|
|
c |= 0x10000;
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
case 3:
|
|
output[2] = 0x80 | (c & 0x3F);
|
|
c = c >> 6;
|
|
c |= 0x800;
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
case 2:
|
|
output[1] = 0x80 | (c & 0x3F);
|
|
c = c >> 6;
|
|
c |= 0xc0;
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
case 1:
|
|
output[0] = (unsigned char)c;
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
default:
|
|
return (int)size;
|
|
}
|
|
}
|
|
|
|
int cmpp__read_hex_bytes(unsigned char const *zPos,
|
|
unsigned int howMany,
|
|
unsigned int * rv ){
|
|
unsigned int rc = 0;
|
|
int ch1, ch2;
|
|
unsigned int n = 0;
|
|
assert(!(howMany%2) && "howMany must be an even number!");
|
|
assert(zPos && rv);
|
|
while(n<howMany){
|
|
ch1 = *zPos ? cmpp__hexbyte(*zPos++) : -1;
|
|
ch2 = (ch1>=0) ? cmpp__hexbyte(*zPos++) : -1;
|
|
if(ch2<0) return -1;
|
|
rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2);
|
|
n += 2;
|
|
}
|
|
*rv = rc;
|
|
return (int)n;
|
|
}
|
|
|
|
|
|
CMPP__EXPORT(void,cmpp_b_unescape)(cmpp_b * b){
|
|
unsigned char const * z = b->z;
|
|
unsigned char * oz = b->z;
|
|
unsigned char const * const zEnd = z ? z + b->n : 0;
|
|
unsigned char const *zEsc = 0;
|
|
while( z < zEnd ){
|
|
switch( *z ){
|
|
case '\\':
|
|
if( zEsc ){
|
|
*oz++ = '\\';
|
|
zEsc = 0;
|
|
}else{
|
|
zEsc = z;
|
|
}
|
|
++z;
|
|
continue;
|
|
case 'b': case 'n': case 'r': case 't':
|
|
case '"': case '`': case '\'':
|
|
if( zEsc ){
|
|
switch( *z ){
|
|
case 'b': *oz++ = '\b'; break;
|
|
case 'n': *oz++ = '\n'; break;
|
|
case 'r': *oz++ = '\r'; break;
|
|
case 't': *oz++ = '\t'; break;
|
|
/* No \f or \v. Nobody uses those. And no \0 because
|
|
it's inappropriate in this context. */
|
|
case '"': case '`': case '\'': *oz++ = *z; break;
|
|
case 'u': case 'U':{
|
|
/* \uXXXX and \UXXXXXXXX */
|
|
unsigned int uChar = 0;
|
|
unsigned const ulen = 'u'==*z ? (unsigned)4 : (unsigned)8;
|
|
unsigned char const uCount = z+ulen<zEnd
|
|
? cmpp__read_hex_bytes(z+1, ulen, &uChar)
|
|
: 0;
|
|
zEsc = 0;
|
|
if( uCount != ulen ){
|
|
no_such_u:
|
|
/* We don't have a way of reporting an error here
|
|
beyond b->errCode BUT the tokenizer we feed this
|
|
from is supposed to have validated these already.
|
|
We'll just emit invalid ones as-is. */
|
|
*oz++ = *zEsc;
|
|
*oz++ = *z++;
|
|
continue;
|
|
}
|
|
assert(0 == (uCount % 2));
|
|
int const check = cmpp__utf8_char_to_cstr(uChar, oz,
|
|
uCount);
|
|
if(check<0){
|
|
goto no_such_u;
|
|
}
|
|
oz += check;
|
|
z += uCount;
|
|
continue;
|
|
}
|
|
}
|
|
++z;
|
|
break;
|
|
}
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
default:
|
|
if( zEsc ) *oz++ = '\\' /* put it back */;
|
|
*oz++ = *z++;
|
|
}
|
|
zEsc = 0;
|
|
}
|
|
if( oz ){
|
|
assert( oz<=zEnd );
|
|
if( zEsc ){
|
|
assert( oz<zEnd );
|
|
/* Put trailing backslash back. */
|
|
*oz++ = '\\';
|
|
}
|
|
*oz = 0;
|
|
b->n = (cmpp_size_t)(oz - b->z);
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_b_cmp)(cmpp_b const * b, void const *p,
|
|
cmpp_ssize32_t n){
|
|
unsigned char const * z = p;
|
|
cmpp_size_t const nn = cmpp__strlenu(z, n);
|
|
int const rc = memcmp(z, b->z, nn<b->n ? nn : b->n);
|
|
return (rc || b->n==nn)
|
|
? rc
|
|
: (b->n<nn ? -1 : 1/* prefix match: shortest first */);
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_b_append_json_value)(cmpp *pp, cmpp_b *b,
|
|
unsigned char const *z,
|
|
cmpp_strlen_t n){
|
|
int64_t j = 0;
|
|
int rc;
|
|
n = cmpp__strlenu(z, n);
|
|
if( cmpp_is_int64(z, n, &j) ){
|
|
rc = cmpp_b_append_i64(b, j);
|
|
if( rc ) cmpp_err(pp, rc, 0, 0);
|
|
}else if( cmpp_json_valid(pp, z, n, 3) ){
|
|
rc = cmpp_b_append4(pp, b, z, n);
|
|
}else {
|
|
sqlite3_str * const s = cmpp__sqlite3_str_new(pp);
|
|
if( s ){
|
|
sqlite3_str_appendf(s, "%J", z);
|
|
int nn = 0;
|
|
char * const zz = cmpp_sqlite3_str_finish(pp, s, &nn);
|
|
if( zz ){
|
|
rc = cmpp_b_append4(pp, b, zz, n);
|
|
}else{
|
|
rc = cmpp_err_has(pp);
|
|
assert( rc );
|
|
}
|
|
cmpp_mfree(zz);
|
|
}else{
|
|
rc = cmpp_err_has(pp);
|
|
assert( rc );
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#undef PC
|
|
#undef WS
|
|
#undef ND
|
|
#undef PAD_CHAR
|
|
#undef UB_TYPEDEF
|
|
#undef BX_DV_PROTO
|
|
#undef IS_BX_DIGIT
|
|
#undef IS_BX_WS
|
|
#undef IS_BX_PAD
|
|
#undef BX_NUMERAL
|
|
#undef B64_DARK_MAX
|
|
|
|
static int cmpp__stmt_prepare(cmpp *pp, bool honorError,
|
|
sqlite3_stmt **pStmt,
|
|
const char * zSql, cmpp_strlen_t nSql){
|
|
if( honorError && ppCode ) return ppCode;
|
|
nSql = cmpp__strlen(zSql, nSql);
|
|
int const rc = sqlite3_prepare_v2(pp->pimpl->db.dbh, zSql, nSql, pStmt, 0);
|
|
if( 0==rc && !*pStmt ){
|
|
cmpp_err_c(pp, CMPP_RC_MISUSE,
|
|
"Empty queries are not permitted here.");
|
|
}else if( cmpp_db_rc_v2(pp, rc, zSql, nSql) ){
|
|
g_warn("SQLite rc=%d Offending SQL: %.*s", rc, nSql, zSql);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__stmt_preparev(cmpp *pp, bool honorError,
|
|
sqlite3_stmt **pStmt,
|
|
const char * zSql, va_list va){
|
|
if( honorError && ppCode ) return ppCode;
|
|
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
|
|
char * z = 0;
|
|
int n = 0;
|
|
assert( pp->pimpl->db.dbh );
|
|
sqlite3_str_vappendf(str, zSql, va);
|
|
z = cmpp_sqlite3_str_finish(pp, str, &n);
|
|
if( z ){
|
|
(void)cmpp__stmt_prepare(pp, honorError, pStmt, z, n);
|
|
sqlite3_free(z);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_stmt_prepare)(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, cmpp_strlen_t nSql){
|
|
return cmpp__stmt_prepare(pp, true, pStmt, zSql, nSql);
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_stmt_preparev)(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, va_list va){
|
|
return cmpp__stmt_preparev(pp, true, pStmt, zSql, va);
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_stmt_preparef)(cmpp *pp, sqlite3_stmt **pStmt,
|
|
const char * zSql, ...){
|
|
if( ppCode ) return ppCode;
|
|
int rc;
|
|
va_list va;
|
|
va_start(va, zSql);
|
|
rc = cmpp__stmt_preparev(pp, true, pStmt, zSql, va);
|
|
va_end(va);
|
|
return rc;
|
|
}
|
|
|
|
sqlite3_stmt * cmpp__stmt(cmpp * pp, enum CmppStmt_e which,
|
|
bool prepEvenIfErr){
|
|
if( !pp->pimpl->db.dbh && cmpp__db_init(pp) ) return NULL;
|
|
sqlite3_stmt ** q = 0;
|
|
char const * zSql = 0;
|
|
switch(which){
|
|
default:
|
|
cmpp__fatal("Maintenance required: not a valid CmppStmt ID: %d", which);
|
|
return NULL /* not reached */;
|
|
#define E(N,S) case CmppStmt_ ## N: zSql = S; q = &pp->pimpl->stmt.N; break;
|
|
CmppStmt_map(E)
|
|
#undef E
|
|
}
|
|
assert( q );
|
|
assert( zSql && *zSql );
|
|
if( !*q && (!ppCode || prepEvenIfErr) ){
|
|
cmpp__stmt_prepare(pp, false, q, zSql, -1);
|
|
}
|
|
return *q;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_stmt_reset)(cmpp *pp, sqlite3_stmt * const q){
|
|
int rc = 0;
|
|
if( q ){
|
|
sqlite3_clear_bindings(q);
|
|
rc = sqlite3_reset(q);
|
|
switch( rc ){
|
|
case 0:
|
|
case SQLITE_DONE:
|
|
case SQLITE_ROW:
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static inline int cmpp__stmt_is_sp(cmpp const * const pp,
|
|
sqlite3_stmt const * const q){
|
|
return q==pp->pimpl->stmt.spBegin
|
|
|| q==pp->pimpl->stmt.spRelease
|
|
|| q==pp->pimpl->stmt.spRollback;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_stmt_step)(cmpp * const pp, sqlite3_stmt * const q, bool resetIt){
|
|
int rc = SQLITE_ERROR;
|
|
assert( q );
|
|
if( !ppCode || cmpp__stmt_is_sp(pp,q) ){
|
|
rc = sqlite3_step(q);
|
|
cmpp_db_rc_v2(pp, rc, sqlite3_sql(q), -1);
|
|
}
|
|
if( resetIt /* even if ppCode!=0 */ ) cmpp_stmt_reset(pp, q);
|
|
assert( 0!=rc );
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_int)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col, int64_t val){
|
|
return ppCode
|
|
? ppCode
|
|
: cmpp_db_rc(pp, sqlite3_bind_int64(pStmt, col, val),
|
|
"from cmpp_stmt_bind_int()");
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_int_text)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col, int64_t val){
|
|
unsigned char buf[32];
|
|
snprintf((char *)buf, sizeof(buf), "%" PRIi64, val);
|
|
return cmpp_stmt_bind_textn(pp, pStmt, col, buf, -1);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_null)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col){
|
|
return ppCode
|
|
? ppCode
|
|
: cmpp_db_rc(pp, sqlite3_bind_null(pStmt, col),
|
|
"from cmpp_stmt_bind_null()");
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_textx)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col, unsigned const char * zStr,
|
|
cmpp_ssize32_t n, void (*dtor)(void *)){
|
|
if( 0==ppCode ){
|
|
cmpp_db_rc_v2(
|
|
pp, (zStr && n)
|
|
? sqlite3_bind_text(pStmt, col,
|
|
(char const *)zStr,
|
|
(int)n, dtor)
|
|
: sqlite3_bind_null(pStmt, col),
|
|
sqlite3_sql(pStmt),
|
|
-1
|
|
);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_textn)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col,
|
|
unsigned const char * zStr,
|
|
cmpp_ssize32_t n){
|
|
return cmpp_stmt_bind_textx(pp, pStmt, col, zStr, (int)n,
|
|
SQLITE_TRANSIENT);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_stmt_bind_text)(cmpp *pp, sqlite3_stmt *pStmt,
|
|
int col,
|
|
unsigned const char * zStr){
|
|
return cmpp_stmt_bind_textn(pp, pStmt, col, zStr, -1);
|
|
}
|
|
|
|
#if 0
|
|
int cmpp_stmt_bind_textv(cmpp*pp, sqlite3_stmt *pStmt, int col,
|
|
const char * zFmt, ...){
|
|
if( 0==p->err.code ){
|
|
int rc;
|
|
sqlite3_str * str = sqlite3_str_new(pp->pimpl->db.dbh);
|
|
int n = 0;
|
|
char * z;
|
|
va_list va;
|
|
va_start(va,zFmt);
|
|
sqlite3_str_vappendf(str, zFmt, va);
|
|
va_end(va);
|
|
z = cmpp_str_finish(str, &n);
|
|
cmpp_db_rc(
|
|
pp, z
|
|
? sqlite3_bind_text(pStmt, col, z, n, sqlite3_free)
|
|
: sqlite3_bind_null(pStmt, col),
|
|
sqlite3_sql(pStmt)
|
|
);
|
|
cmpp_mfree(z);
|
|
}
|
|
return p->err.code;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
Expects an SQLITE_... result code and returns an approximate match
|
|
from cmpp_rc_e. It specifically treats SQLITE_ROW and SQLITE_DONE
|
|
as non-errors, returning 0 for those.
|
|
*/
|
|
static int cmpp__db_errcode(sqlite3 * const db, int sqliteCode){
|
|
(void)db;
|
|
int rc = 0;
|
|
switch(sqliteCode & 0xff){
|
|
case SQLITE_ROW:
|
|
case SQLITE_DONE:
|
|
case SQLITE_OK: rc = 0; break;
|
|
case SQLITE_NOMEM: rc = CMPP_RC_OOM; break;
|
|
case SQLITE_CORRUPT: rc = CMPP_RC_CORRUPT; break;
|
|
case SQLITE_TOOBIG:
|
|
case SQLITE_FULL:
|
|
case SQLITE_RANGE: rc = CMPP_RC_RANGE; break;
|
|
case SQLITE_NOTFOUND: rc = CMPP_RC_NOT_FOUND; break;
|
|
case SQLITE_PERM:
|
|
case SQLITE_AUTH:
|
|
case SQLITE_BUSY:
|
|
case SQLITE_LOCKED:
|
|
case SQLITE_READONLY: rc = CMPP_RC_ACCESS; break;
|
|
case SQLITE_CANTOPEN:
|
|
case SQLITE_IOERR: rc = CMPP_RC_IO; break;
|
|
case SQLITE_NOLFS: rc = CMPP_RC_UNSUPPORTED; break;
|
|
default:
|
|
//MARKER(("sqlite3_errcode()=0x%04x\n", rc));
|
|
rc = CMPP_RC_DB; break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_db_rc_v2)(cmpp *pp, int dbRc, char const *zMsg,
|
|
cmpp_strlen_t nMsg){
|
|
switch(dbRc){
|
|
case 0:
|
|
case SQLITE_DONE:
|
|
case SQLITE_ROW:
|
|
return 0;
|
|
case SQLITE_NOMEM:
|
|
return CMPP_RC_OOM;
|
|
default:
|
|
if( zMsg && (nMsg = cmpp__strlen(zMsg, nMsg)) ){
|
|
return cmpp_errf(
|
|
pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
|
|
"SQLite error #%d: %s: %.*s",
|
|
dbRc,
|
|
pp->pimpl->db.dbh
|
|
? sqlite3_errmsg(pp->pimpl->db.dbh)
|
|
: "<no db handle>",
|
|
nMsg, zMsg
|
|
);
|
|
}else{
|
|
return cmpp_errf(
|
|
pp, cmpp__db_errcode(pp->pimpl->db.dbh, dbRc),
|
|
"SQLite error #%d: %s",
|
|
dbRc, pp->pimpl->db.dbh
|
|
? sqlite3_errmsg(pp->pimpl->db.dbh)
|
|
: "<no db handle>"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
The base "define" impl. Requires q to be an INSERT for one of the
|
|
define tables and have the (t,k,v) columns set up to bind to ?1,
|
|
?2, and ?3. nVal must be the number of bytes of zVal to bind. If
|
|
zVal is NULL then (zVal="1", nVal=1) is assumed, else if nVal is 0
|
|
then zVal is bound as SQL NULL. That's wonky, but it's what we
|
|
currently have.
|
|
*/
|
|
static
|
|
int cmpp__define_impl(cmpp * const pp,
|
|
sqlite3_stmt * const q,
|
|
unsigned char const * zKey,
|
|
cmpp_strlen_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_strlen_t nVal,
|
|
int tType,
|
|
bool resetStmt){
|
|
if( 0==ppCode){
|
|
assert( q );
|
|
nKey = cmpp__strlenu(zKey, nKey);
|
|
nVal = cmpp__strlenu(zVal, nVal);
|
|
if( 0==cmpp_stmt_bind_textn(pp, q, 2, zKey, (int)nKey)
|
|
&& 0==cmpp_stmt_bind_int(pp, q, 1, tType) ){
|
|
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
|
|
/* TODO? if tType==cmpp_TT_Blob, bind it as a blob */
|
|
if( zVal ){
|
|
if( nVal ){
|
|
cmpp_stmt_bind_textn(pp, q, 3, zVal, (int)nVal);
|
|
}else{
|
|
/* Arguable */
|
|
cmpp_stmt_bind_null(pp, q, 3);
|
|
}
|
|
}else{
|
|
cmpp_stmt_bind_int(pp, q, 3, 1);
|
|
}
|
|
cmpp_stmt_step(pp, q, resetStmt);
|
|
g_debug(pp,2,("define: %s [%s]=[%.*s]\n",
|
|
cmpp_tt_cstr(tType), zKey, (int)nVal, zVal));
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__define2(cmpp *pp,
|
|
unsigned char const * zKey,
|
|
cmpp_strlen_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_strlen_t nVal,
|
|
cmpp_tt_e tType){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( q ){
|
|
cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, tType, true);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
The legacy variant of define() which accepts X=Y in zKey. This
|
|
continues to exist because it's convenient for passing args from
|
|
main().
|
|
*/
|
|
static int cmpp__define_legacy(cmpp *pp, const char * zKey, char const *zVal,
|
|
cmpp_tt_e ttype ){
|
|
|
|
if(ppCode) return ppCode;
|
|
CmppKvp kvp = CmppKvp_empty;
|
|
if( CmppKvp_parse(pp, &kvp, ustr_c(zKey), -1,
|
|
zVal
|
|
? CmppKvp_op_none
|
|
: CmppKvp_op_eq1) ) {
|
|
return ppCode;
|
|
}
|
|
if( kvp.v.z ){
|
|
if( zVal ){
|
|
assert(!"cannot happen - CmppKvp_op_none will prevent it");
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Cannot assign two values to [%.*s] [%.*s] [%s]",
|
|
kvp.k.n, kvp.k.z, kvp.v.n, kvp.v.z, zVal);
|
|
}
|
|
}else{
|
|
kvp.v.z = (unsigned char const *)zVal;
|
|
kvp.v.n = zVal ? (int)strlen(zVal) : 0;
|
|
}
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_defIns, false);
|
|
if( !q ) return ppCode;
|
|
int64_t intCheck = 0;
|
|
switch( ttype ){
|
|
case cmpp_tt_None:
|
|
if(kvp.v.n){
|
|
if( cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
|
|
ttype = cmpp_tt_IntDec;
|
|
if( '+'==*kvp.v.z ){
|
|
++kvp.v.z;
|
|
--kvp.v.n;
|
|
}
|
|
}else{
|
|
ttype = cmpp_tt_DbString;
|
|
}
|
|
}else if( kvp.v.z ){
|
|
ttype = cmpp_tt_DbString;
|
|
}else{
|
|
ttype = cmpp_tt_IntDec;
|
|
intCheck = 1 /* No value ==> value of 1. */;
|
|
}
|
|
break;
|
|
case cmpp_tt_IntDec:
|
|
if( !cmpp_is_int64(kvp.v.z, kvp.v.n, &intCheck) ){
|
|
ttype = cmpp_tt_DbString;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if( 0==cmpp_stmt_bind_textn(pp, q, 2, kvp.k.z, kvp.k.n)
|
|
&& 0==cmpp_stmt_bind_int(pp, q, 1, ttype) ){
|
|
//g_stderr("zKey=%s\nzVal=%s\nzEq=%s\n", zKey, zVal, zEq);
|
|
switch( ttype ){
|
|
case cmpp_tt_IntDec:
|
|
cmpp_stmt_bind_int(pp, q, 3, intCheck);
|
|
break;
|
|
case cmpp_tt_Null:
|
|
cmpp_stmt_bind_null(pp, q, 3);
|
|
break;
|
|
default:
|
|
cmpp_stmt_bind_textn(pp, q, 3, kvp.v.z, (int)kvp.v.n);
|
|
break;
|
|
}
|
|
cmpp_stmt_step(pp, q, true);
|
|
g_debug(pp,2,("define: [%.*s]=[%.*s]\n",
|
|
kvp.k.n, kvp.k.z,
|
|
kvp.v.n, kvp.v.z));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_legacy)(cmpp *pp, const char * zKey, char const *zVal){
|
|
return cmpp__define_legacy(pp, zKey, zVal, cmpp_tt_None);
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_v2)(cmpp *pp, const char * zKey, char const *zVal){
|
|
return cmpp__define2(pp, ustr_c(zKey), -1, ustr_c(zVal), -1,
|
|
cmpp_tt_DbString);
|
|
}
|
|
|
|
static
|
|
int cmpp__define_shadow(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_strlen_t nKey,
|
|
unsigned char const *zVal,
|
|
cmpp_strlen_t nVal,
|
|
int ttype,
|
|
int64_t * pId){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefIns, false);
|
|
if( q ){
|
|
if( 0==cmpp__define_impl(pp, q, zKey, nKey, zVal, nVal, ttype, false)
|
|
&& pId ){
|
|
*pId = sqlite3_column_int64(q, 0);
|
|
assert( *pId );
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_shadow)(cmpp *pp, char const *zKey,
|
|
char const *zVal, int64_t *pId){
|
|
assert( pId );
|
|
return cmpp__define_shadow(pp, ustr_c(zKey), -1,
|
|
ustr_c(zVal), -1, cmpp_tt_DbString, pId);
|
|
}
|
|
|
|
static
|
|
int cmpp__define_unshadow(cmpp *pp, unsigned char const *zKey,
|
|
cmpp_strlen_t nKey, int64_t id){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_sdefDel, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_textn(pp, q, 1, zKey, (int)nKey);
|
|
cmpp_stmt_bind_int(pp, q, 2, id);
|
|
cmpp_stmt_step(pp, q, true);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_define_unshadow)(cmpp *pp, char const *zKey, int64_t id){
|
|
return cmpp__define_unshadow(pp, ustr_c(zKey), -1, id);
|
|
}
|
|
|
|
/*
|
|
** This sqlite3_trace_v2() callback outputs tracing info using
|
|
** ((cmpp*)c)->sqlTrace.pFile.
|
|
*/
|
|
static int cmpp__db_sq3TraceV2(unsigned dx,void*c,void*p,void*x){
|
|
switch(dx){
|
|
case SQLITE_TRACE_STMT:{
|
|
char const * const zSql = x;
|
|
cmpp * const pp = c;
|
|
cmpp__pi(pp);
|
|
if(pi->sqlTrace.out.out){
|
|
char * const zExp = pi->sqlTrace.expandSql
|
|
? sqlite3_expanded_sql((sqlite3_stmt*)p)
|
|
: 0;
|
|
sqlite3_str * const s = sqlite3_str_new(pi->db.dbh);
|
|
if( pi->dx ){
|
|
cmpp__dx_append_script_info(pi->dx, s);
|
|
sqlite3_str_appendchar(s, 1, ':');
|
|
sqlite3_str_appendchar(s, 1, ' ');
|
|
}
|
|
sqlite3_str_appendall(s, zExp ? zExp : zSql);
|
|
sqlite3_str_appendchar(s, 1, '\n');
|
|
int const n = sqlite3_str_length(s);
|
|
if( n ){
|
|
char * const z = sqlite3_str_finish(s);
|
|
if( z ){
|
|
cmpp__out2(pp, &pi->sqlTrace.out, z, (cmpp_size_t)n);
|
|
sqlite3_free(z);
|
|
}
|
|
}
|
|
sqlite3_free(zExp);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#include <sys/stat.h>
|
|
/*
|
|
** sqlite3 UDF which returns true if its argument refers to an
|
|
** accessible file, else false.
|
|
*/
|
|
static void cmpp__udf_file_exists(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
const char *zName;
|
|
(void)(argc); /* Unused parameter */
|
|
zName = (const char*)sqlite3_value_text(argv[0]);
|
|
if( 0!=zName ){
|
|
struct stat sb;
|
|
sqlite3_result_int(context, stat(zName, &sb)
|
|
? 0
|
|
: S_ISREG(sb.st_mode));
|
|
}
|
|
}
|
|
|
|
/*
|
|
** sqlite3 UDF which returns the size of a file, or NULL if
|
|
** stat(2) fails.
|
|
*/
|
|
static void cmpp__udf_file_size(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
const char *zName;
|
|
(void)(argc);
|
|
zName = (const char*)sqlite3_value_text(argv[0]);
|
|
if( 0!=zName && 0!=zName[0] ){
|
|
struct stat sb;
|
|
if( 0==stat(zName, &sb) ){
|
|
sqlite3_result_int64(context, (int64_t)sb.st_size);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmpp__udf_truthy(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
(void)(argc);
|
|
assert(1==argc);
|
|
int buul = 0;
|
|
sqlite3_value * const sv = argv[0];
|
|
switch( sqlite3_value_type(sv) ){
|
|
case SQLITE_NULL:
|
|
break;
|
|
case SQLITE_FLOAT:
|
|
buul = 0.0!=sqlite3_value_double(sv);
|
|
break;
|
|
case SQLITE_INTEGER:
|
|
buul = 0!=sqlite3_value_int(sv);
|
|
break;
|
|
case SQLITE_TEXT:
|
|
case SQLITE_BLOB:{
|
|
int const n = sqlite3_value_bytes(sv);
|
|
if( n>1 ) buul = 1;
|
|
else if( 1==n ){
|
|
const char *z =
|
|
(const char*)sqlite3_value_text(sv);
|
|
buul = z
|
|
? 0!=strcmp(z,"0")
|
|
: 0;
|
|
}
|
|
}
|
|
}
|
|
sqlite3_result_int(context, buul);
|
|
}
|
|
|
|
/**
|
|
SQLite3 UDF which compares its two arguments using memcmp()
|
|
semantics. NULL will compare equal to NULL, but less than anything
|
|
else.
|
|
*/
|
|
static void cmpp__udf_compare(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
(void)(argc); /* Unused parameter */
|
|
assert(2==argc);
|
|
sqlite3_value * const v1 = argv[0];
|
|
sqlite3_value * const v2 = argv[1];
|
|
unsigned char const * const z1 = sqlite3_value_text(v1);
|
|
unsigned char const * const z2 = sqlite3_value_text(v2);
|
|
int const n1 = sqlite3_value_bytes(v1);
|
|
int const n2 = sqlite3_value_bytes(v2);
|
|
int rv;
|
|
if( !z1 ){
|
|
rv = z2 ? -1 : 0;
|
|
}else if( !z2 ){
|
|
rv = 1;
|
|
}else{
|
|
rv = strncmp((char const *)z1, (char const *)z2, n1>n2 ? n1 : n2);
|
|
}
|
|
if(0) g_stderr("udf_compare (%s,%s) = %d\n", z1, z2, rv);
|
|
sqlite3_result_int(context, rv);
|
|
}
|
|
|
|
int cmpp__db_init(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
if( pi->db.dbh || ppCode ) return ppCode;
|
|
int rc;
|
|
char * zErr = 0;
|
|
const char * zDrops =
|
|
"BEGIN EXCLUSIVE;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".def;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".incl;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".inclpath;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".predef;"
|
|
"DROP TABLE IF EXISTS " CMPP__DB_MAIN_NAME ".ttype;"
|
|
"DROP VIEW IF EXISTS " CMPP__DB_MAIN_NAME ".vdef;"
|
|
"COMMIT;"
|
|
;
|
|
const char * zSchema =
|
|
"BEGIN EXCLUSIVE;"
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".def("
|
|
/* ^^^ defines */
|
|
"t INTEGER DEFAULT NULL,"
|
|
/*^^ type: cmpp_tt or NULL */
|
|
"k TEXT PRIMARY KEY NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
") WITHOUT ROWID;"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".incl("
|
|
/* ^^^ files currently being included */
|
|
"file TEXT PRIMARY KEY NOT NULL,"
|
|
"srcFile TEXT DEFAULT NULL,"
|
|
"srcLine INTEGER DEFAULT 0"
|
|
") WITHOUT ROWID;"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".inclpath("
|
|
/* ^^^ include path. We use (ORDER BY priority DESC, rowid) to
|
|
make their priority correct. priority should only be set by the
|
|
#include directive for its cwd entry. */
|
|
"priority INTEGER DEFAULT 0," /* higher sorts first */
|
|
"dir TEXT UNIQUE NOT NULL ON CONFLICT IGNORE"
|
|
");"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".modpath("
|
|
/* ^^^ module path. We use ORDER BY ROWID to make their
|
|
priority correct. */
|
|
"dir TEXT PRIMARY KEY NOT NULL ON CONFLICT IGNORE"
|
|
");"
|
|
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".predef("
|
|
/* ^^^ pre-defines */
|
|
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
|
|
"k TEXT PRIMARY KEY NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
") WITHOUT ROWID;"
|
|
"INSERT INTO " CMPP__DB_MAIN_NAME ".predef (t,k,v)"
|
|
" VALUES(NULL,'cmpp::version','" CMPP_VERSION "')"
|
|
";"
|
|
|
|
/**
|
|
sdefs - "scoped defines" or "shadow defines". The problem these
|
|
solve is the one of supporting a __FILE__ define in cmpp input
|
|
sources, such that it remains valid both before and after an
|
|
#include, but has a new name in the scope of an #include. We
|
|
can't use savepoints for that because they're a nuclear option
|
|
affecting _all_ #defines in the #include'd file, whereas we
|
|
normally want #defines to stick around across files.
|
|
|
|
See cmpp_define_shadow() and cmpp_define_unshadow().
|
|
*/
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".sdef("
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
"t INTEGER DEFAULT NULL," /* a cmpp_tt or NULL */
|
|
"k TEXT NOT NULL,"
|
|
"v TEXT DEFAULT NULL"
|
|
");"
|
|
|
|
/**
|
|
vdef is a view consolidating the various #define stores. It's
|
|
intended to be used for all general-purpose fetching of defines
|
|
and it orders the results such that the library's defines
|
|
supercede all others, then scoped keys, then client-level
|
|
defines.
|
|
|
|
To push a new sdef we simply insert into sdef. Then vdef will
|
|
order the newest sdef before any entry from the def table.
|
|
*/
|
|
"CREATE VIEW " CMPP__DB_MAIN_NAME ".vdef(source,t,k,v) AS"
|
|
" SELECT NULL,t,k,v FROM " CMPP__DB_MAIN_NAME ".predef"
|
|
/* ------^^^^ sorts before numbers */
|
|
" UNION ALL"
|
|
" SELECT -rowid,t,k,v FROM " CMPP__DB_MAIN_NAME ".sdef"
|
|
/* ^^^^ sorts newest of matching keys first */
|
|
" UNION ALL"
|
|
" SELECT 0,t,k,v FROM " CMPP__DB_MAIN_NAME ".def"
|
|
" ORDER BY 1, 3"
|
|
";"
|
|
|
|
|
|
#if 0
|
|
"CREATE TABLE " CMPP__DB_MAIN_NAME ".ttype("
|
|
/* ^^^ token types */
|
|
"t INTEGER PRIMARY KEY NOT NULL,"
|
|
/*^^ type: cmpp_tt */
|
|
"n TEXT NOT NULL,"
|
|
/*^^ cmpp_tt_... name. */
|
|
"s TEXT DEFAULT NULL"
|
|
/* Symbolic or directive name, if any. */
|
|
");"
|
|
#endif
|
|
|
|
"COMMIT;"
|
|
"BEGIN EXCLUSIVE;"
|
|
;
|
|
cmpp__err_reuse(pp);
|
|
int openFlags = SQLITE_OPEN_READWRITE;
|
|
if( pi->db.zName ){
|
|
openFlags |= SQLITE_OPEN_CREATE;
|
|
}
|
|
rc = sqlite3_open_v2(
|
|
pi->db.zName ? pi->db.zName : ":memory:",
|
|
&pi->db.dbh, openFlags, 0);
|
|
if(rc){
|
|
cmpp_db_rc_v2(pp, rc, pi->db.zName
|
|
? pi->db.zName
|
|
: ":memory:", -1);
|
|
sqlite3_close(pi->db.dbh);
|
|
pi->db.dbh = 0;
|
|
assert(ppCode);
|
|
return rc;
|
|
}
|
|
sqlite3_busy_timeout(pi->db.dbh, 5000);
|
|
sqlite3_db_config(pi->db.dbh, SQLITE_DBCONFIG_MAINDBNAME,
|
|
CMPP__DB_MAIN_NAME);
|
|
rc = sqlite3_trace_v2(pi->db.dbh, SQLITE_TRACE_STMT,
|
|
cmpp__db_sq3TraceV2, pp);
|
|
if( cmpp_db_rc(pp, rc, "Installing tracer failed") ){
|
|
goto end;
|
|
}
|
|
//g_warn("Schema:\n%s\n",zSchema);
|
|
struct {
|
|
/* SQL UDFs */
|
|
char const * const zName;
|
|
void (*xUdf)(sqlite3_context *,int,sqlite3_value **);
|
|
int arity;
|
|
int flags;
|
|
} aFunc[] = {
|
|
{
|
|
.zName = "cmpp_file_exists",
|
|
.xUdf = cmpp__udf_file_exists,
|
|
.arity = 1,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
|
|
},
|
|
{
|
|
.zName = "cmpp_file_size",
|
|
.xUdf = cmpp__udf_file_size,
|
|
.arity = 1,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY
|
|
},
|
|
{
|
|
.zName = "cmpp_truthy",
|
|
.xUdf = cmpp__udf_truthy,
|
|
.arity = 1,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
|
|
},
|
|
{
|
|
.zName = "cmpp_compare",
|
|
.xUdf = cmpp__udf_compare,
|
|
.arity = 2,
|
|
.flags = SQLITE_UTF8 | SQLITE_DIRECTONLY | SQLITE_DETERMINISTIC
|
|
}
|
|
};
|
|
assert( 0==rc );
|
|
for( unsigned int i = 0; 0==rc && i < sizeof(aFunc)/sizeof(aFunc[0]); ++i ){
|
|
rc = sqlite3_create_function(
|
|
pi->db.dbh, aFunc[i].zName, aFunc[i].arity,
|
|
aFunc[i].flags, 0, aFunc[i].xUdf, 0, 0
|
|
);
|
|
}
|
|
if( cmpp_db_rc(pp, rc, "UDF registration failed.") ){
|
|
return ppCode;
|
|
}
|
|
if( pi->db.zName ){
|
|
/* Drop all cmpp tables when using a persistent db so that we are
|
|
not beholden to a given structure. TODO: a config flag to
|
|
toggle this. */
|
|
rc = sqlite3_exec(pi->db.dbh, zDrops, 0, 0, &zErr);
|
|
}
|
|
if( !rc ){
|
|
rc = sqlite3_exec(pi->db.dbh, zSchema, 0, 0, &zErr);
|
|
}
|
|
|
|
if( !rc ){
|
|
extern int sqlite3_series_init(sqlite3 *, char **, const sqlite3_api_routines *);
|
|
rc = sqlite3_series_init(pi->db.dbh, &zErr, NULL);
|
|
}
|
|
|
|
if(rc){
|
|
if( zErr ){
|
|
cmpp_errf(pp, cmpp__db_errcode(pi->db.dbh, rc),
|
|
"SQLite error #%d initializing DB: %s", rc, zErr);
|
|
sqlite3_free(zErr);
|
|
}else{
|
|
cmpp_errf(pp, cmpp__db_errcode(pi->db.dbh, rc),
|
|
"SQLite error #%d initializing DB", rc);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
#if 0
|
|
while(0){
|
|
/* Insert the ttype mappings. We don't yet make use of this but
|
|
only for lack of a use case ;). */
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_insTtype, false);
|
|
if( !q ) goto end;
|
|
#define E(N,STR) \
|
|
cmpp_stmt_bind_int(pp, q, 1, cmpp_tt_ ## N); \
|
|
cmpp_stmt_bind_textn(pp, q, 2, \
|
|
ustr_c("cmpp_tt_ " # N), sizeof("cmpp_tt_" # N)-1); \
|
|
if( STR ) cmpp_stmt_bind_textn(pp, q, 3, ustr_c(STR), sizeof(STR)-1); \
|
|
else cmpp_stmt_bind_null(pp, q, 3); \
|
|
if( SQLITE_DONE!=cmpp_stmt_step(pp, q, true) ) return ppCode;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
sqlite3_finalize(q);
|
|
pi->stmt.insTtype = 0;
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
end:
|
|
if( !ppCode ){
|
|
/*
|
|
** Keep us from getting in the situation later that delayed
|
|
** preparation if one of the savepoint statements fails (e.g. due
|
|
** to OOM or memory corruption). We need to be able to run these
|
|
** even in OOM cases (while admitting that they may fail to run in
|
|
** OOM cases).
|
|
*/
|
|
cmpp__stmt(pp, CmppStmt_spBegin, false);
|
|
cmpp__stmt(pp, CmppStmt_spRelease, false);
|
|
cmpp__stmt(pp, CmppStmt_spRollback, false);
|
|
cmpp__lazy_init(pp);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
static int cmpp__dx_err_just_once(cmpp_dx *dx, cmpp_arg const *arg){
|
|
return cmpp_dx_errf(dx, CMPP_RC_MISUSE, "'%s' may only be used once.",
|
|
arg->z);
|
|
}
|
|
|
|
/* No-op cmpp_f() impl. */
|
|
static void cmpp_f_noop(cmpp_f_args const * args){
|
|
(void)args;
|
|
}
|
|
|
|
/**
|
|
cmpp_kav_each_f() impl for use by #define {k->v}.
|
|
*/
|
|
static int cmpp_kav_each_f_define__group(
|
|
cmpp *pp,
|
|
unsigned char const *zKey, cmpp_size_t nKey,
|
|
unsigned char const *zVal, cmpp_size_t nVal,
|
|
void* callbackState
|
|
){
|
|
if( (callbackState==pp)
|
|
&& cmpp__has(pp, (char const*)zKey, nKey) ){
|
|
return ppCode;
|
|
}
|
|
return cmpp__define2(pp, zKey, nKey, zVal, nVal,
|
|
cmpp_tt_DbString);
|
|
}
|
|
|
|
/* #error impl. */
|
|
static void cmpp_f_error(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
const char *zBegin = (char const *)dx->args.z;
|
|
unsigned n = (unsigned)dx->args.nz;
|
|
if( n>2 && (('"' ==*zBegin || '\''==*zBegin) && zBegin[n-1]==*zBegin) ){
|
|
++zBegin;
|
|
n -= 2;
|
|
}
|
|
if( n ){
|
|
cmpp_dx_errf(dx, CMPP_RC_ERROR, "%.*s", n, zBegin);
|
|
}else{
|
|
cmpp_dx_errf(dx, CMPP_RC_ERROR, "(no additional info)");
|
|
}
|
|
}
|
|
|
|
/* Impl. for #define. */
|
|
static void cmpp_f_define(cmpp_f_args const * args){
|
|
assert(args->argc>0 && "V4 arg semantics.");
|
|
assert(args->argv && "V4|!RAW arg semantics.");
|
|
assert( cmpp_arg_equals(args->argv[0],
|
|
(char const*)args->dx->d->name.z) );
|
|
cmpp_dx * const dx = args->dx;
|
|
if( args->argc<2 ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp_arg const * aKey = 0;
|
|
int nChomp = 0;
|
|
unsigned nHeredoc = 0;
|
|
unsigned char acHeredoc[128] = {0} /* TODO: cmpp_args_clone() */;
|
|
bool ifNotDefined = false /* true if '?' arg */;
|
|
cmpp_arg const *aAppend = 0;
|
|
bool raw = false;
|
|
#define checkIsDefined(ARG) \
|
|
if(ifNotDefined && (cmpp__has(dx->pp, (char const*)ARG->z, ARG->n) \
|
|
|| dxppCode)) break
|
|
for( unsigned i = 1; 0==dxppCode && i < args->argc; ++i){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
//g_warn("arg=%s", arg->z);
|
|
if( 1==i && cmpp_arg_equals_c(arg, "?") ){
|
|
/* Only set the key if it's not already defined. */
|
|
ifNotDefined = true;
|
|
continue;
|
|
}
|
|
switch( arg->ttype ){
|
|
case cmpp_tt_OpShiftL3:
|
|
++nChomp;
|
|
/* fall through */
|
|
case cmpp_tt_OpShiftL:
|
|
if( i<1 || i+1<args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Ill-placed '%s'.", arg->z);
|
|
}else if( arg->n >= sizeof(acHeredoc)-1 ){
|
|
cmpp_dx_errf(dx, CMPP_RC_RANGE,
|
|
"Heredoc name is too large.");
|
|
}else if( !aKey ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Missing key before %s.",
|
|
cmpp__tt_cstr2(cmpp_tt_OpShiftL, false));
|
|
}else{
|
|
assert( aKey );
|
|
nHeredoc = aKey->n;
|
|
memcpy(acHeredoc, aKey->z, aKey->n+1/*NUL*/);
|
|
}
|
|
break;
|
|
case cmpp_tt_OpCmpEq:
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Ill-placed '%s'.", arg->z);
|
|
break;
|
|
case cmpp_tt_IntDec:
|
|
case cmpp_tt_StringBT:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:
|
|
case cmpp_tt_Word:
|
|
if( cmpp_arg_isflag_c(arg,"-chomp") ){
|
|
++nChomp;
|
|
break;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg,"-raw") ){
|
|
raw = true;
|
|
break;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg,"-append")
|
|
|| cmpp_arg_isflag_c(arg,"-a") ){
|
|
aAppend = args->argv[++i];
|
|
if( !aAppend ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting argument for %s",
|
|
arg->z);
|
|
}
|
|
arg = aAppend;
|
|
break;
|
|
}
|
|
handle_value:
|
|
if( aKey ){
|
|
/* This is the second arg - the value */
|
|
#if 1
|
|
if( i+1<args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Unexpected token after %s",
|
|
arg->z);
|
|
break;
|
|
}
|
|
#endif
|
|
checkIsDefined(aKey);
|
|
cmpp_b * const os = cmpp_b_borrow(dx->pp);
|
|
cmpp_b * const ba = aAppend ? cmpp_b_borrow(dx->pp) : 0;
|
|
while( os ){
|
|
if( ba ){
|
|
cmpp__get_b(dx->pp, aKey->z, aKey->n, ba, false);
|
|
if( dxppCode ) break;
|
|
if( 0 ){
|
|
g_warn("key=%s\n", aKey->z);
|
|
g_warn("ba=%u %.*s\n", ba->n, ba->n, ba->z);
|
|
}
|
|
}
|
|
if( cmpp_arg_interpolate(args->pp, arg, os,
|
|
cmpp_arg_interpolate_BRACE_CALL) ) break;
|
|
cmpp_b * const which = (ba && ba->n) ? ba : os;
|
|
if( which==ba && os->n ){
|
|
if( ba->n ) cmpp_b_append4(dx->pp, ba, aAppend->z, aAppend->n);
|
|
cmpp_b_append4(args->pp, ba, os->z, os->n);
|
|
}
|
|
int n = nChomp;
|
|
while( n-- && cmpp_b_chomp(which) ){}
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n, which->z, which->n,
|
|
arg->ttype);
|
|
if( 0 ){
|
|
g_warn("aKey=%u z=[%.*s]\n", aKey->n, (int)aKey->n, aKey->z);
|
|
g_warn("nExp=%u z=[%.*s]\n", which->n, (int)which->n, which->z);
|
|
}
|
|
break;
|
|
}
|
|
cmpp_b_return(dx->pp, os);
|
|
cmpp_b_return(dx->pp, ba);
|
|
aKey = 0;
|
|
}else if( cmpp_tt_Word!=arg->ttype ){
|
|
cmpp_dx_errf(dx, CMPP_RC_TYPE,
|
|
"Expecting a define-name token here.");
|
|
}else if( i+1<args->argc ){
|
|
aKey = arg;
|
|
}else{
|
|
/* No value = a value of 1. */
|
|
checkIsDefined(arg);
|
|
cmpp__define2(dx->pp, arg->z, arg->n,
|
|
ustr_c("1"), 1, cmpp_tt_IntDec);
|
|
}
|
|
break;
|
|
case cmpp_tt_GroupSquiggly:
|
|
assert( !acHeredoc[0] );
|
|
if( aKey ){
|
|
/* Treat this as a value */
|
|
goto handle_value;
|
|
}else if( (ifNotDefined ? i>2 : i>1) || i+1<args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"{...} must be the only argument.")
|
|
/* This is for simplicity's sake. */;
|
|
}else{
|
|
cmpp_kav_each(args->pp, arg->z, arg->n,
|
|
cmpp_kav_each_f_define__group,
|
|
ifNotDefined ? args->pp : NULL,
|
|
cmpp_kav_each_F_NOT_EMPTY
|
|
| cmpp_kav_each_F_CALL_VAL
|
|
| cmpp_kav_each_F_PARENS_EXPR
|
|
//TODO cmpp_kav_each_F_IF_UNDEF
|
|
);
|
|
}
|
|
aKey = 0;
|
|
break;
|
|
case cmpp_tt_GroupParen:{
|
|
if( !aKey ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"(...) is not permitted as a key.");
|
|
break;
|
|
}
|
|
checkIsDefined(aKey);
|
|
int d = 0;
|
|
if( 0==cmpp__arg_evalSubToInt(args->pp, arg, &d) ){
|
|
char exprBuf[32] = {0};
|
|
cmpp_size_t nVal =
|
|
(cmpp_size_t)snprintf(&exprBuf[0],
|
|
sizeof(exprBuf), "%d", d);
|
|
assert(nVal>0);
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n,
|
|
ustr_c(&exprBuf[0]), nVal,
|
|
cmpp_tt_IntDec);
|
|
}
|
|
break;
|
|
}
|
|
case cmpp_tt_GroupBrace:{
|
|
if( !aKey ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"[...] is not permitted as a key.");
|
|
break;
|
|
}
|
|
checkIsDefined(aKey);
|
|
cmpp_b * const b = cmpp_b_borrow(dx->pp);
|
|
if( b && 0==cmpp_call_str(dx->pp, arg->z, arg->n, b, 0) ){
|
|
int n = nChomp;
|
|
while( n-- && cmpp_b_chomp(b) ){}
|
|
cmpp__define2(dx->pp, aKey->z, aKey->n,
|
|
b->z, b->n, cmpp_tt_AnyType);
|
|
}
|
|
cmpp_b_return(dx->pp, b);
|
|
break;
|
|
}
|
|
default:
|
|
// TODO: treat (...) as an expression
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Unhandled arg type %s: %s",
|
|
cmpp__tt_cstr2(arg->ttype, true), arg->z);
|
|
break;
|
|
}
|
|
}
|
|
if( 0==dxppCode && nHeredoc ){
|
|
// Process (#define KEY <<)
|
|
cmpp_b * const os = cmpp_b_borrow(dx->pp);
|
|
assert( args->d->closer );
|
|
if( os &&
|
|
0==cmpp_dx_consume_b(dx, os, &args->d->closer, 1,
|
|
raw
|
|
? cmpp_dx_consume_F_ATPOL_OFF
|
|
: cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
while( nChomp-- && cmpp_b_chomp(os) ){}
|
|
g_debug(dx->pp,2,("define heredoc: [%s]=[%.*s]\n",
|
|
acHeredoc, (int)os->n, os->z));
|
|
if( !ifNotDefined
|
|
|| !cmpp__has(dx->pp, (char const*)acHeredoc, nHeredoc) ){
|
|
cmpp__define2(
|
|
dx->pp, acHeredoc, nHeredoc, os->z, os->n,
|
|
cmpp_tt_DbString
|
|
);
|
|
}
|
|
}
|
|
cmpp_b_return(dx->pp, os);
|
|
}
|
|
#undef checkIsDefined
|
|
return;
|
|
}
|
|
|
|
/* Impl. for #undef */
|
|
static void cmpp_f_undef(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
if( args->argc<2 ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp_size_t n = 0;
|
|
char const * z;
|
|
for( unsigned i = 1; i < args->argc && 0==dxppCode; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
z = cmpp_arg_cstr2(arg, &n);
|
|
if( 0 ){
|
|
g_stderr(" %s: %s %p n=%d %.*s\n", args->d->name.z,
|
|
cmpp__tt_cstr2(arg->ttype, true), arg->z,
|
|
(int)arg->n, (int)arg->n, arg->z);
|
|
}
|
|
if( cmpp_tt_Word==arg->ttype ){
|
|
#if 0
|
|
/* Too strict? */
|
|
if( 0==cmpp__legal_key_check(dx->pp, z, n, false) ) {
|
|
cmpp_undefine(dx->pp, (char const *)z);
|
|
}
|
|
#else
|
|
cmpp_undefine(dx->pp, z, n);
|
|
#endif
|
|
}else{
|
|
cmpp_errf(dx->pp, CMPP_RC_MISUSE, "Invalid arg for %s: %s",
|
|
args->d->name.z, arg->z);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Impl. for #eval. */
|
|
static void cmpp_f_eval(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
if( 2!=args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"#eval requires one argument");
|
|
return;
|
|
}
|
|
cmpp_arg const * const arg = args->argv[1];
|
|
cmpp_b * const b = arg->n ? cmpp_b_borrow(dx->pp) : 0;
|
|
if( b ){
|
|
/* We need to collect the line info before processing the arg so
|
|
that it points to the start of the script. */
|
|
sqlite3_str * const sstr = cmpp__sqlite3_str_new(args->pp);
|
|
if( sstr ){
|
|
cmpp__dx_append_script_info(dx, sstr);
|
|
char * const z = sqlite3_str_finish(sstr);
|
|
if(0==cmpp_check_oom(args->pp, z)
|
|
&& 0==cmpp_arg_interpolate(args->pp, arg, b,
|
|
cmpp_arg_interpolate_BRACE_CALL)
|
|
&& b->n){
|
|
cmpp_process_string(args->pp, z ? z : args->d->name.z,
|
|
b->z, b->n);
|
|
}
|
|
sqlite3_free(z);
|
|
}
|
|
cmpp_b_return(args->pp, b);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
/* Impl. for #expand. */
|
|
static void cmpp_f_expand(cmpp_f_args const * args){
|
|
if( args->argc<2 || args->argc>3 ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"%sexpand requires one or two arguments",
|
|
cmpp__dx_zdelim(args->dx));
|
|
return;
|
|
}
|
|
cmpp_arg const * argKav = 0 /* {key -> value ...} */;
|
|
cmpp_arg const * argToB = 0 /* one of X or Y from: #expand X Y */;
|
|
cmpp_arg const * aLhs = 0;
|
|
cmpp_arg const * aRhs = 0;
|
|
cmpp_arg const * aErr = 0; /* Current arg for error reportin */
|
|
cmpp_b * const osIn = cmpp_b_borrow(args->pp);
|
|
int nChomp = 0 /* Strip nChomp newlines from result */;
|
|
int unChomp = 0 /* Add unChomp newlines to result */;
|
|
bool raw = false /* interpret block content as raw or
|
|
preprocessor-ready? */;
|
|
bool inSp = false /* started a local savepoint? */;
|
|
if( !osIn ) goto end;
|
|
for( unsigned i = 1; i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
aErr = arg;
|
|
switch( arg->ttype ){
|
|
case cmpp_tt_Word:
|
|
if( cmpp_arg_isflag_c(arg,"-chomp") ){
|
|
++nChomp;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg,"-unchomp") ){
|
|
++unChomp;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg,"-raw") ){
|
|
raw = true;
|
|
continue;
|
|
}
|
|
if( !aLhs ){
|
|
aLhs = arg;
|
|
continue;
|
|
}
|
|
goto unexpected_arg;
|
|
default:
|
|
assert( !aRhs );
|
|
if( aLhs ){
|
|
if( i+1<args->argc ){
|
|
assert( arg!=aLhs );
|
|
assert( !aRhs );
|
|
goto unexpected_arg;
|
|
}
|
|
aRhs = arg;
|
|
}else{
|
|
aLhs = arg;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if( !aLhs ){
|
|
cmpp_err(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting one or two arguments.", -1);
|
|
goto end;
|
|
}
|
|
if( aRhs ){
|
|
if( cmpp_tt_GroupSquiggly!=aRhs->ttype ){
|
|
aErr = aRhs;
|
|
goto unexpected_arg;
|
|
}
|
|
argKav = aRhs;
|
|
argToB = aLhs;
|
|
}else{
|
|
if( cmpp_tt_GroupSquiggly==aLhs->ttype ){
|
|
argKav = aLhs;
|
|
}else{
|
|
argToB = aLhs;
|
|
}
|
|
}
|
|
assert( argKav || argToB );
|
|
if( argToB ){
|
|
if( cmpp_arg_interpolate(args->pp, argToB, osIn,
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
}
|
|
if( argKav ){
|
|
assert(!argKav->next);
|
|
if( (aErr = argKav->next) ){
|
|
goto unexpected_arg;
|
|
}
|
|
if( cmpp_sp_begin(args->pp) ) goto end;
|
|
inSp = true;
|
|
if( cmpp_kav_each(args->pp, argKav->z, argKav->n,
|
|
cmpp_kav_each_f_define__group,
|
|
0,
|
|
cmpp_kav_each_F_EXPAND_VAL
|
|
| cmpp_kav_each_F_CALL_VAL
|
|
| cmpp_kav_each_F_PARENS_EXPR) ){
|
|
goto end;
|
|
}
|
|
}
|
|
if( argToB && cmpp_tt_GroupSquiggly==argToB->ttype ){
|
|
cmpp_outputer xout = cmpp_outputer_b;
|
|
cmpp_outputer old = {0};
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
if( !(xout.state = b) ) goto end;
|
|
cmpp__outputer_swap(args->pp, &xout, &old);
|
|
cmpp_process_string(args->pp, "#expand buffer", osIn->z, osIn->n);
|
|
cmpp__outputer_swap(args->pp, &old, &xout);
|
|
cmpp_b_swap(b, osIn);
|
|
cmpp_b_return(args->pp, b);
|
|
if( cmpp_err_has(args->pp) ) goto end;
|
|
}
|
|
if( !argToB ){
|
|
assert(!osIn->n);
|
|
if( args->isDxCall ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"#expand.../#expand is not legal for [call].");
|
|
goto end;
|
|
}
|
|
if( cmpp_dx_consume_b(args->dx, osIn, &args->d->closer, 1,
|
|
raw
|
|
? cmpp_dx_consume_F_ATPOL_OFF
|
|
: cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
goto end;
|
|
}
|
|
}
|
|
while( nChomp-- && cmpp_b_chomp(osIn) ){}
|
|
while( unChomp-- && cmpp_b_append_ch(osIn, '\n') ){}
|
|
//MARKER(("Emitting %d bytes: %.*s\n", (int)osIn->n, (int)osIn->n, osIn->z));
|
|
cmpp_out_expand(args->pp, NULL, osIn->z, osIn->n, cmpp_atpol_CURRENT);
|
|
|
|
end:
|
|
if( inSp ){
|
|
cmpp_sp_rollback(args->pp);
|
|
}
|
|
cmpp_b_return(args->pp, osIn);
|
|
return;
|
|
unexpected_arg:
|
|
assert( aErr );
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Unexpected argument: %.*s",
|
|
(int)aErr->n, aErr->z);
|
|
goto end;
|
|
}
|
|
|
|
/* Impl. for #once. */
|
|
static void cmpp_f_once(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_d const * const d = args->d;
|
|
assert(d);
|
|
assert(d->closer);
|
|
if( dx->args.arg0 ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting no arguments");
|
|
return;
|
|
}
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
if( !b ) return;
|
|
else{
|
|
/* Synthesize an ID from the source code position. Typically
|
|
we'll hit any position only once in a given run, but #include
|
|
and #query loops can cause re-visits. */
|
|
cmpp__delim const * const dlim = cmpp__dx_delim(args->dx);
|
|
cmpp_b_append4(args->pp, b, dlim->open.z, dlim->open.n);
|
|
cmpp_b_append4(args->pp, b, d->name.z, d->name.n);
|
|
cmpp_b_append_ch(b, ':');
|
|
char const * zFile = 0;
|
|
cmpp_size_t lnNo = 0;
|
|
cmpp_dx_src_pos_info(dx, &zFile, &lnNo);
|
|
cmpp_b_append4_str(args->pp, b, zFile, -1);
|
|
cmpp_b_append_ch(b, ':');
|
|
cmpp_b_append_i32(b, (int)lnNo);
|
|
if( b->errCode ) goto end;
|
|
}
|
|
if( 0 ){
|
|
g_warn("#once dline=%d pos.lineNo=%d, key: %s",
|
|
(int)dx->pimpl->dline.lineNo,
|
|
(int)dx->pimpl->pos.lineNo,
|
|
b->z);
|
|
}
|
|
int const had = cmpp__has(args->pp, (char const *)b->z, b->n);
|
|
if( dxppCode ) goto end;
|
|
else if( had ){
|
|
/* Consume until #/once in elide mode. */
|
|
CmppLvl * const lvl = CmppLvl_push(dx);
|
|
if( lvl ){
|
|
CmppLvl_elide(lvl, true);
|
|
cmpp_outputer devNull = cmpp_outputer_empty;
|
|
cmpp_dx_consume(dx, &devNull, &d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
CmppLvl_pop(dx, lvl);
|
|
}
|
|
}else if( 0==cmpp_define_v2(args->pp, (char const*)b->z, "1") ){
|
|
cmpp_dx_consume(dx, NULL, &d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
}
|
|
end:
|
|
cmpp_b_return(args->pp, b);
|
|
return;
|
|
}
|
|
|
|
|
|
/* Impl. for #/define, /#query, /#pipe. */
|
|
CMPP__EXPORT(void, cmpp_f_dangling_closer)(
|
|
cmpp_f_args const * args
|
|
){
|
|
cmpp_dx * const dx = args->dx;
|
|
char const * const zD = cmpp_dx_delim(dx);
|
|
dxserr("%s%s used without its opening directive.",
|
|
zD, args->d->name.z);
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_INCLUDE
|
|
static int cmpp__including_has(cmpp *pp, unsigned const char * zName){
|
|
int rc = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclHas, false);
|
|
if( q && 0==cmpp_stmt_bind_text(pp, q, 1, zName) ){
|
|
if(SQLITE_ROW == cmpp_stmt_step(pp, q, true)){
|
|
rc = 1;
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
g_debug(pp,2,("inclpath has [%s] = %d\n",zName, rc));
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
char * cmpp__include_search(cmpp *pp, unsigned const char * zKey,
|
|
cmpp_size_t * nVal){
|
|
char * zName = 0;
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclSearch, false);
|
|
if( nVal ) *nVal = 0;
|
|
if( q && 0==cmpp_stmt_bind_text(pp, q, 1, zKey) ){
|
|
int const rc = cmpp_stmt_step(pp, q, false);
|
|
if(SQLITE_ROW==rc){
|
|
const unsigned char * z = sqlite3_column_text(q, 0);
|
|
int const n = sqlite3_column_bytes(q,0);
|
|
zName = n ? sqlite3_mprintf("%.*s", n, z) : 0;
|
|
if( n ) cmpp_check_oom(pp, zName);
|
|
if( nVal ) *nVal = n;
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
return zName;
|
|
}
|
|
|
|
/**
|
|
Removes zKey from the currently-being-`#include`d list
|
|
list.
|
|
*/
|
|
static int cmpp__include_rm(cmpp *pp, unsigned const char * zKey){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclDel, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_text(pp, q, 1, ustr_c(zKey));
|
|
cmpp_stmt_step(pp, q, true);
|
|
g_debug(pp,2,("incl rm [%s]\n", zKey));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
Adds the given filename to the list of being-`#include`d files,
|
|
using the given source file name and line number of error reporting
|
|
purposes. If recursion is later detected.
|
|
*/
|
|
static int cmpp__including_add(cmpp *pp, unsigned const char * zKey,
|
|
unsigned const char * zSrc, cmpp_size_t srcLine){
|
|
sqlite3_stmt * const q = cmpp__stmt(pp, CmppStmt_inclIns, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_text(pp, q, 1, zKey);
|
|
cmpp_stmt_bind_text(pp, q, 2, zSrc);
|
|
cmpp_stmt_bind_int(pp, q, 3, srcLine);
|
|
cmpp_stmt_step(pp, q, true);
|
|
g_debug(pp,2,("is-including-file add [%s] from [%s]:%"
|
|
CMPP_SIZE_T_PFMT "\n", zKey, zSrc, srcLine));
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/* Impl. for #include. */
|
|
static void cmpp_f_include(cmpp_f_args const * args){
|
|
char * zResolved = 0;
|
|
cmpp_size_t nResolved;
|
|
cmpp_b * const ob = cmpp_b_borrow(args->pp);
|
|
bool raw = false;
|
|
bool resolve = false;
|
|
cmpp_arg const * argDelim = 0;
|
|
if( !ob ){
|
|
goto end;
|
|
}
|
|
unsigned i = 1;
|
|
for( ; i < args->argc; ++i){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
if( cmpp_arg_isflag_c(arg, "-raw") ){
|
|
if( resolve ) goto no_raw_resolve;
|
|
raw = true;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg, "-delimiter") ){
|
|
if( ++i==args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"-delimiter requires an argument");
|
|
goto end;
|
|
}
|
|
argDelim = args->argv[i];
|
|
continue;
|
|
}
|
|
if( cmpp_arg_isflag_c(arg, "-resolve") ){
|
|
if( raw ){
|
|
no_raw_resolve:
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"-raw and -resolve may not be used together.");
|
|
goto end;
|
|
}else{
|
|
resolve = true;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
#undef FLAG
|
|
}
|
|
if( i>=args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_SYNTAX,
|
|
"Expecting at least one filename argument.");
|
|
}
|
|
if( argDelim && (raw || resolve) ) argDelim = 0;
|
|
if( argDelim ){
|
|
if( cmpp_arg_interpolate(args->pp, argDelim, cmpp_b_reuse(ob),
|
|
cmpp_arg_interpolate_BRACE_CALL)
|
|
|| cmpp_delimiter_push(args->pp, (char*)ob->z) ){
|
|
argDelim = 0/*our signal to not pop the delimiter*/;
|
|
goto end;
|
|
}
|
|
}
|
|
for( ; i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(ob),
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
break;
|
|
}
|
|
//g_stderr("zFile=%s zResolved=%s\n", zFile, zResolved);
|
|
if(!raw && !resolve && cmpp__including_has(args->pp, ob->z)){
|
|
/* Note that different spellings of the same filename
|
|
** will elude this check, but that seems okay, as different
|
|
** spellings means that we're not re-running the exact same
|
|
** invocation. We might want some other form of multi-include
|
|
** protection, rather than this, however. There may well be
|
|
** sensible uses for recursion. */
|
|
cmpp_errf(args->pp, CMPP_RC_RANGE,
|
|
"Recursive include of file: %s",
|
|
ob->z);
|
|
break;
|
|
}
|
|
cmpp_mfree(zResolved);
|
|
nResolved = 0;
|
|
zResolved = cmpp__include_search(args->pp, ob->z, &nResolved);
|
|
if( !zResolved ){
|
|
if( !cmpp_err_has(args->pp) ){
|
|
cmpp_errf(args->pp, CMPP_RC_NOT_FOUND,
|
|
"file not found: %s", ob->z);
|
|
}
|
|
break;
|
|
}
|
|
if( resolve ){
|
|
cmpp_out_raw(args->pp, zResolved, nResolved);
|
|
}else if( raw ){
|
|
if( !args->pp->pimpl->out.out ) break;
|
|
FILE * const fp = cmpp_fopen(zResolved, "r");
|
|
if( fp ){
|
|
int const rc = cmpp_stream(cmpp_input_f_FILE, fp,
|
|
args->pp->pimpl->out.out,
|
|
args->pp->pimpl->out.state);
|
|
if( rc ){
|
|
cmpp_errf(args->pp, rc, "Unknown error streaming file %s.",
|
|
arg->z);
|
|
}
|
|
cmpp_fclose(fp);
|
|
}else{
|
|
cmpp_errf(args->pp, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Unknown error opening file %s.", arg->z);
|
|
}
|
|
}else{
|
|
cmpp__including_add(args->pp, ob->z,
|
|
ustr_c(args->dx->sourceName),
|
|
args->dx->pimpl->dline.lineNo);
|
|
cmpp_process_file(args->pp, zResolved);
|
|
cmpp__include_rm(args->pp, ob->z);
|
|
}
|
|
}
|
|
end:
|
|
if( argDelim ){
|
|
cmpp_delimiter_pop(args->pp);
|
|
}
|
|
cmpp_mfree(zResolved);
|
|
cmpp_b_return(args->pp, ob);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_INCLUDE */
|
|
|
|
/**
|
|
cmpp_f() callback state for cmpp_f_if(): pointers to the
|
|
various directives of that family.
|
|
*/
|
|
struct CmppIfState {
|
|
cmpp_d * dIf;
|
|
cmpp_d * dElif;
|
|
cmpp_d * dElse;
|
|
cmpp_d const * dEndif;
|
|
};
|
|
typedef struct CmppIfState CmppIfState;
|
|
|
|
/* Version 2 of #if. */
|
|
static void cmpp_f_if(cmpp_f_args const * xargs){
|
|
cmpp_dx * const dx = xargs->dx;
|
|
/* Reminder to self:
|
|
|
|
We need to be able to recurse, even in skip mode, for #if nesting
|
|
to work. That's not great because it means we are evaluating
|
|
stuff we ideally should be skipping over, but it's keeping the
|
|
current tests working as-is. We can/do, however, avoid evaluating
|
|
expressions and such when recursing via skip mode. If we can
|
|
eliminate that here, by keeping track of the #if stack depth,
|
|
then we can possibly eliminate the whole CmppLvl_F_ELIDE
|
|
flag stuff.
|
|
|
|
The more convoluted version 1 #if (which this replaced not hours
|
|
ago) kept track of the skip state across a separate directive
|
|
function for #if and #/if. That was more complex but did avoid
|
|
having to recurse into #if in order to straighten out #elif and
|
|
#else. Update: tried a non-recursive variant involving moving
|
|
this function's gotTruth into the CmppLvl object() and
|
|
managing the CmppLvl stack here, but it just didn't want to
|
|
work for me and i was too tired to figure out why.
|
|
*/
|
|
int gotTruth = 0 /*expr result*/;
|
|
CmppIfState const * const cis = xargs->d->impl.state;
|
|
cmpp_d const * dClosers[] = {
|
|
cis->dElif, cis->dElse, cis->dEndif
|
|
};
|
|
CmppLvl * lvl = 0;
|
|
CmppDLine const dline = dx->pimpl->dline;
|
|
cmpp_args args = cmpp_args_empty;
|
|
char delim[20] = {0};
|
|
#define skipOn CmppLvl_elide((lvl), true)
|
|
#define skipOff CmppLvl_elide((lvl), false)
|
|
|
|
assert( xargs->d==cis->dIf );
|
|
if( !dx->args.arg0 ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting an expression.");
|
|
return;
|
|
}
|
|
snprintf(delim, sizeof(delim), "%s", cmpp_dx_delim(dx));
|
|
delim[sizeof(delim)-1] = 0;
|
|
lvl = CmppLvl_push(dx);
|
|
if( !lvl ) goto end;
|
|
if( cmpp_dx_is_eliding(dx) ){
|
|
gotTruth = 1;
|
|
}else if( cmpp__args_evalToInt(xargs->pp, &dx->pimpl->args,
|
|
&gotTruth) ){
|
|
goto end;
|
|
}else if( !gotTruth ){
|
|
skipOn;
|
|
}
|
|
|
|
cmpp_d const * dPrev = xargs->d;
|
|
cmpp_outputer devNull = cmpp_outputer_empty;
|
|
while( !dxppCode ){
|
|
dPrev = dx->d;
|
|
bool const isFinal = dPrev==cis->dElse
|
|
/* true if expecting an #/if. */;
|
|
if( cmpp_dx_consume(dx,
|
|
CmppLvl_is_eliding(lvl) ? &devNull : NULL,
|
|
isFinal ? &cis->dEndif : dClosers,
|
|
isFinal ? 1 : sizeof(dClosers)/sizeof(dClosers[0]),
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
break;
|
|
}
|
|
cmpp_d const * const d2 = dx->d;
|
|
if( !d2 ){
|
|
dxserr("Reached end of input in an untermined %s%s opened "
|
|
"at line %" CMPP_SIZE_T_PFMT ".",
|
|
delim, cis->dIf->name.z, dline.lineNo);
|
|
}
|
|
if( d2==cis->dEndif ){
|
|
break;
|
|
}else if( isFinal ){
|
|
assert(!"cannot happen - caught by consume()");
|
|
dxserr("Expecting %s%s to close %s%s.",
|
|
delim, cis->dEndif->name.z,
|
|
delim, dPrev->name.z);
|
|
break;
|
|
}else if( gotTruth ){
|
|
skipOn;
|
|
continue;
|
|
}else if( d2==cis->dElif ){
|
|
if( 0==cmpp_dx_args_parse(dx, &args)
|
|
&& 0==cmpp__args_evalToInt(xargs->pp, &args, &gotTruth) ){
|
|
if( gotTruth ) skipOff;
|
|
else skipOn;
|
|
}
|
|
continue;
|
|
}else{
|
|
assert( d2==cis->dElse
|
|
&& "Else (haha!) we cannot have gotten here" );
|
|
skipOff;
|
|
continue;
|
|
}
|
|
assert(!"unreachable");
|
|
}
|
|
|
|
#undef skipOff
|
|
#undef skipOn
|
|
end:
|
|
cmpp_args_cleanup(&args);
|
|
if( lvl ){
|
|
bool const lvlIsOk = CmppLvl_get(dx)==lvl;
|
|
CmppLvl_pop(dx, lvl);
|
|
if( !lvlIsOk && !dxppCode ){
|
|
assert(!"i naively believe that this is not possible");
|
|
cmpp_dx_errf(dx, CMPP_RC_SYNTAX,
|
|
"Mis-terminated %s%s opened at line "
|
|
"%" CMPP_SIZE_T_PFMT ".",
|
|
delim, cis->dIf->name.z, dline.lineNo);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* Version 2 of #elif, #else, and #/if. */
|
|
static void cmpp_f_if_dangler(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
CmppIfState const * const cis = args->d->impl.state;
|
|
char const *zDelim = cmpp_dx_delim(dx);
|
|
cmpp_dx_errf(dx, CMPP_RC_SYNTAX,
|
|
"%s%s with no matching %s%s",
|
|
zDelim, args->d->name.z,
|
|
zDelim, cis->dIf->name.z);
|
|
}
|
|
|
|
static void cmpp__dump_sizeofs(void){
|
|
#define SO(X) printf("sizeof(" # X ") = %u\n", (unsigned)sizeof(X))
|
|
SO(cmpp);
|
|
SO(cmpp_api_thunk);
|
|
SO(cmpp_arg);
|
|
SO(cmpp_args);
|
|
SO(cmpp_args_pimpl);
|
|
SO(cmpp_b);
|
|
SO(cmpp_d);
|
|
SO(cmpp_d_reg);
|
|
SO(cmpp__delim);
|
|
SO(cmpp__delim_list);
|
|
SO(cmpp_dx);
|
|
SO(cmpp__dx_pimpl);
|
|
SO(cmpp__dx_pimpl);
|
|
SO(cmpp_outputer);
|
|
SO(cmpp__pimpl);
|
|
SO(((cmpp__pimpl*)0)->stmt);
|
|
SO(((cmpp__pimpl*)0)->policy);
|
|
SO(cmpp_tizer);
|
|
SO(cmpp_token);
|
|
SO(CmppArgList);
|
|
SO(CmppDLine);
|
|
SO(CmppDList);
|
|
SO(CmppDList_entry);
|
|
SO(CmppLvl);
|
|
SO(PodList__atpol);
|
|
printf("cmpp_tt__last = %d\n",
|
|
cmpp_tt__last);
|
|
#undef SO
|
|
}
|
|
|
|
/* Impl. for #pragma. */
|
|
static void cmpp_f_pragma(cmpp_f_args const * args){
|
|
if(args->argc<2){
|
|
cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Expecting an argument");
|
|
return;
|
|
}else if(args->argc>2){
|
|
cmpp_errf(args->pp, CMPP_RC_SYNTAX, "Too many arguments");
|
|
return;
|
|
}
|
|
cmpp_arg const * arg = args->argv[1];
|
|
const char * const zArg = (char const *)arg->z;
|
|
#define M(X) 0==strcmp(zArg,X)
|
|
if(M("defines")){
|
|
cmpp__dump_defines(args->pp, stderr, 1);
|
|
return;
|
|
}
|
|
if(M("sizeof")){
|
|
cmpp__dump_sizeofs();
|
|
return;
|
|
}
|
|
if(M("chomp-F")){
|
|
args->pp->pimpl->flags.chompF = 1;
|
|
return;
|
|
}
|
|
if(M("no-chomp-F")){
|
|
args->pp->pimpl->flags.chompF = 0;
|
|
return;
|
|
}
|
|
if(M("api-thunk")){
|
|
return;
|
|
}/*API thunk*/
|
|
|
|
#undef M
|
|
cmpp_errf(args->pp, CMPP_RC_NOT_FOUND, "Unknown pragma: %s", zArg);
|
|
}
|
|
|
|
/* Impl. for #savepoint. */
|
|
static void cmpp_f_savepoint(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
if( 2 != args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_SYNTAX, "Expecting one argument");
|
|
return;
|
|
}
|
|
cmpp_arg const * const arg = args->argv[1];
|
|
if( cmpp_arg_isflag_c(arg, "begin") ){
|
|
cmpp__dx_sp_begin(dx);
|
|
}else if( cmpp_arg_isflag_c(arg, "rollback") ){
|
|
cmpp__dx_sp_rollback(dx);
|
|
}else if( cmpp_arg_isflag_c(arg, "commit") ){
|
|
cmpp__dx_sp_commit(dx);
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unknown savepoint option: %s",
|
|
arg->z);
|
|
}
|
|
}
|
|
|
|
/* #stderr impl. */
|
|
static void cmpp_f_stderr(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
if(dx->args.z){
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": %.*s\n", dx->sourceName,
|
|
dx->pimpl->dline.lineNo,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}else{
|
|
cmpp_d const * d = args->d;
|
|
g_stderr("%s:%" CMPP_SIZE_T_PFMT ": (no %s%s argument)\n",
|
|
dx->sourceName, dx->pimpl->dline.lineNo,
|
|
cmpp_dx_delim(dx), d->name.z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Manages both the @token@ policy and the delimiters.
|
|
|
|
#@ ?push? policy NAME ?<<?
|
|
#@ ?push? delimiter OPEN CLOSE ?<<?
|
|
#@ ?push? policy NAME delimiter OPEN CLOSE ?<<?
|
|
#@ pop policy
|
|
#@ pop delimiter
|
|
#@ pop policy delimiter
|
|
#@ pop both
|
|
|
|
Function call forms:
|
|
|
|
[@ policy]
|
|
[@ delimiter]
|
|
*/
|
|
static void cmpp_f_at(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
if( !arg ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting arguments.");
|
|
return;
|
|
}
|
|
enum ops { op_none, op_set, op_push, op_pop, op_heredoc };
|
|
enum popWhichE { pop_policy = 0x01, pop_delim = 0x02,
|
|
pop_both = pop_policy | pop_delim };
|
|
enum ops op = op_none /* what to do */;
|
|
int popWhich = 0 /* what to pop */;
|
|
bool gotPolicy = false;
|
|
bool checkedCallForm = !args->isDxCall;
|
|
cmpp_arg const * argDelimO = 0 /* @token@ opener */;
|
|
cmpp_arg const * argDelimC = 0 /* @token@ closer */;
|
|
cmpp__pi(args->pp);
|
|
cmpp_atpol_e polNew = cmpp_atpol_get(args->pp);
|
|
for( ; arg; arg = arg ? arg->next : NULL ){
|
|
//g_warn("arg=%s", arg->z);
|
|
if( !checkedCallForm ){
|
|
assert( args->isDxCall );
|
|
checkedCallForm = true;
|
|
if( cmpp_arg_equals_c(arg, "policy") ){
|
|
char const * z =
|
|
cmpp__atpol_name(args->pp, cmpp__policy(args->pp,at));
|
|
if( z ){
|
|
cmpp_out_raw(args->pp, z, strlen(z));
|
|
}
|
|
}else if( cmpp_arg_equals_c(arg, "delimiter") ){
|
|
char const * zO = 0;
|
|
char const * zC = 0;
|
|
cmpp_atdelim_get(args->pp, &zO, &zC);
|
|
if( zC ){
|
|
cmpp_out_raw(args->pp, zO, strlen(zO));
|
|
cmpp_out_raw(args->pp, " ", 1);
|
|
cmpp_out_raw(args->pp, zC, strlen(zC));
|
|
}
|
|
goto end;
|
|
}else{
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"In call form, '%s' expects one of "
|
|
"'policy' or delimiter'.");
|
|
}
|
|
goto end;
|
|
}/* checkedCallForm */
|
|
if( !argDelimC && op_none==op ){
|
|
/* Look for push|pop. */
|
|
if( cmpp_arg_equals_c(arg, "pop") ){
|
|
arg = arg->next;
|
|
if( !arg ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'pop' expects arguments of 'policy' "
|
|
"and/or 'delimiter' and/or 'both'.");
|
|
goto end;
|
|
}
|
|
for( ; arg; arg = arg->next ){
|
|
if( 0==(pop_policy & popWhich)
|
|
&& cmpp_arg_equals_c(arg, "policy") ){
|
|
popWhich |= pop_policy;
|
|
}else if( 0==(pop_delim & popWhich)
|
|
&& cmpp_arg_equals_c(arg, "delimiter") ){
|
|
popWhich |= pop_delim;
|
|
}else if( 0==(pop_both & popWhich)
|
|
&& cmpp_arg_equals_c(arg, "both") ){
|
|
popWhich |= pop_both;
|
|
}else{
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Invalid argument to 'pop': ", arg->z);
|
|
goto end;
|
|
}
|
|
}
|
|
assert( !arg );
|
|
op = op_pop;
|
|
break;
|
|
}/* pop */
|
|
if( cmpp_arg_equals_c(arg, "push") ){
|
|
op = op_push;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals_c(arg, "set") ){
|
|
/* set is implied if neither of push/pop are and we get
|
|
a policy name. */
|
|
op = op_set;
|
|
continue;
|
|
}
|
|
/* Fall through */
|
|
}/* !argDelimC && op_none==op */
|
|
if( !gotPolicy && cmpp_arg_equals_c(arg, "policy") ){
|
|
arg = arg->next;
|
|
if( !arg ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'policy' requires a policy name argument.");
|
|
goto end;
|
|
}
|
|
polNew = cmpp_atpol_from_str(NULL, (char const*)arg->z);
|
|
if( cmpp_atpol_invalid==polNew ){
|
|
cmpp_atpol_from_str(args->pp, (char const*)arg->z)
|
|
/* Will set the error state to something informative. */;
|
|
goto end;
|
|
}
|
|
if( op_none==op ) op = op_set;
|
|
gotPolicy = true;
|
|
continue;
|
|
}
|
|
if( !argDelimC && cmpp_arg_equals_c(arg, "delimiter") ){
|
|
assert( !argDelimO && !argDelimC );
|
|
argDelimO = arg->next;
|
|
argDelimC = argDelimO ? argDelimO->next : NULL;
|
|
if( !argDelimC ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'delimiter' requires two arguments.");
|
|
goto end;
|
|
}
|
|
arg = argDelimC->next;
|
|
continue;
|
|
}
|
|
if( op_pop!=op ){
|
|
if( cmpp_arg_equals_c(arg,"<<") ){
|
|
if( arg->next ) {
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'%s' must be the final argument.",
|
|
arg->z);
|
|
goto end;
|
|
}
|
|
op = op_heredoc;
|
|
break;
|
|
}
|
|
}
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unhandled argument: %s", arg->z);
|
|
return;
|
|
}/*arg collection*/
|
|
|
|
assert( !dxppCode );
|
|
assert( cmpp_atpol_invalid!=polNew );
|
|
|
|
#define popcheck(LIST) \
|
|
if(dxppCode) goto end; \
|
|
if(!LIST.n) goto bad_pop
|
|
|
|
if( op_pop==op ){
|
|
assert( popWhich>0 && popWhich<=3 );
|
|
if( pop_policy & popWhich ){
|
|
popcheck(pi->policy.at);
|
|
cmpp_atpol_pop(args->pp);
|
|
}
|
|
if( pop_delim & popWhich ){
|
|
popcheck(pi->delim.at);
|
|
cmpp_atdelim_pop(args->pp);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
assert( op_set==op || op_push==op || op_heredoc==op );
|
|
if( argDelimC ){
|
|
/* Push or set the @token@ delimiters */
|
|
if( 0 ){
|
|
g_warn("%s @delims@: %s %s", (op_set==op) ? "set" : "push",
|
|
argDelimO->z, argDelimC->z);
|
|
}
|
|
if( op_push==op || op_heredoc==op ){
|
|
if( cmpp_atdelim_push(args->pp, (char const*)argDelimO->z,
|
|
(char const*)argDelimC->z) ){
|
|
goto end;
|
|
}
|
|
argDelimO = 0 /* Re-use argDelimC as a flag in case we need to
|
|
roll this back on an error below. */;
|
|
}else{
|
|
assert( op_set==op );
|
|
if( cmpp_atdelim_set(args->pp, (char const*)argDelimO->z,
|
|
(char const*)argDelimC->z) ){
|
|
goto end;
|
|
}
|
|
argDelimO = argDelimC = 0;
|
|
}
|
|
}
|
|
|
|
assert( !dxppCode );
|
|
assert( !argDelimO );
|
|
if( op_heredoc==op ){
|
|
if( cmpp_atpol_push(args->pp, polNew) ){
|
|
if( argDelimC ){
|
|
popcheck(pi->delim.at);
|
|
cmpp_atdelim_pop(args->pp);
|
|
}
|
|
}else{
|
|
bool const pushedDelim = NULL!=argDelimC;
|
|
assert( args->d->closer );
|
|
cmpp_dx_consume(dx, NULL, &args->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D)
|
|
/* !Invalidates argDelimO and argDelimC! */;
|
|
popcheck(pi->policy.at);
|
|
cmpp_atpol_pop(args->pp);
|
|
if( pushedDelim ) cmpp_atdelim_pop(args->pp);
|
|
}
|
|
}else if( op_push==op ){
|
|
if( cmpp_atpol_push(args->pp, polNew) && argDelimC ){
|
|
/* Roll back delimiter push */
|
|
cmpp_atdelim_pop(args->pp);
|
|
}
|
|
}else{
|
|
assert( op_set==op );
|
|
if( cmpp__policy(args->pp,at)!=polNew ){
|
|
cmpp_atpol_set(args->pp, polNew);
|
|
}
|
|
}
|
|
end:
|
|
return;
|
|
bad_pop:
|
|
cmpp_dx_errf(dx, CMPP_RC_RANGE,
|
|
"Cannot pop an empty stack.");
|
|
#undef popcheck
|
|
}
|
|
|
|
|
|
static void cmpp_f_expr(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
int rv = 0;
|
|
assert( dx->args.z );
|
|
if( 0 ){
|
|
g_stderr("%s() argc=%d arg0 [%.*s]\n", __func__, dx->args.argc,
|
|
dx->args.arg0->n, dx->args.arg0->z);
|
|
g_stderr("%s() dx->args.z [%.*s]\n", __func__,
|
|
(int)dx->args.nz, dx->args.z);
|
|
}
|
|
if( !dx->args.argc ){
|
|
dxserr("An empty expression is not permitted.");
|
|
return;
|
|
}
|
|
#if 0
|
|
for( cmpp_arg const * a = dx->args.arg0; a; a = a->next ){
|
|
g_stderr("got type=%s n=%u z=%.*s\n",
|
|
cmpp__tt_cstr2(a->ttype, true),
|
|
(unsigned)a->n, (int)a->n, a->z);
|
|
}
|
|
#endif
|
|
if( 0==cmpp__args_evalToInt(args->pp, &dx->pimpl->args, &rv) ){
|
|
if( 'a'==args->d->name.z[0] ){
|
|
if( !rv ){
|
|
cmpp_errf(args->pp, CMPP_RC_ASSERT,
|
|
"Assertion failed: %s",
|
|
args->raw.z);
|
|
}
|
|
}else{
|
|
char buf[60];
|
|
snprintf(buf, sizeof(buf), "%d\n", rv);
|
|
cmpp_out_raw(args->pp, buf, strlen(buf));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cmpp_f_undef_policy(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_unpol_e up = cmpp_unpol_invalid;
|
|
int nSeen = 0;
|
|
assert( args->argc>0 && "v4 args" );
|
|
enum ops { op_invalid, op_set, op_push, op_pop };
|
|
enum ops op = op_invalid;
|
|
if( 1==args->argc ){
|
|
cmpp_f_v4_err_usage(args);
|
|
return;
|
|
}
|
|
unsigned i = 1;
|
|
cmpp_arg const * arg;
|
|
cmpp_arg const * argPol = 0;
|
|
again:
|
|
arg = args->argv[i];
|
|
++nSeen;
|
|
if( 1==nSeen ){
|
|
if( cmpp_arg_equals_c(arg, "push") ){
|
|
if( ++i==args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting argument to 'push'.");
|
|
return;
|
|
}
|
|
op = op_push;
|
|
goto again;
|
|
}else if( cmpp_arg_equals_c(arg, "pop") ){
|
|
if( ++i < args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Extra argument after 'pop': %s",
|
|
cmpp_arg_cstr(args->argv[i]));
|
|
return;
|
|
}
|
|
op = op_pop;
|
|
}
|
|
}
|
|
if( op_pop!=op ){
|
|
if( cmpp_arg_equals_c(arg,"error") ){
|
|
if( op_invalid==op ) op = op_set;
|
|
argPol = arg;
|
|
up = cmpp_unpol_ERROR;
|
|
}else if( cmpp_arg_equals_c(arg,"null") ){
|
|
if( op_invalid==op ) op = op_set;
|
|
argPol = arg;
|
|
up = cmpp_unpol_NULL;
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s",
|
|
cmpp_arg_cstr(arg));
|
|
return;
|
|
}
|
|
}
|
|
assert( op_invalid!=op );
|
|
if( op_pop!=op && cmpp_unpol_invalid==up ){
|
|
if( argPol ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unhandled undefined-policy '%s'."
|
|
" Try one of: error, null",
|
|
cmpp_arg_cstr(argPol));
|
|
}else{
|
|
cmpp_f_v4_err_usage(args);
|
|
}
|
|
return;
|
|
}
|
|
switch( op ){
|
|
case op_set: cmpp_unpol_set(args->pp, up); break;
|
|
case op_push: cmpp_unpol_push(args->pp, up); break;
|
|
case op_pop:
|
|
if( !cmpp__epol(args->pp,un).n ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"No %s%s push is active.",
|
|
cmpp_dx_delim(dx), args->d->name.z);
|
|
}else{
|
|
cmpp_unpol_pop(args->pp);
|
|
}
|
|
break;
|
|
default:
|
|
cmpp__fatal("Not possible - unhandled switch case");
|
|
}
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
/* Impl. for #attach. */
|
|
static void cmpp_f_attach(cmpp_f_args const * args){
|
|
if( 4!=args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"%s expects: STRING as NAME",
|
|
args->d->name.z);
|
|
return;
|
|
}
|
|
cmpp_b * bDbFile = 0;
|
|
cmpp_b * bSchema = 0;
|
|
for( unsigned i = 1; i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
if( !bDbFile ){
|
|
bDbFile = cmpp_b_borrow(args->pp);
|
|
if( !bDbFile ) goto end;
|
|
if( 0==cmpp_arg_interpolate(args->pp, arg, bDbFile,
|
|
cmpp_arg_interpolate_BRACE_CALL)
|
|
&& !bDbFile->n ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Empty db file name is not permitted. "
|
|
"If '%s' is intended as a value, "
|
|
"it should be quoted.", arg->z);
|
|
goto end;
|
|
}
|
|
if( ++i == args->argc
|
|
|| !cmpp_arg_equals_c(args->argv[i], "as") ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting 'as' after db file name.");
|
|
goto end;
|
|
}
|
|
continue;
|
|
}
|
|
if( !bSchema ){
|
|
bSchema = cmpp_b_borrow(args->pp);
|
|
if( !bSchema ) goto end;
|
|
if( 0==cmpp_arg_interpolate(args->pp, arg, bSchema,
|
|
cmpp_arg_interpolate_BRACE_CALL)
|
|
&& !bSchema->n ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Empty db schema name is not permitted."
|
|
"If '%s' is intended as a value, "
|
|
"it should be quoted.",
|
|
arg->z);
|
|
goto end;
|
|
}
|
|
continue;
|
|
}
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s", arg->z);
|
|
goto end;
|
|
}
|
|
if( !bSchema ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Missing schema name.");
|
|
goto end;
|
|
}
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(args->pp, CmppStmt_dbAttach, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_textn(args->pp, q, 1, bDbFile->z, bDbFile->n);
|
|
cmpp_stmt_bind_textn(args->pp, q, 2, bSchema->z, bSchema->n);
|
|
cmpp_stmt_step(args->pp, q, true);
|
|
}
|
|
end:
|
|
cmpp_b_return(args->pp, bDbFile);
|
|
cmpp_b_return(args->pp, bSchema);
|
|
}
|
|
|
|
/* Impl. for #detach. */
|
|
static void cmpp_f_detach(cmpp_f_args const * args){
|
|
if( 2!=args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"%s expects: NAME", args->d->name.z);
|
|
return;
|
|
}
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
if( !b ) return;
|
|
if( cmpp_arg_interpolate(args->pp, args->argv[1], b,
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
if( b->n ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Empty db schema name is not permitted.");
|
|
goto end;
|
|
}
|
|
sqlite3_stmt * const q =
|
|
cmpp__stmt(args->pp, CmppStmt_dbDetach, false);
|
|
if( q ){
|
|
cmpp_stmt_bind_textn(args->pp, q, 1, b->z, b->n);
|
|
cmpp_stmt_step(args->pp, q, true);
|
|
}
|
|
end:
|
|
cmpp_b_return(args->pp, b);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_DB */
|
|
|
|
static void cmpp_f_delimiter(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
enum ops { op_none, op_set, op_push, op_pop };
|
|
enum ops op = op_none;
|
|
cmpp_arg const * argD = 0;
|
|
bool doHeredoc = false;
|
|
for( ; arg; arg = arg->next ){
|
|
if( op_none==op ){
|
|
/* Look for push|pop. */
|
|
if( cmpp_arg_equals_c(arg, "push") ){
|
|
op = op_push;
|
|
continue;
|
|
}else if( cmpp_arg_equals_c(arg, "pop") ){
|
|
if( arg->next ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'pop' expects no arguments.");
|
|
return;
|
|
}
|
|
op = op_pop;
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
}
|
|
if( !argD ){
|
|
if( op_none==op ) op = op_set;
|
|
argD = arg;
|
|
continue;
|
|
}else if( !doHeredoc && cmpp_arg_equals_c(arg,"<<") ){
|
|
if( args->isDxCall ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'%s' is not legal in [call] form.", arg->z);
|
|
return;
|
|
}else if( arg->next ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"'%s' must be the final argument.", arg->z);
|
|
return;
|
|
}
|
|
op = op_push;
|
|
doHeredoc = true;
|
|
continue;
|
|
}
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
|
|
return;
|
|
}
|
|
if( op_pop==op ){
|
|
cmpp_delimiter_pop(args->pp);
|
|
}else if( !argD ){
|
|
if( args->isDxCall ){
|
|
cmpp__delim const * const del = cmpp__dx_delim(dx);
|
|
if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n);
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "No delimiter specified.");
|
|
}
|
|
return;
|
|
}else{
|
|
char const * const z =
|
|
(0==strcmp("default",(char*)argD->z))
|
|
? NULL
|
|
: (char const*)argD->z;
|
|
if( op_push==op ){
|
|
cmpp_delimiter_push(args->pp, (char const*)argD->z);
|
|
}else{
|
|
assert( op_set==op );
|
|
if( doHeredoc ) cmpp_delimiter_push(args->pp, z);
|
|
else cmpp_delimiter_set(args->pp, z);
|
|
}
|
|
}
|
|
if( !cmpp_err_has(dx->pp) ){
|
|
if( args->isDxCall ){
|
|
cmpp__delim const * const del = cmpp__dx_delim(dx);
|
|
if( del ) cmpp_out_raw(args->pp, del->open.z, del->open.n);
|
|
}else if( doHeredoc ){
|
|
assert( op_push==op );
|
|
cmpp_dx_consume(dx, NULL, &args->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D);
|
|
cmpp_delimiter_pop(args->pp);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
/* Experimenting grounds. */
|
|
static void cmpp_f_experiment(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
void * st = args->d->impl.state;
|
|
(void)st;
|
|
g_warn("raw args: %s", args->raw.z);
|
|
g_warn("argc=%u", args->argc);
|
|
g_warn("isCall=%d\n", args->isDxCall);
|
|
|
|
if( 1 ){
|
|
g_warn("argc=%u", args->argc);
|
|
for(unsigned i = 0; i < args->argc; ++i ){
|
|
cmpp_arg const * const a = args->argv[i];
|
|
g_stderr("got type=%s, n=%u z=%.*s\n",
|
|
cmpp__tt_cstr2(a->ttype, true),
|
|
(unsigned)a->n, (int)a->n, a->z);
|
|
}
|
|
}
|
|
|
|
if( 0 ){
|
|
cmpp__dump_sizeofs();
|
|
}
|
|
|
|
if( 0 ){
|
|
cmpp_tizer tz = cmpp_tizer_empty;
|
|
cmpp_token const * const tok = &tz.token;
|
|
int rc = 0;
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
assert( b );
|
|
cmpp_b_append4(args->pp, b, args->raw.z, args->raw.n);
|
|
cmpp_tizer_init(&tz, &args->pp->pimpl->err, b->z, b->n);
|
|
g_warn0("Begin tizer test");
|
|
cmpp_b * const bc = cmpp_b_borrow(args->pp);
|
|
const cmpp_flag32_t cflags = 0;//cmpp_token_content_WHOLE;
|
|
while( 0==(rc = cmpp_tizer_next(&tz, 0))
|
|
&& cmpp_tt_EOF!=tok->ttype ){
|
|
cmpp_token_content(&tz, 0, bc, cflags);
|
|
g_warn("token type=%d: %.*s ==> %.*s",
|
|
tok->ttype, (int)tok->n, tok->z, (int)bc->n, bc->z);
|
|
}
|
|
cmpp_b_return(args->pp, bc);
|
|
g_warn("End tizer test. rc=%d", dxppCode);
|
|
cmpp_dx_errf(dx, 0, 0);
|
|
cmpp_tizer_dtor(&tz);
|
|
cmpp_b_return(args->pp, b);
|
|
if(rc) return;
|
|
}
|
|
}
|
|
#endif /* #ifndef NDEBUG */
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
|
|
/**
|
|
Helper for #query and friends. Expects arg to be an SQL value. If
|
|
arg->next is "bind" then this consumes the following two arguments(
|
|
"bind" BIND_ARG), where BIND_ARG must be one of either
|
|
cmpp_tt_GroupSquiggly or cmpp_tt_GroupBrace.
|
|
|
|
If it returns 0 then:
|
|
|
|
- If "bind" was found then *pBind is set to the BIND_ARG argument
|
|
and *pNext is set to the one after that.
|
|
|
|
- Else *pBind is set to NULL and and *pNext is set to
|
|
arg->next.
|
|
|
|
In either case, *pNext may be set to NULL.
|
|
*/
|
|
static
|
|
int cmpp__consume_sql_args(cmpp *pp, cmpp_f_args const *args,
|
|
unsigned int * i,
|
|
cmpp_arg const **pBind){
|
|
if( 0==ppCode ){
|
|
#define nextArg (++*i<args->argc ? args->argv[*i] : 0)
|
|
*pBind = 0;
|
|
cmpp_arg const *pN = nextArg;
|
|
if( pN && cmpp_arg_equals_c(pN, "bind") ){
|
|
pN = nextArg;
|
|
if( !pN || (
|
|
cmpp_tt_GroupSquiggly!=pN->ttype
|
|
&& cmpp_tt_GroupBrace!=pN->ttype
|
|
) ){
|
|
return serr("Expecting {...} or [...] after 'bind'.");
|
|
}
|
|
*pBind = pN;
|
|
}else{
|
|
*pBind = 0;
|
|
}
|
|
#undef nextArg
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
cmpp_kav_each_f() impl for used by #query's `bind {...}` argument.
|
|
*/
|
|
static int cmpp_kav_each_f_query__bind(
|
|
cmpp * const pp,
|
|
unsigned char const * const zKey, cmpp_size_t nKey,
|
|
unsigned char const * const zVal, cmpp_size_t nVal,
|
|
void * const callbackState
|
|
){
|
|
/* Expecting: :bindName -> bindValue */
|
|
if( ':'!=zKey[0] && '$'!=zKey[0] && '@'!=zKey[0] ){
|
|
cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Bind keys must start with ':' or '$' or '@'.");
|
|
}else{
|
|
sqlite3_stmt * const q = callbackState;
|
|
assert( q );
|
|
int const bindNdx =
|
|
sqlite3_bind_parameter_index(q, (char const*)zKey);
|
|
if( bindNdx ){
|
|
cmpp_stmt_bind_textn(pp, q, bindNdx, zVal, nVal);
|
|
}else{
|
|
cmpp_errf(pp, CMPP_RC_RANGE, "Invalid bind name: %.*s",
|
|
(int)nKey, zKey);
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__bind_group(cmpp * const pp, sqlite3_stmt * const q,
|
|
cmpp_arg const * const aGroup){
|
|
if( ppCode ) return ppCode;
|
|
if( cmpp_tt_GroupSquiggly==aGroup->ttype ){
|
|
return cmpp_kav_each(
|
|
pp, aGroup->z, aGroup->n,
|
|
cmpp_kav_each_f_query__bind, q,
|
|
cmpp_kav_each_F_NOT_EMPTY
|
|
| cmpp_kav_each_F_CALL_VAL
|
|
| cmpp_kav_each_F_PARENS_EXPR
|
|
);
|
|
}
|
|
if( cmpp_tt_GroupBrace!=aGroup->ttype ){
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"Expecting {...} or [...] "
|
|
"for SQL binding list.");
|
|
}
|
|
int bindNdx = 0;
|
|
cmpp_args args = cmpp_args_empty;
|
|
cmpp_args_parse(pp, &args, aGroup->z, aGroup->n, 0);
|
|
if( !args.argc && !ppCode ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Empty SQL bind list is not permitted.");
|
|
/* Keep going so we can clean up a partially-parsed args. */
|
|
}
|
|
for( cmpp_arg const * aVal = args.arg0;
|
|
!ppCode && aVal;
|
|
aVal = aVal->next ){
|
|
++bindNdx;
|
|
if( 0 ){
|
|
g_warn("bind #%d %s <<%s>>", bindNdx,
|
|
cmpp__tt_cstr2(aVal->ttype, true), aVal->z);
|
|
}
|
|
cmpp__bind_arg(pp, q, bindNdx, aVal);
|
|
}
|
|
cmpp_args_cleanup(&args);
|
|
return ppCode;
|
|
}
|
|
|
|
/** #query impl */
|
|
static void cmpp_f_query(cmpp_f_args const * args){
|
|
if( !args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting one or more arguments");
|
|
return;
|
|
}
|
|
cmpp * const pp = args->pp;
|
|
cmpp_dx * const dx = args->dx;
|
|
sqlite3_stmt * q = 0;
|
|
cmpp_b * const obBody = cmpp_b_borrow(args->pp);
|
|
cmpp_b * const sql = cmpp_b_borrow(args->pp);
|
|
cmpp_outputer obNull = cmpp_outputer_empty;
|
|
int nChomp = 0;
|
|
int nUnchomp = 0;
|
|
bool spStarted = false;
|
|
bool seenDefine = false;
|
|
bool seenEmit = false;
|
|
bool batchMode = false;
|
|
cmpp_arg const * aBind = 0;
|
|
cmpp_d const * const dNoRows = args->d->impl.state;
|
|
cmpp_d const * const dClosers[2] = {args->d->closer, dNoRows};
|
|
|
|
if( !obBody || !sql ) goto cleanup;
|
|
|
|
assert( dNoRows );
|
|
//g_warn("args.argc=%d", args.argc);
|
|
|
|
#define nextArg (++i<args->argc ? args->argv[i] : 0)
|
|
for( unsigned i = 1; i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
//g_warn("arg=%s <<%s>>", cmpp_tt_cstr(arg->ttype), arg->z);
|
|
if( cmpp_arg_equals_c(arg, "define") ){
|
|
if( seenDefine ){
|
|
cmpp__dx_err_just_once(dx, arg);
|
|
goto cleanup;
|
|
}
|
|
seenDefine = true;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals_c(arg, "emit") ){
|
|
if( seenEmit ){
|
|
cmpp__dx_err_just_once(dx, arg);
|
|
goto cleanup;
|
|
}
|
|
seenEmit = true;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals_c(arg, "-chomp") ){
|
|
++nChomp;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals_c(arg, "-unchomp") ){
|
|
++nUnchomp;
|
|
continue;
|
|
}
|
|
if( cmpp_arg_equals_c(arg, "-batch") ){
|
|
if( batchMode ){
|
|
cmpp__dx_err_just_once(dx, arg);
|
|
goto cleanup;
|
|
}
|
|
batchMode = true;
|
|
continue;
|
|
}
|
|
if( !sql->n ){
|
|
if( !arg->n ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting a non-empty SQL argument.");
|
|
goto cleanup;
|
|
}
|
|
if( cmpp__consume_sql_args(pp, args, &i, &aBind) ){
|
|
goto cleanup;
|
|
}
|
|
if( cmpp_arg_interpolate(pp, arg, sql, cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto cleanup;
|
|
}
|
|
//g_warn("SQL: <<%s>>", sql->z);
|
|
continue;
|
|
}
|
|
cmpp_errf(pp, CMPP_RC_MISUSE, "Unhandled arg: %s", arg->z);
|
|
goto cleanup;
|
|
}
|
|
#undef nextArg
|
|
|
|
if( ppCode ) goto cleanup;
|
|
seenEmit |= args->isDxCall;
|
|
|
|
if( seenDefine || seenEmit ){
|
|
if( seenDefine && nChomp ){
|
|
/* fixme (2026-07-30): support chomp for define */
|
|
serr("-chomp and define may not be used together.");
|
|
goto cleanup;
|
|
}else if( batchMode ){
|
|
serr("-batch and define/emit may not be used together.");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if( !sql->n ){
|
|
serr("Expecting an SQL-string argument.");
|
|
goto cleanup;
|
|
}
|
|
|
|
if( batchMode ){
|
|
if( aBind ){
|
|
serr("Bindable emits may not be used with -batch.");
|
|
goto cleanup;
|
|
}
|
|
char *zErr = 0;
|
|
cmpp__pi(args->pp);
|
|
int rc = sqlite3_exec(pi->db.dbh, (char const *)sql->z, 0, 0, &zErr);
|
|
rc = cmpp_db_rc(args->pp, rc, zErr);
|
|
sqlite3_free(zErr);
|
|
goto cleanup;
|
|
}
|
|
|
|
if( cmpp_db_rc_v2(pp, sqlite3_prepare_v2(
|
|
pp->pimpl->db.dbh, (char const *)sql->z,
|
|
(int)sql->n, &q, 0), 0, 0) ){
|
|
goto cleanup;
|
|
}else if( !q ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Empty SQL is not permitted.");
|
|
goto cleanup;
|
|
}
|
|
//g_warn("SQL via stmt: <<%s>>", sqlite3_sql(q));
|
|
int const nCol = sqlite3_column_count(q);
|
|
if( !nCol ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"SQL does not have any result columns.");
|
|
goto cleanup;
|
|
}
|
|
if( !seenDefine ){
|
|
if( cmpp_sp_begin(pp) ) goto cleanup;
|
|
spStarted = true;
|
|
}
|
|
|
|
if( aBind && cmpp__bind_group(pp, q, aBind) ){
|
|
goto cleanup;
|
|
}
|
|
|
|
bool gotARow = false;
|
|
cmpp_dx_pos dxPosStart;
|
|
cmpp_flag32_t const consumeFlags = cmpp_dx_consume_F_PROCESS_OTHER_D;
|
|
cmpp_dx_pos_save(dx, &dxPosStart);
|
|
int const nChompOrig = nChomp;
|
|
while( 0==ppCode ){
|
|
int const dbrc = cmpp_stmt_step(pp, q, false);
|
|
if( SQLITE_ROW==dbrc ){
|
|
gotARow = true;
|
|
nChomp = nChompOrig;
|
|
cmpp_b_reuse(obBody);
|
|
if( cmpp__define_from_row(pp, q, false,
|
|
seenEmit ? obBody : 0) ){
|
|
break;
|
|
}
|
|
if( seenEmit ){
|
|
while( nChomp-- && cmpp_b_chomp(obBody) ){}
|
|
while( nUnchomp-- ) cmpp_b_append_ch(obBody, '\n');
|
|
if( seenEmit && !obBody->errCode && obBody->n ){
|
|
cmpp_out_raw(args->pp, obBody->z, obBody->n);
|
|
}
|
|
break;
|
|
}else if( seenDefine ){
|
|
break;
|
|
}
|
|
cmpp_dx_pos_restore(dx, &dxPosStart);
|
|
/* If it weren't for -chomp, we wouldn't need to
|
|
buffer this. */
|
|
if( cmpp_dx_consume_b(dx, obBody, dClosers,
|
|
sizeof(dClosers)/sizeof(dClosers[0]),
|
|
consumeFlags) ){
|
|
goto cleanup;
|
|
}
|
|
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
|
|
while( nChomp-- && cmpp_b_chomp(obBody) ){}
|
|
if( obBody->n && cmpp_out_raw(args->pp, obBody->z, obBody->n) ){
|
|
break;
|
|
}
|
|
if( dx->d == dNoRows ){
|
|
if( cmpp_dx_consume(dx, &obNull, dClosers, 1/*one!*/,
|
|
consumeFlags) ){
|
|
goto cleanup;
|
|
}
|
|
assert( dx->d == dClosers[0] );
|
|
/* TODO? chomp? */
|
|
}
|
|
continue;
|
|
}
|
|
if( 0==ppCode && (seenDefine || seenEmit) ){
|
|
/* If we got here, there was no result row. */
|
|
cmpp__define_from_row(pp, q, true, 0);
|
|
}
|
|
break;
|
|
}/*result row loop*/
|
|
cmpp_stmt_reset(pp, q);
|
|
if( ppCode ) goto cleanup;
|
|
|
|
while( !seenDefine && !seenEmit && !gotARow ){
|
|
/* No result rows. Skip past the body, emitting the #query:no-rows
|
|
content, if any. We disable @token processing for that first
|
|
step because (A) the output is not going anywhere, so no need
|
|
to expand it (noting that expanding may have side effects via
|
|
@[call...]@) and (B) the @tokens@ referring to this query's
|
|
results will not have been set because there was no row to set
|
|
them from, so @expanding@ them would fail. */
|
|
cmpp_atpol_e const atpol = cmpp_atpol_get(args->pp);
|
|
if( cmpp_atpol_set(args->pp, cmpp_atpol_OFF) ) break;
|
|
cmpp_dx_consume(dx, &obNull, dClosers,
|
|
sizeof(dClosers)/sizeof(dClosers[0]),
|
|
consumeFlags);
|
|
cmpp_atpol_set(args->pp, atpol);
|
|
if( dxppCode ) break;
|
|
assert( dx->d == dClosers[0] || dx->d == dClosers[1] );
|
|
if( dx->d == dNoRows ){
|
|
if( cmpp_dx_consume(dx, 0, dClosers, 1/*one!*/,
|
|
consumeFlags) ){
|
|
break;
|
|
}
|
|
assert( dx->d == dClosers[0] );
|
|
/* TODO? chomp? */
|
|
}
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
cmpp_b_return(args->pp, obBody);
|
|
cmpp_b_return(args->pp, sql);
|
|
sqlite3_finalize(q);
|
|
if( spStarted ) cmpp_sp_rollback(pp);
|
|
}
|
|
#endif /* #ifndef CMPP_OMIT_D_DB */
|
|
|
|
|
|
/**
|
|
#sum ...args
|
|
|
|
Emits the sum of its arguments, treating each as an integer.
|
|
Non-integer arguments are silently skipped, i.e. treated as zeroes.
|
|
*/
|
|
static void cmpp_f_sum(cmpp_f_args const * args){
|
|
int64_t nTotal = 0, nn = 0;
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
if( !b ) return;
|
|
int rc = b ? 0 : CMPP_RC_OOM;
|
|
for( unsigned i = 1; 0==rc && i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
rc = cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b),
|
|
cmpp_arg_interpolate_BRACE_CALL);
|
|
if( 0==rc && cmpp_is_int64(b->z, b->n, &nn) ){
|
|
nTotal += nn;
|
|
}
|
|
}
|
|
if( 0==rc ){
|
|
cmpp_b_append_i64(cmpp_b_reuse(b), nTotal);
|
|
cmpp_out_raw(args->pp, b->z, b->n);
|
|
}
|
|
cmpp_b_return(args->pp, b);
|
|
}
|
|
|
|
/**
|
|
#arg ?flags? the-arg
|
|
|
|
-trim-left
|
|
-trim-right
|
|
-trim: trim both sides
|
|
|
|
It sends its arg to cmpp_arg_interpolate() to expand it, optionally
|
|
trims the result, and emits that value.
|
|
|
|
This directive is not expected to be useful except, perhaps in
|
|
testing cmpp itself. Its trim flags, in particular, aren't commonly
|
|
useful because #arg is only useful in a function call context and
|
|
those unconditionally trim their output.
|
|
*/
|
|
static void cmpp_f_arg(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
|
|
bool trimL = false, trimR = false;
|
|
cmpp_arg const * arg = dx->args.arg0;
|
|
if( !dx->args.argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Expecting an argument.");
|
|
return;
|
|
}
|
|
//g_warn("argc=%d", dx->args.argc);
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
for( ; arg && !cmpp_err_has(args->pp); arg = arg->next ){
|
|
#define FLAG(X)if( cmpp_arg_isflag_c(arg, X) )
|
|
FLAG("-raw") {
|
|
a2bFlags = cmpp_arg_interpolate_FORCE_STRING;
|
|
continue;
|
|
}
|
|
FLAG("-trim-left") { trimL=true; continue; }
|
|
FLAG("-trim-right") { trimR=true; continue; }
|
|
FLAG("-trim") { trimL=trimR=true; continue; }
|
|
#undef FLAG
|
|
if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), a2bFlags) ){
|
|
unsigned char const * zz = b->z;
|
|
unsigned char const * zzEnd = b->z + b->n;
|
|
if( trimL ) cmpp_skip_snl(&zz, zzEnd);
|
|
if( trimR ) cmpp_skip_snl_trailing(zz, &zzEnd);
|
|
if( 0 ){
|
|
g_warn("arg n=%d bn=%d %s %.*s",
|
|
(int)arg->n, (int)b->n, cmpp_tt_cstr(arg->ttype),
|
|
(int)(zzEnd-zz), zz);
|
|
}
|
|
//assert(!cmpp_dx_is_eliding(args->dx));
|
|
if( zzEnd-zz>0 ){
|
|
cmpp_out_raw(args->pp, zz, zzEnd-zz);
|
|
//g_warn0("here");
|
|
}
|
|
}
|
|
}
|
|
cmpp_b_return(args->pp, b);
|
|
}
|
|
|
|
/**
|
|
#join ?flags? ...args
|
|
|
|
-s SEPARATOR: sets the separator for its RHS arguments. Default=space.
|
|
|
|
-nl: append a newline (will be stripped by [call]s!). This is the default
|
|
when !(flags & cmpp_f_F_IS_CALL).
|
|
|
|
-nonl: do not append a newline. Default when dx->isDxCall.
|
|
*/
|
|
static void cmpp_f_join(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
cmpp_b * const b = cmpp_b_borrow(args->pp);
|
|
cmpp_b * const bSep = cmpp_b_borrow(args->pp);
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
|
|
bool addNl = !args->isDxCall;
|
|
int n = 0;
|
|
if( !b || !bSep ) goto end;
|
|
if( !dx->args.argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"%s%s expects ?flags? ...args",
|
|
cmpp_dx_delim(dx), args->d->name.z);
|
|
goto end;
|
|
}
|
|
cmpp_b_append_ch(bSep, ' ');
|
|
cmpp_check_oom(args->pp, bSep->z);
|
|
for( cmpp_arg const * arg = dx->args.arg0; arg
|
|
&& !b->errCode
|
|
&& !bSep->errCode
|
|
&& !cmpp_err_has(args->pp);
|
|
arg = arg->next ){
|
|
#define FLAG(X)if( cmpp_arg_isflag_c(arg, X) )
|
|
FLAG("-s"){
|
|
if( !arg->next ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Missing SEPARATOR argument to -s.");
|
|
break;
|
|
}
|
|
cmpp_arg_interpolate(args->pp, arg->next,
|
|
cmpp_b_reuse(bSep),
|
|
cmpp_arg_interpolate_BRACE_CALL);
|
|
arg = arg->next;
|
|
/* FIXME: unescape bSep */
|
|
continue;
|
|
}
|
|
//FLAG("-nl"){ addNl=true; continue; }
|
|
FLAG("-nonl"){ addNl=false; continue; }
|
|
#undef FLAG
|
|
if( n++ && cmpp_out_raw(args->pp, bSep->z, bSep->n) ){
|
|
break;
|
|
}
|
|
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b), a2bFlags) ){
|
|
break;
|
|
}
|
|
cmpp_out_raw(args->pp, b->z, b->n);
|
|
}
|
|
if( !cmpp_err_has(args->pp) ){
|
|
if( !n ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting at least one argument.");
|
|
}else if( addNl ){
|
|
cmpp_out_raw(args->pp, "\n", 1);
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(args->pp, b);
|
|
cmpp_b_return(args->pp, bSep);
|
|
}
|
|
|
|
/* Impl. for #file */
|
|
static void cmpp_f_file(cmpp_f_args const * args){
|
|
cmpp_dx * const dx = args->dx;
|
|
enum e_op {
|
|
op_none, op_exists, op_join
|
|
};
|
|
cmpp_b * const b0 = cmpp_b_borrow(args->pp);
|
|
if( !b0 ) goto end;
|
|
enum e_op op = op_none;
|
|
cmpp_arg const * arg = 0;
|
|
#define nextArg if( ++i==args->argc ) goto missing_arg; arg = args->argv[i]
|
|
unsigned i = 1;
|
|
for(; i < args->argc; ++i ){
|
|
arg = args->argv[i];
|
|
if( op_none==op ){
|
|
if( cmpp_arg_equals_c(arg, "exists") ){
|
|
nextArg;
|
|
op = op_exists;
|
|
if( i+1<args->argc ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"%s%s exists: too many arguments",
|
|
cmpp_dx_delim(dx), args->d->name.z);
|
|
goto end;
|
|
}
|
|
break;
|
|
}else if( cmpp_arg_equals_c(arg, "join") ){
|
|
nextArg;
|
|
op = op_join;
|
|
break;
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unknown %s%s command: %s",
|
|
cmpp_dx_delim(dx), args->d->name.z, arg->z);
|
|
goto end;
|
|
}
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"%s%s unhandled argument: %s",
|
|
cmpp_dx_delim(dx), args->d->name.z, arg->z);
|
|
goto end;
|
|
}
|
|
}
|
|
cmpp_flag32_t const bFlags = cmpp_arg_interpolate_BRACE_CALL;
|
|
switch( op ){
|
|
case op_none: goto missing_arg;
|
|
case op_join: {
|
|
assert( arg );
|
|
for( int j = 0; arg; ++j ){
|
|
if( cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags)
|
|
|| (j && cmpp_out_raw(args->pp, "/", 1))
|
|
|| (b0->n && cmpp_out_raw(args->pp, b0->z, b0->n)) ){
|
|
break;
|
|
}
|
|
arg = ++i==args->argc ? 0 : args->argv[i]
|
|
/* The internals actually NULL-pad argv, so we don't _need_
|
|
to do that ternary, but that detail is not part of the
|
|
public API and i'm not yet sure whether it should be. */;
|
|
}
|
|
cmpp_out_raw(args->pp, "\n", 1);
|
|
break;
|
|
}
|
|
case op_exists: {
|
|
assert( arg );
|
|
if( 0==cmpp_arg_interpolate(args->pp, arg, cmpp_b_reuse(b0), bFlags) ){
|
|
bool const b = cmpp__file_is_readable((char const *)b0->z);
|
|
cmpp_out_raw(args->pp, b ? "1\n" : "0\n", 2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#undef nextArg
|
|
end:
|
|
cmpp_b_return(args->pp, b0);
|
|
return;
|
|
missing_arg:
|
|
if( arg ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s %s: missing argument",
|
|
cmpp_dx_delim(dx), args->d->name.z, arg->z );
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "%s%s: missing subcommand",
|
|
cmpp_dx_delim(dx), args->d->name.z);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
/**
|
|
#cmp LHS op RHS
|
|
*/
|
|
static void cmpp_f_cmp(cmpp_f_args const * args){
|
|
cmpp_b * const bL = cmpp_b_borrow(args->pp);
|
|
cmpp_b * const bR = cmpp_b_borrow(args->pp);
|
|
cmpp_flag32_t a2bFlags = cmpp_arg_interpolate_BRACE_CALL;
|
|
if( !bL || !!bR ) goto end;
|
|
for( cmpp_arg const * arg = args->dx->args.arg0; arg
|
|
&& !cmpp_err_has(args->pp);
|
|
arg = arg->next ){
|
|
if( !bL->z ){
|
|
cmpp_arg_interpolate(args->pp, arg, bL, a2bFlags);
|
|
continue;
|
|
}
|
|
if( !bR->z ){
|
|
cmpp_arg_interpolate(args->pp, arg, bR, a2bFlags);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( cmpp_err_has(args->pp) ) goto end;
|
|
if( !bL->z || !bR->z ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE, "Usage: LHS RHS");
|
|
goto end;
|
|
}
|
|
assert( bL->z );
|
|
assert( bR->z );
|
|
char cbuf[20];
|
|
int const cmp = strcmp((char*)bL->z, (char*)bR->z);
|
|
int const n = snprintf(cbuf, sizeof(cbuf), "%d", cmp);
|
|
assert(n>0);
|
|
cmpp_out_raw(args->pp, cbuf, (cmpp_size_t)n);
|
|
|
|
end:
|
|
cmpp_b_return(args->pp, bL);
|
|
cmpp_b_return(args->pp, bR);
|
|
}
|
|
|
|
/** Impl for #base64. */
|
|
static void cmpp_f_base64(cmpp_f_args const * args){
|
|
bool comma = false;
|
|
bool quote = false;
|
|
cmpp_arg const * argFile = 0;
|
|
bool preprocessFileArg = false;
|
|
cmpp_b * const ob = cmpp_b_borrow(args->pp);
|
|
if( !ob ){
|
|
goto end;
|
|
}
|
|
for( unsigned i = 1; i < args->argc; ++i ){
|
|
cmpp_arg const * arg = args->argv[i];
|
|
#define M(STR) cmpp_arg_equals_c(arg, STR)
|
|
if( M("-comma") ) comma = true;
|
|
else if( M("-quote") ) quote = true;
|
|
else if( M("-f") || M("-file")
|
|
|| (preprocessFileArg=M("-F")) || (preprocessFileArg=M("-FILE"))
|
|
){
|
|
if( ++i==args->argc ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting an argument after %s.", arg->z);
|
|
goto end;
|
|
}
|
|
argFile = arg = args->argv[i];
|
|
continue;
|
|
}else{
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s", arg->z);
|
|
goto end;
|
|
}
|
|
#undef M
|
|
}
|
|
|
|
if( argFile ){
|
|
if(cmpp_arg_consume_as_file(args->dx, argFile, NULL, ob,
|
|
preprocessFileArg) ){
|
|
goto end;
|
|
}
|
|
}else{
|
|
if( args->isDxCall ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"#%s...#/%s is not legal for a [call]. "
|
|
"Pass the -f FILENAME flag instead.",
|
|
args->d->name.z, args->d->name.z);
|
|
goto end;
|
|
}else{
|
|
assert( !ob->n );
|
|
if( cmpp_dx_consume_b(args->dx, ob, &args->d->closer, 1,
|
|
cmpp_dx_consume_F_PROCESS_OTHER_D) ){
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ob->n ){
|
|
cmpp_b * const b64 = cmpp_b_borrow(args->pp);
|
|
if( !b64 ) goto end;
|
|
int cmpp__b_base64_encode(cmpp *, cmpp_b const *, cmpp_b *)/* in b.c */;
|
|
cmpp__b_base64_encode(args->pp, ob, b64);
|
|
cmpp_b_swap(ob, b64);
|
|
cmpp_b_return(args->pp, b64);
|
|
if( cmpp_err_has(args->pp) ) goto end;
|
|
}
|
|
unsigned char const * zEnd = ob->z + ob->n;
|
|
#define out(P,N) if( cmpp_out_raw(args->pp, P, N) ) goto end
|
|
if( !quote ){
|
|
cmpp_out_raw(args->pp, ob->z, ob->n);
|
|
goto end;
|
|
}
|
|
for( unsigned char const * z = ob->z; z < zEnd; ++z ){
|
|
if( quote ){
|
|
out("\"", 1);
|
|
}
|
|
bool closed = false;
|
|
for( ;z<zEnd;++z ){
|
|
if( '\n'==*z ){
|
|
if( quote ){
|
|
if( comma && z+1<zEnd ){
|
|
out("\\n\",\n",5);
|
|
}else{
|
|
out("\\n\"\n",4);
|
|
}
|
|
}else{
|
|
out(z, 1);
|
|
}
|
|
closed = true;
|
|
break;
|
|
}else{
|
|
out(z, 1);
|
|
}
|
|
}
|
|
if( quote && !closed ){
|
|
out("\\n\"\n",4);
|
|
}
|
|
}
|
|
#undef out
|
|
end:
|
|
cmpp_b_return(args->pp, ob);
|
|
return;
|
|
}
|
|
|
|
|
|
/**
|
|
If zName matches one of the delayed-load directives, that directive
|
|
is registered and 0 is returned. CMPP_RC_NO_DIRECTIVE is returned if
|
|
no match is found, but pp's error state is not updated in that
|
|
case. If a match is found and registration fails, that result code
|
|
will propagate via pp.
|
|
*/
|
|
int cmpp__d_delayed_load(cmpp *pp, char const *zName){
|
|
if( ppCode ) return ppCode;
|
|
int rc = CMPP_RC_NO_DIRECTIVE;
|
|
unsigned const nName = strlen(zName);
|
|
|
|
pp->pimpl->flags.isInternalDirectiveReg = true;
|
|
|
|
#define M(NAME) (nName==sizeof(NAME)-1 && 0==strcmp(zName,NAME))
|
|
#define M_OC(NAME) (M(NAME) || M("/" NAME))
|
|
#define M_IF(NAME) if( M(NAME) )
|
|
#define CF(X) cmpp_f_F_ ## X
|
|
#define F_A_RAW CF(ARGS_RAW)
|
|
#define F_A_V4 CF(ARGS_V4)
|
|
#define F_A_LIST CF(ARGS_LIST)
|
|
#define F_EXPR CF(ARGS_LIST) | CF(NOT_SIMPLIFY)
|
|
#define F_UNSAFE cmpp_f_F_NOT_IN_SAFEMODE
|
|
#define F_NC cmpp_f_F_NO_CALL
|
|
#define F_CALL cmpp_f_F_CALL_ONLY
|
|
#define DREG0(SYMNAME, NAME, OPENER, OFLAGS, CLOSER, CFLAGS) \
|
|
cmpp_d_reg SYMNAME = { \
|
|
.name = NAME, \
|
|
.opener = { \
|
|
.f = OPENER, \
|
|
.flags = OFLAGS \
|
|
}, \
|
|
.closer = { \
|
|
.f = CLOSER, \
|
|
.flags = CFLAGS \
|
|
}, \
|
|
.dtor = 0, \
|
|
.state = 0, \
|
|
.arity = -1, \
|
|
.usage = "#directive .usage is TODO. See README.md for docs." \
|
|
}
|
|
|
|
#define DREG(NAME, OPENER, OFLAGS, CLOSER, CFLAGS ) \
|
|
DREG0(const rReg, NAME, OPENER, OFLAGS, CLOSER, CFLAGS ); \
|
|
rc = cmpp_d_register(pp, &rReg, NULL); \
|
|
goto end
|
|
|
|
/* The #if family requires some hand-holding... */
|
|
if( M_OC("if") || M("elif") || M("else") ) {
|
|
DREG0(rIf, "if",
|
|
cmpp_f_if, F_EXPR | F_NC | CF(FLOW_CONTROL),
|
|
cmpp_f_if_dangler, 0);
|
|
DREG0(rElif, "elif",
|
|
cmpp_f_if_dangler, F_NC,
|
|
0, 0);
|
|
DREG0(rElse, "else",
|
|
cmpp_f_if_dangler, F_NC,
|
|
0, 0);
|
|
CmppIfState * const cis = cmpp_malloc2(pp, sizeof(*cis));
|
|
if( !cis ) goto end;
|
|
memset(cis, 0, sizeof(*cis));
|
|
rIf.state = cis;
|
|
rIf.dtor = cmpp_mfree;
|
|
if( cmpp_d_register(pp, &rIf, &cis->dIf)
|
|
/* rIf must be first to avoid leaking cis on error */
|
|
|| cmpp_d_register(pp, &rElif, &cis->dElif)
|
|
|| cmpp_d_register(pp, &rElse, &cis->dElse) ){
|
|
rc = ppCode;
|
|
}else{
|
|
assert( cis->dIf && cis->dElif && cis->dElse );
|
|
assert( !cis->dEndif );
|
|
assert( cis == cis->dIf->impl.state );
|
|
assert( cmpp_mfree==cis->dIf->impl.dtor );
|
|
cis->dElif->impl.state
|
|
= cis->dElse->impl.state
|
|
= cis;
|
|
cis->dElif->closer
|
|
= cis->dElse->closer
|
|
= cis->dEndif
|
|
= cis->dIf->closer;
|
|
rc = 0;
|
|
}
|
|
goto end;
|
|
}/* #if and friends */
|
|
|
|
/* Basic core directives... */
|
|
#define M_IF_CORE(N,OPENER,OFLAGS,CLOSER,CFLAGS) \
|
|
if( M_OC(N) ){ \
|
|
DREG(N, OPENER, OFLAGS, CLOSER, CFLAGS); \
|
|
} (void)0
|
|
|
|
M_IF_CORE("@", cmpp_f_at, F_A_LIST,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("arg", cmpp_f_arg, F_A_LIST, 0, 0);
|
|
M_IF_CORE("assert", cmpp_f_expr, F_EXPR, 0, 0);
|
|
M_IF_CORE("base64", cmpp_f_base64, F_A_V4,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("cmp", cmpp_f_cmp, F_A_LIST, 0, 0);
|
|
M_IF_CORE("define", cmpp_f_define, F_A_V4,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("delimiter", cmpp_f_delimiter, F_A_LIST,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("error", cmpp_f_error, F_A_RAW, 0, 0);
|
|
M_IF_CORE("eval", cmpp_f_eval, F_A_V4,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("expand", cmpp_f_expand, F_A_V4,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("expr", cmpp_f_expr, F_EXPR, 0, 0);
|
|
M_IF_CORE("join", cmpp_f_join, F_A_LIST, 0, 0);
|
|
M_IF_CORE("once", cmpp_f_once, F_A_LIST | F_NC,
|
|
cmpp_f_dangling_closer, 0);
|
|
M_IF_CORE("pragma", cmpp_f_pragma, F_A_V4, 0, 0);
|
|
M_IF_CORE("savepoint", cmpp_f_savepoint, F_A_V4, 0, 0);
|
|
M_IF_CORE("stderr", cmpp_f_stderr, F_A_RAW, 0, 0);
|
|
M_IF_CORE("sum", cmpp_f_sum, F_A_V4, 0, 0);
|
|
M_IF_CORE("undef", cmpp_f_undef, F_A_V4, 0, 0);
|
|
M_IF_CORE("undefined-policy", cmpp_f_undef_policy, F_A_V4, 0, 0);
|
|
M_IF_CORE("//", cmpp_f_noop, F_A_RAW, 0, 0);
|
|
M_IF_CORE("file", cmpp_f_file,
|
|
F_A_V4 | F_UNSAFE, 0, 0);
|
|
|
|
#undef M_IF_CORE
|
|
|
|
|
|
/* Directives which can be disabled via build flags or
|
|
flags to cmpp_ctor()... */
|
|
#define M_IF_FLAGGED(NAME,FLAG,OPENER,OFLAGS,CLOSER,CFLAGS) \
|
|
M_IF(NAME) { \
|
|
if( 0==(FLAG & pp->pimpl->flags.ctorFlags) ) { \
|
|
DREG(NAME,OPENER,OFLAGS,CLOSER,CFLAGS); \
|
|
} \
|
|
goto end; \
|
|
}
|
|
|
|
#ifndef CMPP_OMIT_D_INCLUDE
|
|
M_IF_FLAGGED("include", cmpp_ctor_F_NO_INCLUDE,
|
|
cmpp_f_include, F_A_V4 | F_UNSAFE,
|
|
0, 0);
|
|
#endif
|
|
|
|
|
|
#ifndef CMPP_OMIT_D_DB
|
|
M_IF_FLAGGED("attach", cmpp_ctor_F_NO_DB,
|
|
cmpp_f_attach, F_A_V4 | F_UNSAFE,
|
|
0, 0);
|
|
M_IF_FLAGGED("detach", cmpp_ctor_F_NO_DB,
|
|
cmpp_f_detach, F_A_V4 | F_UNSAFE,
|
|
0, 0);
|
|
if( 0==(cmpp_ctor_F_NO_DB & pp->pimpl->flags.ctorFlags)
|
|
&& (M_OC("query") || M("query:no-rows")) ){
|
|
DREG0(rQ, "query", cmpp_f_query, F_A_V4 | F_UNSAFE,
|
|
cmpp_f_dangling_closer, 0);
|
|
cmpp_d * dQ = 0;
|
|
rc = cmpp_d_register(pp, &rQ, &dQ);
|
|
if( rc ) goto end;
|
|
/*
|
|
It would be preferable to delay registration of query:no-rows
|
|
until we need it, but doing so causes an error when:
|
|
|
|
|#if 0
|
|
|#query
|
|
|...
|
|
|#query:no-rows HERE
|
|
|...
|
|
|#/query
|
|
|#/if
|
|
|
|
Because query:no-rows won't have been registered, and unknown
|
|
directives are an error even in skip mode. Maybe they shouldn't
|
|
be. Maybe we should just skip them in skip mode. That's only
|
|
been an issue since doing delayed registration of directives, so
|
|
it's not come up until recently (as of 2025-10-27). i was so
|
|
hoping to be able to get _rid_ of skip mode at some point.
|
|
*/
|
|
cmpp_d * dNoRows = 0;
|
|
cmpp_d_reg const rNR = {
|
|
.name = "query:no-rows",
|
|
.arity = -1,
|
|
.opener = {
|
|
.f = cmpp_f_dangling_closer,
|
|
.flags = F_NC
|
|
}
|
|
};
|
|
rc = cmpp_d_register(pp, &rNR, &dNoRows);
|
|
if( rc ) goto end;
|
|
dNoRows->closer = dQ->closer;
|
|
assert( !dQ->impl.state );
|
|
dQ->impl.state = dNoRows;
|
|
goto end;
|
|
}
|
|
#endif /*CMPP_OMIT_D_DB*/
|
|
|
|
|
|
#undef M_IF_FLAGGED
|
|
|
|
#ifndef NDEBUG
|
|
M_IF("experiment"){
|
|
DREG("experiment", cmpp_f_experiment,
|
|
F_A_V4 | F_UNSAFE, 0, 0);
|
|
}
|
|
#endif
|
|
|
|
end:
|
|
#undef DREG
|
|
#undef DREG0
|
|
#undef F_EXPR
|
|
#undef F_A_RAW
|
|
#undef F_A_LIST
|
|
#undef F_A_V4
|
|
#undef F_UNSAFE
|
|
#undef F_NC
|
|
#undef F_CALL
|
|
#undef CF
|
|
#undef M
|
|
#undef M_OC
|
|
#undef M_IF
|
|
pp->pimpl->flags.isInternalDirectiveReg = false;
|
|
return ppCode ? ppCode : rc;
|
|
}
|
|
|
|
#include <unistd.h>
|
|
|
|
/**
|
|
There are APIs i'd _like_ to have here, but the readily-available
|
|
code for them BSD license, so can't be pasted in here. Examples:
|
|
|
|
- Filename canonicalization.
|
|
|
|
- Cross-platform getcwd() (see below).
|
|
|
|
- Windows support. This requires, in addition to the different
|
|
filesystem APIs, converting strings into something it can use.
|
|
|
|
All of that adds up to infrastructure... which already exists
|
|
elsewhere but can't be copied here while retaining this project's
|
|
license.
|
|
*/
|
|
|
|
bool cmpp__file_is_readable(char const *zFile){
|
|
return 0==access(zFile, R_OK);
|
|
}
|
|
|
|
#if 0
|
|
FILE *cmpp__fopen(const char *zName, const char *zMode){
|
|
FILE *f;
|
|
if(zName && ('-'==*zName && !zName[1])){
|
|
f = (strchr(zMode, 'w') || strchr(zMode,'+'))
|
|
? stdout
|
|
: stdin
|
|
;
|
|
}else{
|
|
f = fopen(zName, zMode);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
void cmpp__fclose( FILE * f ){
|
|
if(f && (stdin!=f) && (stdout!=f) && (stderr!=f)){
|
|
fclose(f);
|
|
}
|
|
}
|
|
#endif
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
const cmpp_args_pimpl cmpp_args_pimpl_empty =
|
|
cmpp_args_pimpl_empty_m;
|
|
const cmpp_args cmpp_args_empty = cmpp_args_empty_m;
|
|
const cmpp_arg cmpp_arg_empty = cmpp_arg_empty_m;
|
|
|
|
CMPP__EXPORT(bool, cmpp_arg_equals)(cmpp_arg const *arg,
|
|
char const *z){
|
|
return 0==memcmp(arg->z, z, arg->n)
|
|
? arg->n==strlen(z)
|
|
: 0;
|
|
}
|
|
|
|
|
|
|
|
CMPP__EXPORT(char const *,cmpp_arg_cstr)(cmpp_arg const *arg){
|
|
return (char const *)arg->z;
|
|
}
|
|
|
|
CMPP__EXPORT(char const *,cmpp_arg_cstr2)(cmpp_arg const *arg,
|
|
cmpp_size_t *n){
|
|
*n = arg->n;
|
|
return (char const *)arg->z;
|
|
}
|
|
|
|
|
|
//just in case these ever get dynamic state
|
|
void cmpp_arg_cleanup(cmpp *pp, cmpp_arg *arg){
|
|
if( arg ){
|
|
(void)pp;
|
|
*arg = cmpp_arg_empty;
|
|
}
|
|
}
|
|
|
|
void cmpp_arg_reuse(cmpp_arg *arg){
|
|
if( arg ){
|
|
*arg = cmpp_arg_empty;
|
|
}
|
|
}
|
|
|
|
void CmppArgList_reuse(cmpp*pp, CmppArgList *li){
|
|
for(cmpp_size_t n = li->na; n; ){
|
|
cmpp_arg * const arg = &li->list[--n];
|
|
if( arg ){
|
|
(void)pp;
|
|
cmpp_arg_reuse( arg );
|
|
}
|
|
assert( !li->list[n].next );
|
|
}
|
|
if( li->avb ) cmpp_b_reuse(li->avb);
|
|
li->n = 0;
|
|
}
|
|
|
|
void CmppArgList_dtor(cmpp *pp, CmppArgList *li){
|
|
static const CmppArgList CmppArgList_empty = CmppArgList_empty_m;
|
|
assert(pp);
|
|
while( li->na ){
|
|
cmpp_arg * const arg = &li->list[--li->na];
|
|
if( arg ){
|
|
cmpp_arg_cleanup(pp, arg);
|
|
}
|
|
}
|
|
cmpp_mfree(li->list);
|
|
cmpp_b_return(pp, li->avb);
|
|
*li = CmppArgList_empty;
|
|
}
|
|
|
|
cmpp_arg const ** CmppArgList_makeArgv(cmpp*pp, CmppArgList*li,
|
|
bool ensureNulTerminated){
|
|
if( li->n ){
|
|
if( !li->avb ){
|
|
li->avb = cmpp_b_borrow(pp);
|
|
if( !li->avb ) return 0;
|
|
}
|
|
cmpp_size_t sz = (
|
|
/* argv */
|
|
sizeof(cmpp_arg*) * (li->n+1/*NULL-pad it*/)
|
|
);
|
|
cmpp_size_t const strOffset =
|
|
/* Offset into li->abv->z of the string table */ sz;
|
|
if( ensureNulTerminated ){
|
|
for(cmpp_size_t i = 0; i < li->n; ++i ){
|
|
cmpp_arg const * const a = &li->list[i];
|
|
if( a->n && a->z[a->n]/* not NUL-terminated*/ ){
|
|
sz += a->n + 1/*NUL*/;
|
|
}
|
|
}
|
|
}
|
|
if( cmpp_b_reserve(li->avb, sz) ){
|
|
cmpp_b_return(pp, li->avb);
|
|
li->avb = 0;
|
|
return 0;
|
|
}
|
|
li->avb->n = sz;
|
|
/* Direct argv[X] to a suitable pointer and argv[X]->z
|
|
to a NUL-terminated copy of itself. */
|
|
cmpp_arg const ** const av = (cmpp_arg const **)li->avb->z;
|
|
unsigned char * oz = li->avb->z + strOffset;
|
|
for(cmpp_size_t i = 0; i < li->n; ++i){
|
|
cmpp_arg * const a = &li->list[i];
|
|
av[i] = a;
|
|
if( ensureNulTerminated
|
|
&& a->n && a->z[a->n]/* not NUL-terminated*/ ){
|
|
//g_warn("arg: %.*s", (int)a->n, a->z);
|
|
assert( oz < (li->avb->z + li->avb->n - a->n) );
|
|
memcpy(oz, a->z, a->n);
|
|
a->z = oz;
|
|
oz += a->n;
|
|
*oz++ = 0;
|
|
}
|
|
#if 0
|
|
/* Legacy cruft we don't want at this level */
|
|
if( i && av[i] && av[i-1] ){
|
|
av[i-1]->next = av[i];
|
|
}
|
|
#endif
|
|
}
|
|
av[li->n] = 0;
|
|
return av;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
cmpp_arg * CmppArgList_append(cmpp *pp, CmppArgList *li){
|
|
cmpp_arg * p = 0;
|
|
assert( li->list ? li->na : 0==li->na );
|
|
if( 0==ppCode
|
|
&& 0==CmppArgList_reserve(pp, li,
|
|
cmpp__li_reserve1_size(li,10)) ){
|
|
p = &li->list[li->n++];
|
|
cmpp_arg_reuse( p );
|
|
if( li->avb ){
|
|
cmpp_b_return(pp, li->avb);
|
|
li->avb = 0;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
#if 0
|
|
/** Returns the most-recently-appended arg of li back to li's
|
|
free-list. */
|
|
static void CmppArgList_unappend(CmppArgList *li){
|
|
assert( li->n );
|
|
if( li->n ){
|
|
cmpp_arg_reuse( &li->list[--li->n] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void cmpp__args_pimpl_cleanup(cmpp_args_pimpl *p){
|
|
assert( p->pp );
|
|
assert( !p->nextFree );
|
|
cmpp_b_clear(&p->argOut);
|
|
CmppArgList_dtor(p->pp, &p->argli);
|
|
*p = cmpp_args_pimpl_empty;
|
|
}
|
|
|
|
static void cmpp__args_pimpl_reuse(cmpp_args_pimpl *p){
|
|
assert( p->pp );
|
|
assert( !p->nextFree );
|
|
cmpp_b_reuse(&p->argOut);
|
|
CmppArgList_reuse(p->pp, &p->argli);
|
|
assert( !p->argOut.n );
|
|
assert( !p->argli.n );
|
|
}
|
|
|
|
static void cmpp__args_pimpl_return(cmpp *pp, cmpp_args_pimpl *p){
|
|
if( p ){
|
|
assert( p->pp );
|
|
cmpp__pi(pp);
|
|
assert( !p->nextFree );
|
|
cmpp__args_pimpl_reuse(p);
|
|
p->nextFree = pi->recycler.argPimpl;
|
|
pi->recycler.argPimpl = p;
|
|
}
|
|
}
|
|
|
|
static cmpp_args_pimpl * cmpp__args_pimpl_borrow(cmpp *pp){
|
|
cmpp__pi(pp);
|
|
cmpp_args_pimpl * p = 0;
|
|
if( pi->recycler.argPimpl ){
|
|
p = pi->recycler.argPimpl;
|
|
pi->recycler.argPimpl = p->nextFree;
|
|
p->nextFree = 0;
|
|
p->pp = pp;
|
|
assert( !p->argOut.n && "Buffer was used when not borrowed" );
|
|
}else{
|
|
p = cmpp_malloc2(pp, sizeof(*p));
|
|
if( 0==cmpp_check_oom(pp, p) ) {
|
|
*p = cmpp_args_pimpl_empty;
|
|
p->pp = pp;
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_args_cleanup)(cmpp_args *a){
|
|
if( a ){
|
|
if( a->pimpl ){
|
|
cmpp * const pp = a->pimpl->pp;
|
|
assert( pp );
|
|
if( pp ){
|
|
cmpp__args_pimpl_return(pp, a->pimpl);
|
|
}else{
|
|
cmpp__args_pimpl_cleanup(a->pimpl);
|
|
cmpp_mfree(a->pimpl);
|
|
}
|
|
}
|
|
*a = cmpp_args_empty;
|
|
}
|
|
}
|
|
|
|
//CMPP__EXPORT(void, cmpp_args_reuse)(cmpp_args *a)
|
|
void cmpp__args_reuse(cmpp_args *a){
|
|
cmpp_args_pimpl * const p = a->pimpl;
|
|
if( p ) cmpp__args_pimpl_reuse(p);
|
|
*a = cmpp_args_empty;
|
|
a->pimpl = p;
|
|
}
|
|
|
|
int cmpp__args_init(cmpp * pp, cmpp_args * a){
|
|
if( 0==ppCode ){
|
|
if( a->pimpl ){
|
|
assert( a->pimpl->pp == pp );
|
|
cmpp__args_reuse(a);
|
|
assert(! a->pimpl->argOut.n );
|
|
assert( a->pimpl->pp == pp );
|
|
}else{
|
|
a->pimpl = cmpp__args_pimpl_borrow(pp);
|
|
assert( !a->pimpl || a->pimpl->pp==pp );
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
Declare cmpp_argOp_f_NAME().
|
|
*/
|
|
#define cmpp_argOp_decl(NAME) \
|
|
static void cmpp_argOp_f_ ## NAME (cmpp *pp, \
|
|
cmpp_argOp const *op, \
|
|
cmpp_arg const *vLhs, \
|
|
cmpp_arg const **pvRhs, \
|
|
int *pResult)
|
|
cmpp_argOp_decl(compare);
|
|
|
|
#if 0
|
|
cmpp_argOp_decl(logical1);
|
|
cmpp_argOp_decl(logical2);
|
|
cmpp_argOp_decl(defined);
|
|
#endif
|
|
|
|
static const struct {
|
|
const cmpp_argOp opAnd;
|
|
const cmpp_argOp opOr;
|
|
const cmpp_argOp opGlob;
|
|
const cmpp_argOp opNotGlob;
|
|
const cmpp_argOp opNot;
|
|
const cmpp_argOp opDefined;
|
|
#define cmpp_argOps_cmp_map(E) E(Eq) E(Neq) E(Lt) E(Le) E(Gt) E(Ge)
|
|
#define E(NAME) const cmpp_argOp op ## NAME;
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
} cmpp_argOps = {
|
|
.opAnd = {
|
|
.ttype = cmpp_tt_OpAnd,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_logical2
|
|
},
|
|
.opOr = {
|
|
.ttype = cmpp_tt_OpOr,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_logical2
|
|
},
|
|
.opGlob = {
|
|
.ttype = cmpp_tt_OpGlob,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_glob
|
|
},
|
|
.opNotGlob = {
|
|
.ttype = cmpp_tt_OpNotGlob,
|
|
.arity = 2,
|
|
.assoc = 0,
|
|
.xCall = 0//cmpp_argOp_f_glob
|
|
},
|
|
.opNot = {
|
|
.ttype = cmpp_tt_OpNot,
|
|
.arity = 1,
|
|
.assoc = 1,
|
|
.xCall = 0//cmpp_argOp_f_logical1
|
|
},
|
|
.opDefined = {
|
|
.ttype = cmpp_tt_OpDefined,
|
|
.arity = 1,
|
|
.assoc = 1,
|
|
.xCall = 0//cmpp_argOp_f_defined
|
|
},
|
|
/* Comparison ops... */
|
|
#define E(NAME) .op ## NAME = { \
|
|
.ttype = cmpp_tt_OpCmp ## NAME, .arity = 2, .assoc = 0, \
|
|
.xCall = cmpp_argOp_f_compare },
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
};
|
|
|
|
cmpp_argOp const * cmpp_argOp_for_tt(cmpp_tt_e tt){
|
|
switch(tt){
|
|
case cmpp_tt_OpAnd: return &cmpp_argOps.opAnd;
|
|
case cmpp_tt_OpOr: return &cmpp_argOps.opOr;
|
|
case cmpp_tt_OpGlob: return &cmpp_argOps.opGlob;
|
|
case cmpp_tt_OpNot: return &cmpp_argOps.opNot;
|
|
case cmpp_tt_OpDefined: return &cmpp_argOps.opDefined;
|
|
#define E(NAME) case cmpp_tt_OpCmp ## NAME: return &cmpp_argOps.op ## NAME;
|
|
cmpp_argOps_cmp_map(E)
|
|
#undef E
|
|
default: return NULL;
|
|
}
|
|
}
|
|
#define argOp(ARG) cmpp_argOp_for_tt((ARG)->ttype)
|
|
|
|
/**
|
|
An internal helper for cmpp_argOp_...(). It binds some value of
|
|
*paArg to column bindNdx of query q and sets *paArg to the next
|
|
argument to be consumed. This function expects that q is set up to
|
|
do the right thing when *paArg is a Word-type value (see
|
|
cmpp_argOp_f_compare()).
|
|
*/
|
|
static void cmpp_argOp__cmp_bind(cmpp * const pp,
|
|
sqlite3_stmt * const q,
|
|
int bindNdx,
|
|
cmpp_arg const ** paArg){
|
|
cmpp_arg const * const arg = *paArg;
|
|
assert(arg);
|
|
switch( ppCode ? 0 : arg->ttype ){
|
|
case 0: break;
|
|
case cmpp_tt_Word:
|
|
/* In this case, q is supposed to be set up to use
|
|
CMPP__SEL_V_FROM(bindNdx), i.e. it expects the verbatim word
|
|
and performs the expansion to its value in the query. */
|
|
cmpp_stmt_bind_textn(pp, q, bindNdx, arg->z, arg->n);
|
|
*paArg = arg->next;
|
|
break;
|
|
case cmpp_tt_StringBT:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:
|
|
case cmpp_tt_IntDec:{
|
|
cmpp__bind_arg(pp, q, bindNdx, arg);
|
|
*paArg = arg->next;
|
|
break;
|
|
}
|
|
case cmpp_tt_OpNot:
|
|
case cmpp_tt_OpDefined:
|
|
case cmpp_tt_GroupParen:{
|
|
int rv = 0;
|
|
if( 0==cmpp__arg_toBool(pp, arg, &rv, paArg) ){
|
|
cmpp_stmt_bind_int(pp, q, bindNdx, rv);
|
|
}
|
|
*paArg = arg->next;
|
|
break;
|
|
}
|
|
/* TODO? cmpp_tt_GroupParen */
|
|
default:
|
|
cmpp_errf(pp, CMPP_RC_TYPE,
|
|
"Invalid argument type (%s) for the comparison "
|
|
"queries: %s",
|
|
cmpp_tt_cstr(arg->ttype), arg->z);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Internal helper for cmp_argOp_...().
|
|
|
|
Expects q to be a query with an integer in result column 0. This
|
|
steps/resets the query and applies the given comparison operator's
|
|
logic to column 0's value, placing the result of the operator in
|
|
*pResult.
|
|
|
|
If q has no result row, a default value of 0 is assumed.
|
|
*/
|
|
static void cmpp_argOp__cmp_apply(cmpp * const pp,
|
|
cmpp_argOp const * const op,
|
|
sqlite3_stmt * const q,
|
|
int * const pResult){
|
|
if( 0==ppCode ){
|
|
int rc = cmpp_stmt_step(pp, q, false);
|
|
assert( SQLITE_ROW==rc || ppCode );
|
|
if( SQLITE_ROW==rc ){
|
|
rc = sqlite3_column_int(q, 0);
|
|
}else{
|
|
rc = 0;
|
|
}
|
|
switch( op->ttype ){
|
|
case 0: break;
|
|
case cmpp_tt_OpCmpEq: *pResult = 0==rc; break;
|
|
case cmpp_tt_OpCmpNeq: *pResult = 0!=rc; break;
|
|
case cmpp_tt_OpCmpLt: *pResult = rc<0; break;
|
|
case cmpp_tt_OpCmpLe: *pResult = rc<=0; break;
|
|
case cmpp_tt_OpCmpGt: *pResult = rc>0; break;
|
|
case cmpp_tt_OpCmpGe: *pResult = rc>=0; break;
|
|
default:
|
|
cmpp__fatal("Cannot happen: invalid arg mapping");
|
|
}
|
|
}
|
|
cmpp_stmt_reset(pp, q);
|
|
}
|
|
|
|
/**
|
|
Applies *paRhs as the RHS of an integer binary operator, the LHS of
|
|
which is the lhs argument. The result is put in *pResult. On
|
|
success *paRhs is set to the next argument for the expression to
|
|
parse.
|
|
*/
|
|
static void cmpp_argOp_applyTo(cmpp *pp,
|
|
cmpp_argOp const * const op,
|
|
int lhs,
|
|
cmpp_arg const ** paRhs,
|
|
int * pResult){
|
|
sqlite3_stmt * q = 0;
|
|
cmpp_arg const * aRhs = *paRhs;
|
|
assert(aRhs);
|
|
q = cmpp_tt_Word==aRhs->ttype
|
|
? cmpp__stmt(pp, CmppStmt_cmpVD, false)
|
|
: cmpp__stmt(pp, CmppStmt_cmpVV, false);
|
|
if( q ){
|
|
char numbuf[32];
|
|
int const nNum = snprintf(numbuf, sizeof(numbuf), "%d", lhs);
|
|
cmpp_stmt_bind_textn(pp, q, 1, ustr_c(numbuf), nNum);
|
|
cmpp_argOp__cmp_bind(pp, q, 2, paRhs);
|
|
cmpp_argOp__cmp_apply(pp, op, q, pResult);
|
|
}
|
|
}
|
|
|
|
cmpp_argOp_decl(compare){
|
|
cmpp_arg const * const vRhs = *pvRhs;
|
|
sqlite3_stmt * q = 0;
|
|
/* Select which query to use, depending on whether each
|
|
of the LHS/RHS are Word tokens. For Word tokens
|
|
the corresponding query columns get bound to
|
|
a subquery which resolves the word. Non-word
|
|
tokens get bound as-is. */
|
|
if( cmpp_tt_Word==vLhs->ttype ){
|
|
q = cmpp_tt_Word==vRhs->ttype
|
|
? cmpp__stmt(pp, CmppStmt_cmpDD, false)
|
|
: cmpp__stmt(pp, CmppStmt_cmpDV, false);
|
|
if(0){
|
|
g_warn("\nvLhs=%s %s\nvRhs=%s %s\n",
|
|
cmpp_tt_cstr(vLhs->ttype), vLhs->z,
|
|
cmpp_tt_cstr(vRhs->ttype), vRhs->z);
|
|
}
|
|
}else if( cmpp_tt_Word==vRhs->ttype ){
|
|
q = cmpp__stmt(pp, CmppStmt_cmpVD, false);
|
|
}else{
|
|
q = cmpp__stmt(pp, CmppStmt_cmpVV, false);
|
|
}
|
|
if( q ){
|
|
//cmpp_stmt_bind_textn(pp, q, 1, vLhs->z, vLhs->n);
|
|
cmpp_argOp__cmp_bind(pp, q, 1, &vLhs);
|
|
cmpp_argOp__cmp_bind(pp, q, 2, pvRhs);
|
|
cmpp_argOp__cmp_apply(pp, op, q, pResult);
|
|
}
|
|
}
|
|
|
|
#undef cmpp_argOp_decl
|
|
|
|
#if 0
|
|
static inline int cmpp_dxt_isBinOp(cmpp_tt tt){
|
|
cmpp_argOp const * const a = cmpp_argOp_for_tt(tt);
|
|
return a ? 2==a->arity : 0;
|
|
}
|
|
|
|
static inline int cmpp_dxt_isUnaryOp(cmpp_tt tt){
|
|
return tt==cmpp_tt_OpNot || cmpp_tt_OpDefined;
|
|
}
|
|
|
|
static inline int cmpp_dxt_isGroup(cmpp_tt tt){
|
|
return tt==cmpp_tt_GroupParen || tt==cmpp_tt_GroupBrace || cmpp_tt_GroupSquiggly;
|
|
}
|
|
#endif
|
|
|
|
int cmpp__arg_evalSubToInt(cmpp *pp,
|
|
cmpp_arg const *arg,
|
|
int * pResult){
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(pp, &sub, pResult);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__args_evalToInt(cmpp * const pp,
|
|
cmpp_args const *pArgs,
|
|
int * pResult){
|
|
if( ppCode ) return ppCode;
|
|
|
|
cmpp_arg const * pNext = 0;
|
|
cmpp_arg const * pPrev = 0;
|
|
int result = *pResult;
|
|
cmpp_b osL = cmpp_b_empty;
|
|
cmpp_b osR = cmpp_b_empty;
|
|
static int level = 0;
|
|
++level;
|
|
|
|
#define lout(fmt,...) if(0) g_stderr("%.*c" fmt, level*2, ' ', __VA_ARGS__)
|
|
|
|
//lout("START %s(): %s\n", __func__, pArgs->pimpl->buf.argsRaw.z);
|
|
for( cmpp_arg const *arg = pArgs->arg0;
|
|
arg && 0==ppCode;
|
|
pPrev = arg, arg = pNext ){
|
|
pNext = arg->next;
|
|
if( cmpp_tt_Noop==arg->ttype ){
|
|
arg = pPrev /* help the following arg to DTRT */;
|
|
continue;
|
|
}
|
|
cmpp_argOp const * const thisOp = argOp(arg);
|
|
cmpp_argOp const * const nextOp = pNext ? argOp(pNext) : 0;
|
|
if( 0 ){
|
|
lout("arg: %s @%p %s\n",
|
|
cmpp__tt_cstr2(arg->ttype, true), arg, arg->z);
|
|
if(1){
|
|
if( pPrev ) lout(" prev arg: %s %s\n",
|
|
cmpp__tt_cstr2(pPrev->ttype, true), pPrev->z);
|
|
if( pNext ) lout(" next arg: %s %s\n",
|
|
cmpp__tt_cstr2(pNext->ttype, true), pNext->z);
|
|
}
|
|
}
|
|
if( thisOp ){ /* Basic validation */
|
|
if( !pNext ){
|
|
serr("Missing '%s' RHS.",
|
|
cmpp__tt_cstr2(thisOp->ttype, false));
|
|
break;
|
|
}else if( !pPrev && 2==thisOp->arity ){
|
|
serr("Missing %s LHS.",
|
|
cmpp__tt_cstr2(thisOp->ttype, false));
|
|
break;
|
|
}
|
|
if( nextOp && nextOp->arity>1 ){
|
|
serr("Invalid '%s' RHS: %s", arg->z, pNext->z);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch( arg->ttype ){
|
|
|
|
case cmpp_tt_OpNot:
|
|
case cmpp_tt_OpDefined:
|
|
if( pPrev && !argOp(pPrev) ){
|
|
cmpp_errf(pp, CMPP_RC_CANNOT_HAPPEN,
|
|
"We expected to have consumed '%s' by "
|
|
"this point.",
|
|
pPrev->z);
|
|
}else{
|
|
cmpp__arg_toBool(pp, arg, &result, &pNext);
|
|
}
|
|
break;
|
|
|
|
case cmpp_tt_OpAnd:
|
|
case cmpp_tt_OpOr:{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
/* Reminder to self: we can't add short-circuiting of the RHS
|
|
right now because the handling of chained unary ops on the
|
|
RHS is handled via cmpp__arg_toBool(). */
|
|
int rv = 0;
|
|
if( 0==cmpp__arg_toBool(pp, pNext, &rv, &pNext) ){
|
|
if( cmpp_tt_OpAnd==arg->ttype ) result = result && rv;
|
|
else result = result || rv;
|
|
}
|
|
//g_warn("post-and/or pNext=%s\n", pNext ? pNext->z : 0);
|
|
break;
|
|
}
|
|
|
|
case cmpp_tt_OpNotGlob:
|
|
case cmpp_tt_OpGlob:{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
assert( pNext!=arg );
|
|
assert( pPrev!=arg );
|
|
if( cmpp_arg_interpolate(pp, pNext, &osL, 0) ){
|
|
break;
|
|
}
|
|
unsigned char const * const zGlob = osL.z;
|
|
if( 0==cmpp_arg_interpolate(pp, pPrev, &osR, 0) ){
|
|
if( 0 ){
|
|
g_warn("zGlob=[%s] z=[%s]", zGlob, osR.z);
|
|
}
|
|
result = 0==sqlite3_strglob((char const *)zGlob,
|
|
(char const *)osR.z);
|
|
if( cmpp_tt_OpNotGlob==arg->ttype ){
|
|
result = !result;
|
|
}
|
|
//g_warn("\nzGlob=%s\nz=%s\nresult=%d", zGlob, z, result);
|
|
}
|
|
pNext = pNext->next;
|
|
break;
|
|
}
|
|
|
|
#define E(NAME) case cmpp_tt_OpCmp ## NAME:
|
|
cmpp_argOps_cmp_map(E) {
|
|
cmpp_argOp const * const prevOp = pPrev ? argOp(pPrev) : 0;
|
|
if( prevOp ){
|
|
/* Chained operators */
|
|
cmpp_argOp_applyTo(pp, thisOp, result, &pNext, &result);
|
|
}else{
|
|
assert( pNext );
|
|
assert( pPrev );
|
|
assert( thisOp );
|
|
assert( thisOp->xCall );
|
|
thisOp->xCall(pp, thisOp, pPrev, &pNext, &result);
|
|
}
|
|
break;
|
|
}
|
|
#undef E
|
|
|
|
#define checkConsecutiveNonOps \
|
|
if( pPrev && !argOp(pPrev) ){ \
|
|
serr("Illegal consecutive non-operators: %s %s", \
|
|
pPrev->z, arg->z); \
|
|
break; \
|
|
}(void)0
|
|
|
|
case cmpp_tt_IntDec:
|
|
case cmpp_tt_StringBT:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:
|
|
checkConsecutiveNonOps;
|
|
if( !cmpp_is_int(arg->z, arg->n, &result) ){
|
|
/* This is mostly for and/or ops. glob will reach back and
|
|
grab arg->z. */
|
|
result = 0;
|
|
}
|
|
break;
|
|
case cmpp_tt_Word:
|
|
checkConsecutiveNonOps;
|
|
cmpp__get_int(pp, arg->z, arg->n, &result);
|
|
break;
|
|
case cmpp_tt_GroupParen:{
|
|
checkConsecutiveNonOps;
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(pp, &sub, &result);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
case cmpp_tt_GroupBrace:{
|
|
checkConsecutiveNonOps;
|
|
cmpp_b b = cmpp_b_empty;
|
|
if( 0==cmpp_call_str(pp, arg->z, arg->n, &b, 0) ){
|
|
cmpp_is_int(b.z, b.n, &result);
|
|
}
|
|
cmpp_b_clear(&b);
|
|
break;
|
|
}
|
|
#undef checkConsecutiveNonOps
|
|
default:
|
|
assert( arg->z );
|
|
serr("Illegal expression token %s: %s",
|
|
cmpp__tt_cstr2(arg->ttype, true), arg->z);
|
|
}/*switch(arg->ttype)*/
|
|
}/* foreach arg */
|
|
if( 0 ){
|
|
lout("END %s() result=%d\n", __func__, result);
|
|
}
|
|
--level;
|
|
if( !ppCode ){
|
|
*pResult = result;
|
|
}
|
|
cmpp_b_clear(&osL);
|
|
cmpp_b_clear(&osR);
|
|
return ppCode;
|
|
#undef lout
|
|
}
|
|
|
|
#undef argOp
|
|
#undef cmpp_argOp_decl
|
|
|
|
/**
|
|
Workaround for legacy (pre-2026-08-30) behaviour: trim
|
|
leading spaces and one leading newline on groups. We do not
|
|
really want cmpp_tizer_next() to do that so we'll account
|
|
for it here to avoid a regression causing extra output
|
|
lines for directive arguments. This was only ever
|
|
relevant for squiggly groups, as neither (...) nor
|
|
[...] were ever used in ways which affected output.
|
|
|
|
A sample case:
|
|
|
|
#define x {
|
|
blah @foo@
|
|
}
|
|
#expand x {foo -> 1}
|
|
|
|
If we don't retain the legacy behavior then such constructs start
|
|
producing a stray newline where they didn't before.
|
|
*/
|
|
void cmpp__squiggly_strip_lead(cmpp_token *tok){
|
|
if( cmpp_tt_GroupSquiggly!=tok->ttype
|
|
|| !tok->nInner ) return;
|
|
if( tok->nInner ){
|
|
unsigned char const * inz = tok->zInner;
|
|
unsigned char const * enz = tok->zInner + tok->nInner;
|
|
cmpp_skip_space(&inz, tok->z+tok->nInner);
|
|
if( inz < tok->zInner+tok->nInner ){
|
|
if( '\n'==*inz ) ++inz;
|
|
else if(inz+1<enz && '\r'==*inz && '\n'==inz[1]) inz+=2;
|
|
//g_warn("trimmed to %.*s", (int)(tok->zInner+tok->nInner - inz), inz);
|
|
}
|
|
tok->n = tok->nInner = (tok->zInner+tok->nInner - inz);
|
|
tok->z = tok->zInner = inz;
|
|
}
|
|
}
|
|
|
|
|
|
static void cmpp__t2a_ttype_set(cmpp_token const *tok,
|
|
cmpp_arg * arg){
|
|
arg->ttype = tok->ttype;
|
|
arg->token = *tok;
|
|
if( cmpp_tt_Word==arg->ttype ){
|
|
int const ttop = cmpp__tt_tok_op(tok->z, tok->n);
|
|
if( ttop ){
|
|
//g_warn("ttop %s", cmpp_tt_cstr(ttop));
|
|
arg->ttype = ttop;
|
|
}
|
|
/*else if( cmpp_is_int64(arg->z, arg->n, 0) ){
|
|
arg->ttype = cmpp_tt_IntDec;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_args_parse)(cmpp * const pp,
|
|
cmpp_args * const pArgs,
|
|
unsigned char const * const zInBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_flag32_t flags){
|
|
assert( zInBegin );
|
|
unsigned char const * const zInEnd =
|
|
zInBegin + cmpp__strlenu(zInBegin, nIn);
|
|
nIn = cmpp__strlenu(zInBegin, nIn);
|
|
cmpp_tizer tz = cmpp_tizer_empty;
|
|
cmpp_token * const tok = &tz.token;
|
|
int rc = 0;
|
|
cmpp_b * bUnesc = 0;
|
|
unsigned char * zOut = 0;
|
|
unsigned char const * zOutEnd = 0;
|
|
#if !defined(NDEBUG)
|
|
unsigned char const * zReallocCheck = 0;
|
|
#endif
|
|
cmpp_tizer_init(&tz, &pp->pimpl->err,
|
|
zInBegin, (cmpp_size_t)nIn);
|
|
|
|
if( cmpp__args_init(pp, pArgs) ) return ppCode;
|
|
if( 0 ){
|
|
g_warn("whole input = <<%.*s>>", (int)(zInEnd-zInBegin),
|
|
zInBegin);
|
|
}
|
|
cmpp_size_t const nBuffer =
|
|
/* Buffer size for our copy of the args. We need to know the
|
|
size before we start so that we can have each arg reliably
|
|
point back into this without it being reallocated during
|
|
parsing. */
|
|
(cmpp_size_t)(zInEnd - zInBegin)
|
|
/* Plus we need one final NUL and one NUL byte per argument, but
|
|
we don't yet know how many arguments we will have, so let's
|
|
estimate... */
|
|
+ ((cmpp_size_t)(zInEnd - zInBegin))/2
|
|
+ 5/*fudge room*/;
|
|
cmpp_b * const buffer = &pArgs->pimpl->argOut;
|
|
assert( !buffer->n );
|
|
if( cmpp_b_reserve3(pp, buffer, nBuffer) ){
|
|
goto end;
|
|
}
|
|
if( cmpp_args_F_UNESCAPE & flags ){
|
|
bUnesc = cmpp_b_borrow(pp);
|
|
if( !bUnesc ) goto end;
|
|
}
|
|
zOut = buffer->z;
|
|
zOutEnd = zOut + buffer->nAlloc - 1;
|
|
#if !defined(NDEBUG)
|
|
zReallocCheck = buffer->z;
|
|
#endif
|
|
if(0) g_warn("pre-parsed line: %.*s", (zInEnd - zInBegin),
|
|
zInBegin);
|
|
pArgs->arg0 = NULL;
|
|
pArgs->argc = 0;
|
|
cmpp_flag32_t const tFlags = cmpp_tizer_next_SKIP_NL
|
|
| cmpp_tizer_next_SKIP_C_COMMENTS;
|
|
for( int i = 0; (0==(rc=cmpp_tizer_next(&tz,tFlags)))
|
|
&& cmpp_tt_EOF!=tok->ttype
|
|
; ++i){
|
|
(void)i;
|
|
#if 0
|
|
if( cmpp_tt_EOC==tok->ttype
|
|
&& ';'!=*tok->z ){
|
|
//EOL
|
|
continue;
|
|
}
|
|
#endif
|
|
if( !(cmpp_args_F_NO_SQUIGGLY_HACK & flags) ){
|
|
cmpp__squiggly_strip_lead(tok);
|
|
}
|
|
cmpp_arg * const arg =
|
|
CmppArgList_append(pp, &pArgs->pimpl->argli);
|
|
if( !arg ) break;
|
|
assert( pArgs->pimpl->argli.n );
|
|
assert(zReallocCheck==buffer->z
|
|
&& "Else buffer was reallocated, invalidating other arg->z");
|
|
if( 0 ){
|
|
g_warn("tok %s=%.*s", cmpp_tt_cstr(tok->ttype), (int)(tok->n), tok->z);
|
|
if( tok->z!=tok->zInner ){
|
|
g_warn("tok => %.*s", (int)(tok->nInner), tok->zInner);
|
|
}
|
|
}
|
|
unsigned char const * zX = 0;
|
|
unsigned char const * zXEnd = 0;
|
|
if( bUnesc ){
|
|
if( cmpp_b_append4(pp, cmpp_b_reuse(bUnesc),
|
|
tok->zInner, tok->nInner) ){
|
|
break;
|
|
}
|
|
cmpp_b_unescape(bUnesc);
|
|
zX = bUnesc->z;
|
|
zXEnd = zX + bUnesc->n;
|
|
}else{
|
|
zX = tok->zInner;
|
|
zXEnd = zX + tok->nInner;
|
|
}
|
|
assert( zOut + (zXEnd-zX) < zOutEnd );
|
|
if( 0 ){
|
|
g_warn("arg %s: %.*s",
|
|
cmpp_tt_cstr(tok->ttype),
|
|
(int)(zXEnd-zX), zX);
|
|
}
|
|
memcpy(zOut, zX, (zXEnd-zX));
|
|
arg->z = zOut;
|
|
arg->n = (zXEnd-zX);
|
|
zOut += arg->n;
|
|
*zOut++ = 0;
|
|
cmpp__t2a_ttype_set(tok, arg);
|
|
assert( arg->token.z==tok->z );
|
|
if( 0 ){
|
|
g_warn("tok.ttype=%s arg ttype %s: %.*s",
|
|
cmpp_tt_cstr(tok->ttype),
|
|
cmpp_tt_cstr(arg->ttype),
|
|
(int)arg->n, arg->z);
|
|
}
|
|
assert( arg->ttype );
|
|
if( 0==(flags & cmpp_args_F_NO_PARENS)
|
|
&& cmpp_tt_GroupParen==tok->ttype ){
|
|
/* Sub-expression. We tokenize it here just to ensure that we
|
|
can, so we can fail earlier rather than later. This is why
|
|
we need a recycler for the cmpp_args buffer memory. */
|
|
cmpp_args sub = cmpp_args_empty;
|
|
cmpp_args_parse(pp, &sub, arg->z, arg->n, flags);
|
|
//g_stderr("Parsed sub-expr: %s\n", sub.buffer.z);
|
|
cmpp_args_cleanup(&sub);
|
|
if( ppCode ) break;
|
|
}
|
|
}/*foreach token*/
|
|
//g_stderr("rc=%s argc=%d\n", cmpp_rc_cstr(ppCode), pArgs->args.n);
|
|
end:
|
|
cmpp_b_return(pp, bUnesc);
|
|
cmpp_tizer_dtor(&tz);
|
|
if( 0==ppCode ){
|
|
pArgs->argc = pArgs->pimpl->argli.n;
|
|
assert( !pArgs->arg0 );
|
|
for( unsigned int i = 1; i < pArgs->argc; ++i ){
|
|
/* We have to link arg->next after the loop because
|
|
CmppArgList_append() can realloc, invalidating
|
|
the links.
|
|
|
|
2026-09-04: this is legacy stuff but many directives rely on
|
|
it.
|
|
*/
|
|
cmpp_arg * const a = &pArgs->pimpl->argli.list[i];
|
|
assert( ! a->next );
|
|
pArgs->pimpl->argli.list[i-1].next = a;
|
|
assert( a->token.z );
|
|
}
|
|
if( pArgs->argc ) pArgs->arg0 = pArgs->pimpl->argli.list;
|
|
if( zOut<zOutEnd ) *zOut = 0;
|
|
if( 0 ){
|
|
for( cmpp_arg const * a = pArgs->arg0; a; a = a->next ){
|
|
g_stderr(" got: %s %.*s\n", cmpp__tt_cstr2(a->ttype, true),
|
|
a->n, a->z);
|
|
}
|
|
}
|
|
}
|
|
assert(zReallocCheck==buffer->z
|
|
&& "Else buffer was reallocated, invalidating argN->z");
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_args_clone)(cmpp *pp, cmpp_arg const * const a0,
|
|
cmpp_args * const dest){
|
|
if( cmpp__args_init(pp, dest) || !a0 ) return ppCode;
|
|
cmpp_b * const ob = &dest->pimpl->argOut;
|
|
CmppArgList * const argli = &dest->pimpl->argli;
|
|
unsigned int i = 0;
|
|
cmpp_size_t nReserve = 0 /* arg buffer mem to preallocate */;
|
|
|
|
assert( !ob->n );
|
|
assert( !dest->arg0 );
|
|
assert( !dest->argc );
|
|
assert( !argli->n );
|
|
|
|
/* Preallocate ob->z to fit a copy of a0's args. */
|
|
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
|
|
nReserve += a->n + 1/*NUL byte*/;
|
|
}
|
|
if( cmpp_b_reserve3(pp, ob, nReserve+1)
|
|
|| CmppArgList_reserve(pp, argli, i) ){
|
|
goto end;
|
|
}
|
|
assert( argli->na>=i );
|
|
i = 0;
|
|
#ifndef NDEBUG
|
|
unsigned char const * const zReallocCheck = ob->z;
|
|
#endif
|
|
for( cmpp_arg const * a = a0; a; ++i, a = a->next ){
|
|
cmpp_arg * const aNew = &argli->list[i];
|
|
aNew->n = a->n;
|
|
aNew->z = ob->z + ob->n;
|
|
aNew->ttype = a->ttype;
|
|
aNew->token = a->token;
|
|
if( i ) argli->list[i-1].next = aNew;
|
|
assert( !a->z[a->n] && "Expecting a NUL byte there" );
|
|
cmpp_b_append4(pp, ob, a->z, a->n+1/*NUL byte*/);
|
|
if( 0 ){
|
|
g_warn("arg#%d=%s <<<%.*s>>> %s", i, cmpp_tt_cstr(a->ttype),
|
|
(int)a->n, a->z, a->z);
|
|
}
|
|
assert( zReallocCheck==ob->z
|
|
&& "This cannot fail: ob->z was pre-allocated" );
|
|
}
|
|
dest->argc = i;
|
|
dest->arg0 = i ? &argli->list[0] : 0;
|
|
end:
|
|
if( ppCode ){
|
|
cmpp__args_reuse(dest);
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_args_clone)(cmpp_dx * dx, cmpp_args *pOut){
|
|
return cmpp_args_clone(dx->pp, dx->args.arg0, pOut);
|
|
}
|
|
|
|
CMPP__EXPORT(char *,cmpp_arg_strdup)(cmpp *pp, cmpp_arg const *arg){
|
|
char * z = 0;
|
|
if( 0==ppCode ){
|
|
z = sqlite3_mprintf("%s",arg->z);
|
|
cmpp_check_oom(pp, z);
|
|
}
|
|
return z;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_arg_parse)(
|
|
cmpp * const pp, cmpp_arg *pOut,
|
|
unsigned char const **pzIn,
|
|
unsigned char const *zInEnd,
|
|
unsigned char ** pzOut,
|
|
unsigned char const * zOutEnd
|
|
){
|
|
cmpp_tizer tz = cmpp_tizer_empty;
|
|
cmpp_token * const tok = &tz.token;
|
|
cmpp_tizer_init(&tz, &pp->pimpl->err, *pzIn,
|
|
(zInEnd-*pzIn));
|
|
unsigned char * zo = *pzOut;
|
|
int rc = 0;
|
|
cmpp_flag32_t const tnFlags = cmpp_tizer_next_SKIP_NL
|
|
| cmpp_tizer_next_SKIP_C_COMMENTS;
|
|
cmpp_arg_reuse( pOut );
|
|
pOut->z = zo;
|
|
rc = cmpp_tizer_next(&tz, tnFlags);
|
|
if( rc ) return rc/*pp's err already set*/;
|
|
if( cmpp_tt_EOF==tok->ttype ){
|
|
pOut->z = zInEnd;
|
|
pOut->n = 0;
|
|
pOut->ttype = cmpp_tt_EOF;
|
|
return 0;
|
|
}
|
|
if( zo+tok->nInner>=zOutEnd-1 ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Ran out of output space (%u bytes) while "
|
|
"parsing an argument",
|
|
(unsigned)(zOutEnd-*pzOut));
|
|
}else{
|
|
cmpp__squiggly_strip_lead(tok);
|
|
pOut->n = tok->n;
|
|
memcpy(zo, tok->zInner, tok->nInner);
|
|
zo += tok->nInner;
|
|
*zo++ = 0;
|
|
*pzOut = zo;
|
|
*pzIn = tz.zPos;
|
|
cmpp__t2a_ttype_set(tok, pOut);
|
|
if(0){
|
|
g_stderr("parse1: %s n=%u <<%.*s>>",
|
|
cmpp__tt_cstr2(pOut->ttype, true), pOut->n,
|
|
pOut->n, pOut->z);
|
|
}
|
|
}
|
|
#undef out
|
|
#undef eot_break
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__arg_toBool(cmpp * const pp, cmpp_arg const *arg,
|
|
int * pResult, cmpp_arg const **pNext){
|
|
switch( ppCode ? 0 : arg->ttype ){
|
|
case 0: break;
|
|
|
|
case cmpp_tt_Word:
|
|
*pNext = arg->next;
|
|
*pResult = cmpp__get_bool(pp, arg->z, arg->n);
|
|
break;
|
|
|
|
case cmpp_tt_IntDec:
|
|
*pNext = arg->next;
|
|
cmpp_is_int(arg->z, arg->n, pResult)/*was already validated*/;
|
|
break;
|
|
|
|
case cmpp_tt_StringBT:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:{
|
|
int rc = 0;
|
|
*pNext = arg->next;
|
|
if( cmpp_is_int(arg->z, arg->n, &rc) ){
|
|
*pResult = rc;
|
|
}else{
|
|
*pResult = arg->n>0 && 0!=memcmp("0\0", arg->z, 2);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case cmpp_tt_GroupParen:{
|
|
*pNext = arg->next;
|
|
cmpp_args sub = cmpp_args_empty;
|
|
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0) ){
|
|
cmpp__args_evalToInt(pp, &sub, pResult);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
|
|
case cmpp_tt_OpDefined:
|
|
if( !arg->next ){
|
|
serr("Missing '%s' RHS.", arg->z);
|
|
}else if( cmpp_tt_Word!=arg->next->ttype ){
|
|
serr( "Invalid '%s' RHS: %s", arg->z, arg->next->z);
|
|
}else{
|
|
cmpp_arg const * aOperand = arg->next;
|
|
*pNext = aOperand->next;
|
|
if( aOperand->n>1
|
|
&& '#'==aOperand->z[0]
|
|
&& !!cmpp__d_search3(pp, (char const*)aOperand->z+1,
|
|
cmpp__d_search3_F_NO_DLL) ){
|
|
*pResult = 1;
|
|
}else{
|
|
*pResult = cmpp__has(pp, (char const *)aOperand->z,
|
|
aOperand->n);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case cmpp_tt_OpNot:{
|
|
assert( arg->next && "See cmpp_args__not_simplify()");
|
|
assert( cmpp_tt_OpNot!=arg->next->ttype && "See cmpp_args__not_simplify()");
|
|
if( 0==cmpp__arg_toBool(pp, arg->next, pResult, pNext) ){
|
|
*pResult = !*pResult;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
serr("Invalid token type %s for %s(): %s",
|
|
cmpp__tt_cstr2(arg->ttype, true), __func__, arg->z);
|
|
break;
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_arg_interpolate)(cmpp * const pp, cmpp_arg const *arg,
|
|
cmpp_b * ob, cmpp_flag32_t flags){
|
|
/**
|
|
Reminder to self: this function specifically does not do any
|
|
expression evaluation of its arguments. Please avoid the
|
|
temptation to make it do so. Unless it proves necessary. Or
|
|
useful. Even then, though, consider the implications deeply
|
|
before doing so.
|
|
|
|
2026-09-21: The above still currently applies but we're going to
|
|
want expressions if/when the v1 expression cruft is replaced with
|
|
v2.
|
|
*/
|
|
if( !ob ){
|
|
if( arg->interp ){
|
|
ob = cmpp_b_reuse(arg->interp);
|
|
}else{
|
|
return cmpp_errf(pp, CMPP_RC_MISUSE,
|
|
"%s() only accepts a NULL cmpp_b if arg->interp "
|
|
"is not NULL.", __func__);
|
|
}
|
|
}
|
|
switch( ppCode
|
|
? 0
|
|
: ((cmpp_arg_interpolate_FORCE_STRING & flags)
|
|
? cmpp_tt_shape_string : arg->ttype) ){
|
|
|
|
case 0:
|
|
break;
|
|
case cmpp_tt_Word:
|
|
if( 0==(flags & cmpp_arg_interpolate_NO_DEFINES) ){
|
|
cmpp__get_b(pp, arg->z, arg->n, ob, true);
|
|
break;
|
|
}
|
|
goto theDefault;
|
|
case cmpp_tt_GroupBrace:
|
|
if( !(cmpp_arg_interpolate_NO_BRACE_CALL & flags)
|
|
&& (cmpp_arg_interpolate_BRACE_CALL & flags) ){
|
|
cmpp_call_str(pp, arg->z, arg->n, ob, 0);
|
|
break;
|
|
}
|
|
/* fall through */
|
|
default: {
|
|
theDefault: ;
|
|
cmpp_outputer oss = cmpp_outputer_b;
|
|
oss.state = ob;//no: cmpp_b_reuse(ob); Append instead.
|
|
cmpp__out2(pp, &oss, arg->z, arg->n);
|
|
break;
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
int cmpp__bind_arg(cmpp * const pp, sqlite3_stmt * const q,
|
|
int bindNdx, cmpp_arg const * const arg){
|
|
|
|
if( 0 ){
|
|
g_warn("bind #%d %s <<%.*s>>", bindNdx,
|
|
cmpp__tt_cstr2(arg->ttype, true),
|
|
(int)arg->n, arg->z);
|
|
}
|
|
switch( arg->ttype ){
|
|
default:
|
|
case cmpp_tt_IntDec:
|
|
case cmpp_tt_StringBT:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:
|
|
cmpp_stmt_bind_textn(pp, q, bindNdx, arg->z, (int)arg->n);
|
|
break;
|
|
|
|
case cmpp_tt_Word:{
|
|
cmpp_b os = cmpp_b_empty;
|
|
if( 0==cmpp_arg_interpolate(pp, arg, &os, 0) ){
|
|
if( 0 ){
|
|
g_warn("bind #%d <<%s>> => <<%.*s>>",
|
|
bindNdx, arg->z, (int)os.n, os.z);
|
|
}
|
|
cmpp_stmt_bind_textn(pp, q, bindNdx, os.z, (int)os.n);
|
|
}
|
|
cmpp_b_clear(&os);
|
|
break;
|
|
}
|
|
|
|
case cmpp_tt_GroupParen:{
|
|
cmpp_args sub = cmpp_args_empty;
|
|
int i = 0;
|
|
if( 0==cmpp_args_parse(pp, &sub, arg->z, arg->n, 0)
|
|
&& 0==cmpp__args_evalToInt(pp, &sub, &i) ){
|
|
/* See comment above about cmpp_tt_Int. */
|
|
cmpp_stmt_bind_int_text(pp, q, bindNdx, i);
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
break;
|
|
}
|
|
|
|
case cmpp_tt_GroupBrace:{
|
|
cmpp_b b = cmpp_b_empty;
|
|
cmpp_call_str(pp, arg->z, arg->n, &b, 0);
|
|
cmpp_stmt_bind_textn(pp, q, bindNdx, b.z, b.n);
|
|
cmpp_b_clear(&b);
|
|
break;
|
|
}
|
|
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/**
|
|
If a is in li->list, return its non-const pointer from li->list
|
|
(O(1)), else return NULL.
|
|
*/
|
|
static cmpp_arg * CmppArgList_arg_nc(CmppArgList *li, cmpp_arg const * a){
|
|
if( li->na && a>=li->list && a<(li->list + li->na) ){
|
|
return li->list + (a - li->list);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
To be called only by cmpp_dx_args_parse() and only if the current
|
|
directive asked for it via cmpp_d::flags cmpp_d_F_NOT_SIMPLIFY.
|
|
|
|
Filter chains of "not" operators from pArgs, removing unnecessary
|
|
ones. Also collapse "not glob" into a single cmpp_tt_OpNotGlob argument.
|
|
Performs some basic validation as well to simplify downstream
|
|
operations. Returns p->err.code and is a no-op if that's set
|
|
before this is called.
|
|
*/
|
|
static int cmpp_args__not_simplify(cmpp * const pp, cmpp_args *pArgs){
|
|
cmpp_arg * pPrev = 0;
|
|
cmpp_arg * pNext = 0;
|
|
CmppArgList * const ali = &pArgs->pimpl->argli;
|
|
pArgs->argc = 0;
|
|
for( cmpp_arg * arg = ali->n ? &ali->list[0] : NULL;
|
|
arg && !ppCode;
|
|
pPrev=arg, arg = pNext ){
|
|
pNext = CmppArgList_arg_nc(ali, arg->next);
|
|
assert( pNext || !arg->next );
|
|
if( cmpp_tt_OpNot==arg->ttype ){
|
|
if( !pNext ){
|
|
serr("Missing '%s' RHS", arg->z);
|
|
break;
|
|
}
|
|
cmpp_argOp const * const nop = cmpp_argOp_for_tt(pNext->ttype);
|
|
if( nop && nop->arity>1 && cmpp_tt_OpGlob!=nop->ttype ){
|
|
serr("Illegal '%s' RHS: binary '%s' operator",
|
|
arg->z, pNext->z);
|
|
break;
|
|
}
|
|
int bNeg = 1;
|
|
if( '!'==*arg->z ){
|
|
/* odd number of ! == negate */
|
|
bNeg = arg->n & 1;
|
|
}
|
|
while( pNext && cmpp_tt_OpNot==pNext->ttype ){
|
|
bNeg = !bNeg;
|
|
arg->next = pNext = CmppArgList_arg_nc(ali, pNext->next);
|
|
}
|
|
if( pNext && cmpp_tt_OpGlob==pNext->ttype ){
|
|
/* Transform it to a cmpp_tt_OpNotGlob or cmpp_tt_OpGlob. */
|
|
assert( pNext->z > arg->z + arg->n );
|
|
arg->n = pNext->z + pNext->n - arg->z;
|
|
arg->next = pNext->next;
|
|
arg->ttype = bNeg
|
|
? cmpp_tt_OpNotGlob
|
|
: pNext->ttype;
|
|
++pArgs->argc;
|
|
}else if( pPrev ){
|
|
if( bNeg ){
|
|
++pArgs->argc;
|
|
}else{
|
|
/* Snip this node out. */
|
|
pPrev->next = pNext;
|
|
}
|
|
}else{
|
|
assert( 0==pArgs->argc );
|
|
++pArgs->argc;
|
|
if( !bNeg ){
|
|
arg->ttype = cmpp_tt_Noop;
|
|
}
|
|
}
|
|
/* Potential bug in waiting/fixme: by eliding all nots we are
|
|
** changing the behavior from forced coercion to bool to
|
|
** coercion to whatever the LHS wants. */
|
|
}else{
|
|
++pArgs->argc;
|
|
}
|
|
}
|
|
pArgs->arg0 = pArgs->argc ? &ali->list[0] : NULL;
|
|
return ppCode;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_dx_args_parse)(cmpp_dx *dx, cmpp_args *args){
|
|
if( !dxppCode ){
|
|
cmpp_flag32_t const flags = cmpp_args_F_NO_PARENS
|
|
| ((cmpp_f_F_NO_SQUIGGLY_HACK & dx->d->flags)
|
|
? cmpp_args_F_NO_SQUIGGLY_HACK
|
|
: 0/* legacy compatibility */);
|
|
if( 0==cmpp_args_parse(dx->pp, args, dx->args.z,
|
|
dx->args.nz, flags)
|
|
&& (cmpp_f_F_NOT_SIMPLIFY & dx->d->flags) ){
|
|
cmpp_args__not_simplify(dx->pp, args);
|
|
}
|
|
}
|
|
return dxppCode;
|
|
}
|
|
|
|
/* Helper for cmpp_kav_each() and friends. */
|
|
static
|
|
int cmpp__each_parse_args(cmpp *pp,
|
|
cmpp_args *args,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nz,
|
|
cmpp_flag32_t flags){
|
|
if( 0==cmpp_args_parse(pp, args, zBegin, nz, cmpp_args_F_NO_PARENS) ){
|
|
if( !args->argc
|
|
&& (cmpp_kav_each_F_NOT_EMPTY & flags) ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Empty list is not permitted here.");
|
|
}
|
|
}
|
|
return ppCode;
|
|
}
|
|
|
|
/* Helper for cmpp_kav_each() and friends. */
|
|
static
|
|
int cmpp__each_paren_expr(cmpp *pp, cmpp_arg const * arg,
|
|
unsigned char * pOut, size_t nOut){
|
|
cmpp_args sub = cmpp_args_empty;
|
|
int rc = cmpp_args_parse(pp, &sub, arg->z, arg->n, 0);
|
|
if( 0==rc ){
|
|
int d = 0;
|
|
rc = cmpp__args_evalToInt(pp, &sub, &d);
|
|
if( 0==rc ){
|
|
snprintf((char *)pOut, nOut, "%d", d);
|
|
}
|
|
}
|
|
cmpp_args_cleanup(&sub);
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_kav_each)(cmpp *pp,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_kav_each_f callback,
|
|
void *callbackState,
|
|
cmpp_flag32_t flags){
|
|
if( ppCode ) return ppCode;
|
|
/* Reminder to self: we cannot reuse internal buffers here because a
|
|
callback could recurse into this or otherwise use APIs which use
|
|
those same buffers. */
|
|
cmpp_args args = cmpp_args_empty;
|
|
cmpp_b * const bKey = cmpp_b_borrow(pp);
|
|
cmpp_b * const bVal = bKey ? cmpp_b_borrow(pp) : 0;
|
|
if( !bVal ) goto end;
|
|
bool const reqArrow = 0==(cmpp_kav_each_F_NO_ARROW & flags);
|
|
unsigned char exprBuf[32] = {0};
|
|
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
|
|
cmpp_flag32_t a2bK = 0, a2bV = 0 /*cmpp_arg_interpolate() flags*/;
|
|
assert( zBegin );
|
|
|
|
if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){
|
|
goto end;
|
|
}else if( reqArrow && 0!=args.argc%3 ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Expecting a list of 3 tokens per entry: "
|
|
"KEY -> VALUE");
|
|
goto end;
|
|
}else if( !reqArrow && 0!=args.argc%2 ){
|
|
cmpp_errf(pp, CMPP_RC_RANGE,
|
|
"Expecting a list of 2 tokens per entry: "
|
|
"KEY VALUE");
|
|
goto end;
|
|
}
|
|
if( cmpp_kav_each_F_CALL_KEY & flags ){
|
|
a2bK |= cmpp_arg_interpolate_BRACE_CALL;
|
|
flags |= cmpp_kav_each_F_EXPAND_KEY;
|
|
}
|
|
if( cmpp_kav_each_F_CALL_VAL & flags ){
|
|
a2bV |= cmpp_arg_interpolate_BRACE_CALL;
|
|
flags |= cmpp_kav_each_F_EXPAND_VAL;
|
|
}
|
|
cmpp_arg const * aNext = 0;
|
|
for( cmpp_arg const * aKey = args.arg0;
|
|
!ppCode && aKey;
|
|
aKey = aNext ){
|
|
aNext = aKey->next;
|
|
cmpp_arg const * aVal = aKey->next;
|
|
if( !aVal ){
|
|
serr("Expecting %s after key '%s'.",
|
|
(reqArrow ? "->" : "a value"),
|
|
aKey->z);
|
|
break;
|
|
}
|
|
if( reqArrow ){
|
|
if( cmpp_tt_ThinArrowR!=aVal->ttype ){
|
|
serr("Expecting -> after key '%s'.", aKey->z);
|
|
break;
|
|
}
|
|
aVal = aVal->next;
|
|
if( !aVal ){
|
|
serr("Expecting a value after '%s' ->.", aKey->z);
|
|
break;
|
|
}
|
|
}
|
|
//g_warn("\nkey=[%s]\nval=[%s]", aKey->z, aVal->z);
|
|
/* Expand the key/value parts if needed... */
|
|
unsigned char const *zKey;
|
|
unsigned char const *zVal;
|
|
cmpp_size_t nKey, nVal;
|
|
if( cmpp_kav_each_F_EXPAND_KEY & flags ){
|
|
if( cmpp_arg_interpolate(pp, aKey, cmpp_b_reuse(bKey), a2bK) ){
|
|
break;
|
|
}
|
|
zKey = bKey->z;
|
|
nKey = bKey->n;
|
|
}else{
|
|
zKey = aKey->z;
|
|
nKey = aKey->n;
|
|
}
|
|
if( cmpp_tt_GroupParen==aVal->ttype
|
|
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
|
|
if( cmpp__each_paren_expr(pp, aVal, &exprBuf[0],
|
|
sizeof(exprBuf)-1) ){
|
|
break;
|
|
}
|
|
zVal = &exprBuf[0];
|
|
nVal = cmpp__strlenu(zVal, -1);
|
|
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
|
|
if( cmpp_arg_interpolate(pp, aVal, cmpp_b_reuse(bVal),
|
|
a2bV) ){
|
|
break;
|
|
}
|
|
zVal = bVal->z;
|
|
nVal = bVal->n;
|
|
}else{
|
|
zVal = aVal->z;
|
|
nVal = aVal->n;
|
|
}
|
|
aNext = aVal->next;
|
|
if( 0!=callback(pp, zKey, nKey, zVal, nVal, callbackState) ){
|
|
break;
|
|
}
|
|
}
|
|
end:
|
|
cmpp_b_return(pp, bKey);
|
|
cmpp_b_return(pp, bVal);
|
|
cmpp_args_cleanup(&args);
|
|
return ppCode;
|
|
}
|
|
|
|
#if 0
|
|
CMPP__EXPORT(int, cmpp_str_each)(cmpp *pp,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nIn,
|
|
cmpp_kav_each_f callback,
|
|
void *callbackState,
|
|
cmpp_flag32_t flags){
|
|
g_warn0("UNTESTED!");
|
|
if( ppCode ) return ppCode;
|
|
/* Reminder to self: we cannot reuse internal buffers here because a
|
|
callback could recurse into this or otherwise use APIs which use
|
|
those same buffers. */
|
|
cmpp_b ob = cmpp_b_empty;
|
|
cmpp_args args = cmpp_args_empty;
|
|
unsigned char exprBuf[32] = {0};
|
|
cmpp_size_t const nz = cmpp__strlenu(zBegin,nIn);
|
|
assert( zBegin );
|
|
|
|
if( cmpp__each_parse_args(pp, &args, zBegin, nz, flags) ){
|
|
goto cleanup;
|
|
}
|
|
cmpp_arg const * aNext = 0;
|
|
for( cmpp_arg const * arg = args.arg0;
|
|
!ppCode && arg;
|
|
arg = aNext ){
|
|
aNext = arg->next;
|
|
//g_warn("\nkey=[%s]\nval=[%s]", arg->z, aVal->z);
|
|
/* Expand the key/value parts if needed... */
|
|
unsigned char const *zVal;
|
|
cmpp_size_t nVal;
|
|
if( cmpp_tt_GroupParen==arg->ttype
|
|
&& (cmpp_kav_each_F_PARENS_EXPR & flags) ){
|
|
if( cmpp__each_paren_expr(pp, arg, &exprBuf[0],
|
|
sizeof(exprBuf)-1) ){
|
|
break;
|
|
}
|
|
zVal = &exprBuf[0];
|
|
nVal = cmpp__strlenu(zVal, -1);
|
|
}else if( cmpp_kav_each_F_EXPAND_VAL & flags ){
|
|
if( cmpp_arg_interpolate(pp, arg, cmpp_b_reuse(&ob), 0) ){
|
|
break;
|
|
}
|
|
zVal = ob.z;
|
|
nVal = ob.n;
|
|
}else{
|
|
zVal = arg->z;
|
|
nVal = arg->n;
|
|
}
|
|
if( 0!=callback(pp, arg->z, arg->n, zVal, nVal, callbackState) ){
|
|
break;
|
|
}
|
|
}
|
|
cleanup:
|
|
cmpp_b_clear(&ob);
|
|
cmpp_args_cleanup(&args);
|
|
return ppCode;
|
|
}
|
|
#endif
|
|
|
|
|
|
int cmpp_arg_consume_as_file(
|
|
cmpp_dx * dx, cmpp_arg const *arg,
|
|
cmpp_b * bNameOut, cmpp_b * content, bool preprocessIt
|
|
){
|
|
if( !arg ){
|
|
return cmpp_dx_errf(dx, CMPP_RC_MISUSE,
|
|
"Expecting filename argument.");
|
|
}
|
|
cmpp_b * const bFile = cmpp_b_borrow(dx->pp);
|
|
if( !bFile ) return dxppCode;
|
|
else if( cmpp_arg_interpolate(dx->pp, arg, bFile,
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
if( !bFile->n || !bFile->z[0] ){
|
|
cmpp_dx_errf(dx, CMPP_RC_MISUSE, "Empty filename is not permitted.");
|
|
goto end;
|
|
}
|
|
cmpp_FILE * const fp =
|
|
cmpp_fopen((char const*)bFile->z, "r")
|
|
/* Resolving as -Idir does would arguably be a bug. */;
|
|
if( fp ){
|
|
cmpp_outputer out = cmpp_outputer_b;
|
|
int rc;
|
|
out.state = cmpp_b_reuse(content);
|
|
if( preprocessIt ){
|
|
cmpp_outputer oOld = {0};
|
|
cmpp__outputer_swap(dx->pp, &out, &oOld);
|
|
rc = cmpp_process_stream(dx->pp, (char const *)bFile->z,
|
|
cmpp_input_f_FILE, fp);
|
|
cmpp__outputer_swap(dx->pp, &oOld, NULL);
|
|
}else{
|
|
rc = cmpp_stream(cmpp_input_f_FILE, fp, out.out, out.state);
|
|
}
|
|
if( rc && !cmpp_err_has(dx->pp) ){
|
|
/* Propagate error. */
|
|
cmpp_dx_errf(dx, rc, "Unknown error streaming file [%s].",
|
|
bFile->z);
|
|
}
|
|
cmpp_fclose(fp);
|
|
}else{
|
|
cmpp_dx_errf(dx, cmpp_errno_rc(errno, CMPP_RC_IO),
|
|
"Unknown error opening file %s.", bFile->z);
|
|
}
|
|
end:
|
|
if( bNameOut && bFile && !dxppCode ){
|
|
cmpp_b_swap(bFile, bNameOut);
|
|
}
|
|
cmpp_b_return(dx->pp, bFile);
|
|
return dxppCode;
|
|
}
|
|
|
|
|
|
cmpp__ListType_impl(CmppArgList,cmpp_arg)
|
|
|
|
|
|
#if CMPP_ENABLE_DLLS && !defined(CMPP_OMIT_D_MODULE)
|
|
# define CMPP_D_MODULE 1
|
|
#else
|
|
# define CMPP_D_MODULE 0
|
|
#endif
|
|
|
|
#if CMPP_D_MODULE
|
|
/**
|
|
The #module directive:
|
|
|
|
#module dll ?moduleName?
|
|
|
|
Uses cmpp_module_load(dx, dll, moduleName||NULL) to try to load a
|
|
directive module.
|
|
*/
|
|
CMPP_PRIVATE void cmpp_f_module(cmpp_f_args const * args){
|
|
cmpp_arg const * aName = 0;
|
|
cmpp_arg const * aDll = 0;
|
|
cmpp_b * const bDll = cmpp_b_borrow(args->pp);
|
|
cmpp_b * bName = 0;
|
|
if( !bDll ) goto end;
|
|
for( unsigned i = 1; i < args->argc; ++i ){
|
|
cmpp_arg const * const arg = args->argv[i];
|
|
//MARKER(("arg %s=%s\n", cmpp_tt_cstr(arg->ttype), arg->z));
|
|
if( !aDll ){
|
|
aDll = arg;
|
|
continue;
|
|
}
|
|
if( !aName ){
|
|
aName = arg;
|
|
bName = cmpp_b_borrow(args->pp);
|
|
if( !bName ) goto end;
|
|
continue;
|
|
}
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Unhandled argument: %s", arg->z);
|
|
goto end;
|
|
}
|
|
if( !aDll ){
|
|
cmpp_errf(args->pp, CMPP_RC_MISUSE,
|
|
"Expecting (dllName ?directiveName?) arguments.");
|
|
goto end;
|
|
}
|
|
if( cmpp_arg_interpolate(args->pp, aDll, bDll,
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
if( bName && cmpp_arg_interpolate(args->pp, aName, bName,
|
|
cmpp_arg_interpolate_BRACE_CALL) ){
|
|
goto end;
|
|
}
|
|
if( !bDll->n ){
|
|
cmpp_errf(args->pp, CMPP_RC_RANGE,
|
|
"Empty DLL name is not permitted.");
|
|
goto end;
|
|
}
|
|
cmpp_module_load(args->pp, (char const *)bDll->z,
|
|
(bName && bName->n)
|
|
? (char const *)bName->z
|
|
: NULL);
|
|
end:
|
|
cmpp_b_return(args->pp, bDll);
|
|
cmpp_b_return(args->pp, bName);
|
|
return;
|
|
}
|
|
#endif /* #module */
|
|
|
|
/**
|
|
Module loader pedantic licensing note: Most of cmpp's
|
|
module-loading code was copied verbatim from another project[^1],
|
|
but was written by the same author who relicenses it in
|
|
cmpp.
|
|
|
|
[^1]: https://fossil.wanderinghorse.net/r/cwal
|
|
*/
|
|
#if CMPP_ENABLE_DLLS
|
|
#if CMPP_HAVE_DLOPEN
|
|
typedef void * cmpp_soh;
|
|
# include <dlfcn.h> /* this actually has a different name on some platforms! */
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
# include <ltdl.h>
|
|
typedef lt_dlhandle cmpp_soh;
|
|
#elif CMPP_ENABLE_DLLS
|
|
# error "We have no dlopen() impl for this configuration."
|
|
#endif
|
|
|
|
static cmpp_soh cmpp__dlopen(char const * fname,
|
|
char const **errMsg){
|
|
static int once = 0;
|
|
cmpp_soh soh = 0;
|
|
if(!once && ++once){
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlopen( 0, RTLD_NOW | RTLD_GLOBAL );
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlinit();
|
|
lt_dlopen( 0 );
|
|
#endif
|
|
}
|
|
#if CMPP_HAVE_DLOPEN
|
|
soh = dlopen(fname, RTLD_NOW | RTLD_GLOBAL);
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
soh = lt_dlopen(fname);
|
|
#endif
|
|
if(!soh && errMsg){
|
|
#if CMPP_HAVE_DLOPEN
|
|
*errMsg = dlerror();
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
*errMsg = lt_dlerror();
|
|
#endif
|
|
}
|
|
return soh;
|
|
}
|
|
|
|
static
|
|
cmpp_module const * cmpp__dlsym(cmpp_soh soh,
|
|
char const * mname){
|
|
cmpp_module const ** sym =
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlsym(soh, mname)
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlsym(soh, mname)
|
|
#else
|
|
NULL
|
|
#endif
|
|
;
|
|
return sym ? *sym : NULL;
|
|
}
|
|
|
|
static void cmpp__dlclose(cmpp_soh soh){
|
|
if( soh ) {
|
|
#if CMPP_CLOSE_DLLS
|
|
/* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */
|
|
#if CMPP_HAVE_DLOPEN
|
|
dlclose(soh);
|
|
#elif CMPP_HAVE_LTDLOPEN
|
|
lt_dlclose(soh);
|
|
#endif
|
|
#endif
|
|
}
|
|
}
|
|
#endif /* CMPP_ENABLE_DLLS */
|
|
|
|
#define CmppSohList_works (CMPP_ENABLE_DLLS && CMPP_CLOSE_DLLS)
|
|
|
|
#if CmppSohList_works
|
|
int CmppSohList_append(cmpp *pp, CmppSohList *soli, void *soh){
|
|
int const rc = cmpp_array_reserve(pp, (void**)&soli->list,
|
|
soli->n
|
|
? (soli->n==soli->na
|
|
? soli->na*3/2
|
|
: soli->n+1)
|
|
: 8,
|
|
&soli->na, sizeof(void*));
|
|
if( 0==rc ){
|
|
soli->list[soli->n++] = soh;
|
|
}
|
|
return rc;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if 0
|
|
/**
|
|
Passes soli to CmppSohList_close() then frees soli. Results are
|
|
undefined if soli is not NULL but was not returned from
|
|
CmppSohList_new().
|
|
|
|
Special case: if built without DLL-closing support, this is a no-op.
|
|
*/
|
|
//static void CmppSohList_free(CmppSohList *soli);
|
|
void CmppSohList_free(CmppSohList *s){
|
|
if( s ){
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns a new, cleanly-initialized CmppSohList or NULL
|
|
on allocation error. The returned instance must eventually be
|
|
passed to CmppSohList_free().
|
|
|
|
Special case: if built without DLL-closing support, this returns a
|
|
no-op singleton instance.
|
|
*/
|
|
//static CmppSohList * CmppSohList_new(void);
|
|
CmppSohList * CmppSohList_new(void){
|
|
#if CmppSohList_works
|
|
CmppSohList * s = cmpp_malloc(sizeof(*s));
|
|
if( s ) *s = CmppSohList_empty;
|
|
return s;
|
|
#else
|
|
static CmppSohList soli = CmppSohList_empty;
|
|
return &soli;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#undef CmppSohList_works
|
|
|
|
|
|
|
|
#if !CMPP_ENABLE_DLLS
|
|
static int cmpp__err_no_dlls(cmpp * const pp){
|
|
return cmpp_errf(pp, CMPP_RC_UNSUPPORTED,
|
|
"No dlopen() equivalent is installed "
|
|
"for this build configuration.");
|
|
}
|
|
#endif
|
|
|
|
|
|
//no: CMPP_WASM_EXPORT
|
|
int cmpp_module_load(cmpp * pp, char const * fname,
|
|
char const * symName){
|
|
(void)fname; (void)symName;
|
|
return cmpp__err_no_dlls(pp);
|
|
}
|
|
/*
|
|
** 2015-08-18, 2023-04-28
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** May you do good and not evil.
|
|
** May you find forgiveness for yourself and forgive others.
|
|
** May you share freely, never taking more than you give.
|
|
**
|
|
*************************************************************************
|
|
**
|
|
** This file demonstrates how to create a table-valued-function using
|
|
** a virtual table. This demo implements the generate_series() function
|
|
** which gives the same results as the eponymous function in PostgreSQL,
|
|
** within the limitation that its arguments are signed 64-bit integers.
|
|
**
|
|
** Considering its equivalents to generate_series(start,stop,step): A
|
|
** value V[n] sequence is produced for integer n ascending from 0 where
|
|
** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 )
|
|
** for each produced value (independent of production time ordering.)
|
|
**
|
|
** All parameters must be either integer or convertable to integer.
|
|
** The start parameter is required.
|
|
** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff)
|
|
** The step parameter defaults to 1 and 0 is treated as 1.
|
|
**
|
|
** Examples:
|
|
**
|
|
** SELECT * FROM generate_series(0,100,5);
|
|
**
|
|
** The query above returns integers from 0 through 100 counting by steps
|
|
** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total
|
|
** of 21 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,100);
|
|
**
|
|
** Integers from 0 through 100 with a step size of 1. 101 rows.
|
|
**
|
|
** SELECT * FROM generate_series(20) LIMIT 10;
|
|
**
|
|
** Integers 20 through 29. 10 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,-100,-5);
|
|
**
|
|
** Integers 0 -5 -10 ... -100. 21 rows.
|
|
**
|
|
** SELECT * FROM generate_series(0,-1);
|
|
**
|
|
** Empty sequence.
|
|
**
|
|
** HOW IT WORKS
|
|
**
|
|
** The generate_series "function" is really a virtual table with the
|
|
** following schema:
|
|
**
|
|
** CREATE TABLE generate_series(
|
|
** value,
|
|
** start HIDDEN,
|
|
** stop HIDDEN,
|
|
** step HIDDEN
|
|
** );
|
|
**
|
|
** The virtual table also has a rowid which is an alias for the value.
|
|
**
|
|
** Function arguments in queries against this virtual table are translated
|
|
** into equality constraints against successive hidden columns. In other
|
|
** words, the following pairs of queries are equivalent to each other:
|
|
**
|
|
** SELECT * FROM generate_series(0,100,5);
|
|
** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5;
|
|
**
|
|
** SELECT * FROM generate_series(0,100);
|
|
** SELECT * FROM generate_series WHERE start=0 AND stop=100;
|
|
**
|
|
** SELECT * FROM generate_series(20) LIMIT 10;
|
|
** SELECT * FROM generate_series WHERE start=20 LIMIT 10;
|
|
**
|
|
** The generate_series virtual table implementation leaves the xCreate method
|
|
** set to NULL. This means that it is not possible to do a CREATE VIRTUAL
|
|
** TABLE command with "generate_series" as the USING argument. Instead, there
|
|
** is a single generate_series virtual table that is always available without
|
|
** having to be created first.
|
|
**
|
|
** The xBestIndex method looks for equality constraints against the hidden
|
|
** start, stop, and step columns, and if present, it uses those constraints
|
|
** to bound the sequence of generated values. If the equality constraints
|
|
** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step.
|
|
** xBestIndex returns a small cost when both start and stop are available,
|
|
** and a very large cost if either start or stop are unavailable. This
|
|
** encourages the query planner to order joins such that the bounds of the
|
|
** series are well-defined.
|
|
**
|
|
** Update on 2024-08-22:
|
|
** xBestIndex now also looks for equality and inequality constraints against
|
|
** the value column and uses those constraints as additional bounds against
|
|
** the sequence range. Thus, a query like this:
|
|
**
|
|
** SELECT value FROM generate_series($SA,$EA)
|
|
** WHERE value BETWEEN $SB AND $EB;
|
|
**
|
|
** Is logically the same as:
|
|
**
|
|
** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB));
|
|
**
|
|
** Constraints on the value column can server as substitutes for constraints
|
|
** on the hidden start and stop columns. So, the following two queries
|
|
** are equivalent:
|
|
**
|
|
** SELECT value FROM generate_series($S,$E);
|
|
** SELECT value FROM generate_series WHERE value BETWEEN $S and $E;
|
|
**
|
|
*/
|
|
#if 0
|
|
#include "sqlite3ext.h"
|
|
#else
|
|
#include "sqlite3.h"
|
|
#endif
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
|
|
/* series_cursor is a subclass of sqlite3_vtab_cursor which will
|
|
** serve as the underlying representation of a cursor that scans
|
|
** over rows of the result.
|
|
**
|
|
** iOBase, iOTerm, and iOStep are the original values of the
|
|
** start=, stop=, and step= constraints on the query. These are
|
|
** the values reported by the start, stop, and step columns of the
|
|
** virtual table.
|
|
**
|
|
** iBase, iTerm, iStep, and bDescp are the actual values used to generate
|
|
** the sequence. These might be different from the iOxxxx values.
|
|
** For example in
|
|
**
|
|
** SELECT value FROM generate_series(1,11,2)
|
|
** WHERE value BETWEEN 4 AND 8;
|
|
**
|
|
** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7.
|
|
** Another example:
|
|
**
|
|
** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC;
|
|
**
|
|
** The cursor initialization for the above query is:
|
|
**
|
|
** iOBase = 1 iBase = 13
|
|
** iOTerm = 15 iTerm = 1
|
|
** iOStep = 3 iStep = 3 bDesc = 1
|
|
**
|
|
** The actual step size is unsigned so that can have a value of
|
|
** +9223372036854775808 which is needed for querys like this:
|
|
**
|
|
** SELECT value
|
|
** FROM generate_series(9223372036854775807,
|
|
** -9223372036854775808,
|
|
** -9223372036854775808)
|
|
** ORDER BY value ASC;
|
|
**
|
|
** The setup for the previous query will be:
|
|
**
|
|
** iOBase = 9223372036854775807 iBase = -1
|
|
** iOTerm = -9223372036854775808 iTerm = 9223372036854775807
|
|
** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0
|
|
*/
|
|
typedef unsigned char u8;
|
|
typedef struct series_cursor series_cursor;
|
|
struct series_cursor {
|
|
sqlite3_vtab_cursor base; /* Base class - must be first */
|
|
sqlite3_int64 iOBase; /* Original starting value ("start") */
|
|
sqlite3_int64 iOTerm; /* Original terminal value ("stop") */
|
|
sqlite3_int64 iOStep; /* Original step value */
|
|
sqlite3_int64 iBase; /* Starting value to actually use */
|
|
sqlite3_int64 iTerm; /* Terminal value to actually use */
|
|
sqlite3_uint64 iStep; /* The step size */
|
|
sqlite3_int64 iValue; /* Current value */
|
|
u8 bDesc; /* iStep is really negative */
|
|
u8 bDone; /* True if stepped past last element */
|
|
};
|
|
|
|
/*
|
|
** Computed the difference between two 64-bit signed integers using a
|
|
** convoluted computation designed to work around the silly restriction
|
|
** against signed integer overflow in C.
|
|
*/
|
|
static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){
|
|
assert( a>=b );
|
|
return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b);
|
|
}
|
|
|
|
/*
|
|
** Add or substract an unsigned 64-bit integer from a signed 64-bit integer
|
|
** and return the new signed 64-bit integer.
|
|
*/
|
|
static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){
|
|
sqlite3_uint64 x = *(sqlite3_uint64*)&a;
|
|
x += b;
|
|
return *(sqlite3_int64*)&x;
|
|
}
|
|
static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){
|
|
sqlite3_uint64 x = *(sqlite3_uint64*)&a;
|
|
x -= b;
|
|
return *(sqlite3_int64*)&x;
|
|
}
|
|
|
|
/*
|
|
** The seriesConnect() method is invoked to create a new
|
|
** series_vtab that describes the generate_series virtual table.
|
|
**
|
|
** Think of this routine as the constructor for series_vtab objects.
|
|
**
|
|
** All this routine needs to do is:
|
|
**
|
|
** (1) Allocate the series_vtab object and initialize all fields.
|
|
**
|
|
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
|
|
** result set of queries against generate_series will look like.
|
|
*/
|
|
static int seriesConnect(
|
|
sqlite3 *db,
|
|
void *pUnused,
|
|
int argcUnused, const char *const*argvUnused,
|
|
sqlite3_vtab **ppVtab,
|
|
char **pzErrUnused
|
|
){
|
|
sqlite3_vtab *pNew;
|
|
int rc;
|
|
|
|
/* Column numbers */
|
|
#define SERIES_COLUMN_ROWID (-1)
|
|
#define SERIES_COLUMN_VALUE 0
|
|
#define SERIES_COLUMN_START 1
|
|
#define SERIES_COLUMN_STOP 2
|
|
#define SERIES_COLUMN_STEP 3
|
|
|
|
(void)pUnused;
|
|
(void)argcUnused;
|
|
(void)argvUnused;
|
|
(void)pzErrUnused;
|
|
rc = sqlite3_declare_vtab(db,
|
|
"CREATE TABLE x(value,start hidden,stop hidden,step hidden)");
|
|
if( rc==SQLITE_OK ){
|
|
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
|
if( pNew==0 ) return SQLITE_NOMEM;
|
|
memset(pNew, 0, sizeof(*pNew));
|
|
sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** This method is the destructor for series_cursor objects.
|
|
*/
|
|
static int seriesDisconnect(sqlite3_vtab *pVtab){
|
|
sqlite3_free(pVtab);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Constructor for a new series_cursor object.
|
|
*/
|
|
static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){
|
|
series_cursor *pCur;
|
|
(void)pUnused;
|
|
pCur = sqlite3_malloc( sizeof(*pCur) );
|
|
if( pCur==0 ) return SQLITE_NOMEM;
|
|
memset(pCur, 0, sizeof(*pCur));
|
|
*ppCursor = &pCur->base;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Destructor for a series_cursor.
|
|
*/
|
|
static int seriesClose(sqlite3_vtab_cursor *cur){
|
|
sqlite3_free(cur);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
** Advance a series_cursor to its next row of output.
|
|
*/
|
|
static int seriesNext(sqlite3_vtab_cursor *cur){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
if( pCur->iValue==pCur->iTerm ){
|
|
pCur->bDone = 1;
|
|
}else if( pCur->bDesc ){
|
|
pCur->iValue = sub64(pCur->iValue, pCur->iStep);
|
|
assert( pCur->iValue>=pCur->iTerm );
|
|
}else{
|
|
pCur->iValue = add64(pCur->iValue, pCur->iStep);
|
|
assert( pCur->iValue<=pCur->iTerm );
|
|
}
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return values of columns for the row at which the series_cursor
|
|
** is currently pointing.
|
|
*/
|
|
static int seriesColumn(
|
|
sqlite3_vtab_cursor *cur, /* The cursor */
|
|
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
|
int i /* Which column to return */
|
|
){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
sqlite3_int64 x = 0;
|
|
switch( i ){
|
|
case SERIES_COLUMN_START: x = pCur->iOBase; break;
|
|
case SERIES_COLUMN_STOP: x = pCur->iOTerm; break;
|
|
case SERIES_COLUMN_STEP: x = pCur->iOStep; break;
|
|
default: x = pCur->iValue; break;
|
|
}
|
|
sqlite3_result_int64(ctx, x);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
#ifndef LARGEST_UINT64
|
|
#define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL)
|
|
#define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL)
|
|
#define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL)
|
|
#endif
|
|
|
|
/*
|
|
** The rowid is the same as the value.
|
|
*/
|
|
static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
*pRowid = pCur->iValue;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return TRUE if the cursor has been moved off of the last
|
|
** row of output.
|
|
*/
|
|
static int seriesEof(sqlite3_vtab_cursor *cur){
|
|
series_cursor *pCur = (series_cursor*)cur;
|
|
return pCur->bDone;
|
|
}
|
|
|
|
/* True to cause run-time checking of the start=, stop=, and/or step=
|
|
** parameters. The only reason to do this is for testing the
|
|
** constraint checking logic for virtual tables in the SQLite core.
|
|
*/
|
|
#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY
|
|
# define SQLITE_SERIES_CONSTRAINT_VERIFY 0
|
|
#endif
|
|
|
|
/*
|
|
** Return the number of steps between pCur->iBase and pCur->iTerm if
|
|
** the step width is pCur->iStep.
|
|
*/
|
|
static sqlite3_uint64 seriesSteps(series_cursor *pCur){
|
|
if( pCur->bDesc ){
|
|
assert( pCur->iBase >= pCur->iTerm );
|
|
return span64(pCur->iBase, pCur->iTerm)/pCur->iStep;
|
|
}else{
|
|
assert( pCur->iBase <= pCur->iTerm );
|
|
return span64(pCur->iTerm, pCur->iBase)/pCur->iStep;
|
|
}
|
|
}
|
|
|
|
#if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32)
|
|
/*
|
|
** Case 1 (the most common case):
|
|
** The standard math library is available so use ceil() and floor() from there.
|
|
*/
|
|
static double seriesCeil(double r){ return ceil(r); }
|
|
static double seriesFloor(double r){ return floor(r); }
|
|
#elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC)
|
|
/*
|
|
** Case 2 (2nd most common): Use GCC/Clang builtins
|
|
*/
|
|
static double seriesCeil(double r){ return __builtin_ceil(r); }
|
|
static double seriesFloor(double r){ return __builtin_floor(r); }
|
|
#else
|
|
/*
|
|
** Case 3 (rarely happens): Use home-grown ceil() and floor() routines.
|
|
*/
|
|
static double seriesCeil(double r){
|
|
sqlite3_int64 x;
|
|
if( r!=r ) return r;
|
|
if( r<=(-4503599627370496.0) ) return r;
|
|
if( r>=(+4503599627370496.0) ) return r;
|
|
x = (sqlite3_int64)r;
|
|
if( r==(double)x ) return r;
|
|
if( r>(double)x ) x++;
|
|
return (double)x;
|
|
}
|
|
static double seriesFloor(double r){
|
|
sqlite3_int64 x;
|
|
if( r!=r ) return r;
|
|
if( r<=(-4503599627370496.0) ) return r;
|
|
if( r>=(+4503599627370496.0) ) return r;
|
|
x = (sqlite3_int64)r;
|
|
if( r==(double)x ) return r;
|
|
if( r<(double)x ) x--;
|
|
return (double)x;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
** This method is called to "rewind" the series_cursor object back
|
|
** to the first row of output. This method is always called at least
|
|
** once prior to any call to seriesColumn() or seriesRowid() or
|
|
** seriesEof().
|
|
**
|
|
** The query plan selected by seriesBestIndex is passed in the idxNum
|
|
** parameter. (idxStr is not used in this implementation.) idxNum
|
|
** is a bitmask showing which constraints are available:
|
|
**
|
|
** 0x0001: start=VALUE
|
|
** 0x0002: stop=VALUE
|
|
** 0x0004: step=VALUE
|
|
** 0x0008: descending order
|
|
** 0x0010: ascending order
|
|
** 0x0020: LIMIT VALUE
|
|
** 0x0040: OFFSET VALUE
|
|
** 0x0080: value=VALUE
|
|
** 0x0100: value>=VALUE
|
|
** 0x0200: value>VALUE
|
|
** 0x1000: value<=VALUE
|
|
** 0x2000: value<VALUE
|
|
**
|
|
** This routine should initialize the cursor and position it so that it
|
|
** is pointing at the first row, or pointing off the end of the table
|
|
** (so that seriesEof() will return true) if the table is empty.
|
|
*/
|
|
static int seriesFilter(
|
|
sqlite3_vtab_cursor *pVtabCursor,
|
|
int idxNum, const char *idxStrUnused,
|
|
int argc, sqlite3_value **argv
|
|
){
|
|
series_cursor *pCur = (series_cursor *)pVtabCursor;
|
|
int iArg = 0; /* Arguments used so far */
|
|
int i; /* Loop counter */
|
|
sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */
|
|
sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */
|
|
sqlite3_int64 iLimit = 0; /* if >0, the value of the LIMIT */
|
|
sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */
|
|
|
|
(void)idxStrUnused;
|
|
|
|
/* If any constraints have a NULL value, then return no rows.
|
|
** See ticket https://sqlite.org/src/info/fac496b61722daf2
|
|
*/
|
|
for(i=0; i<argc; i++){
|
|
if( sqlite3_value_type(argv[i])==SQLITE_NULL ){
|
|
goto series_no_rows;
|
|
}
|
|
}
|
|
|
|
/* Capture the three HIDDEN parameters to the virtual table and insert
|
|
** default values for any parameters that are omitted.
|
|
*/
|
|
if( idxNum & 0x01 ){
|
|
pCur->iOBase = sqlite3_value_int64(argv[iArg++]);
|
|
}else{
|
|
pCur->iOBase = 0;
|
|
}
|
|
if( idxNum & 0x02 ){
|
|
pCur->iOTerm = sqlite3_value_int64(argv[iArg++]);
|
|
}else{
|
|
pCur->iOTerm = 0xffffffff;
|
|
}
|
|
if( idxNum & 0x04 ){
|
|
pCur->iOStep = sqlite3_value_int64(argv[iArg++]);
|
|
if( pCur->iOStep==0 ) pCur->iOStep = 1;
|
|
}else{
|
|
pCur->iOStep = 1;
|
|
}
|
|
|
|
/* If there are constraints on the value column but there are
|
|
** no constraints on the start, stop, and step columns, then
|
|
** initialize the default range to be the entire range of 64-bit signed
|
|
** integers. This range will contracted by the value column constraints
|
|
** further below.
|
|
*/
|
|
if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){
|
|
pCur->iOBase = SMALLEST_INT64;
|
|
}
|
|
if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){
|
|
pCur->iOTerm = LARGEST_INT64;
|
|
}
|
|
pCur->iBase = pCur->iOBase;
|
|
pCur->iTerm = pCur->iOTerm;
|
|
if( pCur->iOStep>0 ){
|
|
pCur->iStep = pCur->iOStep;
|
|
}else if( pCur->iOStep>SMALLEST_INT64 ){
|
|
pCur->iStep = -pCur->iOStep;
|
|
}else{
|
|
pCur->iStep = LARGEST_INT64;
|
|
pCur->iStep++;
|
|
}
|
|
pCur->bDesc = pCur->iOStep<0;
|
|
if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
if( pCur->bDesc!=0 && pCur->iBase<pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
|
|
/* Extract the LIMIT and OFFSET values, but do not apply them yet.
|
|
** The range must first be constrained by the limits on value.
|
|
*/
|
|
if( idxNum & 0x20 ){
|
|
iLimit = sqlite3_value_int64(argv[iArg++]);
|
|
if( idxNum & 0x40 ){
|
|
iOffset = sqlite3_value_int64(argv[iArg++]);
|
|
}
|
|
}
|
|
|
|
/* Narrow the range of iMin and iMax (the minimum and maximum outputs)
|
|
** based on equality and inequality constraints on the "value" column.
|
|
*/
|
|
if( idxNum & 0x3380 ){
|
|
if( idxNum & 0x0080 ){ /* value=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r==seriesCeil(r)
|
|
&& r>=(double)SMALLEST_INT64
|
|
&& r<=(double)LARGEST_INT64
|
|
){
|
|
iMin = iMax = (sqlite3_int64)r;
|
|
}else{
|
|
goto series_no_rows;
|
|
}
|
|
}else{
|
|
iMin = iMax = sqlite3_value_int64(argv[iArg++]);
|
|
}
|
|
}else{
|
|
if( idxNum & 0x0300 ){ /* value>X or value>=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r<(double)SMALLEST_INT64 ){
|
|
iMin = SMALLEST_INT64;
|
|
}else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){
|
|
iMin = (sqlite3_int64)seriesCeil(r+1.0);
|
|
}else{
|
|
iMin = (sqlite3_int64)seriesCeil(r);
|
|
}
|
|
}else{
|
|
iMin = sqlite3_value_int64(argv[iArg++]);
|
|
if( (idxNum & 0x0200)!=0 ){
|
|
if( iMin==LARGEST_INT64 ){
|
|
goto series_no_rows;
|
|
}else{
|
|
iMin++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( idxNum & 0x3000 ){ /* value<X or value<=X */
|
|
if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){
|
|
double r = sqlite3_value_double(argv[iArg++]);
|
|
if( r>(double)LARGEST_INT64 ){
|
|
iMax = LARGEST_INT64;
|
|
}else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){
|
|
iMax = (sqlite3_int64)(r-1.0);
|
|
}else{
|
|
iMax = (sqlite3_int64)seriesFloor(r);
|
|
}
|
|
}else{
|
|
iMax = sqlite3_value_int64(argv[iArg++]);
|
|
if( idxNum & 0x2000 ){
|
|
if( iMax==SMALLEST_INT64 ){
|
|
goto series_no_rows;
|
|
}else{
|
|
iMax--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( iMin>iMax ){
|
|
goto series_no_rows;
|
|
}
|
|
}
|
|
|
|
/* Try to reduce the range of values to be generated based on
|
|
** constraints on the "value" column.
|
|
*/
|
|
if( pCur->bDesc==0 ){
|
|
if( pCur->iBase<iMin ){
|
|
sqlite3_uint64 span = span64(iMin,pCur->iBase);
|
|
pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
|
|
if( pCur->iBase<iMin ){
|
|
if( pCur->iBase > sub64(LARGEST_INT64, pCur->iStep) ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iBase = add64(pCur->iBase, pCur->iStep);
|
|
}
|
|
}
|
|
if( pCur->iTerm>iMax ){
|
|
pCur->iTerm = iMax;
|
|
}
|
|
}else{
|
|
if( pCur->iBase>iMax ){
|
|
sqlite3_uint64 span = span64(pCur->iBase,iMax);
|
|
pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep);
|
|
if( pCur->iBase>iMax ){
|
|
if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iBase = sub64(pCur->iBase, pCur->iStep);
|
|
}
|
|
}
|
|
if( pCur->iTerm<iMin ){
|
|
pCur->iTerm = iMin;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Adjust iTerm so that it is exactly the last value of the series.
|
|
*/
|
|
if( pCur->bDesc==0 ){
|
|
if( pCur->iBase>pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iTerm = sub64(pCur->iTerm,
|
|
span64(pCur->iTerm,pCur->iBase) % pCur->iStep);
|
|
}else{
|
|
if( pCur->iBase<pCur->iTerm ){
|
|
goto series_no_rows;
|
|
}
|
|
pCur->iTerm = add64(pCur->iTerm,
|
|
span64(pCur->iBase,pCur->iTerm) % pCur->iStep);
|
|
}
|
|
|
|
/* Transform the series generator to output values in the requested
|
|
** order.
|
|
*/
|
|
if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0)
|
|
|| ((idxNum & 0x0010)!=0 && pCur->bDesc!=0)
|
|
){
|
|
sqlite3_int64 tmp = pCur->iBase;
|
|
pCur->iBase = pCur->iTerm;
|
|
pCur->iTerm = tmp;
|
|
pCur->bDesc = !pCur->bDesc;
|
|
}
|
|
|
|
/* Apply LIMIT and OFFSET constraints, if any */
|
|
assert( pCur->iStep!=0 );
|
|
if( idxNum & 0x20 ){
|
|
if( iOffset>0 ){
|
|
if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){
|
|
goto series_no_rows;
|
|
}else if( pCur->bDesc ){
|
|
pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset);
|
|
}else{
|
|
pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset);
|
|
}
|
|
}
|
|
if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){
|
|
pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep);
|
|
}
|
|
}
|
|
pCur->iValue = pCur->iBase;
|
|
pCur->bDone = 0;
|
|
return SQLITE_OK;
|
|
|
|
series_no_rows:
|
|
pCur->iBase = 0;
|
|
pCur->iTerm = 0;
|
|
pCur->iStep = 1;
|
|
pCur->bDesc = 0;
|
|
pCur->bDone = 1;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** SQLite will invoke this method one or more times while planning a query
|
|
** that uses the generate_series virtual table. This routine needs to create
|
|
** a query plan for each invocation and compute an estimated cost for that
|
|
** plan.
|
|
**
|
|
** In this implementation idxNum is used to represent the
|
|
** query plan. idxStr is unused.
|
|
**
|
|
** The query plan is represented by bits in idxNum:
|
|
**
|
|
** 0x0001 start = $num
|
|
** 0x0002 stop = $num
|
|
** 0x0004 step = $num
|
|
** 0x0008 output is in descending order
|
|
** 0x0010 output is in ascending order
|
|
** 0x0020 LIMIT $num
|
|
** 0x0040 OFFSET $num
|
|
** 0x0080 value = $num
|
|
** 0x0100 value >= $num
|
|
** 0x0200 value > $num
|
|
** 0x1000 value <= $num
|
|
** 0x2000 value < $num
|
|
**
|
|
** Only one of 0x0100 or 0x0200 will be returned. Similarly, only
|
|
** one of 0x1000 or 0x2000 will be returned. If the 0x0080 is set, then
|
|
** none of the 0xff00 bits will be set.
|
|
**
|
|
** The order of parameters passed to xFilter is as follows:
|
|
**
|
|
** * The argument to start= if bit 0x0001 is in the idxNum mask
|
|
** * The argument to stop= if bit 0x0002 is in the idxNum mask
|
|
** * The argument to step= if bit 0x0004 is in the idxNum mask
|
|
** * The argument to LIMIT if bit 0x0020 is in the idxNum mask
|
|
** * The argument to OFFSET if bit 0x0040 is in the idxNum mask
|
|
** * The argument to value=, or value>= or value> if any of
|
|
** bits 0x0380 are in the idxNum mask
|
|
** * The argument to value<= or value< if either of bits 0x3000
|
|
** are in the mask
|
|
**
|
|
*/
|
|
static int seriesBestIndex(
|
|
sqlite3_vtab *pVTab,
|
|
sqlite3_index_info *pIdxInfo
|
|
){
|
|
int i, j; /* Loop over constraints */
|
|
int idxNum = 0; /* The query plan bitmask */
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
int bStartSeen = 0; /* EQ constraint seen on the START column */
|
|
#endif
|
|
int unusableMask = 0; /* Mask of unusable constraints */
|
|
int nArg = 0; /* Number of arguments that seriesFilter() expects */
|
|
int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET,
|
|
** and value. aIdx[5] covers value=, value>=, and
|
|
** value>, aIdx[6] covers value<= and value< */
|
|
const struct sqlite3_index_constraint *pConstraint;
|
|
|
|
/* This implementation assumes that the start, stop, and step columns
|
|
** are the last three columns in the virtual table. */
|
|
assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 );
|
|
assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 );
|
|
|
|
aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1;
|
|
pConstraint = pIdxInfo->aConstraint;
|
|
for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){
|
|
int iCol; /* 0 for start, 1 for stop, 2 for step */
|
|
int iMask; /* bitmask for those column */
|
|
int op = pConstraint->op;
|
|
if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT
|
|
&& op<=SQLITE_INDEX_CONSTRAINT_OFFSET
|
|
){
|
|
if( pConstraint->usable==0 ){
|
|
/* do nothing */
|
|
}else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){
|
|
aIdx[3] = i;
|
|
idxNum |= 0x20;
|
|
}else{
|
|
assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET );
|
|
aIdx[4] = i;
|
|
idxNum |= 0x40;
|
|
}
|
|
continue;
|
|
}
|
|
if( pConstraint->iColumn<SERIES_COLUMN_START ){
|
|
if( (pConstraint->iColumn==SERIES_COLUMN_VALUE ||
|
|
pConstraint->iColumn==SERIES_COLUMN_ROWID)
|
|
&& pConstraint->usable
|
|
){
|
|
switch( op ){
|
|
case SQLITE_INDEX_CONSTRAINT_EQ:
|
|
case SQLITE_INDEX_CONSTRAINT_IS: {
|
|
idxNum |= 0x0080;
|
|
idxNum &= ~0x3300;
|
|
aIdx[5] = i;
|
|
aIdx[6] = -1;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_GE: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x0100;
|
|
idxNum &= ~0x0200;
|
|
aIdx[5] = i;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_GT: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x0200;
|
|
idxNum &= ~0x0100;
|
|
aIdx[5] = i;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
bStartSeen = 1;
|
|
#endif
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_LE: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x1000;
|
|
idxNum &= ~0x2000;
|
|
aIdx[6] = i;
|
|
break;
|
|
}
|
|
case SQLITE_INDEX_CONSTRAINT_LT: {
|
|
if( idxNum & 0x0080 ) break;
|
|
idxNum |= 0x2000;
|
|
idxNum &= ~0x1000;
|
|
aIdx[6] = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
iCol = pConstraint->iColumn - SERIES_COLUMN_START;
|
|
assert( iCol>=0 && iCol<=2 );
|
|
iMask = 1 << iCol;
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
|
bStartSeen = 1;
|
|
}
|
|
#endif
|
|
if( pConstraint->usable==0 ){
|
|
unusableMask |= iMask;
|
|
continue;
|
|
}else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){
|
|
idxNum |= iMask;
|
|
aIdx[iCol] = i;
|
|
}
|
|
}
|
|
if( aIdx[3]==0 ){
|
|
/* Ignore OFFSET if LIMIT is omitted */
|
|
idxNum &= ~0x60;
|
|
aIdx[4] = 0;
|
|
}
|
|
for(i=0; i<7; i++){
|
|
if( (j = aIdx[i])>=0 ){
|
|
pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg;
|
|
pIdxInfo->aConstraintUsage[j].omit =
|
|
!SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3;
|
|
}
|
|
}
|
|
/* The current generate_column() implementation requires at least one
|
|
** argument (the START value). Legacy versions assumed START=0 if the
|
|
** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES
|
|
** to obtain the legacy behavior */
|
|
#ifndef ZERO_ARGUMENT_GENERATE_SERIES
|
|
if( !bStartSeen ){
|
|
sqlite3_free(pVTab->zErrMsg);
|
|
pVTab->zErrMsg = sqlite3_mprintf(
|
|
"first argument to \"generate_series()\" missing or unusable");
|
|
return SQLITE_ERROR;
|
|
}
|
|
#endif
|
|
if( (unusableMask & ~idxNum)!=0 ){
|
|
/* The start, stop, and step columns are inputs. Therefore if there
|
|
** are unusable constraints on any of start, stop, or step then
|
|
** this plan is unusable */
|
|
return SQLITE_CONSTRAINT;
|
|
}
|
|
if( (idxNum & 0x03)==0x03 ){
|
|
/* Both start= and stop= boundaries are available. This is the
|
|
** the preferred case */
|
|
pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0));
|
|
pIdxInfo->estimatedRows = 1000;
|
|
if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){
|
|
if( pIdxInfo->aOrderBy[0].desc ){
|
|
idxNum |= 0x08;
|
|
}else{
|
|
idxNum |= 0x10;
|
|
}
|
|
pIdxInfo->orderByConsumed = 1;
|
|
}
|
|
}else if( (idxNum & 0x21)==0x21 ){
|
|
/* We have start= and LIMIT */
|
|
pIdxInfo->estimatedRows = 2500;
|
|
}else{
|
|
/* If either boundary is missing, we have to generate a huge span
|
|
** of numbers. Make this case very expensive so that the query
|
|
** planner will work hard to avoid it. */
|
|
pIdxInfo->estimatedRows = 2147483647;
|
|
}
|
|
pIdxInfo->idxNum = idxNum;
|
|
#ifdef SQLITE_INDEX_SCAN_HEX
|
|
pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX;
|
|
#endif
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** This following structure defines all the methods for the
|
|
** generate_series virtual table.
|
|
*/
|
|
static sqlite3_module seriesModule = {
|
|
0, /* iVersion */
|
|
0, /* xCreate */
|
|
seriesConnect, /* xConnect */
|
|
seriesBestIndex, /* xBestIndex */
|
|
seriesDisconnect, /* xDisconnect */
|
|
0, /* xDestroy */
|
|
seriesOpen, /* xOpen - open a cursor */
|
|
seriesClose, /* xClose - close a cursor */
|
|
seriesFilter, /* xFilter - configure scan constraints */
|
|
seriesNext, /* xNext - advance a cursor */
|
|
seriesEof, /* xEof - check for end of scan */
|
|
seriesColumn, /* xColumn - read data */
|
|
seriesRowid, /* xRowid - read data */
|
|
0, /* xUpdate */
|
|
0, /* xBegin */
|
|
0, /* xSync */
|
|
0, /* xCommit */
|
|
0, /* xRollback */
|
|
0, /* xFindMethod */
|
|
0, /* xRename */
|
|
0, /* xSavepoint */
|
|
0, /* xRelease */
|
|
0, /* xRollbackTo */
|
|
0, /* xShadowName */
|
|
0 /* xIntegrity */
|
|
};
|
|
|
|
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
|
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
int sqlite3_series_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
int rc = SQLITE_OK;
|
|
(void)pApi;
|
|
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
|
if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){
|
|
*pzErrMsg = sqlite3_mprintf(
|
|
"generate_series() requires SQLite 3.8.12 or later");
|
|
return SQLITE_ERROR;
|
|
}
|
|
rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0);
|
|
#endif
|
|
return rc;
|
|
}
|
|
/*
|
|
** 2026-08-22:
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** * May you do good and not evil.
|
|
** * May you find forgiveness for yourself and forgive others.
|
|
** * May you share freely, never taking more than you give.
|
|
**
|
|
************************************************************************
|
|
** This file houses the low-level tokenizer used by cmpp directives
|
|
** and cmpp_itch callbacks.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
|
|
const cmpp_tizer cmpp_tizer_empty = cmpp_tizer_empty_m;
|
|
const cmpp_token cmpp_token_empty = cmpp_token_empty_m;
|
|
|
|
CMPP__EXPORT(char const *, cmpp_tt_cstr)(int e){
|
|
switch(e){
|
|
#define E(NAME,ID,MASK) case ID | MASK: return # NAME;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
default: return 0;
|
|
}
|
|
}
|
|
char const * cmpp__tt_cstr2(int tt, bool bSymbolName){
|
|
switch(tt){
|
|
#define E(N,V,M) case cmpp_tt_ ## N: \
|
|
return bSymbolName ? "cmpp_tt_" # N : # N;
|
|
cmpp_tt_map(E)
|
|
#undef E
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_token_lc)(cmpp_tizer const * tz,
|
|
cmpp_token const * tok,
|
|
cmpp_size_t *l, cmpp_size_t *c){
|
|
if( !tok ) tok = &tz->token;
|
|
assert(tok->z);
|
|
unsigned char const * zb = 0;
|
|
unsigned char const * ze = 0;
|
|
cmpp_tizer_full_range(tz, &zb, &ze);
|
|
*l = 1 + cmpp_count_nl(zb, tok->z+1, tok->z, c);
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_tizer_eof)(cmpp_tizer const *tz){
|
|
return tz->zPos>=tz->zEnd || cmpp_tt_EOF==tz->token.ttype;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_token_is_group)(cmpp_token const * t){
|
|
return cmpp_tt_shape_group==(t->ttype & cmpp_tt_mask_shape);
|
|
}
|
|
CMPP__EXPORT(bool, cmpp_token_is_string)(cmpp_token const * t){
|
|
return cmpp_tt_shape_string==(t->ttype & cmpp_tt_mask_shape);
|
|
}
|
|
|
|
int cmpp__tt_for_group_char(int ch){
|
|
switch(ch){
|
|
case '{': return cmpp_tt_GroupSquiggly;
|
|
case '[': return cmpp_tt_GroupBrace;
|
|
case '(': return cmpp_tt_GroupParen;
|
|
case '"': return cmpp_tt_StringDQ;
|
|
case '\'': return cmpp_tt_StringSQ;
|
|
case '`': return cmpp_tt_StringBT;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Proxy for cmpp_errf(tx->err,...).
|
|
*/
|
|
static int cmpp_tizer_err_setf(cmpp_tizer * tz, int code,
|
|
char const *zFmt, ...);
|
|
|
|
/** va_list counterpart of cmpp_tizer_err_set(). */
|
|
static int cmpp_tizer_err_setv(cmpp_tizer * tz, int code,
|
|
char const *zFmt, va_list vargs);
|
|
|
|
/** True only for bytes which may legally immediately follow a group
|
|
token. */
|
|
static bool cmpp_tizer_terminates_group(int groupType, int ch){
|
|
switch(ch){
|
|
case 0:
|
|
case ' ': case '\n': case '\r': case '\t': case ';': return true;
|
|
case ']': case ')': case '}':
|
|
/* experiment 2026-08-31 to get {a b !!} to work */
|
|
//return cmpp_tt_shape_string==(cmpp_tt_mask_shape & groupType);
|
|
(void)groupType;
|
|
return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_full_range)(cmpp_tizer const * tz,
|
|
unsigned char const **zBegin,
|
|
unsigned char const **zEnd){
|
|
cmpp_tizer const * up;
|
|
while( (up = tz->parent) ){
|
|
/* If tz lives in the same byte range, use it for purposes of
|
|
figuring out the source range. */
|
|
if( tz->zBegin>=up->zBegin
|
|
&& tz->zEnd<=up->zEnd ){
|
|
tz = up;
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
*zBegin = tz->zBegin;
|
|
*zEnd = tz->zEnd;
|
|
}
|
|
|
|
CMPP__EXPORT(bool, cmpp_tizer_contains)(cmpp_tizer const * tz,unsigned char const *z){
|
|
unsigned char const * zB = 0;
|
|
unsigned char const * zE = 0;
|
|
cmpp_tizer_full_range(tz, &zB, &zE);
|
|
return z>=zB && z<=zE;
|
|
}
|
|
|
|
#if 0
|
|
CMPP__EXPORT(cmpp_size_t, cmpp_tizer_count_nl)(cmpp_tizer const * tz, unsigned char const *z){
|
|
unsigned char const * zB = 0;
|
|
unsigned char const * zE = 0;
|
|
cmpp_tizer_full_range(tz, &zB, &zE);
|
|
return (z>=zB && z<=zE)
|
|
? cmpp_count_nl(zB, zE, z, 0)
|
|
: 0;
|
|
}
|
|
#endif
|
|
|
|
CMPP__EXPORT(char const *, cmpp_tizer_name)(cmpp_tizer const *tz){
|
|
char const * z = tz->name;
|
|
cmpp_tizer const * up = tz;
|
|
while( !z && (up = up->parent) ){
|
|
z = up->name;
|
|
}
|
|
return z;
|
|
}
|
|
|
|
CMPP__EXPORT(sqlite3_str *, cmpp_tizer_err_prefix)(cmpp_tizer const *tz,
|
|
unsigned char const *z){
|
|
if( !z ) z = cmpp_tizer_errpos(tz);
|
|
unsigned char const *zB = 0;
|
|
unsigned char const *zE = 0;
|
|
cmpp_tizer_full_range(tz, &zB, &zE);
|
|
assert( z>=zB && z<=zE && "Else internal misuse of this API" );
|
|
sqlite3_str * str = cmpp__sqlite3_str_new(0);
|
|
if( str && z>=zB && z<=zE ){
|
|
cmpp_size_t col = 0;
|
|
cmpp_size_t const ln = 1+cmpp_count_nl(zB, zE, z, &col);
|
|
char const * zName = cmpp_tizer_name(tz);
|
|
if( !zName ) zName = "<unnamed script>";
|
|
sqlite3_str_appendf(str, "Error in [%s], possibly near %u:%u: ",
|
|
zName, (unsigned)ln, (unsigned)col);
|
|
if( sqlite3_str_errcode(str) ){
|
|
sqlite3_str_free(str);
|
|
str = 0;
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
CMPP__EXPORT(sqlite3_str *, cmpp_tizer_err_prefix_t)(cmpp_tizer const *tz,
|
|
cmpp_token const * tok){
|
|
return cmpp_tizer_err_prefix(tz, tok ? tok->z : 0);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_errpos_set)(cmpp_tizer *tz,
|
|
unsigned char const *z){
|
|
if( z ){
|
|
if( cmpp_tizer_contains(tz, z) ){
|
|
tz->errToken = cmpp_token_empty;
|
|
tz->errToken.z = tz->errToken.zInner = z;
|
|
}/*else{
|
|
// This mis-identifies positions of interpolation errors.
|
|
tz->errToken = tz->token;
|
|
}*/
|
|
}else{
|
|
tz->errToken = cmpp_token_empty;
|
|
}
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_errpos_set_t)(cmpp_tizer *tz,
|
|
cmpp_token const * tok){
|
|
assert( tok );
|
|
cmpp_tizer_errpos_set(tz, tok->z);
|
|
}
|
|
|
|
CMPP__EXPORT(unsigned char const *, cmpp_tizer_errpos)(cmpp_tizer const * tz){
|
|
if( tz ){
|
|
unsigned char const * const z = tz->errToken.z
|
|
? tz->errToken.z
|
|
: (tz->token.z
|
|
? tz->token.z
|
|
: tz->zPos);
|
|
return cmpp_tizer_contains(tz, z) ? z : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_tizer_err_setv)(cmpp_tizer * tz, int code, char const *zFmt, va_list vargs){
|
|
if( tz->err ){
|
|
cmpp_token * const tok = &tz->token;
|
|
tok->ttype = cmpp_tt_Error;
|
|
if( !tok->z ){
|
|
tok->z = (tz->zPos>=tz->zBegin && tz->zPos<tz->zEnd)
|
|
? tz->zPos
|
|
: tz->zEnd;
|
|
}
|
|
code = cmpp_errinfo_setv(tz->err, code, zFmt, vargs);
|
|
}
|
|
return code;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_tizer_err_setf)(cmpp_tizer * tz, int code, char const *zFmt, ...){
|
|
va_list va;
|
|
va_start(va, zFmt);
|
|
code = cmpp_tizer_err_setv(tz, code, zFmt, va);
|
|
va_end(va);
|
|
return code;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_init)(cmpp_tizer * tz, cmpp_errinfo *err,
|
|
unsigned char const *zBegin,
|
|
cmpp_strlen_t nBytes){
|
|
nBytes = cmpp__strlenu(zBegin, nBytes);
|
|
unsigned char const * const zEnd = zBegin + nBytes;
|
|
assert( zEnd );
|
|
assert( zBegin && zBegin<=zEnd );
|
|
tz->err = err;
|
|
tz->zBegin = zBegin;
|
|
tz->zEnd = zEnd;
|
|
cmpp_tizer_rewind(tz);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_rewind)(cmpp_tizer *tz){
|
|
tz->zPos = tz->zBegin;
|
|
tz->token = tz->pbToken = tz->errToken = cmpp_token_empty;
|
|
tz->atBOX = true;
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_inherit)(cmpp_tizer const * tzParent,
|
|
cmpp_tizer * tz){
|
|
assert( tz->zBegin );
|
|
assert( tz->zBegin>=tzParent->zBegin );
|
|
assert( tz->zEnd<=tzParent->zEnd );
|
|
tz->parent = tzParent;
|
|
if( !tz->err ) tz->err = tzParent->err;
|
|
if( !tz->name ) tz->name = cmpp_tizer_name(tzParent);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_init_sub)(cmpp_tizer const *tzParent,
|
|
cmpp_token const * tok,
|
|
cmpp_tizer * tzSub){
|
|
if( !tok ) tok = &tzParent->token;
|
|
cmpp_tizer_inherit(tzParent, tzSub);
|
|
//tzSub->name = cmpp_tizer_name(tzParent);
|
|
cmpp_tizer_init(tzSub, tzParent->err, tok->zInner, tok->nInner);
|
|
}
|
|
|
|
CMPP__EXPORT(void, cmpp_tizer_dtor)(cmpp_tizer * tz){
|
|
*tz = cmpp_tizer_empty;
|
|
}
|
|
|
|
static int cmpp_tizer_next_impl(cmpp_tizer * const tz, cmpp_flag32_t flags){
|
|
cmpp_token * const tok = &tz->token;
|
|
unsigned char const * z = tz->zPos;
|
|
unsigned char const * const zEnd = tz->zEnd;
|
|
unsigned char const * const zOrigin = z;
|
|
unsigned char const * zEsc = 0;
|
|
bool keepGoing = true;
|
|
int rc = 0;
|
|
bool const skipNL = cmpp_tizer_next_SKIP_NL & flags;
|
|
bool const skipCComments = cmpp_tizer_next_SKIP_C_COMMENTS & flags;
|
|
|
|
tz->errToken = cmpp_token_empty;
|
|
assert( cmpp_tt_None==0 && "API requirement" );
|
|
assert( tz->zPos < tz->zEnd && "Expected from cmpp_tizer_next()" );
|
|
assert( 0==tok->n && "Expected from cmpp_tizer_next()" );
|
|
assert( tz->zPos == tok->z && "Expected from cmpp_tizer_next()" );
|
|
if( skipNL ){
|
|
assert( !cmpp_isspace(*z) && '\n'!=*z
|
|
&& tz->zPos==tok->z && "Expected from cmpp_tizer_next()" );
|
|
}
|
|
tok->z = z;
|
|
tok->zInner = 0;
|
|
tok->n = tok->nInner = 0;
|
|
tok->ttype = cmpp_tt_None;
|
|
#define maybeSetTtype(T) if(!tok->ttype) tok->ttype= cmpp_tt_ ## T
|
|
while( z<zEnd && 0==rc && keepGoing ){
|
|
switch( *z ){
|
|
case '/':
|
|
if( tok->ttype || !skipCComments || zEsc
|
|
|| z+1==zEnd || '*'!=z[1] ){
|
|
goto do_word;
|
|
}
|
|
/* Skip C-style comments */
|
|
z += 2;
|
|
while( z+1<zEnd && '*'!=*z && '/'!=z[1] ){
|
|
++z;
|
|
}
|
|
if( z==zEnd || '*'!=*z ){
|
|
cmpp_tizer_errpos_set(tz, tok->z);
|
|
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
|
|
"Did not find closing */ for ",
|
|
"C comment.");
|
|
break;
|
|
}
|
|
assert( z+1<zEnd && '*'==*z && '/'==z[1] );
|
|
z += 2;
|
|
//g_warn("C comment: %.*s", (int)(z-tok->z), tok->z);
|
|
tok->z = z;
|
|
break;
|
|
case '!':
|
|
/* This is a consolation for older cmpp_arg code: a run of '!'
|
|
is tagged as OpNot and the expression engine is responsible
|
|
for dealing with it by reducing it to a single NOT or NOT
|
|
NOT operation. */
|
|
if( zEsc ){
|
|
maybeSetTtype(Word);
|
|
zEsc = 0;
|
|
++z;
|
|
}else if( tok->ttype ){
|
|
goto do_word;
|
|
}else{
|
|
while( '!'==*z ) ++z;
|
|
//g_warn("not(s): <<%.*s>>", (z+1-tok->z), tok->z);
|
|
if( z<zEnd && !cmpp_tizer_terminates_group(0,*z) ){
|
|
tok->ttype = cmpp_tt_Word;
|
|
goto do_word;
|
|
}
|
|
//g_warn("not(s): <<%.*s>>", (z+1-tok->z), tok->z);
|
|
tok->ttype = cmpp_tt_OpNot;
|
|
keepGoing = false;
|
|
}
|
|
break;
|
|
case '\\':
|
|
if( zEsc ){
|
|
maybeSetTtype(Word);
|
|
zEsc = 0;
|
|
}else{
|
|
zEsc = z;
|
|
}
|
|
++z;
|
|
tz->atBOX = false;
|
|
continue;
|
|
case ';':
|
|
if( zEsc ){
|
|
++z;
|
|
maybeSetTtype(Word);
|
|
break;
|
|
}else if( tok->ttype ){
|
|
/* Don't consume this - keep the content before it and we'll
|
|
pick this up as an EOC on the next trip. */
|
|
keepGoing = false;
|
|
break;
|
|
}else{
|
|
tok->ttype = cmpp_tt_EOC;
|
|
++z;
|
|
keepGoing = false;
|
|
tz->atBOX = true;
|
|
break;
|
|
}
|
|
case '-':
|
|
case '+':
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':{
|
|
if( zEsc ){
|
|
tz->errToken = *tok;
|
|
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
|
|
"Invalid escape sequence \\%c.",
|
|
*z);
|
|
break;
|
|
}
|
|
if( tok->ttype ){
|
|
goto do_word;
|
|
}
|
|
assert( z==tok->z );
|
|
//unsigned char const * const zLeft = z;
|
|
if( *z < '0' /*signed*/ ) ++z;
|
|
while( z<zEnd && isdigit(*z) ) ++z;
|
|
if( z==zEnd || ';'==*z || '\n'==*z || cmpp_isspace(*z) ){
|
|
/* ^^^ reminder to self: we don't need to catch ')', '}', or
|
|
end-of-string here for "{a + 1}" or similar because
|
|
sub-tokenization of grouping constructs is limited to the
|
|
token's inner part. */
|
|
#if 0
|
|
/* FIXME: we want this but it's inexplicably breaking
|
|
a test. */
|
|
if( z==zLeft+1 ){
|
|
/* Standlone + or start of a non-numeric word */
|
|
tok->ttype = cmpp_tt_Word;
|
|
break;
|
|
}
|
|
#endif
|
|
tok->ttype = cmpp_tt_IntDec;
|
|
keepGoing = false;
|
|
break;
|
|
}else{
|
|
z = tok->z /* rewind and do this as a word */;
|
|
goto do_word;
|
|
}
|
|
case '#':
|
|
if( zEsc ){
|
|
assert( !tz->atBOX );
|
|
maybeSetTtype(Word);
|
|
++z;
|
|
break;
|
|
}else if( tz->atBOX ){
|
|
/* Skip shell/tcl/perl-style comments. */
|
|
continue_comment:
|
|
while( z<zEnd && '\n'!=*++z );
|
|
/* There's no(?) world in which newlines which immediately
|
|
follow a single-line comment are relevant to command
|
|
execution. OTOH, a one-line comment may end a command... so
|
|
let's treat the comment as an EOL. */;
|
|
if( z<zEnd ){
|
|
assert( '\n'==*z );
|
|
if( '\\'==z[-1] ){
|
|
++z;
|
|
goto continue_comment;
|
|
}
|
|
++z /* consume \n */;
|
|
if( skipNL ){
|
|
tok->z = z;
|
|
tok->ttype = 0;
|
|
}else{
|
|
tok->ttype = cmpp_tt_EOL;
|
|
keepGoing = false;
|
|
}
|
|
tz->atBOX = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
goto do_word;
|
|
}
|
|
case '\r':
|
|
if( z<zEnd && '\n'==z[1] ){
|
|
++z;
|
|
goto do_nl;
|
|
}else{
|
|
g_warn0("What do we do this this abomination?");
|
|
goto do_word;
|
|
break;
|
|
}
|
|
case '\n':
|
|
do_nl:
|
|
if( zEsc ){
|
|
/* Treat a backslash-escaped newline like space */
|
|
if( tok->ttype ){
|
|
z = zEsc /* go back to the backslash */;
|
|
keepGoing = false;
|
|
break;
|
|
}
|
|
++z;
|
|
cmpp_skip_space(&z, zEnd);
|
|
tok->z = z;
|
|
tz->atBOX = false;
|
|
}else if( tok->ttype ){
|
|
/* Don't consume - there's a pending word */
|
|
keepGoing = false;
|
|
tz->atBOX = true;
|
|
break;
|
|
}else if( skipNL ){
|
|
tok->z = ++z;
|
|
tok->ttype = 0;
|
|
tz->atBOX = true;
|
|
}else{
|
|
++z;
|
|
keepGoing = false;
|
|
tz->atBOX = true;
|
|
tok->ttype = cmpp_tt_EOL;
|
|
}
|
|
break;
|
|
case ' ': case '\t':
|
|
if( zEsc ){
|
|
++z;
|
|
maybeSetTtype(Word);
|
|
}else if( tok->ttype ){
|
|
/* Terminate pending token here */
|
|
keepGoing = false;
|
|
}else{
|
|
//assert( tz->atBOX );
|
|
tok->z = ++z;
|
|
}
|
|
//assert( !tz->atBOX );
|
|
break;
|
|
case '{': case '[': case '(':
|
|
case '"': case '`': case '\'':{
|
|
if( tok->ttype || zEsc ){
|
|
/* Scratch that - this is a continuation */
|
|
goto do_word;
|
|
}
|
|
//g_warn("*z=%c isEsc=%d", *z, (int)!!zEsc);
|
|
int const tty = cmpp__tt_for_group_char(*z);
|
|
unsigned char const chOpen = *z;
|
|
assert( tty );
|
|
tok->zInner = z+1;
|
|
if( 0==cmpp__find_closing(0, &z, zEnd) ){
|
|
tok->ttype = tty;
|
|
tok->nInner = (cmpp_size_t)(z - tok->zInner);
|
|
++z/*consume closer*/;
|
|
if( 0 ){
|
|
g_warn("<<%.*s>> => <<%.*s>>",
|
|
(int)(z - tok->z), tok->z,
|
|
(int)tok->nInner, tok->zInner);
|
|
}
|
|
if( z<zEnd ){
|
|
if( !cmpp_tizer_terminates_group(tty, *z) ){
|
|
/* We're eventually going to want to support a Tcl-like
|
|
bellybutton operation, in which case this has to be
|
|
patched. Or maybe we'll use JS syntax for that
|
|
instead: ...$x vs. {*}$x. */
|
|
tz->errToken = *tok;
|
|
cmpp_size_t col = 0, ln = 0;
|
|
cmpp_token_lc(tz, tok, &ln, &col);
|
|
if( 1 ){
|
|
g_warn("<<%.*s>> => <<%.*s>>",
|
|
(int)(z+1 - tok->z), tok->z);
|
|
}
|
|
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
|
|
"Trailing junk after group "
|
|
"token (char=%c) opened at script line "
|
|
"%d column %d.",
|
|
*z, (int)ln, (int)col);
|
|
}
|
|
}
|
|
}else{
|
|
cmpp_size_t col = 0, ln = 0;
|
|
cmpp_token_lc(tz, 0, &ln, &col);
|
|
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX,
|
|
"Did not find closing character "
|
|
"for '%c' opened at script line %d "
|
|
"column %d.",
|
|
(char)chOpen, (int)ln, (int)col);
|
|
}
|
|
tz->atBOX = false;
|
|
break;
|
|
}
|
|
case 'u': case 'U':
|
|
if( zEsc ){
|
|
/* \uXXXX and \UXXXXXXXX */
|
|
unsigned char uCount = 0;
|
|
unsigned int uChar = 0;
|
|
const unsigned ulen = 'u'==*z ? (unsigned)4 : (unsigned)8;
|
|
uCount = z+ulen<zEnd
|
|
? cmpp__read_hex_bytes(z+1, ulen, &uChar)
|
|
: 0;
|
|
if(uCount != ulen){
|
|
tz->errToken = *tok;
|
|
rc = cmpp_tizer_err_setf(
|
|
tz, CMPP_RC_SYNTAX,
|
|
"Invalid \\%c value. "
|
|
"Expecting \\uNNNN or \\UNNNNNNNN", *z
|
|
);
|
|
break;
|
|
}
|
|
assert(0 == (uCount % 2));
|
|
unsigned char ignored[12];
|
|
int const check = cmpp__utf8_char_to_cstr(uChar, ignored,
|
|
uCount);
|
|
if(check<0){
|
|
tz->errToken = *tok;
|
|
rc = cmpp_tizer_err_setf(
|
|
tz, CMPP_RC_SYNTAX,
|
|
"Invalid UTF: \\%c sequence. Expecting %u hex digits.\n",
|
|
*z, (int)ulen
|
|
);
|
|
break;
|
|
}
|
|
z += uCount;
|
|
maybeSetTtype(Word);
|
|
break;
|
|
}/* \u... and \U... */
|
|
CMPP_SWITCH_FALL_THROUGH;
|
|
default:
|
|
do_word:
|
|
++z;
|
|
maybeSetTtype(Word);
|
|
tz->atBOX = false;
|
|
break;
|
|
}
|
|
zEsc = NULL;
|
|
}
|
|
#undef maybeSetTtype
|
|
if( 0==rc ){
|
|
if( z==tok->z ){
|
|
tok->ttype = cmpp_tt_EOF;
|
|
tok->n = tok->nInner = 0;
|
|
tok->zInner = z;
|
|
}else if( zEsc ){
|
|
cmpp_size_t col = 0;
|
|
cmpp_size_t const ln =
|
|
cmpp_count_nl(tz->zBegin, tz->zEnd, zEsc, &col);
|
|
rc = cmpp_tizer_err_setf(tz, CMPP_RC_SYNTAX, "Stray escape sequence "
|
|
"near line %d column %d.", (int)ln, (int)col);
|
|
}else{
|
|
assert( z>tok->z );
|
|
tok->n = (cmpp_size_t)(z - tok->z);
|
|
assert( tok->ttype > 0 );
|
|
if( !tok->zInner ){
|
|
tok->zInner = tok->z;
|
|
tok->nInner = tok->n;
|
|
}
|
|
if( 0 ){
|
|
g_warn("token ttype=%d = %.*s", tok->ttype, (int)tok->n, tok->z);
|
|
}
|
|
}
|
|
tz->zPos = z;
|
|
}else{
|
|
tok->ttype = cmpp_tt_Error;
|
|
tok->n = tok->nInner = 0;
|
|
if( z<zEnd ) tok->z = z;
|
|
tok->zInner = z;
|
|
if( !tz->errToken.z ){
|
|
tz->errToken.z = zOrigin;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_tizer_next)(cmpp_tizer * const tz,
|
|
cmpp_flag32_t flags){
|
|
cmpp_token * const tok = &tz->token;
|
|
if( tz->zPos<tz->zEnd ){
|
|
tz->pbToken = tz->token;
|
|
tok->n = 0;
|
|
if( tz->zPos==tz->zBegin || (cmpp_tizer_next_SKIP_NL & flags) ){
|
|
cmpp_skip_snl( &tz->zPos, tz->zEnd );
|
|
}else{
|
|
cmpp_skip_space( &tz->zPos, tz->zEnd );
|
|
}
|
|
if( tz->zPos < tz->zEnd ){
|
|
tok->z = tz->zPos;
|
|
return cmpp_tizer_next_impl(tz, flags);
|
|
}
|
|
}
|
|
tok->ttype = cmpp_tt_EOF;
|
|
tok->n = tok->nInner = 0;
|
|
tok->z = tok->zInner = tz->zEnd;
|
|
return 0;
|
|
}
|
|
|
|
CMPP__EXPORT(int,cmpp_token_content)(cmpp_tizer const *tz,
|
|
cmpp_token const * tok,
|
|
cmpp_b * b, cmpp_flag32_t flags){
|
|
if( !tok ) tok = &tz->token;
|
|
if( 0==(cmpp_token_content_APPEND & flags) ){
|
|
cmpp_b_reuse(b);
|
|
}
|
|
if( cmpp_token_content_WHOLE & flags ){
|
|
return cmpp_b_append(b, tok->z, tok->n);
|
|
}
|
|
switch(tok->ttype){
|
|
case cmpp_tt_None:
|
|
case cmpp_tt_EOF:
|
|
case cmpp_tt_Error:
|
|
return 0;
|
|
|
|
case cmpp_tt_IntDec:{
|
|
assert(tok->n);
|
|
const bool plus = '+'==*tok->z;
|
|
assert(plus==0 || plus==1);
|
|
return cmpp_b_append(b, tok->z+plus, tok->n-plus);
|
|
}
|
|
|
|
case cmpp_tt_GroupBrace:
|
|
case cmpp_tt_GroupParen:
|
|
case cmpp_tt_GroupSquiggly:
|
|
case cmpp_tt_StringDQ:
|
|
case cmpp_tt_StringSQ:
|
|
case cmpp_tt_StringBT:{
|
|
assert( tok->n > 1 );
|
|
assert( tok->nInner == tok->n-2 );
|
|
assert( tok->zInner >= tok->z );
|
|
unsigned char const * z = tok->zInner;
|
|
unsigned char const * zEnd = z + tok->nInner;
|
|
//g_warn("group inner: %.*s", (int)(zEnd-z), z);
|
|
if( cmpp_token_content_TRIM & flags ){
|
|
cmpp_skip_snl(&z, zEnd);
|
|
cmpp_skip_snl_trailing(z, &zEnd);
|
|
}
|
|
assert( z<=zEnd );
|
|
if( zEnd==z ) return 0;
|
|
int const rc = cmpp_b_append(b, z, (cmpp_size_t)(zEnd-z));
|
|
if( 0==rc
|
|
&& cmpp_tt_shape_string==(cmpp_tt_shape_string & tok->ttype)
|
|
&& 0==(cmpp_token_content_NO_UNESCAPE & flags)
|
|
){
|
|
cmpp_b_unescape(b);
|
|
}
|
|
return rc;
|
|
}
|
|
case cmpp_tt_Word:
|
|
default:
|
|
return cmpp_b_append(b, tok->z, tok->n);
|
|
}
|
|
}
|
|
|
|
int cmpp__tt_tok_op(unsigned char const * z, uint8_t n){
|
|
int rc = 0;
|
|
switch(n){
|
|
case 1:
|
|
switch(*z){
|
|
case '+': rc = cmpp_tt_OpPlus; break;
|
|
case '-': rc = cmpp_tt_OpMinus; break;
|
|
case '<': rc = cmpp_tt_OpCmpLt; break;
|
|
case '>': rc = cmpp_tt_OpCmpGt; break;
|
|
case '=': rc = cmpp_tt_OpCmpEq; break;
|
|
case '!': rc = cmpp_tt_OpNot; break;
|
|
//case '^': rc = cmpp_tt_OpXOr; break;
|
|
//SQLite doesn't do that.
|
|
case '~': rc = cmpp_tt_OpBitwiseNegate; break;
|
|
case '&': rc = cmpp_tt_OpBitwiseAnd; break;
|
|
case '|': rc = cmpp_tt_OpBitwiseOr; break;
|
|
case '*': rc = cmpp_tt_OpMultiply; break;
|
|
case '/': rc = cmpp_tt_OpDivide; break;
|
|
case '%': rc = cmpp_tt_OpModulo; break;
|
|
}
|
|
break;
|
|
case 2:
|
|
switch(*z){
|
|
#define IFN(X,C,T) case X: if(C==z[1]) rc = T
|
|
IFN('n','e', cmpp_tt_OpCmpNeq); break;
|
|
IFN('=','=', cmpp_tt_OpCmpEq); break;
|
|
IFN('e','q', cmpp_tt_OpCmpEq); break;
|
|
IFN('o','r', cmpp_tt_OpOr); break;
|
|
IFN('|','|', cmpp_tt_OpOr); break;
|
|
IFN('&','&', cmpp_tt_OpAnd); break;
|
|
IFN('-','>', cmpp_tt_ThinArrowR); break;
|
|
//IFN('l','t', cmpp_tt_OpCmpLt); break;
|
|
//IFN('l','e', cmpp_tt_OpCmpLe); break;
|
|
//IFN('g','t', cmpp_tt_OpCmpGt); break;
|
|
//IFN('g','e', cmpp_tt_OpCmpGe); break;
|
|
#undef IFN
|
|
case '!':
|
|
switch(z[1]){
|
|
case '=': rc = cmpp_tt_OpCmpNeq; break;
|
|
case '!': rc = cmpp_tt_OpNot; break;
|
|
}
|
|
break;
|
|
case '<':
|
|
switch(z[1]){
|
|
case '<': rc = cmpp_tt_OpShiftL; break;
|
|
case '=': rc = cmpp_tt_OpCmpLe; break;
|
|
case '>': rc = cmpp_tt_OpCmpNeq; break;
|
|
case '-': rc = cmpp_tt_ThinArrowL; break;
|
|
}
|
|
break;
|
|
case '>':
|
|
switch(z[1]){
|
|
case '>': rc = cmpp_tt_OpShiftR; break;
|
|
case '=': rc = cmpp_tt_OpCmpGe; break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case 3:
|
|
switch(*z){
|
|
case 'a': if( 'n'==z[1] && 'd'==z[2] ){
|
|
rc = cmpp_tt_OpAnd;
|
|
} break;
|
|
case 'm': if( 'o'==z[1] && 'd'==z[2] ){
|
|
rc = cmpp_tt_OpModulo;
|
|
} break;
|
|
case 'n': if( 'o'==z[1] && 't'==z[2] ){
|
|
rc = cmpp_tt_OpNot;
|
|
} break;
|
|
case '<': if( '<'==z[1] && '<'==z[2] ){
|
|
rc = cmpp_tt_OpShiftL3;
|
|
} break;
|
|
}
|
|
break;
|
|
case 4:
|
|
if( 'g'==*z && 0==memcmp(z,"glob",4) ){
|
|
rc = cmpp_tt_OpGlob;
|
|
} break;
|
|
case 7:
|
|
if( 'd'==*z && 0==memcmp(z,"defined",7) ){
|
|
rc = cmpp_tt_OpDefined;
|
|
} break;
|
|
}
|
|
if( 0==rc && n && '!'==*z ){
|
|
/* Kludge for NOT operator */;
|
|
unsigned char const * ze = z + n;
|
|
while( z<ze && '!'==*z ) ++z;
|
|
rc = z==ze ? cmpp_tt_OpNot : 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
CMPP__EXPORT(char const *, cmpp_token_cstr3)(cmpp_token const * t,
|
|
cmpp_size_t * n, bool inner){
|
|
if( inner && t->zInner != t->z ){
|
|
*n = t->nInner;
|
|
return (char const *)t->zInner;
|
|
}
|
|
*n = t->n;
|
|
return (char const *)t->z;
|
|
}
|
|
|
|
CMPP__EXPORT(int, cmpp_token_strcmp)(cmpp_token const * t, char const *z,
|
|
cmpp_strlen_t n){
|
|
cmpp_size_t const nn = cmpp_strlen(z, n);
|
|
cmpp_size_t const min = nn<t->n ? nn : t->n;
|
|
int const rc = memcmp(t->z, z, min);
|
|
return (rc || nn==t->n) ? rc : (int)nn - (int)t->n;
|
|
}
|
|
|
|
#if defined(CMPP_MAIN) /* add main() */
|
|
int main(int argc, char const * const * argv){
|
|
int rc = 0;
|
|
cmpp * pp = 0;
|
|
cmpp_flag32_t ctorFlags = 0
|
|
#if defined(CMPP_MAIN_SAFEMODE)
|
|
| cmpp_ctor_F_SAFEMODE
|
|
#endif
|
|
;
|
|
cmpp_b bArgs = cmpp_b_empty;
|
|
sqlite3_config(SQLITE_CONFIG_URI,1);
|
|
{
|
|
/* Copy argv to a string so we can #define it. This has proven
|
|
helpful in testing, debugging, and output validation. */
|
|
for( int i = 0; i < argc; ++i ){
|
|
if( i ) cmpp_b_append_ch(&bArgs,' ');
|
|
cmpp_b_append(&bArgs, argv[i], strlen(argv[i]));
|
|
}
|
|
if( (rc = bArgs.errCode) ) goto end;
|
|
if( argc>1 && 0==strcmp("--safe-mode", argv[1]) ){
|
|
ctorFlags |= cmpp_ctor_F_SAFEMODE;
|
|
--argc;
|
|
++argv;
|
|
}
|
|
}
|
|
cmpp_ctor_opt const cfg = {
|
|
.flags = ctorFlags,
|
|
/* CMPP_DB_FILE is an undocumented feature for testing
|
|
purposes. */
|
|
.dbFile = getenv("CMPP_DB_FILE")
|
|
};
|
|
rc = cmpp_ctor(&pp, &cfg);
|
|
if( rc ) goto end;
|
|
/**
|
|
Define CMPP_MAIN_INIT to the name of a function with the signature
|
|
|
|
int (*)(cmpp*)
|
|
|
|
to have it called here. The intent is that custom directives can
|
|
be installed this way without having to edit this code.
|
|
*/
|
|
#if defined(CMPP_MAIN_INIT)
|
|
extern int CMPP_MAIN_INIT(cmpp*);
|
|
if( 0!=(rc = CMPP_MAIN_INIT(pp)) ){
|
|
g_warn0("Initialization via CMPP_MAIN_INIT() failed");
|
|
goto end;
|
|
}
|
|
#endif
|
|
#if defined(CMPP_MAIN_AUTOLOADER)
|
|
{
|
|
extern int CMPP_MAIN_AUTOLOADER(cmpp*,char const *,void*);
|
|
cmpp_d_autoloader al = cmpp_d_autoloader_empty;
|
|
al.f = CMPP_MAIN_AUTOLOADER;
|
|
cmpp_d_autoloader_set(pp, &al);
|
|
}
|
|
#endif
|
|
if( cmpp_define_v2(pp, "c-pp::argv", (char*)bArgs.z) ) goto end;
|
|
cmpp_b_clear(&bArgs);
|
|
rc = cmpp_process_argv(pp, argc-1, argv+1);
|
|
switch( rc ){
|
|
case 0: break;
|
|
case CMPP_RC_HELP:
|
|
rc = 0;
|
|
cmpp_process_argv_usage(argv[0], stdout);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
end:
|
|
cmpp_b_clear(&bArgs);
|
|
if( pp ){
|
|
char const *zErr = 0;
|
|
rc = cmpp_err_get(pp, &zErr);
|
|
cmpp_flush(pp);
|
|
if( rc ){
|
|
g_warn("error %s: %s", cmpp_rc_cstr(rc), zErr);
|
|
}
|
|
cmpp_dtor(pp);
|
|
}else if( rc ){
|
|
g_warn("error #%d/%s", rc, cmpp_rc_cstr(rc));
|
|
}
|
|
sqlite3_shutdown();
|
|
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
|
}
|
|
#endif /* CMPP_MAIN */
|
|
#endif /* include guard */
|