ubuntu-buildroot/output/build/host-gawk-5.2.0/gawkapi.c

1673 lines
36 KiB
C

/*
* gawkapi.c -- Implement the functions defined for gawkapi.h
*/
/*
* Copyright (C) 2012-2019, 2021, 2022, the Free Software Foundation, Inc.
*
* This file is part of GAWK, the GNU implementation of the
* AWK Programming Language.
*
* GAWK is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GAWK is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "awk.h"
/* Declare some globals used by api_get_file: */
extern IOBUF *curfile;
extern INSTRUCTION *main_beginfile;
extern int currule;
static awk_bool_t node_to_awk_value(NODE *node, awk_value_t *result, awk_valtype_t wanted);
static const char *valtype2str(awk_valtype_t type);
static NODE *ns_lookup(const char *name_space, const char *name, char **full_name);
/*
* api_get_argument --- get the count'th paramater, zero-based.
*
* Returns false if count is out of range, or if actual paramater
* does not match what is specified in wanted. In the latter
* case, fills in result->val_type with the actual type.
*/
static awk_bool_t
api_get_argument(awk_ext_id_t id, size_t count,
awk_valtype_t wanted, awk_value_t *result)
{
#ifdef DYNAMIC
NODE *arg;
if (result == NULL)
return awk_false;
(void) id;
/* set up default result */
memset(result, 0, sizeof(*result));
result->val_type = AWK_UNDEFINED;
/*
* Song and dance here. get_array_argument() and get_scalar_argument()
* will force a change in type of a parameter that is Node_var_new.
*
* Start by looking at the unadulterated argument as it was passed.
*/
arg = get_argument(count);
if (arg == NULL)
return awk_false;
/* if type is undefined */
if (arg->type == Node_var_new || arg->type == Node_elem_new) {
if (wanted == AWK_UNDEFINED)
return awk_true;
else if (wanted == AWK_ARRAY) {
goto array;
} else {
goto scalar;
}
}
/* at this point, we have real type */
if (arg->type == Node_var_array || arg->type == Node_array_ref) {
if (wanted != AWK_ARRAY && wanted != AWK_UNDEFINED)
return awk_false;
goto array;
} else
goto scalar;
array:
/* get the array here */
arg = get_array_argument(arg, count);
if (arg == NULL)
return awk_false;
return node_to_awk_value(arg, result, wanted);
scalar:
/* at this point we have a real type that is not an array */
arg = get_scalar_argument(arg, count);
if (arg == NULL)
return awk_false;
return node_to_awk_value(arg, result, wanted);
#else
return awk_false;
#endif
}
/* api_set_argument --- convert an argument to an array */
static awk_bool_t
api_set_argument(awk_ext_id_t id,
size_t count,
awk_array_t new_array)
{
#ifdef DYNAMIC
NODE *arg;
NODE *array = (NODE *) new_array;
(void) id;
if (array == NULL || array->type != Node_var_array)
return awk_false;
if ( (arg = get_argument(count)) == NULL
|| (arg->type != Node_var_new && arg->type != Node_elem_new))
return awk_false;
arg = get_array_argument(arg, count);
if (arg == NULL)
return awk_false;
array->vname = arg->vname;
*arg = *array;
freenode(array);
return awk_true;
#else
return awk_false;
#endif
}
/* awk_value_to_node --- convert a value into a NODE */
NODE *
awk_value_to_node(const awk_value_t *retval)
{
NODE *ext_ret_val = NULL;
NODE *v;
#ifdef HAVE_MPFR
int tval = 0;
#endif
if (retval == NULL)
fatal(_("awk_value_to_node: received null retval"));
switch (retval->val_type) {
case AWK_ARRAY:
ext_ret_val = (NODE *) retval->array_cookie;
break;
case AWK_UNDEFINED:
ext_ret_val = dupnode(Nnull_string);
break;
case AWK_BOOL:
ext_ret_val = make_bool_node(retval->bool_value != awk_false);
break;
case AWK_NUMBER:
switch (retval->num_type) {
case AWK_NUMBER_TYPE_DOUBLE:
ext_ret_val = make_number(retval->num_value);
break;
case AWK_NUMBER_TYPE_MPFR:
#ifdef HAVE_MPFR
if (! do_mpfr)
fatal(_("awk_value_to_node: not in MPFR mode"));
ext_ret_val = make_number_node(MPFN);
mpfr_init(ext_ret_val->mpg_numbr);
tval = mpfr_set(ext_ret_val->mpg_numbr, (mpfr_srcptr) retval->num_ptr, ROUND_MODE);
IEEE_FMT(ext_ret_val->mpg_numbr, tval);
mpfr_clear(retval->num_ptr);
#else
fatal(_("awk_value_to_node: MPFR not supported"));
#endif
break;
case AWK_NUMBER_TYPE_MPZ:
#ifdef HAVE_MPFR
if (! do_mpfr)
fatal(_("awk_value_to_node: not in MPFR mode"));
ext_ret_val = make_number_node(MPZN);
mpz_init(ext_ret_val->mpg_i);
mpz_set(ext_ret_val->mpg_i, (mpz_ptr) retval->num_ptr);
mpz_clear(retval->num_ptr);
#else
fatal(_("awk_value_to_node: MPFR not supported"));
#endif
break;
default:
fatal(_("awk_value_to_node: invalid number type `%d'"), retval->num_type);
break;
}
break;
case AWK_STRING:
ext_ret_val = make_str_node(retval->str_value.str,
retval->str_value.len, ALREADY_MALLOCED);
break;
case AWK_STRNUM:
ext_ret_val = make_str_node(retval->str_value.str,
retval->str_value.len, ALREADY_MALLOCED);
ext_ret_val->flags |= USER_INPUT;
break;
case AWK_REGEX:
ext_ret_val = make_typed_regex(retval->str_value.str,
retval->str_value.len);
break;
case AWK_SCALAR:
v = (NODE *) retval->scalar_cookie;
if (v->type != Node_var)
ext_ret_val = NULL;
else
ext_ret_val = dupnode(v->var_value);
break;
case AWK_VALUE_COOKIE:
ext_ret_val = dupnode((NODE *)(retval->value_cookie));
break;
default: /* any invalid type */
ext_ret_val = NULL;
break;
}
return ext_ret_val;
}
/* Functions to print messages */
/* api_fatal --- print a fatal message and exit */
static void
api_fatal(awk_ext_id_t id, const char *format, ...)
{
va_list args;
(void) id;
va_start(args, format);
err(true, _("fatal: "), format, args);
va_end(args);
}
/* api_nonfatal --- print a non fatal error message */
static void
api_nonfatal(awk_ext_id_t id, const char *format, ...)
{
va_list args;
(void) id;
va_start(args, format);
err(false, _("error: "), format, args);
va_end(args);
}
/* api_warning --- print a warning message */
static void
api_warning(awk_ext_id_t id, const char *format, ...)
{
va_list args;
(void) id;
va_start(args, format);
err(false, _("warning: "), format, args);
va_end(args);
}
/* api_lintwarn --- print a lint warning message and exit if appropriate */
static void
api_lintwarn(awk_ext_id_t id, const char *format, ...)
{
va_list args;
(void) id;
va_start(args, format);
if (lintfunc == r_fatal) {
err(true, _("fatal: "), format, args);
} else {
err(false, _("warning: "), format, args);
}
va_end(args);
}
/* api_register_input_parser --- register an input_parser; for opening files read-only */
static void
api_register_input_parser(awk_ext_id_t id, awk_input_parser_t *input_parser)
{
(void) id;
if (input_parser == NULL)
return;
register_input_parser(input_parser);
}
/* api_register_output_wrapper --- register an output wrapper, for writing files / two-way pipes */
static void api_register_output_wrapper(awk_ext_id_t id,
awk_output_wrapper_t *output_wrapper)
{
(void) id;
if (output_wrapper == NULL)
return;
register_output_wrapper(output_wrapper);
}
/* api_register_two_way_processor --- register a processor for two way I/O */
static void
api_register_two_way_processor(awk_ext_id_t id,
awk_two_way_processor_t *two_way_processor)
{
(void) id;
if (two_way_processor == NULL)
return;
register_two_way_processor(two_way_processor);
}
/* Functions to update ERRNO */
/* api_update_ERRNO_int --- update ERRNO with an integer value */
static void
api_update_ERRNO_int(awk_ext_id_t id, int errno_val)
{
(void) id;
update_ERRNO_int(errno_val);
}
/* api_update_ERRNO_string --- update ERRNO with a string value */
static void
api_update_ERRNO_string(awk_ext_id_t id,
const char *string)
{
(void) id;
if (string == NULL)
return;
update_ERRNO_string(string);
}
/* api_unset_ERRNO --- unset ERRNO */
static void
api_unset_ERRNO(awk_ext_id_t id)
{
(void) id;
unset_ERRNO();
}
/* api_add_ext_func --- add a function to the interpreter, returns true upon success */
static awk_bool_t
api_add_ext_func(awk_ext_id_t id,
const char *name_space,
awk_ext_func_t *func)
{
(void) id;
if (func == NULL)
return awk_false;
if (name_space == NULL)
fatal(_("add_ext_func: received NULL name_space parameter"));
#ifdef DYNAMIC
return make_builtin(name_space, func);
#else
return awk_false;
#endif
}
/* Stuff for exit handler - do it as linked list */
struct ext_exit_handler {
struct ext_exit_handler *next;
void (*funcp)(void *data, int exit_status);
void *arg0;
};
static struct ext_exit_handler *list_head = NULL;
/* run_ext_exit_handlers --- run the extension exit handlers, LIFO order */
void
run_ext_exit_handlers(int exitval)
{
struct ext_exit_handler *p, *next;
for (p = list_head; p != NULL; p = next) {
next = p->next;
p->funcp(p->arg0, exitval);
free(p);
}
list_head = NULL;
}
/* api_awk_atexit --- add an exit call back */
static void
api_awk_atexit(awk_ext_id_t id,
void (*funcp)(void *data, int exit_status),
void *arg0)
{
struct ext_exit_handler *p;
(void) id;
if (funcp == NULL)
return;
/* allocate memory */
emalloc(p, struct ext_exit_handler *, sizeof(struct ext_exit_handler), "api_awk_atexit");
/* fill it in */
p->funcp = funcp;
p->arg0 = arg0;
/* add to linked list, LIFO order */
p->next = list_head;
list_head = p;
}
static struct {
char **strings;
size_t i, size;
} scopy;
/* free_api_string_copies --- release memory used by string copies */
void
free_api_string_copies()
{
size_t i;
for (i = 0; i < scopy.i; i++)
free(scopy.strings[i]);
scopy.i = 0;
}
/* assign_string --- return a string node with NUL termination */
static inline void
assign_string(NODE *node, awk_value_t *val, awk_valtype_t val_type)
{
val->val_type = val_type;
if (node->stptr[node->stlen] != '\0') {
/*
* This is an unterminated field string, so make a copy.
* This should happen only for $n where n > 0 and n < NF.
*/
char *s;
assert((node->flags & MALLOC) == 0);
if (scopy.i == scopy.size) {
/* expand list */
if (scopy.size == 0)
scopy.size = 8; /* initial size */
else
scopy.size *= 2;
erealloc(scopy.strings, char **, scopy.size * sizeof(char *), "assign_string");
}
emalloc(s, char *, node->stlen + 1, "assign_string");
memcpy(s, node->stptr, node->stlen);
s[node->stlen] = '\0';
val->str_value.str = scopy.strings[scopy.i++] = s;
}
else
val->str_value.str = node->stptr;
val->str_value.len = node->stlen;
}
/* assign_number -- return a number node */
#define assign_double(val) \
val->num_value = node->numbr; \
val->num_type = AWK_NUMBER_TYPE_DOUBLE; \
val->num_ptr = NULL
static inline void
assign_number(NODE *node, awk_value_t *val)
{
val->val_type = AWK_NUMBER;
#ifndef HAVE_MPFR
assign_double(val);
#else
switch (node->flags & (MPFN|MPZN)) {
case 0:
assign_double(val);
break;
case MPFN:
val->num_value = mpfr_get_d(node->mpg_numbr, ROUND_MODE);
val->num_type = AWK_NUMBER_TYPE_MPFR;
val->num_ptr = &node->mpg_numbr;
break;
case MPZN:
val->num_value = mpz_get_d(node->mpg_i);
val->num_type = AWK_NUMBER_TYPE_MPZ;
val->num_ptr = &node->mpg_i;
break;
default:
fatal(_("node_to_awk_value: detected invalid numeric flags combination `%s'; please file a bug report"), flags2str(node->flags));
break;
}
#endif
}
#undef assign_double
/* assign_regex --- return a regex node */
static inline void
assign_regex(NODE *node, awk_value_t *val)
{
/* a REGEX node cannot be an unterminated field string */
assert((node->flags & MALLOC) != 0);
assert(node->stptr[node->stlen] == '\0');
val->str_value.str = node->stptr;
val->str_value.len = node->stlen;
val->val_type = AWK_REGEX;
}
/* assign_bool --- return a bool node */
static inline void
assign_bool(NODE *node, awk_value_t *val)
{
assert((node->flags & BOOLVAL) != 0);
val->val_type = AWK_BOOL;
val->bool_value = get_number_si(node) != 0 ? awk_true : awk_false;
}
/* node_to_awk_value --- convert a node into a value for an extension */
static awk_bool_t
node_to_awk_value(NODE *node, awk_value_t *val, awk_valtype_t wanted)
{
awk_bool_t ret = awk_false;
if (node == NULL)
fatal(_("node_to_awk_value: received null node"));
if (val == NULL)
fatal(_("node_to_awk_value: received null val"));
switch (node->type) {
case Node_var_new: /* undefined variable */
case Node_elem_new: /* undefined element */
val->val_type = AWK_UNDEFINED;
if (wanted == AWK_UNDEFINED) {
ret = awk_true;
}
break;
case Node_var:
/* a scalar value */
if (wanted == AWK_SCALAR) {
val->val_type = AWK_SCALAR;
val->scalar_cookie = (void *) node;
ret = awk_true;
break;
}
node = node->var_value;
/* FALL THROUGH */
case Node_val:
/* a scalar value */
switch (wanted) {
case AWK_BOOL:
if ((node->flags & BOOLVAL) != 0) {
assign_bool(node, val);
ret = awk_true;
} else
ret = awk_false;
break;
case AWK_NUMBER:
if ((node->flags & REGEX) != 0)
val->val_type = AWK_REGEX;
else {
(void) force_number(node);
assign_number(node, val);
ret = awk_true;
}
break;
case AWK_STRNUM:
switch (fixtype(node)->flags & (STRING|NUMBER|USER_INPUT|REGEX|BOOLVAL)) {
case NUMBER|BOOLVAL:
val->val_type = AWK_BOOL;
break;
case STRING:
val->val_type = AWK_STRING;
break;
case NUMBER:
(void) force_string(node);
/* fall through */
case NUMBER|USER_INPUT:
assign_string(node, val, AWK_STRNUM);
ret = awk_true;
break;
case REGEX:
val->val_type = AWK_REGEX;
break;
case NUMBER|STRING:
if (node == Nnull_string) {
val->val_type = AWK_UNDEFINED;
break;
}
/* fall through */
default:
warning(_("node_to_awk_value detected invalid flags combination `%s'; please file a bug report"), flags2str(node->flags));
val->val_type = AWK_UNDEFINED;
break;
}
break;
case AWK_STRING:
(void) force_string(node);
assign_string(node, val, AWK_STRING);
ret = awk_true;
break;
case AWK_REGEX:
switch (fixtype(node)->flags & (STRING|NUMBER|USER_INPUT|REGEX|BOOLVAL)) {
case STRING:
val->val_type = AWK_STRING;
break;
case NUMBER|BOOLVAL:
val->val_type = AWK_BOOL;
break;
case NUMBER:
val->val_type = AWK_NUMBER;
break;
case NUMBER|USER_INPUT:
val->val_type = AWK_STRNUM;
break;
case REGEX:
assign_regex(node, val);
ret = awk_true;
break;
case NUMBER|STRING:
if (node == Nnull_string) {
val->val_type = AWK_UNDEFINED;
break;
}
/* fall through */
default:
warning(_("node_to_awk_value detected invalid flags combination `%s'; please file a bug report"), flags2str(node->flags));
val->val_type = AWK_UNDEFINED;
break;
}
break;
case AWK_SCALAR:
switch (fixtype(node)->flags & (STRING|NUMBER|USER_INPUT|REGEX|BOOLVAL)) {
case NUMBER|BOOLVAL:
val->val_type = AWK_BOOL;
break;
case STRING:
val->val_type = AWK_STRING;
break;
case NUMBER:
val->val_type = AWK_NUMBER;
break;
case NUMBER|USER_INPUT:
val->val_type = AWK_STRNUM;
break;
case REGEX:
val->val_type = AWK_REGEX;
break;
case NUMBER|STRING:
if (node == Nnull_string) {
val->val_type = AWK_UNDEFINED;
break;
}
/* fall through */
default:
warning(_("node_to_awk_value detected invalid flags combination `%s'; please file a bug report"), flags2str(node->flags));
val->val_type = AWK_UNDEFINED;
break;
}
break;
case AWK_UNDEFINED:
/* return true and actual type for request of undefined */
switch (fixtype(node)->flags & (STRING|NUMBER|USER_INPUT|REGEX|BOOLVAL)) {
case NUMBER|BOOLVAL:
assign_bool(node, val);
ret = awk_true;
break;
case STRING:
assign_string(node, val, AWK_STRING);
ret = awk_true;
break;
case NUMBER:
assign_number(node, val);
ret = awk_true;
break;
case NUMBER|USER_INPUT:
assign_string(node, val, AWK_STRNUM);
ret = awk_true;
break;
case REGEX:
assign_regex(node, val);
ret = awk_true;
break;
case NUMBER|STRING:
if (node == Nnull_string) {
val->val_type = AWK_UNDEFINED;
ret = awk_true;
break;
}
/* fall through */
default:
warning(_("node_to_awk_value detected invalid flags combination `%s'; please file a bug report"), flags2str(node->flags));
val->val_type = AWK_UNDEFINED;
break;
}
break;
case AWK_ARRAY:
case AWK_VALUE_COOKIE:
break;
}
break;
case Node_var_array:
val->val_type = AWK_ARRAY;
if (wanted == AWK_ARRAY || wanted == AWK_UNDEFINED) {
val->array_cookie = node;
ret = awk_true;
} else
ret = awk_false;
break;
default:
val->val_type = AWK_UNDEFINED;
ret = awk_false;
break;
}
return ret;
}
/*
* Symbol table access:
* - No access to special variables (NF, etc.)
* - One special exception: PROCINFO.
* - Use sym_update() to change a value, including from UNDEFINED
* to scalar or array.
*/
/*
* Lookup a variable, fills in value. No messing with the value
* returned. Returns false if the variable doesn't exist
* or the wrong type was requested.
* In the latter case, fills in vaule->val_type with the real type.
* Built-in variables (except PROCINFO) may not be accessed by an extension.
*/
/* api_sym_lookup --- look up a symbol */
static awk_bool_t
api_sym_lookup(awk_ext_id_t id,
const char *name_space,
const char *name,
awk_valtype_t wanted,
awk_value_t *result)
{
NODE *node;
update_global_values(); /* make sure stuff like NF, NR, are up to date */
if ( name == NULL
|| *name == '\0'
|| result == NULL
|| ! is_valid_identifier(name)
|| name_space == NULL
|| (name_space[0] != '\0' && ! is_valid_identifier(name_space)))
return awk_false;
if ((node = ns_lookup(name_space, name, NULL)) == NULL)
return awk_false;
if (is_off_limits_var(name)) /* a built-in variable */
node->flags |= NO_EXT_SET;
return node_to_awk_value(node, result, wanted);
}
/* api_sym_lookup_scalar --- retrieve the current value of a scalar */
static awk_bool_t
api_sym_lookup_scalar(awk_ext_id_t id,
awk_scalar_t cookie,
awk_valtype_t wanted,
awk_value_t *result)
{
NODE *node = (NODE *) cookie;
if (node == NULL
|| result == NULL
|| node->type != Node_var)
return awk_false;
update_global_values(); /* make sure stuff like NF, NR, are up to date */
return node_to_awk_value(node, result, wanted);
}
/* api_sym_update --- update a symbol's value, see gawkapi.h for semantics */
static awk_bool_t
api_sym_update(awk_ext_id_t id,
const char *name_space,
const char *name,
awk_value_t *value)
{
NODE *node;
NODE *array_node;
if ( name == NULL
|| *name == '\0'
|| value == NULL
|| ! is_valid_identifier(name)
|| name_space == NULL
|| (name_space[0] != '\0' && ! is_valid_identifier(name_space)))
return awk_false;
switch (value->val_type) {
case AWK_NUMBER:
case AWK_STRNUM:
case AWK_STRING:
case AWK_REGEX:
case AWK_UNDEFINED:
case AWK_ARRAY:
case AWK_SCALAR:
case AWK_VALUE_COOKIE:
break;
default:
/* fatal(_("api_sym_update: invalid value for type of new value (%d)"), value->val_type); */
return awk_false;
}
char *full_name = NULL;
node = ns_lookup(name_space, name, & full_name);
if (node == NULL) {
/* new value to be installed */
if (value->val_type == AWK_ARRAY) {
array_node = awk_value_to_node(value);
node = install_symbol(full_name, Node_var_array);
array_node->vname = node->vname;
*node = *array_node;
freenode(array_node);
value->array_cookie = node; /* pass new cookie back to extension */
} else {
/* regular variable */
node = install_symbol(full_name, Node_var);
node->var_value = awk_value_to_node(value);
}
return awk_true;
}
/*
* If we get here, then it exists already. Any valid type is
* OK except for AWK_ARRAY (unless it is in Node_var_new undefined
* state, in which case an array is OK).
*/
if ( (node->flags & NO_EXT_SET) != 0
|| is_off_limits_var(full_name)) { /* most built-in vars not allowed */
node->flags |= NO_EXT_SET;
efree((void *) full_name);
return awk_false;
}
efree((void *) full_name);
if ((node->type == Node_var && value->val_type != AWK_ARRAY)
|| node->type == Node_var_new
|| node->type == Node_elem_new) {
unref(node->var_value);
node->var_value = awk_value_to_node(value);
if ((node->type == Node_var_new || node->type == Node_elem_new)
&& value->val_type != AWK_UNDEFINED)
node->type = Node_var;
return awk_true;
}
return awk_false;
}
/* api_sym_update_scalar --- update a scalar cookie */
static awk_bool_t
api_sym_update_scalar(awk_ext_id_t id,
awk_scalar_t cookie,
awk_value_t *value)
{
NODE *node = (NODE *) cookie;
if (value == NULL
|| node == NULL
|| node->type != Node_var
|| (node->flags & NO_EXT_SET) != 0)
return awk_false;
/*
* Optimization: if valref is 1, and the new value is a string or
* a number, we can avoid calling unref and then making a new node
* by simply installing the new value. First, we follow the same
* recipe used by node.c:r_unref to wipe the current values, and then
* we copy the logic from r_make_number or make_str_node to install
* the new value.
*/
switch (value->val_type) {
case AWK_NUMBER:
if (node->var_value->valref == 1 && ! do_mpfr) {
NODE *r = node->var_value;
/* r_unref: */
if ((r->flags & (MALLOC|STRCUR)) == (MALLOC|STRCUR))
efree(r->stptr);
free_wstr(r);
/* r_make_number: */
r->numbr = value->num_value;
r->flags = MALLOC|NUMBER|NUMCUR;
r->stptr = NULL;
r->stlen = 0;
return awk_true;
}
break;
case AWK_STRING:
case AWK_STRNUM:
if (node->var_value->valref == 1) {
NODE *r = node->var_value;
/* r_unref: */
if ((r->flags & (MALLOC|STRCUR)) == (MALLOC|STRCUR))
efree(r->stptr);
mpfr_unset(r);
free_wstr(r);
/* make_str_node(s, l, ALREADY_MALLOCED): */
r->numbr = 0;
r->flags = (MALLOC|STRING|STRCUR);
if (value->val_type == AWK_STRNUM)
r->flags |= USER_INPUT;
r->stfmt = STFMT_UNUSED;
r->stptr = value->str_value.str;
r->stlen = value->str_value.len;
#ifdef HAVE_MPFR
r->strndmode = MPFR_round_mode;
#endif
return awk_true;
}
break;
case AWK_REGEX:
case AWK_UNDEFINED:
case AWK_SCALAR:
case AWK_VALUE_COOKIE:
break;
default: /* AWK_ARRAY or invalid type */
return awk_false;
}
/* do it the hard (slow) way */
unref(node->var_value);
node->var_value = awk_value_to_node(value);
return awk_true;
}
/*
* valid_subscript_type --- test if a type is allowed for an array subscript.
*
* Any scalar value is fine, so only AWK_ARRAY (or an invalid type) is illegal.
*/
static inline bool
valid_subscript_type(awk_valtype_t valtype)
{
switch (valtype) {
case AWK_UNDEFINED:
case AWK_NUMBER:
case AWK_STRNUM:
case AWK_STRING:
case AWK_REGEX:
case AWK_SCALAR:
case AWK_VALUE_COOKIE:
return true;
default: /* AWK_ARRAY or an invalid type */
return false;
}
}
/* Array management */
/*
* api_get_array_element --- teturn the value of an element - read only!
*
* Use set_array_element to change it.
*/
static awk_bool_t
api_get_array_element(awk_ext_id_t id,
awk_array_t a_cookie,
const awk_value_t *const index,
awk_valtype_t wanted,
awk_value_t *result)
{
NODE *array = (NODE *) a_cookie;
NODE *subscript;
NODE **aptr;
/* don't check for index len zero, null str is ok as index */
if ( array == NULL
|| array->type != Node_var_array
|| result == NULL
|| index == NULL
|| ! valid_subscript_type(index->val_type))
return awk_false;
subscript = awk_value_to_node(index);
/* if it doesn't exist, return false */
if (in_array(array, subscript) == NULL) {
unref(subscript);
return awk_false;
}
aptr = assoc_lookup(array, subscript);
if (aptr == NULL) { /* can't happen */
unref(subscript);
return awk_false;
}
unref(subscript);
return node_to_awk_value(*aptr, result, wanted);
}
/*
* api_set_array_element --- change (or create) element in existing array
* with element->index and element->value.
*/
static awk_bool_t
api_set_array_element(awk_ext_id_t id, awk_array_t a_cookie,
const awk_value_t *const index,
const awk_value_t *const value)
{
NODE *array = (NODE *)a_cookie;
NODE *tmp;
NODE *elem;
/* don't check for index len zero, null str is ok as index */
if ( array == NULL
|| array->type != Node_var_array
|| (array->flags & NO_EXT_SET) != 0
|| index == NULL
|| value == NULL
|| ! valid_subscript_type(index->val_type))
return awk_false;
tmp = awk_value_to_node(index);
elem = awk_value_to_node(value);
if (elem->type == Node_var_array) {
elem->parent_array = array;
elem->vname = estrdup(index->str_value.str,
index->str_value.len);
}
assoc_set(array, tmp, elem);
return awk_true;
}
/*
* remove_element --- remove an array element
* common code used by multiple functions
*/
static void
remove_element(NODE *array, NODE *subscript)
{
NODE *val;
if (array == NULL)
fatal(_("remove_element: received null array"));
if (subscript == NULL)
fatal(_("remove_element: received null subscript"));
val = in_array(array, subscript);
if (val == NULL)
return;
if (val->type == Node_var_array) {
assoc_clear(val);
/* cleared a sub-array, free Node_var_array */
efree(val->vname);
freenode(val);
} else
unref(val);
(void) assoc_remove(array, subscript);
}
/*
* api_del_array_element --- remove the element with the given index.
* Return success if removed or if element did not exist.
*/
static awk_bool_t
api_del_array_element(awk_ext_id_t id,
awk_array_t a_cookie, const awk_value_t* const index)
{
NODE *array, *sub;
array = (NODE *) a_cookie;
if ( array == NULL
|| array->type != Node_var_array
|| (array->flags & NO_EXT_SET) != 0
|| index == NULL
|| ! valid_subscript_type(index->val_type))
return awk_false;
sub = awk_value_to_node(index);
remove_element(array, sub);
unref(sub);
return awk_true;
}
/*
* api_get_element_count --- retrieve total number of elements in array.
* Return false if some kind of error.
*/
static awk_bool_t
api_get_element_count(awk_ext_id_t id,
awk_array_t a_cookie, size_t *count)
{
NODE *node = (NODE *) a_cookie;
if (count == NULL || node == NULL || node->type != Node_var_array)
return awk_false;
*count = node->table_size;
return awk_true;
}
/* api_create_array --- create a new array cookie to which elements may be added */
static awk_array_t
api_create_array(awk_ext_id_t id)
{
NODE *n;
getnode(n);
memset(n, 0, sizeof(NODE));
null_array(n);
return (awk_array_t) n;
}
/* api_clear_array --- clear out an array */
static awk_bool_t
api_clear_array(awk_ext_id_t id, awk_array_t a_cookie)
{
NODE *node = (NODE *) a_cookie;
if ( node == NULL
|| node->type != Node_var_array
|| (node->flags & NO_EXT_SET) != 0)
return awk_false;
assoc_clear(node);
return awk_true;
}
/* api_destroy_array --- destroy an array */
static awk_bool_t
api_destroy_array(awk_ext_id_t id, awk_array_t a_cookie)
{
if (! api_clear_array(id, a_cookie))
return awk_false;
freenode((NODE *) a_cookie);
return awk_true;
}
/* api_flatten_array_typed --- flatten out an array so that it can be looped over easily. */
static awk_bool_t
api_flatten_array_typed(awk_ext_id_t id,
awk_array_t a_cookie,
awk_flat_array_t **data,
awk_valtype_t index_type, awk_valtype_t value_type)
{
NODE **list;
size_t i, j;
NODE *array = (NODE *) a_cookie;
size_t alloc_size;
if ( array == NULL
|| array->type != Node_var_array
|| assoc_empty(array)
|| data == NULL)
return awk_false;
alloc_size = sizeof(awk_flat_array_t) +
(array->table_size - 1) * sizeof(awk_element_t);
ezalloc(*data, awk_flat_array_t *, alloc_size,
"api_flatten_array_typed");
list = assoc_list(array, "@unsorted", ASORTI);
(*data)->opaque1 = array;
(*data)->opaque2 = list;
(*data)->count = array->table_size;
for (i = j = 0; i < 2 * array->table_size; i += 2, j++) {
NODE *index, *value;
index = list[i];
value = list[i + 1]; /* number or string or subarray */
/* Convert index and value to API types. */
if (! node_to_awk_value(index,
& (*data)->elements[j].index, index_type)) {
fatal(_("api_flatten_array_typed: could not convert index %d to %s"),
(int) i, valtype2str(index_type));
}
if (! node_to_awk_value(value,
& (*data)->elements[j].value, value_type)) {
fatal(_("api_flatten_array_typed: could not convert value %d to %s"),
(int) i, valtype2str(value_type));
}
}
return awk_true;
}
/*
* api_release_flattened_array --- release array memory,
* delete any marked elements. Count must match what
* gawk thinks the size is.
*/
static awk_bool_t
api_release_flattened_array(awk_ext_id_t id,
awk_array_t a_cookie,
awk_flat_array_t *data)
{
NODE *array = (NODE *) a_cookie;
NODE **list;
size_t i, j, k;
if ( array == NULL
|| array->type != Node_var_array
|| data == NULL
|| array != (NODE *) data->opaque1
|| data->count != array->table_size
|| data->opaque2 == NULL)
return awk_false;
list = (NODE **) data->opaque2;
/* free index nodes */
for (i = j = 0, k = 2 * array->table_size; i < k; i += 2, j++) {
/* Delete items flagged for delete. */
if ( (data->elements[j].flags & AWK_ELEMENT_DELETE) != 0
&& (array->flags & NO_EXT_SET) == 0) {
remove_element(array, list[i]);
}
unref(list[i]);
}
efree(list);
efree(data);
return awk_true;
}
/* api_create_value --- create a cached value */
static awk_bool_t
api_create_value(awk_ext_id_t id, awk_value_t *value,
awk_value_cookie_t *result)
{
if (value == NULL || result == NULL)
return awk_false;
switch (value->val_type) {
case AWK_NUMBER:
case AWK_STRNUM:
case AWK_STRING:
case AWK_REGEX:
break;
default:
/* reject anything other than a simple scalar */
return awk_false;
}
return (awk_bool_t) ((*result = awk_value_to_node(value)) != NULL);
}
/* api_release_value --- release a cached value */
static awk_bool_t
api_release_value(awk_ext_id_t id, awk_value_cookie_t value)
{
NODE *val = (NODE *) value;
if (val == NULL)
return awk_false;
unref(val);
return awk_true;
}
/* api_get_mpfr --- allocate an mpfr_ptr */
static void *
api_get_mpfr(awk_ext_id_t id)
{
#ifdef HAVE_MPFR
mpfr_ptr p;
emalloc(p, mpfr_ptr, sizeof(mpfr_t), "api_get_mpfr");
mpfr_init(p);
return p;
#else
fatal(_("api_get_mpfr: MPFR not supported"));
return NULL; // silence compiler warning
#endif
}
/* api_get_mpz --- allocate an mpz_ptr */
static void *
api_get_mpz(awk_ext_id_t id)
{
#ifdef HAVE_MPFR
mpz_ptr p;
emalloc(p, mpz_ptr, sizeof (mpz_t), "api_get_mpz");
mpz_init(p);
return p;
#else
fatal(_("api_get_mpfr: MPFR not supported"));
return NULL; // silence compiler warning
#endif
}
/* api_get_file --- return a handle to an existing or newly opened file */
static awk_bool_t
api_get_file(awk_ext_id_t id, const char *name, size_t namelen, const char *filetype,
int fd, const awk_input_buf_t **ibufp, const awk_output_buf_t **obufp)
{
const struct redirect *f;
int flag; /* not used, sigh */
enum redirval redirtype;
if (name == NULL || namelen == 0) {
if (curfile == NULL) {
INSTRUCTION *pc;
int save_rule;
char *save_source;
if (nextfile(& curfile, false) <= 0)
return awk_false;
pc = main_beginfile;
/* save execution state */
save_rule = currule;
save_source = source;
for (;;) {
if (pc == NULL)
fatal(_("cannot find end of BEGINFILE rule"));
if (pc->opcode == Op_after_beginfile)
break;
pc = pc->nexti;
}
pc->opcode = Op_stop;
(void) (*interpret)(main_beginfile);
pc->opcode = Op_after_beginfile;
after_beginfile(& curfile);
/* restore execution state */
currule = save_rule;
source = save_source;
}
*ibufp = &curfile->public;
*obufp = NULL;
return awk_true;
}
redirtype = redirect_none;
switch (filetype[0]) {
case '<':
if (filetype[1] == '\0')
redirtype = redirect_input;
break;
case '>':
switch (filetype[1]) {
case '\0':
redirtype = redirect_output;
break;
case '>':
if (filetype[2] == '\0')
redirtype = redirect_append;
break;
}
break;
case '|':
if (filetype[2] == '\0') {
switch (filetype[1]) {
case '>':
redirtype = redirect_pipe;
break;
case '<':
redirtype = redirect_pipein;
break;
case '&':
redirtype = redirect_twoway;
break;
}
}
break;
}
if (redirtype == redirect_none) {
warning(_("cannot open unrecognized file type `%s' for `%s'"),
filetype, name);
return awk_false;
}
if ((f = redirect_string(name, namelen, 0, redirtype, &flag, fd, false)) == NULL)
return awk_false;
*ibufp = f->iop ? & f->iop->public : NULL;
*obufp = f->output.fp ? & f->output : NULL;
return awk_true;
}
/*
* Register a version string for this extension with gawk.
*/
struct version_info {
const char *version;
struct version_info *next;
};
static struct version_info *vi_head;
/* api_register_ext_version --- add an extension version string to the list */
static void
api_register_ext_version(awk_ext_id_t id, const char *version)
{
struct version_info *info;
if (version == NULL)
return;
(void) id;
emalloc(info, struct version_info *, sizeof(struct version_info), "register_ext_version");
info->version = version;
info->next = vi_head;
vi_head = info;
}
/* the struct api */
gawk_api_t api_impl = {
/* data */
GAWK_API_MAJOR_VERSION, /* major and minor versions */
GAWK_API_MINOR_VERSION,
#ifdef HAVE_MPFR
__GNU_MP_VERSION,
__GNU_MP_VERSION_MINOR,
MPFR_VERSION_MAJOR,
MPFR_VERSION_MINOR,
#else
0, 0, 0, 0,
#endif
{ 0 }, /* do_flags */
/* registration functions */
api_add_ext_func,
api_register_input_parser,
api_register_output_wrapper,
api_register_two_way_processor,
api_awk_atexit,
api_register_ext_version,
/* message printing functions */
api_fatal,
api_warning,
api_lintwarn,
api_nonfatal,
/* updating ERRNO */
api_update_ERRNO_int,
api_update_ERRNO_string,
api_unset_ERRNO,
/* Function arguments */
api_get_argument,
api_set_argument,
/* Accessing and installing variables and constants */
api_sym_lookup,
api_sym_update,
/* Accessing and modifying variables via scalar cookies */
api_sym_lookup_scalar,
api_sym_update_scalar,
/* Cached values */
api_create_value,
api_release_value,
/* Array management */
api_get_element_count,
api_get_array_element,
api_set_array_element,
api_del_array_element,
api_create_array,
api_clear_array,
api_flatten_array_typed,
api_release_flattened_array,
/* Memory allocation */
malloc,
calloc,
realloc,
free,
api_get_mpfr,
api_get_mpz,
/* Find/open a file */
api_get_file,
/* Additional array hook to destroy an array */
api_destroy_array,
};
/* init_ext_api --- init the extension API */
void
init_ext_api()
{
/* force values to 1 / 0 */
api_impl.do_flags[0] = (do_lint ? 1 : 0);
api_impl.do_flags[1] = (do_traditional ? 1 : 0);
api_impl.do_flags[2] = (do_profile ? 1 : 0);
api_impl.do_flags[3] = (do_sandbox ? 1 : 0);
api_impl.do_flags[4] = (do_debug ? 1 : 0);
api_impl.do_flags[5] = (do_mpfr ? 1 : 0);
}
/* update_ext_api --- update the variables in the API that can change */
void
update_ext_api()
{
api_impl.do_flags[0] = (do_lint ? 1 : 0);
}
/* print_ext_versions --- print the list */
extern void
print_ext_versions(void)
{
struct version_info *p;
for (p = vi_head; p != NULL; p = p->next)
printf("%s\n", p->version);
}
/* valtype2str --- return a printable representation of a value type */
static const char *
valtype2str(awk_valtype_t type)
{
static char buf[100];
// Important: keep in same order as in gawkapi.h!
static const char *values[] = {
"AWK_UNDEFINED",
"AWK_NUMBER",
"AWK_STRING",
"AWK_REGEX",
"AWK_STRNUM",
"AWK_ARRAY",
"AWK_SCALAR",
"AWK_VALUE_COOKIE",
};
if (AWK_UNDEFINED <= type && type <= AWK_VALUE_COOKIE)
return values[(int) type];
sprintf(buf, "unknown type! (%d)", (int) type);
return buf;
}
/* ns_lookup --- correctly build name before looking it up */
static NODE *
ns_lookup(const char *name_space, const char *name, char **fullname)
{
assert(name_space != NULL);
assert(name != NULL);
if (name_space[0] == '\0' || strcmp(name_space, awk_namespace) == 0) {
if (fullname != NULL)
*fullname = estrdup(name, strlen(name));
return lookup(name);
}
size_t len = strlen(name_space) + 2 + strlen(name) + 1;
char *buf;
emalloc(buf, char *, len, "ns_lookup");
sprintf(buf, "%s::%s", name_space, name);
NODE *f = lookup(buf);
if (fullname != NULL)
*fullname = buf;
else
efree((void *) buf);
return f;
}