#include "language/lexer/macro.h"
+#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include "language/lexer/segment.h"
#include "language/lexer/scan.h"
#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
#include "libpspp/i18n.h"
#include "libpspp/message.h"
#include "libpspp/str.h"
+#include "libpspp/string-array.h"
+#include "libpspp/string-map.h"
+
+#include "gl/c-ctype.h"
+#include "gl/ftoastr.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
};
struct state state = {
- .segmenter = SEGMENTER_INIT (mode),
+ .segmenter = segmenter_init (mode, true),
.body = src,
};
struct state saved = state;
{
if (token->type != SCAN_SKIP)
{
+ printf ("error\n");
/* XXX report error */
}
}
*/
struct parse_macro_function_ctx
{
- struct macro_token *input;
+ const struct macro_token *input;
size_t n_input;
int nesting_countdown;
const struct macro_set *macros;
const struct macro_expander *me;
+ struct string_map *vars;
bool *expand;
};
static void
macro_expand (const struct macro_tokens *,
int nesting_countdown, const struct macro_set *,
- const struct macro_expander *, bool *expand, struct macro_tokens *exp);
+ const struct macro_expander *, struct string_map *vars,
+ bool *expand, struct macro_tokens *exp);
static bool
expand_macro_function (struct parse_macro_function_ctx *ctx,
- struct macro_token *output,
- size_t *input_consumed);
+ struct string *output, size_t *input_consumed);
+
+/* Returns true if the pair of tokens starting at offset OFS within MTS are !*,
+ false otherwise. */
+static bool
+is_bang_star (const struct macro_token *mts, size_t n, size_t ofs)
+{
+ return (ofs + 1 < n
+ && mts[ofs].token.type == T_MACRO_ID
+ && ss_equals (mts[ofs].token.string, ss_cstr ("!"))
+ && mts[ofs + 1].token.type == T_ASTERISK);
+}
static size_t
parse_function_arg (struct parse_macro_function_ctx *ctx,
- size_t i, struct macro_token *farg)
+ size_t i, struct string *farg)
{
- struct macro_token *tokens = ctx->input;
+ const struct macro_token *tokens = ctx->input;
const struct token *token = &tokens[i].token;
if (token->type == T_MACRO_ID)
{
{
size_t param_idx = param - ctx->me->macro->params;
const struct macro_tokens *marg = ctx->me->args[param_idx];
- if (marg->n == 1)
- macro_token_copy (farg, &marg->mts[0]);
- else
+ for (size_t i = 0; i < marg->n; i++)
+ {
+ if (i)
+ ds_put_byte (farg, ' ');
+ ds_put_substring (farg, marg->mts[i].representation);
+ }
+ return 1;
+ }
+
+ if (is_bang_star (ctx->input, ctx->n_input, i))
+ {
+ for (size_t i = 0; i < ctx->me->macro->n_params; i++)
{
- struct string s = DS_EMPTY_INITIALIZER;
- for (size_t i = 0; i < marg->n; i++)
+ if (!ctx->me->macro->params[i].positional)
+ break;
+
+ const struct macro_tokens *marg = ctx->me->args[i];
+ for (size_t j = 0; j < marg->n; j++)
{
- if (i)
- ds_put_byte (&s, ' ');
- ds_put_substring (&s, marg->mts[i].representation);
+ if (i || j)
+ ds_put_byte (farg, ' ');
+ ds_put_substring (farg, marg->mts[j].representation);
}
+ }
+ return 2;
+ }
- struct substring s_copy;
- ss_alloc_substring (&s_copy, s.ss);
-
- *farg = (struct macro_token) {
- .token = { .type = T_MACRO_ID, .string = s.ss },
- .representation = s_copy,
- };
+ if (ctx->vars)
+ {
+ const char *value = string_map_find__ (ctx->vars,
+ token->string.string,
+ token->string.length);
+ if (value)
+ {
+ ds_put_cstr (farg, value);
+ return 1;
}
- return 1;
}
struct parse_macro_function_ctx subctx = {
.nesting_countdown = ctx->nesting_countdown,
.macros = ctx->macros,
.me = ctx->me,
+ .vars = ctx->vars,
.expand = ctx->expand,
};
size_t subinput_consumed;
return subinput_consumed;
}
- macro_token_copy (farg, &tokens[i]);
+ ds_put_substring (farg, tokens[i].representation);
return 1;
}
static bool
parse_macro_function (struct parse_macro_function_ctx *ctx,
- struct macro_tokens *args,
+ struct string_array *args,
struct substring function,
int min_args, int max_args,
size_t *input_consumed)
{
- struct macro_token *tokens = ctx->input;
+ const struct macro_token *tokens = ctx->input;
size_t n_tokens = ctx->n_input;
if (!n_tokens
|| tokens[0].token.type != T_MACRO_ID
- || !ss_equals_case (tokens[0].token.string, function))
+ || !ss_equals_case (tokens[0].token.string, function)) /* XXX abbrevs allowed */
return false;
if (n_tokens < 2 || tokens[1].token.type != T_LPAREN)
return false;
}
- *args = (struct macro_tokens) { .n = 0 };
+ string_array_init (args);
for (size_t i = 2;; )
{
return true;
}
- i += parse_function_arg (ctx, i, macro_tokens_add_uninit (args));
+ struct string s = DS_EMPTY_INITIALIZER;
+ i += parse_function_arg (ctx, i, &s);
if (i >= n_tokens)
- goto unexpected_end;
+ {
+ ds_destroy (&s);
+ goto unexpected_end;
+ }
+ string_array_append_nocopy (args, ds_steal_cstr (&s));
if (tokens[i].token.type == T_COMMA)
i++;
function.string);
/* Fall through. */
error:
- macro_tokens_uninit (args);
+ string_array_destroy (args);
return false;
}
+static bool
+unquote_string (const char *s, struct string *content)
+{
+ struct string_lexer slex;
+ string_lexer_init (&slex, s, strlen (s), SEG_MODE_INTERACTIVE /* XXX */);
+
+ struct token token1;
+ if (!string_lexer_next (&slex, &token1))
+ return false;
+
+ if (token1.type != T_STRING)
+ {
+ token_uninit (&token1);
+ return false;
+ }
+
+ struct token token2;
+ if (string_lexer_next (&slex, &token2))
+ {
+ token_uninit (&token1);
+ token_uninit (&token2);
+ return false;
+ }
+
+ ds_put_substring (content, token1.string);
+ token_uninit (&token1);
+ return true;
+}
+
+static const char *
+unquote_string_in_place (const char *s, struct string *tmp)
+{
+ ds_init_empty (tmp);
+ return unquote_string (s, tmp) ? ds_cstr (tmp) : s;
+}
+
+static bool
+parse_integer (const char *s, int *np)
+{
+ errno = 0;
+
+ char *tail;
+ long int n = strtol (s, &tail, 10);
+ *np = n < INT_MIN ? INT_MIN : n > INT_MAX ? INT_MAX : n;
+ tail += strspn (tail, CC_SPACES);
+ return *tail == '\0' && errno != ERANGE && n == *np;
+}
+
static bool
expand_macro_function (struct parse_macro_function_ctx *ctx,
- struct macro_token *output,
+ struct string *output,
size_t *input_consumed)
{
- struct macro_tokens args;
+ struct string_array args;
if (parse_macro_function (ctx, &args, ss_cstr ("!length"), 1, 1,
input_consumed))
- {
- size_t length = args.mts[0].representation.length;
- *output = (struct macro_token) {
- .token = { .type = T_POS_NUM, .number = length },
- .representation = ss_cstr (xasprintf ("%zu", length)),
- };
- }
+ ds_put_format (output, "%zu", strlen (args.strings[0]));
else if (parse_macro_function (ctx, &args, ss_cstr ("!blanks"), 1, 1,
input_consumed))
{
- /* XXX this isn't right, it might be a character string containing a
- positive integer, e.g. via !CONCAT. */
- if (args.mts[0].token.type != T_POS_NUM)
+ int n;
+ if (!parse_integer (args.strings[0], &n))
{
- printf ("argument to !BLANKS must be positive integer\n");
- macro_tokens_uninit (&args);
+ printf ("argument to !BLANKS must be non-negative integer (not \"%s\")\n", args.strings[0]);
+ string_array_destroy (&args);
return false;
}
- struct string s = DS_EMPTY_INITIALIZER;
- ds_put_byte_multiple (&s, ' ', args.mts[0].token.number);
-
- struct substring s_copy;
- ss_alloc_substring (&s_copy, s.ss);
-
- *output = (struct macro_token) {
- .token = { .type = T_ID, .string = s.ss },
- .representation = s_copy,
- };
+ ds_put_byte_multiple (output, ' ', n);
}
else if (parse_macro_function (ctx, &args, ss_cstr ("!concat"), 1, INT_MAX,
input_consumed))
{
- struct string s;
- bool all_strings = true;
for (size_t i = 0; i < args.n; i++)
+ if (!unquote_string (args.strings[i], output))
+ ds_put_cstr (output, args.strings[i]);
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!head"), 1, 1,
+ input_consumed))
+ {
+ struct string tmp;
+ const char *s = unquote_string_in_place (args.strings[0], &tmp);
+
+ struct macro_tokens mts = { .n = 0 };
+ macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+ if (mts.n > 0)
+ ds_put_substring (output, mts.mts[0].representation);
+ macro_tokens_uninit (&mts);
+ ds_destroy (&tmp);
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!index"), 2, 2,
+ input_consumed))
+ {
+ const char *haystack = args.strings[0];
+ const char *needle = strstr (haystack, args.strings[1]);
+ ds_put_format (output, "%zu", needle ? needle - haystack + 1 : 0);
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!quote"), 1, 1,
+ input_consumed))
+ {
+ if (unquote_string (args.strings[0], NULL))
+ ds_put_cstr (output, args.strings[0]);
+ else
{
- if (args.mts[i].token.type == T_STRING)
- ds_put_substring (&s, args.mts[i].token.string);
- else
+ ds_extend (output, strlen (args.strings[0]) + 2);
+ ds_put_byte (output, '\'');
+ for (const char *p = args.strings[0]; *p; p++)
{
- all_strings = false;
- ds_put_substring (&s, args.mts[i].representation);
+ if (*p == '\'')
+ ds_put_byte (output, '\'');
+ ds_put_byte (output, *p);
}
+ ds_put_byte (output, '\'');
}
-
- if (all_strings)
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!substr"), 2, 3,
+ input_consumed))
+ {
+ int start;
+ if (!parse_integer (args.strings[1], &start) || start < 1)
{
- *output = (struct macro_token) {
- .token = { .type = T_STRING, .string = s.ss },
- };
- output->representation = ss_cstr (token_to_string (&output->token));
+ printf ("second argument to !SUBSTR must be positive integer (not \"%s\")\n", args.strings[1]);
+ string_array_destroy (&args);
+ return false;
}
- else
+
+ int count = INT_MAX;
+ if (args.n > 2 && (!parse_integer (args.strings[2], &count) || count < 0))
{
- *output = (struct macro_token) {
- .token = { .type = T_MACRO_ID /*XXX*/, .string = s.ss },
- };
- ss_alloc_substring (&output->representation, s.ss);
+ printf ("third argument to !SUBSTR must be non-negative integer (not \"%s\")\n", args.strings[1]);
+ string_array_destroy (&args);
+ return false;
}
+
+ struct substring s = ss_cstr (args.strings[0]);
+ ds_put_substring (output, ss_substr (s, start - 1, count));
}
- else if (parse_macro_function (ctx, &args, ss_cstr ("!quote"), 1, 1,
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!tail"), 1, 1,
input_consumed))
{
- if (args.mts[0].token.type == T_STRING)
- macro_token_copy (output, &args.mts[0]);
- else
+ struct string tmp;
+ const char *s = unquote_string_in_place (args.strings[0], &tmp);
+
+ struct macro_tokens mts = { .n = 0 };
+ macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+ if (mts.n > 1)
{
- *output = (struct macro_token) { .token = { .type = T_STRING } };
- ss_alloc_substring (&output->token.string, args.mts[0].representation);
- output->representation = ss_cstr (token_to_string (&output->token));
+ struct macro_tokens tail = { .mts = mts.mts + 1, .n = mts.n - 1 };
+ macro_tokens_to_representation (&tail, output);
}
+ macro_tokens_uninit (&mts);
+ ds_destroy (&tmp);
}
else if (parse_macro_function (ctx, &args, ss_cstr ("!unquote"), 1, 1,
input_consumed))
{
- if (args.mts[0].token.type == T_STRING)
+ if (!unquote_string (args.strings[0], output))
+ ds_put_cstr (output, args.strings[0]);
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!upcase"), 1, 1,
+ input_consumed))
+ {
+ struct string tmp;
+ const char *s = unquote_string_in_place (args.strings[0], &tmp);
+ char *upper = utf8_to_upper (s);
+ ds_put_cstr (output, upper);
+ free (upper);
+ ds_destroy (&tmp);
+ }
+ else if (parse_macro_function (ctx, &args, ss_cstr ("!eval"), 1, 1,
+ input_consumed))
+ {
+ struct macro_tokens mts = { .n = 0 };
+ macro_tokens_from_string (&mts, ss_cstr (args.strings[0]),
+ SEG_MODE_INTERACTIVE /* XXX */);
+ struct macro_tokens exp = { .n = 0 };
+ macro_expand (&mts, ctx->nesting_countdown - 1, ctx->macros, ctx->me,
+ ctx->vars, ctx->expand, &exp);
+ macro_tokens_to_representation (&exp, output);
+ macro_tokens_uninit (&exp);
+ macro_tokens_uninit (&mts);
+ }
+ else if (ctx->n_input > 0
+ && ctx->input[0].token.type == T_MACRO_ID
+ && ss_equals_case (ctx->input[0].token.string, ss_cstr ("!null")))
+ {
+ *input_consumed = 1;
+ return true;
+ }
+ else
+ return false;
+
+ string_array_destroy (&args);
+ return true;
+}
+
+struct expr_context
+ {
+ int nesting_countdown;
+ const struct macro_set *macros;
+ const struct macro_expander *me;
+ struct string_map *vars;
+ bool *expand;
+ };
+
+static char *macro_evaluate_or (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end);
+
+static char *
+macro_evaluate_literal (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end)
+{
+ const struct macro_token *p = *tokens;
+ if (p >= end)
+ return NULL;
+ if (p->token.type == T_LPAREN)
+ {
+ p++;
+ char *value = macro_evaluate_or (ctx, &p, end);
+ if (!value)
+ return NULL;
+ if (p >= end || p->token.type != T_RPAREN)
{
- *output = (struct macro_token) { .token = { .type = T_MACRO_ID } };
- ss_alloc_substring (&output->token.string, args.mts[0].token.string);
- output->representation = ss_cstr (token_to_string (&output->token));
+ free (value);
+ printf ("expecting ')' in macro expression\n");
+ return NULL;
}
- else
- macro_token_copy (output, &args.mts[0]);
+ p++;
+ *tokens = p;
+ return value;
}
- else
+
+ struct parse_macro_function_ctx fctx = {
+ .input = p,
+ .n_input = end - p,
+ .nesting_countdown = ctx->nesting_countdown,
+ .macros = ctx->macros,
+ .me = ctx->me,
+ .vars = ctx->vars,
+ .expand = ctx->expand,
+ };
+ struct string function_output = DS_EMPTY_INITIALIZER;
+ size_t function_consumed = parse_function_arg (&fctx, 0, &function_output);
+ struct string unquoted = DS_EMPTY_INITIALIZER;
+ if (unquote_string (ds_cstr (&function_output), &unquoted))
+ {
+ ds_swap (&function_output, &unquoted);
+ ds_destroy (&unquoted);
+ }
+ *tokens = p + function_consumed;
+ return ds_steal_cstr (&function_output);
+}
+
+/* Returns true if MT is valid as a macro operator. Only operators written as
+ symbols (e.g. <>) are usable in macro expressions, not operator written as
+ letters (e.g. EQ). */
+static bool
+is_macro_operator (const struct macro_token *mt)
+{
+ return (mt->representation.length > 0
+ && !c_isalpha (mt->representation.string[0]));
+}
+
+static enum token_type
+parse_relational_op (const struct macro_token *mt)
+{
+ switch (mt->token.type)
+ {
+ case T_EQUALS:
+ return T_EQ;
+
+ case T_NE:
+ case T_LT:
+ case T_GT:
+ case T_LE:
+ case T_GE:
+ return is_macro_operator (mt) ? mt->token.type : T_STOP;
+
+ case T_MACRO_ID:
+ return (ss_equals_case (mt->token.string, ss_cstr ("!EQ")) ? T_EQ
+ : ss_equals_case (mt->token.string, ss_cstr ("!NE")) ? T_NE
+ : ss_equals_case (mt->token.string, ss_cstr ("!LT")) ? T_LT
+ : ss_equals_case (mt->token.string, ss_cstr ("!GT")) ? T_GT
+ : ss_equals_case (mt->token.string, ss_cstr ("!LE")) ? T_LE
+ : ss_equals_case (mt->token.string, ss_cstr ("!GE")) ? T_GE
+ : T_STOP);
+
+ default:
+ return T_STOP;
+ }
+}
+
+static char *
+macro_evaluate_relational (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end)
+{
+ const struct macro_token *p = *tokens;
+ char *lhs = macro_evaluate_literal (ctx, &p, end);
+ if (!lhs)
+ return NULL;
+
+ enum token_type op = p >= end ? T_STOP : parse_relational_op (p);
+ if (op == T_STOP)
+ {
+ *tokens = p;
+ return lhs;
+ }
+ p++;
+
+ char *rhs = macro_evaluate_literal (ctx, &p, end);
+ if (!rhs)
+ {
+ free (lhs);
+ return NULL;
+ }
+
+ struct string lhs_tmp, rhs_tmp;
+ int cmp = strcmp/*XXX*/ (unquote_string_in_place (lhs, &lhs_tmp),
+ unquote_string_in_place (rhs, &rhs_tmp));
+ ds_destroy (&lhs_tmp);
+ ds_destroy (&rhs_tmp);
+
+ free (lhs);
+ free (rhs);
+
+ bool b = (op == T_EQUALS || op == T_EQ ? !cmp
+ : op == T_NE ? cmp
+ : op == T_LT ? cmp < 0
+ : op == T_GT ? cmp > 0
+ : op == T_LE ? cmp <= 0
+ :/*op == T_GE*/cmp >= 0);
+
+ *tokens = p;
+ return xstrdup (b ? "1" : "0");
+}
+
+static char *
+macro_evaluate_not (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end)
+{
+ const struct macro_token *p = *tokens;
+
+ unsigned int negations = 0;
+ while (p < end
+ && (ss_equals_case (p->representation, ss_cstr ("!NOT"))
+ || ss_equals (p->representation, ss_cstr ("~"))))
+ {
+ p++;
+ negations++;
+ }
+
+ char *operand = macro_evaluate_relational (ctx, &p, end);
+ if (!operand || !negations)
+ {
+ *tokens = p;
+ return operand;
+ }
+
+ bool b = strcmp (operand, "0") ^ (negations & 1);
+ free (operand);
+ *tokens = p;
+ return xstrdup (b ? "1" : "0");
+}
+
+static char *
+macro_evaluate_and (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end)
+{
+ const struct macro_token *p = *tokens;
+ char *lhs = macro_evaluate_not (ctx, &p, end);
+ if (!lhs)
+ return NULL;
+
+ while (p < end
+ && (ss_equals_case (p->representation, ss_cstr ("!AND"))
+ || ss_equals (p->representation, ss_cstr ("&"))))
+ {
+ p++;
+ char *rhs = macro_evaluate_not (ctx, &p, end);
+ if (!rhs)
+ {
+ free (lhs);
+ return NULL;
+ }
+
+ bool b = strcmp (lhs, "0") && strcmp (rhs, "0");
+ free (lhs);
+ free (rhs);
+ lhs = xstrdup (b ? "1" : "0");
+ }
+ *tokens = p;
+ return lhs;
+}
+
+static char *
+macro_evaluate_or (const struct expr_context *ctx,
+ const struct macro_token **tokens,
+ const struct macro_token *end)
+{
+ const struct macro_token *p = *tokens;
+ char *lhs = macro_evaluate_and (ctx, &p, end);
+ if (!lhs)
+ return NULL;
+
+ while (p < end
+ && (ss_equals_case (p->representation, ss_cstr ("!OR"))
+ || ss_equals (p->representation, ss_cstr ("|"))))
+ {
+ p++;
+ char *rhs = macro_evaluate_and (ctx, &p, end);
+ if (!rhs)
+ {
+ free (lhs);
+ return NULL;
+ }
+
+ bool b = strcmp (lhs, "0") || strcmp (rhs, "0");
+ free (lhs);
+ free (rhs);
+ lhs = xstrdup (b ? "1" : "0");
+ }
+ *tokens = p;
+ return lhs;
+}
+
+static char *
+macro_evaluate_expression (const struct macro_token **tokens, size_t n_tokens,
+ int nesting_countdown, const struct macro_set *macros,
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand)
+{
+ const struct expr_context ctx = {
+ .nesting_countdown = nesting_countdown,
+ .macros = macros,
+ .me = me,
+ .vars = vars,
+ .expand = expand,
+ };
+ return macro_evaluate_or (&ctx, tokens, *tokens + n_tokens);
+}
+
+static bool
+macro_evaluate_number (const struct macro_token **tokens, size_t n_tokens,
+ int nesting_countdown, const struct macro_set *macros,
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand, double *number)
+{
+ char *s = macro_evaluate_expression (tokens, n_tokens, nesting_countdown,
+ macros, me, vars, expand);
+ if (!s)
return false;
- macro_tokens_uninit (&args);
+ struct macro_tokens mts = { .n = 0 };
+ macro_tokens_from_string (&mts, ss_cstr (s), SEG_MODE_INTERACTIVE /* XXX */);
+ if (mts.n != 1 || !token_is_number (&mts.mts[0].token))
+ {
+ macro_tokens_print (&mts, stdout);
+ printf ("expression must evaluate to a number (not %s)\n", s);
+ free (s);
+ macro_tokens_uninit (&mts);
+ return false;
+ }
+
+ *number = token_number (&mts.mts[0].token);
+ free (s);
+ macro_tokens_uninit (&mts);
return true;
}
+static const struct macro_token *
+find_ifend_clause (const struct macro_token *p, const struct macro_token *end)
+{
+ size_t nesting = 0;
+ for (; p < end; p++)
+ {
+ if (p->token.type != T_MACRO_ID)
+ continue;
+
+ if (ss_equals_case (p->token.string, ss_cstr ("!IF")))
+ nesting++;
+ else if (ss_equals_case (p->token.string, ss_cstr ("!IFEND")))
+ {
+ if (!nesting)
+ return p;
+ nesting--;
+ }
+ else if (ss_equals_case (p->token.string, ss_cstr ("!ELSE")) && !nesting)
+ return p;
+ }
+ return NULL;
+}
+
+static size_t
+macro_expand_if (const struct macro_token *tokens, size_t n_tokens,
+ int nesting_countdown, const struct macro_set *macros,
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand, struct macro_tokens *exp)
+{
+ const struct macro_token *p = tokens;
+ const struct macro_token *end = tokens + n_tokens;
+
+ if (p >= end || !ss_equals_case (p->token.string, ss_cstr ("!IF")))
+ return 0;
+
+ p++;
+ char *result = macro_evaluate_expression (&p, end - p,
+ nesting_countdown, macros, me, vars,
+ expand);
+ if (!result)
+ return 0;
+ bool b = strcmp (result, "0");
+ free (result);
+
+ if (p >= end
+ || p->token.type != T_MACRO_ID
+ || !ss_equals_case (p->token.string, ss_cstr ("!THEN")))
+ {
+ printf ("!THEN expected\n");
+ return 0;
+ }
+
+ const struct macro_token *start_then = p + 1;
+ const struct macro_token *end_then = find_ifend_clause (start_then, end);
+ if (!end_then)
+ {
+ printf ("!ELSE or !IFEND expected\n");
+ return 0;
+ }
+
+ const struct macro_token *start_else, *end_if;
+ if (ss_equals_case (end_then->token.string, ss_cstr ("!ELSE")))
+ {
+ start_else = end_then + 1;
+ end_if = find_ifend_clause (start_else, end);
+ if (!end_if
+ || !ss_equals_case (end_if->token.string, ss_cstr ("!IFEND")))
+ {
+ printf ("!IFEND expected\n");
+ return 0;
+ }
+ }
+ else
+ {
+ start_else = NULL;
+ end_if = end_then;
+ }
+
+ const struct macro_token *start;
+ size_t n;
+ if (b)
+ {
+ start = start_then;
+ n = end_then - start_then;
+ }
+ else if (start_else)
+ {
+ start = start_else;
+ n = end_if - start_else;
+ }
+ else
+ {
+ start = NULL;
+ n = 0;
+ }
+
+ if (n)
+ {
+ struct macro_tokens mts = {
+ .mts = CONST_CAST (struct macro_token *, start),
+ .n = n,
+ };
+ macro_expand (&mts, nesting_countdown, macros, me, vars, expand, exp);
+ }
+ return (end_if + 1) - tokens;
+}
+
+static size_t
+macro_parse_let (const struct macro_token *tokens, size_t n_tokens,
+ int nesting_countdown, const struct macro_set *macros,
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand)
+{
+ const struct macro_token *p = tokens;
+ const struct macro_token *end = tokens + n_tokens;
+
+ if (p >= end || !ss_equals_case (p->token.string, ss_cstr ("!LET")))
+ return 0;
+ p++;
+
+ if (p >= end || p->token.type != T_MACRO_ID)
+ {
+ printf ("expected macro variable name following !LET\n");
+ return 0;
+ }
+ const struct substring var_name = p->token.string;
+ p++;
+
+ if (p >= end || p->token.type != T_EQUALS)
+ {
+ printf ("expected = following !LET\n");
+ return 0;
+ }
+ p++;
+
+ char *value = macro_evaluate_expression (&p, end - p,
+ nesting_countdown, macros, me, vars,
+ expand);
+ if (!value)
+ return 0;
+
+ string_map_replace_nocopy (vars, ss_xstrdup (var_name), value);
+ return p - tokens;
+}
+
+static const struct macro_token *
+find_doend (const struct macro_token *p, const struct macro_token *end)
+{
+ size_t nesting = 0;
+ for (; p < end; p++)
+ {
+ if (p->token.type != T_MACRO_ID)
+ continue;
+
+ if (ss_equals_case (p->token.string, ss_cstr ("!DO")))
+ nesting++;
+ else if (ss_equals_case (p->token.string, ss_cstr ("!DOEND")))
+ {
+ if (!nesting)
+ return p;
+ nesting--;
+ }
+ }
+ printf ("missing !DOEND\n");
+ return NULL;
+}
+
+static size_t
+macro_expand_do (const struct macro_token *tokens, size_t n_tokens,
+ int nesting_countdown, const struct macro_set *macros,
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand, struct macro_tokens *exp)
+{
+ const struct macro_token *p = tokens;
+ const struct macro_token *end = tokens + n_tokens;
+
+ if (p >= end || !ss_equals_case (p->token.string, ss_cstr ("!DO")))
+ return 0;
+ p++;
+
+ if (p >= end || p->token.type != T_MACRO_ID)
+ {
+ printf ("expected macro variable name following !DO\n");
+ return 0;
+ }
+ const struct substring var_name = p->token.string;
+ p++;
+
+ if (p < end && p->token.type == T_MACRO_ID
+ && ss_equals_case (p->token.string, ss_cstr ("!IN")))
+ {
+ p++;
+ char *list = macro_evaluate_expression (&p, end - p,
+ nesting_countdown, macros, me, vars,
+ expand);
+ if (!list)
+ return 0;
+
+ struct macro_tokens items = { .n = 0 };
+ macro_tokens_from_string (&items, ss_cstr (list),
+ SEG_MODE_INTERACTIVE /* XXX */);
+ free (list);
+
+ const struct macro_token *do_end = find_doend (p, end);
+ if (!do_end)
+ {
+ macro_tokens_uninit (&items);
+ return 0;
+ }
+
+ const struct macro_tokens inner = {
+ .mts = CONST_CAST (struct macro_token *, p),
+ .n = do_end - p
+ };
+ for (size_t i = 0; i < items.n; i++)
+ {
+ string_map_replace_nocopy (vars, ss_xstrdup (var_name),
+ ss_xstrdup (items.mts[i].representation));
+ macro_expand (&inner, nesting_countdown, macros,
+ me, vars, expand, exp);
+ }
+ return do_end - tokens + 1;
+ }
+ else if (p < end && p->token.type == T_EQUALS)
+ {
+ p++;
+ double first;
+ if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+ vars, expand, &first))
+ return 0;
+
+ if (p >= end || p->token.type != T_MACRO_ID
+ || !ss_equals_case (p->token.string, ss_cstr ("!TO")))
+ {
+ printf ("expecting !TO\n");
+ return 0;
+ }
+ p++;
+
+ double last;
+ if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+ vars, expand, &last))
+ return 0;
+
+ double by = 1.0;
+ if (p < end && p->token.type == T_MACRO_ID
+ && ss_equals_case (p->token.string, ss_cstr ("!BY")))
+ {
+ p++;
+ if (!macro_evaluate_number (&p, end - p, nesting_countdown, macros, me,
+ vars, expand, &by))
+ return 0;
+
+ if (by == 0.0)
+ {
+ printf ("!BY value cannot be zero\n");
+ return 0;
+ }
+ }
+
+ const struct macro_token *do_end = find_doend (p, end);
+ if (!do_end)
+ return 0;
+ const struct macro_tokens inner = {
+ .mts = CONST_CAST (struct macro_token *, p),
+ .n = do_end - p
+ };
+
+ if ((by > 0 && first <= last) || (by < 0 && first >= last))
+ for (double index = first;
+ by > 0 ? (index <= last) : (index >= last);
+ index += by)
+ {
+ char index_s[DBL_BUFSIZE_BOUND];
+ c_dtoastr (index_s, sizeof index_s, 0, 0, index);
+ string_map_replace_nocopy (vars, ss_xstrdup (var_name),
+ xstrdup (index_s));
+ macro_expand (&inner, nesting_countdown, macros,
+ me, vars, expand, exp);
+ }
+
+ return do_end - tokens + 1;
+ }
+ else
+ {
+ printf ("expecting = or !IN in !DO loop\n");
+ return 0;
+ }
+}
+
static void
macro_expand (const struct macro_tokens *mts,
int nesting_countdown, const struct macro_set *macros,
- const struct macro_expander *me, bool *expand,
- struct macro_tokens *exp)
+ const struct macro_expander *me, struct string_map *vars,
+ bool *expand, struct macro_tokens *exp)
{
if (nesting_countdown <= 0)
{
return;
}
+ struct string_map own_vars = STRING_MAP_INITIALIZER (own_vars);
+ if (!vars)
+ vars = &own_vars;
for (size_t i = 0; i < mts->n; i++)
{
const struct macro_token *mt = &mts->mts[i];
const struct macro_tokens *arg = me->args[param - me->macro->params];
//macro_tokens_print (arg, stdout);
if (*expand && param->expand_arg)
- macro_expand (arg, nesting_countdown, macros, NULL, expand, exp);
+ macro_expand (arg, nesting_countdown, macros, NULL, NULL,
+ expand, exp);
else
for (size_t i = 0; i < arg->n; i++)
macro_tokens_add (exp, &arg->mts[i]);
continue;
}
+
+ if (is_bang_star (mts->mts, mts->n, i))
+ {
+ for (size_t j = 0; j < me->macro->n_params; j++)
+ {
+ const struct macro_param *param = &me->macro->params[j];
+ if (!param->positional)
+ break;
+
+ const struct macro_tokens *arg = me->args[j];
+ if (*expand && param->expand_arg)
+ macro_expand (arg, nesting_countdown, macros, NULL, NULL,
+ expand, exp);
+ else
+ for (size_t k = 0; k < arg->n; k++)
+ macro_tokens_add (exp, &arg->mts[k]);
+ }
+ i++;
+ continue;
+ }
+
+ size_t n = macro_expand_if (&mts->mts[i], mts->n - i,
+ nesting_countdown, macros, me, vars,
+ expand, exp);
+ if (n > 0)
+ {
+ i += n - 1;
+ continue;
+ }
+ }
+
+ if (token->type == T_MACRO_ID && vars)
+ {
+ const char *value = string_map_find__ (vars, token->string.string,
+ token->string.length);
+ if (value)
+ {
+ macro_tokens_from_string (exp, ss_cstr (value),
+ SEG_MODE_INTERACTIVE /* XXX */);
+ continue;
+ }
}
if (*expand)
{
i += retval - 1;
macro_expand (&subme->macro->body, nesting_countdown - 1, macros,
- subme, expand, exp);
+ subme, NULL, expand, exp);
macro_expander_destroy (subme);
continue;
}
continue;
}
- /* Maybe each arg should just be a string, either a quoted string or a
- non-quoted string containing tokens. */
struct parse_macro_function_ctx ctx = {
.input = &mts->mts[i],
.n_input = mts->n - i,
.nesting_countdown = nesting_countdown,
.macros = macros,
.me = me,
+ .vars = vars,
.expand = expand,
};
- struct macro_token function_output;
+ struct string function_output = DS_EMPTY_INITIALIZER;
size_t function_consumed;
if (expand_macro_function (&ctx, &function_output, &function_consumed))
{
i += function_consumed - 1;
- if (function_output.token.type == T_MACRO_ID)
- macro_tokens_from_string (exp, function_output.token.string,
- SEG_MODE_INTERACTIVE /* XXX */);
- else
- macro_tokens_add (exp, &function_output);
- macro_token_uninit (&function_output);
+ macro_tokens_from_string (exp, function_output.ss,
+ SEG_MODE_INTERACTIVE /* XXX */);
+ ds_destroy (&function_output);
continue;
}
+ size_t n = macro_parse_let (&mts->mts[i], mts->n - i,
+ nesting_countdown, macros, me, vars,
+ expand);
+ if (n > 0)
+ {
+ i += n - 1;
+ continue;
+ }
+
+ n = macro_expand_do (&mts->mts[i], mts->n - i,
+ nesting_countdown, macros, me, vars,
+ expand, exp);
+ if (n > 0)
+ {
+ i += n - 1;
+ continue;
+ }
+
if (ss_equals_case (token->string, ss_cstr ("!onexpand")))
*expand = true;
else if (ss_equals_case (token->string, ss_cstr ("!offexpand")))
else
macro_tokens_add (exp, mt);
}
+ if (vars == &own_vars)
+ string_map_destroy (&own_vars);
}
void
bool expand = true;
macro_expand (&me->macro->body, settings_get_mnest (),
- me->macros, me, &expand, exp);
+ me->macros, me, NULL, &expand, exp);
#if 0
printf ("expansion:\n");