From a36fb40ac81f2ac6b8283764ce782ec65d18e15c Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 13 Jun 2021 09:31:30 -0700 Subject: [PATCH] !LET --- doc/flow-control.texi | 26 ++++++- src/language/lexer/macro.c | 124 ++++++++++++++++++++++++++----- tests/language/control/define.at | 45 ++++++++++- 3 files changed, 175 insertions(+), 20 deletions(-) diff --git a/doc/flow-control.texi b/doc/flow-control.texi index 27955800e6..0b3cd27866 100644 --- a/doc/flow-control.texi +++ b/doc/flow-control.texi @@ -363,7 +363,8 @@ results visible. @deffn {Macro Function} !CONCAT (arg@dots{}) Expands to the concatenation of all of the arguments. Before concatenation, each quoted string argument is unquoted, as if -@code{!UNQUOTE} were applied. +@code{!UNQUOTE} were applied. This allows for ``token pasting'', +combining two (or more) tokens into a single one: @c Keep these examples in sync with the test for !CONCAT in @c tests/language/control/define.at: @@ -373,6 +374,29 @@ concatenation, each quoted string argument is unquoted, as if !CONCAT(12, 34) @expansion{} 1234 !CONCAT(!NULL, 123) @expansion{} 123 @end example + +@code{!CONCAT} is often used for constructing a series of similar +variable names from a prefix followed by a number and perhaps a +suffix. For example: + +@c Keep these examples in sync with the test for !CONCAT in +@c tests/language/control/define.at: +@example +!CONCAT(x, 0) @expansion{} x0 +!CONCAT(x, 0, y) @expansion{} x0y +@end example + +An identifier token must begin with a letter (or @samp{#} or +@samp{@@}), which means that attempting to use a number as the first +part of an identifier will produce a pair of distinct tokens rather +than a single one. For example: + +@c Keep these examples in sync with the test for !CONCAT in +@c tests/language/control/define.at: +@example +!CONCAT(0, x) @expansion{} 0 x +!CONCAT(0, x, y) @expansion{} 0 xy +@end example @end deffn @deffn {Macro Function} !EVAL (arg) diff --git a/src/language/lexer/macro.c b/src/language/lexer/macro.c index 718f268e65..415b84bade 100644 --- a/src/language/lexer/macro.c +++ b/src/language/lexer/macro.c @@ -31,6 +31,7 @@ #include "libpspp/message.h" #include "libpspp/str.h" #include "libpspp/string-array.h" +#include "libpspp/string-map.h" #include "gl/c-ctype.h" @@ -712,13 +713,15 @@ struct parse_macro_function_ctx 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, @@ -776,12 +779,25 @@ parse_function_arg (struct parse_macro_function_ctx *ctx, return 2; } + 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; + } + } + struct parse_macro_function_ctx subctx = { .input = &ctx->input[i], .n_input = ctx->n_input - i, .nesting_countdown = ctx->nesting_countdown, .macros = ctx->macros, .me = ctx->me, + .vars = ctx->vars, .expand = ctx->expand, }; size_t subinput_consumed; @@ -1035,8 +1051,8 @@ expand_macro_function (struct parse_macro_function_ctx *ctx, 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->expand, &exp); + 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); @@ -1060,6 +1076,7 @@ struct expr_context int nesting_countdown; const struct macro_set *macros; const struct macro_expander *me; + struct string_map *vars; bool *expand; }; @@ -1098,6 +1115,7 @@ macro_evaluate_literal (const struct expr_context *ctx, .nesting_countdown = ctx->nesting_countdown, .macros = ctx->macros, .me = ctx->me, + .vars = ctx->vars, .expand = ctx->expand, }; struct string function_output = DS_EMPTY_INITIALIZER; @@ -1147,8 +1165,8 @@ parse_relational_op (const struct macro_token *mt) static char * macro_evaluate_relational (const struct expr_context *ctx, - const struct macro_token **tokens, - const struct macro_token *end) + const struct macro_token **tokens, + const struct macro_token *end) { const struct macro_token *p = *tokens; char *lhs = macro_evaluate_literal (ctx, &p, end); @@ -1284,12 +1302,14 @@ macro_evaluate_or (const struct expr_context *ctx, 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, bool *expand) + 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); @@ -1321,8 +1341,8 @@ find_ifend_clause (const struct macro_token *p, const struct macro_token *end) 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, bool *expand, - struct macro_tokens *exp) + 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; @@ -1332,7 +1352,8 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens, p++; char *result = macro_evaluate_expression (&p, end - p, - nesting_countdown, macros, me, expand); + nesting_countdown, macros, me, vars, + expand); if (!result) return 0; bool b = strcmp (result, "0"); @@ -1396,16 +1417,54 @@ macro_expand_if (const struct macro_token *tokens, size_t n_tokens, .mts = CONST_CAST (struct macro_token *, start), .n = n, }; - macro_expand (&mts, nesting_countdown, macros, me, expand, exp); + 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 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) { @@ -1415,6 +1474,9 @@ macro_expand (const struct macro_tokens *mts, 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]; @@ -1428,7 +1490,8 @@ macro_expand (const struct macro_tokens *mts, 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]); @@ -1445,7 +1508,8 @@ macro_expand (const struct macro_tokens *mts, const struct macro_tokens *arg = me->args[j]; 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 k = 0; k < arg->n; k++) macro_tokens_add (exp, &arg->mts[k]); @@ -1455,8 +1519,8 @@ macro_expand (const struct macro_tokens *mts, } size_t n = macro_expand_if (&mts->mts[i], mts->n - i, - nesting_countdown, macros, me, expand, - exp); + nesting_countdown, macros, me, vars, + expand, exp); if (n > 0) { i += n - 1; @@ -1464,6 +1528,18 @@ macro_expand (const struct macro_tokens *mts, } } + 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) { struct macro_expander *subme; @@ -1478,7 +1554,7 @@ macro_expand (const struct macro_tokens *mts, { i += retval - 1; macro_expand (&subme->macro->body, nesting_countdown - 1, macros, - subme, expand, exp); + subme, NULL, expand, exp); macro_expander_destroy (subme); continue; } @@ -1498,6 +1574,7 @@ macro_expand (const struct macro_tokens *mts, .nesting_countdown = nesting_countdown, .macros = macros, .me = me, + .vars = vars, .expand = expand, }; struct string function_output = DS_EMPTY_INITIALIZER; @@ -1513,6 +1590,15 @@ macro_expand (const struct macro_tokens *mts, 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; + } + if (ss_equals_case (token->string, ss_cstr ("!onexpand"))) *expand = true; else if (ss_equals_case (token->string, ss_cstr ("!offexpand"))) @@ -1520,6 +1606,8 @@ macro_expand (const struct macro_tokens *mts, else macro_tokens_add (exp, mt); } + if (vars == &own_vars) + string_map_destroy (&own_vars); } void @@ -1535,7 +1623,7 @@ macro_expander_get_expansion (struct macro_expander *me, struct macro_tokens *ex 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"); diff --git a/tests/language/control/define.at b/tests/language/control/define.at index 1f57851732..0571303da1 100644 --- a/tests/language/control/define.at +++ b/tests/language/control/define.at @@ -384,12 +384,20 @@ PSPP_CHECK_MACRO_EXPANSION([!CONCAT], !CONCAT('x', 'y'). !CONCAT(12, 34). !CONCAT(!NULL, 123). +!CONCAT(x, 0). +!CONCAT(x, 0, y). +!CONCAT(0, x). +!CONCAT(0, x, y). !ENDDEFINE], [!c.], [xy. xy. 1234. -123.]) +123. +x0. +x0y. +0 x. +0 xy.]) dnl Keep this test in sync with the examples for !EVAL in the manual. PSPP_CHECK_MACRO_EXPANSION([!EVAL], @@ -713,3 +721,38 @@ true true false done AT_CLEANUP +AT_SETUP([macro !LET]) +AT_KEYWORDS([let]) +AT_DATA([define.sps], [dnl +DEFINE !macro(!POS !CMDEND) +!LET !v1 = !CONCAT('x',!1,'y') +!LET !v2 = !QUOTE(!v1) +!LET !v3 = (!LENGTH(!1) = 1) +!LET !v4 = (!SUBSTR(!1, 3) = !NULL) +v1=!v1. +v2=!v2. +v3=!v3. +v4=!v4. +!ENDDEFINE. +DEBUG EXPAND. +!macro 0. +!macro. +!macro xyzzy. +]) +AT_CHECK([pspp --testing-mode define.sps], [0], [dnl +v1 = x0y. +v2 = 'x0y'. +v3 = 1. +v4 = 1. + +v1 = xy. +v2 = 'xy'. +v3 = 0. +v4 = 1. + +v1 = xxyzzyy. +v2 = 'xxyzzyy'. +v3 = 0. +v4 = 0. +]) +AT_CLEANUP \ No newline at end of file -- 2.30.2