* possibly empty one: word"", wo''rd etc. */
smallint has_quoted_part;
smallint has_empty_slot;
- smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
} o_string;
enum {
EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */
* by prepending \ to *, ?, [, \ */
EXP_FLAG_ESC_GLOB_CHARS = 0x1,
};
-enum {
- MAYBE_ASSIGNMENT = 0,
- DEFINITELY_ASSIGNMENT = 1,
- NOT_ASSIGNMENT = 2,
- /* Not an assignment, but next word may be: "if v=xyz cmd;" */
- WORD_IS_KEYWORD = 3,
-};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
struct command *command;
/* last redirect in command->redirects list */
struct redir_struct *pending_redirect;
+ o_string word;
#if !BB_MMU
o_string as_string;
#endif
+ smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */
#if HAS_KEYWORDS
smallint ctx_res_w;
smallint ctx_inverted; /* "! cmd | cmd" */
struct parse_context *stack;
#endif
};
+enum {
+ MAYBE_ASSIGNMENT = 0,
+ DEFINITELY_ASSIGNMENT = 1,
+ NOT_ASSIGNMENT = 2,
+ /* Not an assignment, but next word may be: "if v=xyz cmd;" */
+ WORD_IS_KEYWORD = 3,
+};
/* On program start, environ points to initial environment.
* putenv adds new pointers into it, unsetenv removes them.
static void initialize_context(struct parse_context *ctx)
{
memset(ctx, 0, sizeof(*ctx));
+ if (MAYBE_ASSIGNMENT != 0)
+ ctx->is_assignment = MAYBE_ASSIGNMENT;
ctx->pipe = ctx->list_head = new_pipe();
/* Create the memory for command, roughly:
* ctx->pipe->cmds = new struct command;
}
/* Return NULL: not a keyword, else: keyword
*/
-static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx)
+static const struct reserved_combo* reserved_word(struct parse_context *ctx)
{
# if ENABLE_HUSH_CASE
static const struct reserved_combo reserved_match = {
# endif
const struct reserved_combo *r;
- if (word->has_quoted_part)
+ if (ctx->word.has_quoted_part)
return 0;
- r = match_reserved_word(word);
+ r = match_reserved_word(&ctx->word);
if (!r)
return r; /* NULL */
initialize_context(ctx);
ctx->stack = old;
} else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
- syntax_error_at(word->data);
+ syntax_error_at(ctx->word.data);
ctx->ctx_res_w = RES_SNTX;
return r;
} else {
ctx->ctx_res_w = r->res;
ctx->old_flag = r->flag;
- word->o_assignment = r->assignment_flag;
- debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
+ ctx->is_assignment = r->assignment_flag;
+ debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]);
if (ctx->old_flag & FLAG_END) {
struct parse_context *old;
* Normal return is 0. Syntax errors return 1.
* Note: on return, word is reset, but not o_free'd!
*/
-static int done_word(o_string *word, struct parse_context *ctx)
+static int done_word(struct parse_context *ctx)
{
struct command *command = ctx->command;
- debug_printf_parse("done_word entered: '%s' %p\n", word->data, command);
- if (word->length == 0 && !word->has_quoted_part) {
+ debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command);
+ if (ctx->word.length == 0 && !ctx->word.has_quoted_part) {
debug_printf_parse("done_word return 0: true null, ignored\n");
return 0;
}
// <<EOF$((1))
// <<EOF`true` [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug]
- ctx->pending_redirect->rd_filename = xstrdup(word->data);
+ ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data);
/* Cater for >\file case:
* >\a creates file a; >\\a, >"\a", >"\\a" create file \a
* Same with heredocs:
if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) {
unbackslash(ctx->pending_redirect->rd_filename);
/* Is it <<"HEREDOC"? */
- if (word->has_quoted_part) {
+ if (ctx->word.has_quoted_part) {
ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED;
}
}
- debug_printf_parse("word stored in rd_filename: '%s'\n", word->data);
+ debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data);
ctx->pending_redirect = NULL;
} else {
#if HAS_KEYWORDS
# if ENABLE_HUSH_CASE
if (ctx->ctx_dsemicolon
- && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */
+ && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */
) {
/* already done when ctx_dsemicolon was set to 1: */
/* ctx->ctx_res_w = RES_MATCH; */
# endif
) {
const struct reserved_combo *reserved;
- reserved = reserved_word(word, ctx);
+ reserved = reserved_word(ctx);
debug_printf_parse("checking for reserved-ness: %d\n", !!reserved);
if (reserved) {
# if ENABLE_HUSH_LINENO_VAR
done_pipe(ctx, PIPE_SEQ);
}
# endif
- o_reset_to_empty_unquoted(word);
+ o_reset_to_empty_unquoted(&ctx->word);
debug_printf_parse("done_word return %d\n",
(ctx->ctx_res_w == RES_SNTX));
return (ctx->ctx_res_w == RES_SNTX);
# if defined(CMD_SINGLEWORD_NOGLOB)
if (0
# if BASH_TEST2
- || strcmp(word->data, "[[") == 0
+ || strcmp(ctx->word.data, "[[") == 0
# endif
/* In bash, local/export/readonly are special, args
* are assignments and therefore expansion of them
* $ "export" i=`echo 'aaa bbb'`; echo "$i"
* aaa
*/
- IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0)
- IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0)
- IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0)
+ IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0)
+ IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0)
+ IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0)
) {
command->cmd_type = CMD_SINGLEWORD_NOGLOB;
}
if (command->group) {
/* "{ echo foo; } echo bar" - bad */
- syntax_error_at(word->data);
+ syntax_error_at(ctx->word.data);
debug_printf_parse("done_word return 1: syntax error, "
"groups and arglists don't mix\n");
return 1;
/* If this word wasn't an assignment, next ones definitely
* can't be assignments. Even if they look like ones. */
- if (word->o_assignment != DEFINITELY_ASSIGNMENT
- && word->o_assignment != WORD_IS_KEYWORD
+ if (ctx->is_assignment != DEFINITELY_ASSIGNMENT
+ && ctx->is_assignment != WORD_IS_KEYWORD
) {
- word->o_assignment = NOT_ASSIGNMENT;
+ ctx->is_assignment = NOT_ASSIGNMENT;
} else {
- if (word->o_assignment == DEFINITELY_ASSIGNMENT) {
+ if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) {
command->assignment_cnt++;
debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt);
}
- debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]);
- word->o_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]);
+ ctx->is_assignment = MAYBE_ASSIGNMENT;
}
- debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
- command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
+ debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]);
+ command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data));
debug_print_strings("word appended to argv", command->argv);
}
#if ENABLE_HUSH_LOOPS
if (ctx->ctx_res_w == RES_FOR) {
- if (word->has_quoted_part
+ if (ctx->word.has_quoted_part
|| !is_well_formed_var_name(command->argv[0], '\0')
) {
/* bash says just "not a valid identifier" */
}
#endif
- o_reset_to_empty_unquoted(word);
+ o_reset_to_empty_unquoted(&ctx->word);
debug_printf_parse("done_word return 0\n");
return 0;
int end_trigger);
-#if !ENABLE_HUSH_FUNCTIONS
-#define parse_group(dest, ctx, input, ch) \
- parse_group(ctx, input, ch)
-#endif
-static int parse_group(o_string *dest, struct parse_context *ctx,
+static int parse_group(struct parse_context *ctx,
struct in_str *input, int ch)
{
- /* dest contains characters seen prior to ( or {.
+ /* ctx->word contains characters seen prior to ( or {.
* Typically it's empty, but for function defs,
* it contains function name (without '()'). */
#if BB_MMU
debug_printf_parse("parse_group entered\n");
#if ENABLE_HUSH_FUNCTIONS
- if (ch == '(' && !dest->has_quoted_part) {
- if (dest->length)
- if (done_word(dest, ctx))
+ if (ch == '(' && !ctx->word.has_quoted_part) {
+ if (ctx->word.length)
+ if (done_word(ctx))
return 1;
if (!command->argv)
goto skip; /* (... */
#if 0 /* Prevented by caller */
if (command->argv /* word [word]{... */
- || dest->length /* word{... */
- || dest->has_quoted_part /* ""{... */
+ || ctx->word.length /* word{... */
+ || ctx->word.has_quoted_part /* ""{... */
) {
syntax_error(NULL);
debug_printf_parse("parse_group return 1: "
int end_trigger)
{
struct parse_context ctx;
- o_string dest = NULL_O_STRING;
int heredoc_cnt;
/* Single-quote triggers a bypass of the main loop until its mate is
- * found. When recursing, quote state is passed in via dest->o_expflags.
+ * found. When recursing, quote state is passed in via ctx.word.o_expflags.
*/
debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
end_trigger ? end_trigger : 'X');
debug_enter();
- /* If very first arg is "" or '', dest.data may end up NULL.
- * Preventing this: */
- o_addchr(&dest, '\0');
- dest.length = 0;
+ initialize_context(&ctx);
+
+ /* If very first arg is "" or '', ctx.word.data may end up NULL.
+ * Preventing this:
+ */
+ o_addchr(&ctx.word, '\0');
+ ctx.word.length = 0;
/* We used to separate words on $IFS here. This was wrong.
* $IFS is used only for word splitting when $var is expanded,
* here we should use blank chars as separators, not $IFS
*/
- if (MAYBE_ASSIGNMENT != 0)
- dest.o_assignment = MAYBE_ASSIGNMENT;
- initialize_context(&ctx);
heredoc_cnt = 0;
while (1) {
const char *is_blank;
ch = i_getch(input);
debug_printf_parse(": ch=%c (%d) escape=%d\n",
- ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
+ ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
if (ch == EOF) {
struct pipe *pi;
goto parse_error;
}
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
- o_free(&dest);
+ o_free(&ctx.word);
done_pipe(&ctx, PIPE_SEQ);
pi = ctx.list_head;
/* If we got nothing... */
SPECIAL_VAR_SYMBOL_STR;
/* Are { and } special here? */
if (ctx.command->argv /* word [word]{... - non-special */
- || dest.length /* word{... - non-special */
- || dest.has_quoted_part /* ""{... - non-special */
+ || ctx.word.length /* word{... - non-special */
+ || ctx.word.has_quoted_part /* ""{... - non-special */
|| (next != ';' /* }; - special */
&& next != ')' /* }) - special */
&& next != '(' /* {( - special */
if (!is_special && !is_blank) { /* ordinary char */
ordinary_char:
- o_addQchr(&dest, ch);
- if ((dest.o_assignment == MAYBE_ASSIGNMENT
- || dest.o_assignment == WORD_IS_KEYWORD)
+ o_addQchr(&ctx.word, ch);
+ if ((ctx.is_assignment == MAYBE_ASSIGNMENT
+ || ctx.is_assignment == WORD_IS_KEYWORD)
&& ch == '='
- && is_well_formed_var_name(dest.data, '=')
+ && is_well_formed_var_name(ctx.word.data, '=')
) {
- dest.o_assignment = DEFINITELY_ASSIGNMENT;
- debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
+ ctx.is_assignment = DEFINITELY_ASSIGNMENT;
+ debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
}
continue;
}
}
/* ch == last eaten whitespace char */
#endif
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
if (ch == '\n') {
* "case ... in <newline> word) ..."
*/
if (IS_NULL_CMD(ctx.command)
- && dest.length == 0 && !dest.has_quoted_part
+ && ctx.word.length == 0 && !ctx.word.has_quoted_part
) {
/* This newline can be ignored. But...
* Without check #1, interactive shell
}
heredoc_cnt = 0;
}
- dest.o_assignment = MAYBE_ASSIGNMENT;
- debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
+ ctx.is_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
ch = ';';
/* note: if (is_blank) continue;
* will still trigger for us */
* Pathological example: { ""}; } should exec "}" cmd
*/
if (ch == '}') {
- if (dest.length != 0 /* word} */
- || dest.has_quoted_part /* ""} */
+ if (ctx.word.length != 0 /* word} */
+ || ctx.word.has_quoted_part /* ""} */
) {
goto ordinary_char;
}
#if ENABLE_HUSH_CASE
&& (ch != ')'
|| ctx.ctx_res_w != RES_MATCH
- || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0)
+ || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0)
)
#endif
) {
syntax_error_unterm_str("here document");
goto parse_error;
}
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
done_pipe(&ctx, PIPE_SEQ);
- dest.o_assignment = MAYBE_ASSIGNMENT;
- debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
+ ctx.is_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
/* Do we sit outside of any if's, loops or case's? */
if (!HAS_KEYWORDS
IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
) {
- o_free(&dest);
+ o_free(&ctx.word);
#if !BB_MMU
debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data);
if (pstring)
* an assignment. a=1 2>z b=2: b=2 is still assignment */
switch (ch) {
case '>':
- redir_fd = redirect_opt_num(&dest);
- if (done_word(&dest, &ctx)) {
+ redir_fd = redirect_opt_num(&ctx.word);
+ if (done_word(&ctx)) {
goto parse_error;
}
redir_style = REDIRECT_OVERWRITE;
goto parse_error;
continue; /* back to top of while (1) */
case '<':
- redir_fd = redirect_opt_num(&dest);
- if (done_word(&dest, &ctx)) {
+ redir_fd = redirect_opt_num(&ctx.word);
+ if (done_word(&ctx)) {
goto parse_error;
}
redir_style = REDIRECT_INPUT;
goto parse_error;
continue; /* back to top of while (1) */
case '#':
- if (dest.length == 0 && !dest.has_quoted_part) {
+ if (ctx.word.length == 0 && !ctx.word.has_quoted_part) {
/* skip "#comment" */
/* note: we do not add it to &ctx.as_string */
/* TODO: in bash:
break;
}
- if (dest.o_assignment == MAYBE_ASSIGNMENT
+ if (ctx.is_assignment == MAYBE_ASSIGNMENT
/* check that we are not in word in "a=1 2>word b=1": */
&& !ctx.pending_redirect
) {
/* ch is a special char and thus this word
* cannot be an assignment */
- dest.o_assignment = NOT_ASSIGNMENT;
- debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
+ ctx.is_assignment = NOT_ASSIGNMENT;
+ debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
}
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
switch (ch) {
case SPECIAL_VAR_SYMBOL:
/* Convert raw ^C to corresponding special variable reference */
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
- o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+ o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS);
/* fall through */
case '#':
/* non-comment #: "echo a#b" etc */
- o_addchr(&dest, ch);
+ o_addchr(&ctx.word, ch);
break;
case '\\':
if (next == EOF) {
}
ch = i_getch(input);
/* note: ch != '\n' (that case does not reach this place) */
- o_addchr(&dest, '\\');
+ o_addchr(&ctx.word, '\\');
/*nommu_addchr(&ctx.as_string, '\\'); - already done */
- o_addchr(&dest, ch);
+ o_addchr(&ctx.word, ch);
nommu_addchr(&ctx.as_string, ch);
/* Example: echo Hello \2>file
* we need to know that word 2 is quoted */
- dest.has_quoted_part = 1;
+ ctx.word.has_quoted_part = 1;
break;
case '$':
- if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) {
+ if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) {
debug_printf_parse("parse_stream parse error: "
"parse_dollar returned 0 (error)\n");
goto parse_error;
}
break;
case '\'':
- dest.has_quoted_part = 1;
+ ctx.word.has_quoted_part = 1;
if (next == '\'' && !ctx.pending_redirect) {
insert_empty_quoted_str_marker:
nommu_addchr(&ctx.as_string, next);
i_getch(input); /* eat second ' */
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
} else {
while (1) {
ch = i_getch(input);
break;
if (ch == SPECIAL_VAR_SYMBOL) {
/* Convert raw ^C to corresponding special variable reference */
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
- o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+ o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS);
}
- o_addqchr(&dest, ch);
+ o_addqchr(&ctx.word, ch);
}
}
break;
case '"':
- dest.has_quoted_part = 1;
+ ctx.word.has_quoted_part = 1;
if (next == '"' && !ctx.pending_redirect)
goto insert_empty_quoted_str_marker;
- if (dest.o_assignment == NOT_ASSIGNMENT)
- dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
- if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1))
+ if (ctx.is_assignment == NOT_ASSIGNMENT)
+ ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
+ if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1))
goto parse_error;
- dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
+ ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
break;
#if ENABLE_HUSH_TICK
case '`': {
USE_FOR_NOMMU(unsigned pos;)
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
- o_addchr(&dest, '`');
- USE_FOR_NOMMU(pos = dest.length;)
- if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0))
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+ o_addchr(&ctx.word, '`');
+ USE_FOR_NOMMU(pos = ctx.word.length;)
+ if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0))
goto parse_error;
# if !BB_MMU
- o_addstr(&ctx.as_string, dest.data + pos);
+ o_addstr(&ctx.as_string, ctx.word.data + pos);
o_addchr(&ctx.as_string, '`');
# endif
- o_addchr(&dest, SPECIAL_VAR_SYMBOL);
- //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos);
+ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL);
+ //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos);
break;
}
#endif
#if ENABLE_HUSH_CASE
case_semi:
#endif
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
done_pipe(&ctx, PIPE_SEQ);
new_cmd:
/* We just finished a cmd. New one may start
* with an assignment */
- dest.o_assignment = MAYBE_ASSIGNMENT;
- debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]);
+ ctx.is_assignment = MAYBE_ASSIGNMENT;
+ debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
break;
case '&':
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
if (next == '\\')
}
goto new_cmd;
case '|':
- if (done_word(&dest, &ctx)) {
+ if (done_word(&ctx)) {
goto parse_error;
}
#if ENABLE_HUSH_CASE
/* "case... in [(]word)..." - skip '(' */
if (ctx.ctx_res_w == RES_MATCH
&& ctx.command->argv == NULL /* not (word|(... */
- && dest.length == 0 /* not word(... */
- && dest.has_quoted_part == 0 /* not ""(... */
+ && ctx.word.length == 0 /* not word(... */
+ && ctx.word.has_quoted_part == 0 /* not ""(... */
) {
continue;
}
#endif
case '{':
- if (parse_group(&dest, &ctx, input, ch) != 0) {
+ if (parse_group(&ctx, input, ch) != 0) {
goto parse_error;
}
goto new_cmd;
IF_HAS_KEYWORDS(pctx = p2;)
} while (HAS_KEYWORDS && pctx);
- o_free(&dest);
+ o_free(&ctx.word);
#if !BB_MMU
if (pstring)
*pstring = NULL;